From 028ebbb43e1b97c4e091fe0266dd5d877e8ace26 Mon Sep 17 00:00:00 2001 From: Finn Eitreim <48069764+feitreim@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:49:03 -0400 Subject: [PATCH] editor: Fix showing completion list with selection does not include the selection (#57405) Fix the completion list not showing the current selection when you execute `editor: show completions`, the stated reason by comment before is that if you are in a tabstop snippet already it wont give you the correct options. So my change is to just check if you are in such a snippet and if not give the new desired behavior. before: https://github.com/user-attachments/assets/1b2f3594-7361-4636-9ef3-f8c9419faa26 after: https://github.com/user-attachments/assets/1be35582-464a-40f3-b6be-ee74f9961796 Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #57386 Release Notes: - editor: include text from selection in completion list. --------- Co-authored-by: Smit Barmase --- crates/editor/src/completions.rs | 24 +++++--- crates/editor/src/editor_tests.rs | 95 +++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/completions.rs b/crates/editor/src/completions.rs index df00100365..e2f0be07ce 100644 --- a/crates/editor/src/completions.rs +++ b/crates/editor/src/completions.rs @@ -266,14 +266,20 @@ impl Editor { let multibuffer_snapshot = self.buffer.read(cx).read(cx); - // Typically `start` == `end`, but with snippet tabstop choices the default choice is - // inserted and selected. To handle that case, the start of the selection is used so that - // the menu starts with all choices. - let position = self - .selections - .newest_anchor() - .start - .bias_right(&multibuffer_snapshot); + let is_showing_snippet_choices = matches!( + completions_source, + Some(CompletionsMenuSource::SnippetChoices) + ); + + let anchor = self.selections.newest_anchor(); + let position = if is_showing_snippet_choices { + // Typically `start` == `end`, but with snippet tabstop choices the default choice is + // inserted and selected. To handle that case, the start of the selection is used so that + // the menu starts with all choices. + anchor.start.bias_right(&multibuffer_snapshot) + } else { + anchor.head().bias_right(&multibuffer_snapshot) + }; if position.diff_base_anchor().is_some() { return; @@ -315,7 +321,7 @@ impl Editor { // Hide the current completions menu when query is empty. Without this, cached // completions from before the trigger char may be reused (#32774). - if query.is_none() && menu_is_open { + if query.is_none() && menu_is_open && !is_showing_snippet_choices { self.hide_context_menu(window, cx); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index a3a0acd392..f8d98a2b06 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -13732,6 +13732,49 @@ async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_snippet_choices_menu_survives_completion_refresh(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let (text, insertion_ranges) = marked_text_ranges( + indoc! {" + ˇ + "}, + false, + ); + + let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); + let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx)); + + _ = editor.update_in(cx, |editor, window, cx| { + let snippet = Snippet::parse("type ${1|i32,u32|} = $2").unwrap(); + + editor + .insert_snippet( + &insertion_ranges + .iter() + .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)) + .collect::>(), + snippet, + window, + cx, + ) + .unwrap(); + + assert!( + editor.context_menu_visible(), + "Snippet choices menu should be visible after inserting the choice tabstop" + ); + + editor.open_or_update_completions_menu(None, None, false, window, cx); + + assert!( + editor.context_menu_visible(), + "Snippet choices menu should remain visible after a completion refresh with an empty query" + ); + }); +} + #[gpui::test] async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -18359,6 +18402,58 @@ async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_completions_use_selection_head(cx: &mut TestAppContext) { + init_test(cx, |language_settings| { + language_settings.defaults.completions = Some(CompletionSettingsContent { + words: Some(WordsCompletionMode::Disabled), + words_min_length: Some(0), + lsp_insert_mode: Some(LspInsertMode::Insert), + ..Default::default() + }); + }); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions::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! {"«applˇ» + applepie + banana + cherry + "}); + 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), + &["applepie"], + "Completion query should use the selection head (`appl`), filtering to words with that prefix" + ); + } else { + panic!("expected completion menu to be open"); + } + }); +} + #[gpui::test] async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) { init_test(cx, |language_settings| {