From 8c46a4f594b496fd666b8357a205ff228846d136 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 3 Jun 2025 14:33:52 -0600 Subject: [PATCH] Make completions menu stay open after after it's manually requested (#32015) Also includes a clarity refactoring to remove `ignore_completion_provider`. Closes #15549 Release Notes: - Fixed completions menu closing on typing after being requested while `show_completions_on_input: false`. --- .../src/context_picker/completion_provider.rs | 5 +- .../src/slash_command.rs | 1 + .../src/chat_panel/message_editor.rs | 1 + .../src/session/running/console.rs | 1 + crates/editor/src/code_context_menus.rs | 15 +++- crates/editor/src/editor.rs | 80 ++++++++++++------- crates/inspector_ui/src/div_inspector.rs | 5 +- 7 files changed, 70 insertions(+), 38 deletions(-) diff --git a/crates/agent/src/context_picker/completion_provider.rs b/crates/agent/src/context_picker/completion_provider.rs index ffc395f888..8d93838be6 100644 --- a/crates/agent/src/context_picker/completion_provider.rs +++ b/crates/agent/src/context_picker/completion_provider.rs @@ -926,8 +926,9 @@ impl CompletionProvider for ContextPickerCompletionProvider { &self, buffer: &Entity, position: language::Anchor, - _: &str, - _: bool, + _text: &str, + _trigger_in_words: bool, + _menu_is_open: bool, cx: &mut Context, ) -> bool { let buffer = buffer.read(cx); diff --git a/crates/assistant_context_editor/src/slash_command.rs b/crates/assistant_context_editor/src/slash_command.rs index fb34d29cca..4c34e94e6e 100644 --- a/crates/assistant_context_editor/src/slash_command.rs +++ b/crates/assistant_context_editor/src/slash_command.rs @@ -342,6 +342,7 @@ impl CompletionProvider for SlashCommandCompletionProvider { position: language::Anchor, _text: &str, _trigger_in_words: bool, + _menu_is_open: bool, cx: &mut Context, ) -> bool { let buffer = buffer.read(cx); diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 7a580896a6..4596f5957f 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -89,6 +89,7 @@ impl CompletionProvider for MessageEditorCompletionProvider { _position: language::Anchor, text: &str, _trigger_in_words: bool, + _menu_is_open: bool, _cx: &mut Context, ) -> bool { text == "@" diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index 389fa24587..fa154ec48c 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -309,6 +309,7 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider { _position: language::Anchor, _text: &str, _trigger_in_words: bool, + _menu_is_open: bool, _cx: &mut Context, ) -> bool { true diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 3d61bfb6a4..f0f53481c1 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -194,6 +194,7 @@ pub enum ContextMenuOrigin { pub struct CompletionsMenu { pub id: CompletionId, + pub source: CompletionsMenuSource, sort_completions: bool, pub initial_position: Anchor, pub initial_query: Option>, @@ -208,7 +209,6 @@ pub struct CompletionsMenu { scroll_handle: UniformListScrollHandle, resolve_completions: bool, show_completion_documentation: bool, - pub(super) ignore_completion_provider: bool, last_rendered_range: Rc>>>, markdown_cache: Rc)>>>, language_registry: Option>, @@ -227,6 +227,13 @@ enum MarkdownCacheKey { }, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum CompletionsMenuSource { + Normal, + SnippetChoices, + Words, +} + // TODO: There should really be a wrapper around fuzzy match tasks that does this. impl Drop for CompletionsMenu { fn drop(&mut self) { @@ -237,9 +244,9 @@ impl Drop for CompletionsMenu { impl CompletionsMenu { pub fn new( id: CompletionId, + source: CompletionsMenuSource, sort_completions: bool, show_completion_documentation: bool, - ignore_completion_provider: bool, initial_position: Anchor, initial_query: Option>, is_incomplete: bool, @@ -258,13 +265,13 @@ impl CompletionsMenu { let completions_menu = Self { id, + source, sort_completions, initial_position, initial_query, is_incomplete, buffer, show_completion_documentation, - ignore_completion_provider, completions: RefCell::new(completions).into(), match_candidates, entries: Rc::new(RefCell::new(Box::new([]))), @@ -328,6 +335,7 @@ impl CompletionsMenu { .collect(); Self { id, + source: CompletionsMenuSource::SnippetChoices, sort_completions, initial_position: selection.start, initial_query: None, @@ -342,7 +350,6 @@ 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_cache: RefCell::new(VecDeque::new()).into(), language_registry: None, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 913aad22a5..b0336c8140 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -211,8 +211,11 @@ use workspace::{ searchable::SearchEvent, }; -use crate::hover_links::{find_url, find_url_from_range}; use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState}; +use crate::{ + code_context_menus::CompletionsMenuSource, + hover_links::{find_url, find_url_from_range}, +}; pub const FILE_HEADER_HEIGHT: u32 = 2; pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1; @@ -4510,30 +4513,40 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - let ignore_completion_provider = self + let completions_source = 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); + .and_then(|menu| match menu { + CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source), + CodeContextMenu::CodeActions(_) => None, + }); - 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()), - }, - window, - cx, - ); - } else { - self.hide_context_menu(window, cx); + match completions_source { + Some(CompletionsMenuSource::Words) => { + self.show_word_completions(&ShowWordCompletions, window, cx) + } + Some(CompletionsMenuSource::Normal) + | Some(CompletionsMenuSource::SnippetChoices) + | None + if self.is_completion_trigger( + text, + trigger_in_words, + completions_source.is_some(), + cx, + ) => + { + self.show_completions( + &ShowCompletions { + trigger: Some(text.to_owned()).filter(|x| !x.is_empty()), + }, + window, + cx, + ) + } + _ => { + self.hide_context_menu(window, cx); + } } } @@ -4541,6 +4554,7 @@ impl Editor { &self, text: &str, trigger_in_words: bool, + menu_is_open: bool, cx: &mut Context, ) -> bool { let position = self.selections.newest_anchor().head(); @@ -4558,6 +4572,7 @@ impl Editor { position.text_anchor, text, trigger_in_words, + menu_is_open, cx, ) } else { @@ -5008,7 +5023,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - self.open_or_update_completions_menu(true, None, window, cx); + self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx); } pub fn show_completions( @@ -5017,12 +5032,12 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - self.open_or_update_completions_menu(false, options.trigger.as_deref(), window, cx); + self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx); } fn open_or_update_completions_menu( &mut self, - ignore_completion_provider: bool, + requested_source: Option, trigger: Option<&str>, window: &mut Window, cx: &mut Context, @@ -5047,10 +5062,13 @@ impl Editor { Self::completion_query(&self.buffer.read(cx).read(cx), position) .map(|query| query.into()); - let provider = if ignore_completion_provider { - None - } else { - self.completion_provider.clone() + let provider = match requested_source { + Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(), + Some(CompletionsMenuSource::Words) => None, + Some(CompletionsMenuSource::SnippetChoices) => { + log::error!("bug: SnippetChoices requested_source is not handled"); + None + } }; let sort_completions = provider @@ -5246,9 +5264,9 @@ impl Editor { .map(|workspace| workspace.read(cx).app_state().languages.clone()); let menu = CompletionsMenu::new( id, + requested_source.unwrap_or(CompletionsMenuSource::Normal), sort_completions, show_completion_documentation, - ignore_completion_provider, position, query.clone(), is_incomplete, @@ -20295,6 +20313,7 @@ pub trait CompletionProvider { position: language::Anchor, text: &str, trigger_in_words: bool, + menu_is_open: bool, cx: &mut Context, ) -> bool; @@ -20612,6 +20631,7 @@ impl CompletionProvider for Entity { position: language::Anchor, text: &str, trigger_in_words: bool, + menu_is_open: bool, cx: &mut Context, ) -> bool { let mut chars = text.chars(); @@ -20626,7 +20646,7 @@ impl CompletionProvider for Entity { let buffer = buffer.read(cx); let snapshot = buffer.snapshot(); - if !snapshot.settings_at(position, cx).show_completions_on_input { + if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input { return false; } let classifier = snapshot.char_classifier_at(position).for_completion(true); diff --git a/crates/inspector_ui/src/div_inspector.rs b/crates/inspector_ui/src/div_inspector.rs index 664c904d39..8b4b796680 100644 --- a/crates/inspector_ui/src/div_inspector.rs +++ b/crates/inspector_ui/src/div_inspector.rs @@ -685,8 +685,9 @@ impl CompletionProvider for RustStyleCompletionProvider { &self, buffer: &Entity, position: language::Anchor, - _: &str, - _: bool, + _text: &str, + _trigger_in_words: bool, + _menu_is_open: bool, cx: &mut Context, ) -> bool { completion_replace_range(&buffer.read(cx).snapshot(), &position).is_some()