diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index ea895f2021..9e05e37793 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2580,9 +2580,9 @@ impl ConversationEditor { let slash_command_registry = conversation.read(cx).slash_command_registry.clone(); let completion_provider = SlashCommandCompletionProvider::new( - cx.view().downgrade(), slash_command_registry.clone(), - workspace.downgrade(), + Some(cx.view().downgrade()), + Some(workspace.downgrade()), ); let editor = cx.new_view(|cx| { diff --git a/crates/assistant/src/prompt_library.rs b/crates/assistant/src/prompt_library.rs index fdeeb4ea85..a56ce5356c 100644 --- a/crates/assistant/src/prompt_library.rs +++ b/crates/assistant/src/prompt_library.rs @@ -1,5 +1,5 @@ use crate::{ - slash_command::SlashCommandLine, CompletionProvider, LanguageModelRequest, + slash_command::SlashCommandCompletionProvider, CompletionProvider, LanguageModelRequest, LanguageModelRequestMessage, Role, }; use anyhow::{anyhow, Result}; @@ -11,17 +11,14 @@ use futures::{ future::{self, BoxFuture, Shared}, FutureExt, }; -use fuzzy::{match_strings, StringMatchCandidate}; +use fuzzy::StringMatchCandidate; use gpui::{ actions, point, size, AnyElement, AppContext, BackgroundExecutor, Bounds, DevicePixels, - EventEmitter, Global, Model, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions, - View, WindowBounds, WindowHandle, WindowOptions, + EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions, View, + WindowBounds, WindowHandle, WindowOptions, }; use heed::{types::SerdeBincode, Database, RoTxn}; -use language::{ - language_settings::SoftWrap, Buffer, Documentation, LanguageRegistry, LanguageServerId, Point, - ToPoint as _, -}; +use language::{language_settings::SoftWrap, Buffer, LanguageRegistry}; use parking_lot::RwLock; use picker::{Picker, PickerDelegate}; use rope::Rope; @@ -482,6 +479,7 @@ impl PromptLibrary { self.set_active_prompt(Some(prompt_id), cx); } else { let language_registry = self.language_registry.clone(); + let commands = SlashCommandRegistry::global(cx); let prompt = self.store.load(prompt_id); self.pending_load = cx.spawn(|this, mut cx| async move { let prompt = prompt.await; @@ -500,8 +498,9 @@ impl PromptLibrary { editor.set_show_gutter(false, cx); editor.set_show_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); - editor - .set_completion_provider(Box::new(SlashCommandCompletionProvider)); + editor.set_completion_provider(Box::new( + SlashCommandCompletionProvider::new(commands, None, None), + )); if focus { editor.focus(cx); } @@ -1092,123 +1091,3 @@ fn title_from_body(body: impl IntoIterator) -> Option None } } - -struct SlashCommandCompletionProvider; - -impl editor::CompletionProvider for SlashCommandCompletionProvider { - fn completions( - &self, - buffer: &Model, - buffer_position: language::Anchor, - cx: &mut ViewContext, - ) -> Task>> { - let Some((command_name, name_range)) = buffer.update(cx, |buffer, _cx| { - let position = buffer_position.to_point(buffer); - let line_start = Point::new(position.row, 0); - let mut lines = buffer.text_for_range(line_start..position).lines(); - let line = lines.next()?; - let call = SlashCommandLine::parse(line)?; - - if call.argument.is_some() { - // Don't autocomplete arguments. - None - } else { - let name = line[call.name.clone()].to_string(); - let name_range_start = Point::new(position.row, call.name.start as u32); - let name_range_end = Point::new(position.row, call.name.end as u32); - let name_range = - buffer.anchor_after(name_range_start)..buffer.anchor_after(name_range_end); - Some((name, name_range)) - } - }) else { - return Task::ready(Ok(Vec::new())); - }; - - let commands = SlashCommandRegistry::global(cx); - let candidates = commands - .command_names() - .into_iter() - .enumerate() - .map(|(ix, def)| StringMatchCandidate { - id: ix, - string: def.to_string(), - char_bag: def.as_ref().into(), - }) - .collect::>(); - let command_name = command_name.to_string(); - cx.spawn(|_, mut cx| async move { - let matches = match_strings( - &candidates, - &command_name, - true, - usize::MAX, - &Default::default(), - cx.background_executor().clone(), - ) - .await; - cx.update(|cx| { - matches - .into_iter() - .filter_map(|mat| { - let command = commands.command(&mat.string)?; - let mut new_text = mat.string.clone(); - let requires_argument = command.requires_argument(); - if requires_argument { - new_text.push(' '); - } - - Some(project::Completion { - old_range: name_range.clone(), - documentation: Some(Documentation::SingleLine(command.description())), - new_text, - label: command.label(cx), - server_id: LanguageServerId(0), - lsp_completion: Default::default(), - show_new_completions_on_confirm: false, - confirm: None, - }) - }) - .collect() - }) - }) - } - - fn resolve_completions( - &self, - _: Model, - _: Vec, - _: Arc>>, - _: &mut ViewContext, - ) -> Task> { - Task::ready(Ok(true)) - } - - fn apply_additional_edits_for_completion( - &self, - _: Model, - _: project::Completion, - _: bool, - _: &mut ViewContext, - ) -> Task>> { - Task::ready(Ok(None)) - } - - fn is_completion_trigger( - &self, - buffer: &Model, - position: language::Anchor, - _text: &str, - _trigger_in_words: bool, - cx: &mut ViewContext, - ) -> bool { - let buffer = buffer.read(cx); - let position = position.to_point(buffer); - let line_start = Point::new(position.row, 0); - let mut lines = buffer.text_for_range(line_start..position).lines(); - if let Some(line) = lines.next() { - SlashCommandLine::parse(line).is_some() - } else { - false - } - } -} diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 5506f898c7..0e2c861a5b 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -27,10 +27,10 @@ pub mod search_command; pub mod tabs_command; pub(crate) struct SlashCommandCompletionProvider { - editor: WeakView, commands: Arc, cancel_flag: Mutex>, - workspace: WeakView, + editor: Option>, + workspace: Option>, } pub(crate) struct SlashCommandLine { @@ -42,9 +42,9 @@ pub(crate) struct SlashCommandLine { impl SlashCommandCompletionProvider { pub fn new( - editor: WeakView, commands: Arc, - workspace: WeakView, + editor: Option>, + workspace: Option>, ) -> Self { Self { cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))), @@ -98,6 +98,30 @@ impl SlashCommandCompletionProvider { new_text.push(' '); } + let confirm = editor.clone().zip(workspace.clone()).and_then( + |(editor, workspace)| { + (!requires_argument).then(|| { + let command_name = mat.string.clone(); + let command_range = command_range.clone(); + let editor = editor.clone(); + let workspace = workspace.clone(); + Arc::new(move |cx: &mut WindowContext| { + editor + .update(cx, |editor, cx| { + editor.run_command( + command_range.clone(), + &command_name, + None, + true, + workspace.clone(), + cx, + ); + }) + .ok(); + }) as Arc<_> + }) + }, + ); Some(project::Completion { old_range: name_range.clone(), documentation: Some(Documentation::SingleLine(command.description())), @@ -106,26 +130,7 @@ impl SlashCommandCompletionProvider { server_id: LanguageServerId(0), lsp_completion: Default::default(), show_new_completions_on_confirm: requires_argument, - confirm: (!requires_argument).then(|| { - let command_name = mat.string.clone(); - let command_range = command_range.clone(); - let editor = editor.clone(); - let workspace = workspace.clone(); - Arc::new(move |cx: &mut WindowContext| { - editor - .update(cx, |editor, cx| { - editor.run_command( - command_range.clone(), - &command_name, - None, - true, - workspace.clone(), - cx, - ); - }) - .ok(); - }) as Arc<_> - }), + confirm, }) }) .collect() @@ -160,34 +165,42 @@ impl SlashCommandCompletionProvider { Ok(completions .await? .into_iter() - .map(|arg| project::Completion { - old_range: argument_range.clone(), - label: CodeLabel::plain(arg.clone(), None), - new_text: arg.clone(), - documentation: None, - server_id: LanguageServerId(0), - lsp_completion: Default::default(), - show_new_completions_on_confirm: false, - confirm: Some(Arc::new({ - let command_name = command_name.clone(); - let command_range = command_range.clone(); - let editor = editor.clone(); - let workspace = workspace.clone(); - move |cx| { - editor - .update(cx, |editor, cx| { - editor.run_command( - command_range.clone(), - &command_name, - Some(&arg), - true, - workspace.clone(), - cx, - ); - }) - .ok(); - } - })), + .map(|command_argument| { + let confirm = + editor + .clone() + .zip(workspace.clone()) + .map(|(editor, workspace)| { + Arc::new({ + let command_range = command_range.clone(); + let command_name = command_name.clone(); + let command_argument = command_argument.clone(); + move |cx: &mut WindowContext| { + editor + .update(cx, |editor, cx| { + editor.run_command( + command_range.clone(), + &command_name, + Some(&command_argument), + true, + workspace.clone(), + cx, + ); + }) + .ok(); + } + }) as Arc<_> + }); + project::Completion { + old_range: argument_range.clone(), + label: CodeLabel::plain(command_argument.clone(), None), + new_text: command_argument.clone(), + documentation: None, + server_id: LanguageServerId(0), + lsp_completion: Default::default(), + show_new_completions_on_confirm: false, + confirm, + } }) .collect()) }) diff --git a/crates/assistant/src/slash_command/active_command.rs b/crates/assistant/src/slash_command/active_command.rs index 14cf86a825..8eea99420b 100644 --- a/crates/assistant/src/slash_command/active_command.rs +++ b/crates/assistant/src/slash_command/active_command.rs @@ -27,7 +27,7 @@ impl SlashCommand for ActiveSlashCommand { &self, _query: String, _cancel: std::sync::Arc, - _workspace: WeakView, + _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) diff --git a/crates/assistant/src/slash_command/default_command.rs b/crates/assistant/src/slash_command/default_command.rs index c7656849b1..56c3a44997 100644 --- a/crates/assistant/src/slash_command/default_command.rs +++ b/crates/assistant/src/slash_command/default_command.rs @@ -34,7 +34,7 @@ impl SlashCommand for DefaultSlashCommand { &self, _query: String, _cancellation_flag: Arc, - _workspace: WeakView, + _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) diff --git a/crates/assistant/src/slash_command/fetch_command.rs b/crates/assistant/src/slash_command/fetch_command.rs index 37483cbb1a..f91c156d61 100644 --- a/crates/assistant/src/slash_command/fetch_command.rs +++ b/crates/assistant/src/slash_command/fetch_command.rs @@ -62,7 +62,7 @@ impl SlashCommand for FetchSlashCommand { &self, _query: String, _cancel: Arc, - _workspace: WeakView, + _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { Task::ready(Ok(Vec::new())) diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index f347a87048..f9c9a66f1a 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -101,10 +101,10 @@ impl SlashCommand for FileSlashCommand { &self, query: String, cancellation_flag: Arc, - workspace: WeakView, + workspace: Option>, cx: &mut AppContext, ) -> Task>> { - let Some(workspace) = workspace.upgrade() else { + let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else { return Task::ready(Err(anyhow!("workspace was dropped"))); }; diff --git a/crates/assistant/src/slash_command/project_command.rs b/crates/assistant/src/slash_command/project_command.rs index 6d67f6f559..4adcc8ffd7 100644 --- a/crates/assistant/src/slash_command/project_command.rs +++ b/crates/assistant/src/slash_command/project_command.rs @@ -105,7 +105,7 @@ impl SlashCommand for ProjectSlashCommand { &self, _query: String, _cancel: Arc, - _workspace: WeakView, + _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) diff --git a/crates/assistant/src/slash_command/prompt_command.rs b/crates/assistant/src/slash_command/prompt_command.rs index c837493fb9..3e3041e170 100644 --- a/crates/assistant/src/slash_command/prompt_command.rs +++ b/crates/assistant/src/slash_command/prompt_command.rs @@ -31,7 +31,7 @@ impl SlashCommand for PromptSlashCommand { &self, query: String, _cancellation_flag: Arc, - _workspace: WeakView, + _workspace: Option>, cx: &mut AppContext, ) -> Task>> { let store = PromptStore::global(cx); diff --git a/crates/assistant/src/slash_command/rustdoc_command.rs b/crates/assistant/src/slash_command/rustdoc_command.rs index cf48dc28dc..164fc969af 100644 --- a/crates/assistant/src/slash_command/rustdoc_command.rs +++ b/crates/assistant/src/slash_command/rustdoc_command.rs @@ -119,7 +119,7 @@ impl SlashCommand for RustdocSlashCommand { &self, _query: String, _cancel: Arc, - _workspace: WeakView, + _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { Task::ready(Ok(Vec::new())) diff --git a/crates/assistant/src/slash_command/search_command.rs b/crates/assistant/src/slash_command/search_command.rs index 54498a35e4..195c5b1633 100644 --- a/crates/assistant/src/slash_command/search_command.rs +++ b/crates/assistant/src/slash_command/search_command.rs @@ -47,7 +47,7 @@ impl SlashCommand for SearchSlashCommand { &self, _query: String, _cancel: Arc, - _workspace: WeakView, + _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { Task::ready(Ok(Vec::new())) diff --git a/crates/assistant/src/slash_command/tabs_command.rs b/crates/assistant/src/slash_command/tabs_command.rs index 2350315a5a..1426b438f6 100644 --- a/crates/assistant/src/slash_command/tabs_command.rs +++ b/crates/assistant/src/slash_command/tabs_command.rs @@ -32,7 +32,7 @@ impl SlashCommand for TabsSlashCommand { &self, _query: String, _cancel: Arc, - _workspace: WeakView, + _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index c748b4fd96..056fe69b36 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -25,7 +25,7 @@ pub trait SlashCommand: 'static + Send + Sync { &self, query: String, cancel: Arc, - workspace: WeakView, + workspace: Option>, cx: &mut AppContext, ) -> Task>>; fn requires_argument(&self) -> bool; diff --git a/crates/extension/src/extension_slash_command.rs b/crates/extension/src/extension_slash_command.rs index 775c318631..fd1aefc3f7 100644 --- a/crates/extension/src/extension_slash_command.rs +++ b/crates/extension/src/extension_slash_command.rs @@ -39,7 +39,7 @@ impl SlashCommand for ExtensionSlashCommand { &self, _query: String, _cancel: Arc, - _workspace: WeakView, + _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { Task::ready(Ok(Vec::new()))