diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index f129eaa617..646313262f 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -423,8 +423,14 @@ impl CompletionsMenu { &None }; + let filter_start = completion.label.filter_range.start; let highlights = gpui::combine_highlights( - mat.ranges().map(|range| (range, FontWeight::BOLD.into())), + mat.ranges().map(|range| { + ( + filter_start + range.start..filter_start + range.end, + FontWeight::BOLD.into(), + ) + }), styled_runs_for_code_label(&completion.label, &style.syntax).map( |(range, mut highlight)| { // Ignore font weight for syntax highlighting, as we'll use it @@ -594,14 +600,6 @@ impl CompletionsMenu { } }); } - - for mat in &mut matches { - let completion = &completions[mat.candidate_id]; - mat.string.clone_from(&completion.label.text); - for position in &mut mat.positions { - *position += completion.label.filter_range.start; - } - } drop(completions); self.matches = matches.into(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 1436fed6bf..6994d4e968 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -10648,7 +10648,9 @@ async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) { +async fn test_completions_resolve_updates_labels_if_filter_text_matches( + cx: &mut gpui::TestAppContext, +) { init_test(cx, |_| {}); let mut cx = EditorLspTestContext::new_rust( @@ -10667,20 +10669,34 @@ async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"}); cx.simulate_keystroke("."); - let completion_item = lsp::CompletionItem { - label: "unresolved".to_string(), + let item1 = lsp::CompletionItem { + label: "id".to_string(), + filter_text: Some("id".to_string()), detail: None, documentation: None, text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), - new_text: ".unresolved".to_string(), + new_text: ".id".to_string(), + })), + ..lsp::CompletionItem::default() + }; + + let item2 = lsp::CompletionItem { + label: "other".to_string(), + filter_text: Some("other".to_string()), + detail: None, + documentation: None, + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), + new_text: ".other".to_string(), })), ..lsp::CompletionItem::default() }; cx.handle_request::(move |_, _, _| { - let item = completion_item.clone(); - async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) } + let item1 = item1.clone(); + let item2 = item2.clone(); + async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) } }) .next() .await; @@ -10695,8 +10711,13 @@ async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) match context_menu { CodeContextMenu::Completions(completions_menu) => { let completions = completions_menu.completions.read(); - assert_eq!(completions.len(), 1, "Should have one completion"); - assert_eq!(completions.get(0).unwrap().label.text, "unresolved"); + assert_eq!( + completions + .iter() + .map(|completion| &completion.label.text) + .collect::>(), + vec!["id", "other"] + ) } CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"), } @@ -10704,12 +10725,33 @@ async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) cx.handle_request::(move |_, _, _| async move { Ok(lsp::CompletionItem { - label: "resolved".to_string(), + label: "method id()".to_string(), + filter_text: Some("id".to_string()), detail: Some("Now resolved!".to_string()), documentation: Some(lsp::Documentation::String("Docs".to_string())), text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), - new_text: ".resolved".to_string(), + new_text: ".id".to_string(), + })), + ..lsp::CompletionItem::default() + }) + }) + .next() + .await; + cx.run_until_parked(); + + cx.update_editor(|editor, cx| { + editor.context_menu_next(&Default::default(), cx); + }); + + cx.handle_request::(move |_, _, _| async move { + Ok(lsp::CompletionItem { + label: "invalid changed label".to_string(), + detail: Some("Now resolved!".to_string()), + documentation: Some(lsp::Documentation::String("Docs".to_string())), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), + new_text: ".id".to_string(), })), ..lsp::CompletionItem::default() }) @@ -10726,11 +10768,13 @@ async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) match context_menu { CodeContextMenu::Completions(completions_menu) => { let completions = completions_menu.completions.read(); - assert_eq!(completions.len(), 1, "Should have one completion"); assert_eq!( - completions.get(0).unwrap().label.text, - "resolved", - "Should update the completion label after resolving" + completions + .iter() + .map(|completion| &completion.label.text) + .collect::>(), + vec!["method id()", "other"], + "Should update first completion label, but not second as the filter text did not match." ); } CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"), diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 471376c0e1..92a4aee225 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -4303,7 +4303,18 @@ impl LspStore { let mut completions = completions.write(); let completion = &mut completions[completion_index]; completion.lsp_completion = completion_item; - completion.label = new_label; + if completion.label.filter_text() == new_label.filter_text() { + completion.label = new_label; + } else { + log::error!( + "Resolved completion changed display label from {} to {}. \ + Refusing to apply this because it changes the fuzzy match text from {} to {}", + completion.label.text(), + new_label.text(), + completion.label.filter_text(), + new_label.filter_text() + ); + } } #[allow(clippy::too_many_arguments)]