@@ -4104,23 +4104,26 @@ impl Editor {
match completion_settings.words {
WordsCompletionMode::Enabled => {
- completions.extend(
- words
- .await
- .into_iter()
- .filter(|(word, _)| word_to_exclude.as_ref() != Some(word))
- .map(|(word, word_range)| Completion {
- old_range: old_range.clone(),
- new_text: word.clone(),
- label: CodeLabel::plain(word, None),
- documentation: None,
- source: CompletionSource::BufferWord {
- word_range,
- resolved: false,
- },
- confirm: None,
- }),
- );
+ let mut words = words.await;
+ if let Some(word_to_exclude) = &word_to_exclude {
+ words.remove(word_to_exclude);
+ }
+ for lsp_completion in &completions {
+ words.remove(&lsp_completion.new_text);
+ }
+ completions.extend(words.into_iter().map(|(word, word_range)| {
+ Completion {
+ old_range: old_range.clone(),
+ new_text: word.clone(),
+ label: CodeLabel::plain(word, None),
+ documentation: None,
+ source: CompletionSource::BufferWord {
+ word_range,
+ resolved: false,
+ },
+ confirm: None,
+ }
+ }));
}
WordsCompletionMode::Fallback => {
if completions.is_empty() {
@@ -9236,11 +9236,11 @@ async fn test_words_completion(cx: &mut TestAppContext) {
Ok(Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
label: "first".into(),
- ..Default::default()
+ ..lsp::CompletionItem::default()
},
lsp::CompletionItem {
label: "last".into(),
- ..Default::default()
+ ..lsp::CompletionItem::default()
},
])))
}
@@ -9290,6 +9290,69 @@ async fn test_words_completion(cx: &mut TestAppContext) {
});
}
+#[gpui::test]
+async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
+ init_test(cx, |language_settings| {
+ language_settings.defaults.completions = Some(CompletionSettings {
+ words: WordsCompletionMode::Enabled,
+ lsp: true,
+ lsp_fetch_timeout_ms: 0,
+ });
+ });
+
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+ ..lsp::CompletionOptions::default()
+ }),
+ signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
+ ..lsp::ServerCapabilities::default()
+ },
+ cx,
+ )
+ .await;
+
+ let _completion_requests_handler =
+ cx.lsp
+ .server
+ .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
+ Ok(Some(lsp::CompletionResponse::Array(vec![
+ lsp::CompletionItem {
+ label: "first".into(),
+ ..lsp::CompletionItem::default()
+ },
+ lsp::CompletionItem {
+ label: "last".into(),
+ ..lsp::CompletionItem::default()
+ },
+ ])))
+ });
+
+ cx.set_state(indoc! {"ˇ
+ first
+ last
+ second
+ "});
+ cx.simulate_keystroke(".");
+ cx.executor().run_until_parked();
+ cx.condition(|editor, _| editor.context_menu_visible())
+ .await;
+ cx.update_editor(|editor, window, cx| {
+ if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
+ {
+ assert_eq!(
+ completion_menu_entries(&menu),
+ &["first", "last", "second"],
+ "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
+ );
+ } else {
+ panic!("expected completion menu to be open");
+ }
+ editor.cancel(&Cancel, window, cx);
+ });
+}
+
#[gpui::test]
async fn test_multiline_completion(cx: &mut TestAppContext) {
init_test(cx, |_| {});