From ac617e278ef0adc7950fe99203cbdc723f4eb681 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 18 Mar 2025 13:19:32 +0200 Subject: [PATCH] Keep and filter word completions on input, if the menu is open (#26979) Follow-up of https://github.com/zed-industries/zed/pull/26410 Release Notes: - N/A --- crates/editor/src/code_context_menus.rs | 4 ++ crates/editor/src/editor.rs | 17 +++++- crates/editor/src/editor_tests.rs | 77 ++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index b931c836eb08a2c93ac77506e98555f45dfbed7d..d59514c169a52c749e646743af6dbd0e828d3c34 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -180,6 +180,7 @@ pub struct CompletionsMenu { scroll_handle: UniformListScrollHandle, resolve_completions: bool, show_completion_documentation: bool, + pub(super) ignore_completion_provider: bool, last_rendered_range: Rc>>>, markdown_element: Option>, } @@ -189,6 +190,7 @@ impl CompletionsMenu { id: CompletionId, sort_completions: bool, show_completion_documentation: bool, + ignore_completion_provider: bool, initial_position: Anchor, buffer: Entity, completions: Box<[Completion]>, @@ -205,6 +207,7 @@ impl CompletionsMenu { initial_position, buffer, show_completion_documentation, + ignore_completion_provider, completions: RefCell::new(completions).into(), match_candidates, entries: RefCell::new(Vec::new()).into(), @@ -266,6 +269,7 @@ impl CompletionsMenu { scroll_handle: UniformListScrollHandle::new(), resolve_completions: false, show_completion_documentation: false, + ignore_completion_provider: false, last_rendered_range: RefCell::new(None).into(), markdown_element: None, } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index dfcdf109e61764d8ce8c2e7bf24a1cc451ae9e31..ef4e8de08a010eb3b2597335849909395ea49c17 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3546,7 +3546,21 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - if self.is_completion_trigger(text, trigger_in_words, cx) { + let ignore_completion_provider = self + .context_menu + .borrow() + .as_ref() + .map(|menu| match menu { + CodeContextMenu::Completions(completions_menu) => { + completions_menu.ignore_completion_provider + } + CodeContextMenu::CodeActions(_) => false, + }) + .unwrap_or(false); + + if ignore_completion_provider { + self.show_word_completions(&ShowWordCompletions, window, cx); + } else if self.is_completion_trigger(text, trigger_in_words, cx) { self.show_completions( &ShowCompletions { trigger: Some(text.to_owned()).filter(|x| !x.is_empty()), @@ -4183,6 +4197,7 @@ impl Editor { id, sort_completions, show_completion_documentation, + ignore_completion_provider, position, buffer.clone(), completions.into(), diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 3482aefec3b24f1de7dad94b28724622c9ee01ce..d813ae40486c4d470333ddf993b996b55b0266d0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -9212,7 +9212,7 @@ async fn test_completion(cx: &mut TestAppContext) { } #[gpui::test] -async fn test_words_completion(cx: &mut TestAppContext) { +async fn test_word_completion(cx: &mut TestAppContext) { let lsp_fetch_timeout_ms = 10; init_test(cx, |language_settings| { language_settings.defaults.completions = Some(CompletionSettings { @@ -9354,7 +9354,7 @@ async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext cx.executor().run_until_parked(); cx.condition(|editor, _| editor.context_menu_visible()) .await; - cx.update_editor(|editor, window, cx| { + cx.update_editor(|editor, _, _| { if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() { assert_eq!( @@ -9365,7 +9365,78 @@ async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext } else { panic!("expected completion menu to be open"); } - editor.cancel(&Cancel, window, cx); + }); +} + +#[gpui::test] +async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) { + init_test(cx, |language_settings| { + language_settings.defaults.completions = Some(CompletionSettings { + words: WordsCompletionMode::Disabled, + 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::(move |_, _| async move { + panic!("LSP completions should not be queried when dealing with word completions") + }); + + cx.set_state(indoc! {"ˇ + first + last + second + "}); + cx.update_editor(|editor, window, cx| { + editor.show_word_completions(&ShowWordCompletions, window, cx); + }); + cx.executor().run_until_parked(); + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + cx.update_editor(|editor, _, _| { + if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() + { + assert_eq!( + completion_menu_entries(&menu), + &["first", "last", "second"], + "`ShowWordCompletions` action should show word completions" + ); + } else { + panic!("expected completion menu to be open"); + } + }); + + cx.simulate_keystroke("s"); + cx.executor().run_until_parked(); + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + cx.update_editor(|editor, _, _| { + if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() + { + assert_eq!( + completion_menu_entries(&menu), + &["second"], + "After showing word completions, further editing should filter them and not query the LSP" + ); + } else { + panic!("expected completion menu to be open"); + } }); }