From 68accaeb00be484448b0e6e55a65a0a860ce9a00 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 5 Jul 2024 13:29:17 -0400 Subject: [PATCH] assistant: Improve `/docs` argument completions (#13876) This PR improves the completions for arguments in the `/docs` slash command. We achieved this by extending the `complete_argument` method on the `SlashCommand` trait to return a `Vec` instead of a `Vec`. In addition to the completion `label`, `ArgumentCompletion` has two new fields that are can be used to customize the completion behavior: - `new_text`: The actual text that will be inserted when the completion is accepted, which may be different from what is shown by the completion label. - `run_command`: Whether the command is run when the completion is accepted. This can be set to `false` to allow accepting a completion without running the command. Release Notes: - N/A --------- Co-authored-by: Antonio --- crates/assistant/src/slash_command.rs | 21 ++++++++++++----- .../src/slash_command/active_command.rs | 3 ++- .../src/slash_command/default_command.rs | 4 ++-- .../src/slash_command/diagnostics_command.rs | 13 ++++++++--- .../src/slash_command/docs_command.rs | 23 +++++++++++++++---- .../src/slash_command/fetch_command.rs | 6 +++-- .../src/slash_command/file_command.rs | 14 +++++++---- .../src/slash_command/now_command.rs | 6 +++-- .../src/slash_command/project_command.rs | 4 ++-- .../src/slash_command/prompt_command.rs | 13 ++++++++--- .../src/slash_command/search_command.rs | 4 ++-- .../src/slash_command/tabs_command.rs | 3 ++- .../src/slash_command/term_command.rs | 12 +++++++--- .../src/assistant_slash_command.rs | 12 +++++++++- .../extension/src/extension_slash_command.rs | 17 +++++++++++--- 15 files changed, 115 insertions(+), 40 deletions(-) diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 37678e0fc604e0e1069085021c90956ba0ea75ba..ebb563313ac8d76d6be8c2c1c2f8319cc5db2bac 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -170,7 +170,7 @@ impl SlashCommandCompletionProvider { .await? .into_iter() .map(|command_argument| { - let confirm = + let confirm = if command_argument.run_command { editor .clone() .zip(workspace.clone()) @@ -178,7 +178,7 @@ impl SlashCommandCompletionProvider { Arc::new({ let command_range = command_range.clone(); let command_name = command_name.clone(); - let command_argument = command_argument.clone(); + let command_argument = command_argument.new_text.clone(); move |cx: &mut WindowContext| { editor .update(cx, |editor, cx| { @@ -194,15 +194,24 @@ impl SlashCommandCompletionProvider { .ok(); } }) as Arc<_> - }); + }) + } else { + None + }; + + let mut new_text = command_argument.new_text.clone(); + if !command_argument.run_command { + new_text.push(' '); + } + project::Completion { old_range: argument_range.clone(), - label: CodeLabel::plain(command_argument.clone(), None), - new_text: command_argument.clone(), + label: CodeLabel::plain(command_argument.label, None), + new_text, documentation: None, server_id: LanguageServerId(0), lsp_completion: Default::default(), - show_new_completions_on_confirm: false, + show_new_completions_on_confirm: !command_argument.run_command, confirm, } }) diff --git a/crates/assistant/src/slash_command/active_command.rs b/crates/assistant/src/slash_command/active_command.rs index f3ec3a45e9dd8a5b086f5d285e2cc30edfca5b96..0f46937560f930ec631b86bec3e20b3492b6d5f0 100644 --- a/crates/assistant/src/slash_command/active_command.rs +++ b/crates/assistant/src/slash_command/active_command.rs @@ -4,6 +4,7 @@ use super::{ SlashCommand, SlashCommandOutput, }; use anyhow::{anyhow, Result}; +use assistant_slash_command::ArgumentCompletion; use editor::Editor; use gpui::{AppContext, Task, WeakView}; use language::LspAdapterDelegate; @@ -33,7 +34,7 @@ impl SlashCommand for ActiveSlashCommand { _cancel: Arc, _workspace: Option>, _cx: &mut AppContext, - ) -> Task>> { + ) -> 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 44ffdfcd11f1d4322d1f68df81cc5efa2ec21d6f..ccc9d1fbdb095358661bffd07a5394150c6b4f71 100644 --- a/crates/assistant/src/slash_command/default_command.rs +++ b/crates/assistant/src/slash_command/default_command.rs @@ -1,7 +1,7 @@ use super::{SlashCommand, SlashCommandOutput}; use crate::prompt_library::PromptStore; use anyhow::{anyhow, Result}; -use assistant_slash_command::SlashCommandOutputSection; +use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection}; use gpui::{AppContext, Task, WeakView}; use language::LspAdapterDelegate; use std::{ @@ -36,7 +36,7 @@ impl SlashCommand for DefaultSlashCommand { _cancellation_flag: Arc, _workspace: Option>, _cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) } diff --git a/crates/assistant/src/slash_command/diagnostics_command.rs b/crates/assistant/src/slash_command/diagnostics_command.rs index ab01b3c8031cf5afc15f38a31ac645370b1e21cb..20e712803b1af8966fa175135c3a0a6c349f6fe7 100644 --- a/crates/assistant/src/slash_command/diagnostics_command.rs +++ b/crates/assistant/src/slash_command/diagnostics_command.rs @@ -1,6 +1,6 @@ use super::{create_label_for_command, SlashCommand, SlashCommandOutput}; use anyhow::{anyhow, Result}; -use assistant_slash_command::SlashCommandOutputSection; +use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection}; use fuzzy::{PathMatch, StringMatchCandidate}; use gpui::{AppContext, Model, Task, View, WeakView}; use language::{ @@ -108,7 +108,7 @@ impl SlashCommand for DiagnosticsSlashCommand { cancellation_flag: Arc, workspace: Option>, cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else { return Task::ready(Err(anyhow!("workspace was dropped"))); }; @@ -143,7 +143,14 @@ impl SlashCommand for DiagnosticsSlashCommand { .map(|candidate| candidate.string), ); - Ok(matches) + Ok(matches + .into_iter() + .map(|completion| ArgumentCompletion { + label: completion.clone(), + new_text: completion, + run_command: true, + }) + .collect()) }) } diff --git a/crates/assistant/src/slash_command/docs_command.rs b/crates/assistant/src/slash_command/docs_command.rs index c66e68bef1d14e9246a4b6458dabe507cccfb123..9b27635fd23546599cad9d4c71df8a0f6eb671a8 100644 --- a/crates/assistant/src/slash_command/docs_command.rs +++ b/crates/assistant/src/slash_command/docs_command.rs @@ -3,7 +3,9 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; use anyhow::{anyhow, bail, Result}; -use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection}; +use assistant_slash_command::{ + ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, +}; use gpui::{AppContext, Model, Task, WeakView}; use indexed_docs::{ IndexedDocsRegistry, IndexedDocsStore, LocalProvider, PackageName, ProviderId, RustdocIndexer, @@ -92,7 +94,7 @@ impl SlashCommand for DocsSlashCommand { _cancel: Arc, workspace: Option>, cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { self.ensure_rustdoc_provider_is_registered(workspace, cx); let indexed_docs_registry = IndexedDocsRegistry::global(cx); @@ -107,10 +109,17 @@ impl SlashCommand for DocsSlashCommand { /// /// We will likely want to extend `complete_argument` with support for replacing just /// a particular range of the argument when a completion is accepted. - fn prefix_with_provider(provider: ProviderId, items: Vec) -> Vec { + fn prefix_with_provider( + provider: ProviderId, + items: Vec, + ) -> Vec { items .into_iter() - .map(|item| format!("{provider} {item}")) + .map(|item| ArgumentCompletion { + label: item.clone(), + new_text: format!("{provider} {item}"), + run_command: true, + }) .collect() } @@ -119,7 +128,11 @@ impl SlashCommand for DocsSlashCommand { let providers = indexed_docs_registry.list_providers(); Ok(providers .into_iter() - .map(|provider| provider.to_string()) + .map(|provider| ArgumentCompletion { + label: provider.to_string(), + new_text: provider.to_string(), + run_command: false, + }) .collect()) } DocsSlashCommandArgs::SearchPackageDocs { diff --git a/crates/assistant/src/slash_command/fetch_command.rs b/crates/assistant/src/slash_command/fetch_command.rs index 7a8230186fbd92f8b11f9f67112b4745ed945a44..547cacf55974dfefa9611d4199e7de8f52c1758a 100644 --- a/crates/assistant/src/slash_command/fetch_command.rs +++ b/crates/assistant/src/slash_command/fetch_command.rs @@ -4,7 +4,9 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; use anyhow::{anyhow, bail, Context, Result}; -use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection}; +use assistant_slash_command::{ + ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, +}; use futures::AsyncReadExt; use gpui::{AppContext, Task, WeakView}; use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler}; @@ -119,7 +121,7 @@ impl SlashCommand for FetchSlashCommand { _cancel: Arc, _workspace: Option>, _cx: &mut AppContext, - ) -> Task>> { + ) -> 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 8acf2d136900142a0a10ad7bdf2088d01f2dbca3..d5d5662914a52f3a5f3d4020124c4ca5af0b5032 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -1,6 +1,6 @@ use super::{diagnostics_command::write_single_file_diagnostics, SlashCommand, SlashCommandOutput}; use anyhow::{anyhow, Result}; -use assistant_slash_command::SlashCommandOutputSection; +use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection}; use fuzzy::PathMatch; use gpui::{AppContext, Model, Task, View, WeakView}; use language::{BufferSnapshot, LineEnding, LspAdapterDelegate}; @@ -105,7 +105,7 @@ impl SlashCommand for FileSlashCommand { cancellation_flag: Arc, workspace: Option>, cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else { return Task::ready(Err(anyhow!("workspace was dropped"))); }; @@ -116,11 +116,17 @@ impl SlashCommand for FileSlashCommand { .await .into_iter() .map(|path_match| { - format!( + let text = format!( "{}{}", path_match.path_prefix, path_match.path.to_string_lossy() - ) + ); + + ArgumentCompletion { + label: text.clone(), + new_text: text, + run_command: true, + } }) .collect()) }) diff --git a/crates/assistant/src/slash_command/now_command.rs b/crates/assistant/src/slash_command/now_command.rs index 108a43027eddc3dd379143d443e655d5be9b3c93..c7bacf8746fa830376ebf3589dd3fcd2b920a212 100644 --- a/crates/assistant/src/slash_command/now_command.rs +++ b/crates/assistant/src/slash_command/now_command.rs @@ -2,7 +2,9 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; use anyhow::Result; -use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection}; +use assistant_slash_command::{ + ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, +}; use chrono::Local; use gpui::{AppContext, Task, WeakView}; use language::LspAdapterDelegate; @@ -34,7 +36,7 @@ impl SlashCommand for NowSlashCommand { _cancel: Arc, _workspace: Option>, _cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { Task::ready(Ok(Vec::new())) } diff --git a/crates/assistant/src/slash_command/project_command.rs b/crates/assistant/src/slash_command/project_command.rs index b51c051e1180c1b2d9f2534d16744b424d7563b5..476e60c5d48fe18642e8133d81578b491200f7ef 100644 --- a/crates/assistant/src/slash_command/project_command.rs +++ b/crates/assistant/src/slash_command/project_command.rs @@ -1,6 +1,6 @@ use super::{SlashCommand, SlashCommandOutput}; use anyhow::{anyhow, Context, Result}; -use assistant_slash_command::SlashCommandOutputSection; +use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection}; use fs::Fs; use gpui::{AppContext, Model, Task, WeakView}; use language::LspAdapterDelegate; @@ -107,7 +107,7 @@ impl SlashCommand for ProjectSlashCommand { _cancel: Arc, _workspace: Option>, _cx: &mut AppContext, - ) -> Task>> { + ) -> 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 ac4d77789e438ee500a9863c85eedb422a42d608..1edf2d51df084fdd4cb513df88bc350e3546446e 100644 --- a/crates/assistant/src/slash_command/prompt_command.rs +++ b/crates/assistant/src/slash_command/prompt_command.rs @@ -1,7 +1,7 @@ use super::{SlashCommand, SlashCommandOutput}; use crate::prompt_library::PromptStore; use anyhow::{anyhow, Context, Result}; -use assistant_slash_command::SlashCommandOutputSection; +use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection}; use gpui::{AppContext, Task, WeakView}; use language::LspAdapterDelegate; use std::sync::{atomic::AtomicBool, Arc}; @@ -33,13 +33,20 @@ impl SlashCommand for PromptSlashCommand { _cancellation_flag: Arc, _workspace: Option>, cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { let store = PromptStore::global(cx); cx.background_executor().spawn(async move { let prompts = store.await?.search(query).await; Ok(prompts .into_iter() - .filter_map(|prompt| Some(prompt.title?.to_string())) + .filter_map(|prompt| { + let prompt_title = prompt.title?.to_string(); + Some(ArgumentCompletion { + label: prompt_title.clone(), + new_text: prompt_title, + run_command: true, + }) + }) .collect()) }) } diff --git a/crates/assistant/src/slash_command/search_command.rs b/crates/assistant/src/slash_command/search_command.rs index 6fa5a16bc969997430f94abbd8b6c1168f3ef4dd..cdf1da7a9b7c9b12d894fef19e55b963fb445a68 100644 --- a/crates/assistant/src/slash_command/search_command.rs +++ b/crates/assistant/src/slash_command/search_command.rs @@ -4,7 +4,7 @@ use super::{ SlashCommand, SlashCommandOutput, }; use anyhow::Result; -use assistant_slash_command::SlashCommandOutputSection; +use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection}; use gpui::{AppContext, Task, WeakView}; use language::{CodeLabel, LineEnding, LspAdapterDelegate}; use semantic_index::SemanticIndex; @@ -46,7 +46,7 @@ impl SlashCommand for SearchSlashCommand { _cancel: Arc, _workspace: Option>, _cx: &mut AppContext, - ) -> Task>> { + ) -> 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 31a66b1c7733523e36c9211ca4656b9688eb0285..78be293bd7c7d195df11dd2a04ea395d2c6af147 100644 --- a/crates/assistant/src/slash_command/tabs_command.rs +++ b/crates/assistant/src/slash_command/tabs_command.rs @@ -4,6 +4,7 @@ use super::{ SlashCommand, SlashCommandOutput, }; use anyhow::{anyhow, Result}; +use assistant_slash_command::ArgumentCompletion; use collections::HashMap; use editor::Editor; use gpui::{AppContext, Entity, Task, WeakView}; @@ -37,7 +38,7 @@ impl SlashCommand for TabsSlashCommand { _cancel: Arc, _workspace: Option>, _cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) } diff --git a/crates/assistant/src/slash_command/term_command.rs b/crates/assistant/src/slash_command/term_command.rs index 60619afcf5bf6d173dcf63fb0ff5ac42647bc485..09b7323a02503d3b46ba32e70a8aa2999ea6f8c6 100644 --- a/crates/assistant/src/slash_command/term_command.rs +++ b/crates/assistant/src/slash_command/term_command.rs @@ -2,7 +2,9 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; use anyhow::Result; -use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection}; +use assistant_slash_command::{ + ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, +}; use gpui::{AppContext, Task, WeakView}; use language::{CodeLabel, LspAdapterDelegate}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; @@ -42,8 +44,12 @@ impl SlashCommand for TermSlashCommand { _cancel: Arc, _workspace: Option>, _cx: &mut AppContext, - ) -> Task>> { - Task::ready(Ok(vec![LINE_COUNT_ARG.to_string()])) + ) -> Task>> { + Task::ready(Ok(vec![ArgumentCompletion { + label: LINE_COUNT_ARG.to_string(), + new_text: LINE_COUNT_ARG.to_string(), + run_command: true, + }])) } fn run( diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index c468d134fe2b869272531918b7b044768fe6942f..d361f49d424e33d4c71f1845a824a815f86dc893 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -15,6 +15,16 @@ pub fn init(cx: &mut AppContext) { SlashCommandRegistry::default_global(cx); } +#[derive(Debug)] +pub struct ArgumentCompletion { + /// The label to display for this completion. + pub label: String, + /// The new text that should be inserted into the command when this completion is accepted. + pub new_text: String, + /// Whether the command should be run when accepting this completion. + pub run_command: bool, +} + pub trait SlashCommand: 'static + Send + Sync { fn name(&self) -> String; fn label(&self, _cx: &AppContext) -> CodeLabel { @@ -28,7 +38,7 @@ pub trait SlashCommand: 'static + Send + Sync { cancel: Arc, workspace: Option>, cx: &mut AppContext, - ) -> Task>>; + ) -> Task>>; fn requires_argument(&self) -> bool; fn run( self: Arc, diff --git a/crates/extension/src/extension_slash_command.rs b/crates/extension/src/extension_slash_command.rs index e10007bfc2eec67c3c039bcdcb8be1a0cc994850..3c8a9659846ffb6f750fefac04ba769d9c29b5d3 100644 --- a/crates/extension/src/extension_slash_command.rs +++ b/crates/extension/src/extension_slash_command.rs @@ -1,7 +1,9 @@ use std::sync::{atomic::AtomicBool, Arc}; use anyhow::{anyhow, Result}; -use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection}; +use assistant_slash_command::{ + ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, +}; use futures::FutureExt; use gpui::{AppContext, Task, WeakView, WindowContext}; use language::LspAdapterDelegate; @@ -41,7 +43,7 @@ impl SlashCommand for ExtensionSlashCommand { _cancel: Arc, _workspace: Option>, cx: &mut AppContext, - ) -> Task>> { + ) -> Task>> { cx.background_executor().spawn(async move { self.extension .call({ @@ -57,7 +59,16 @@ impl SlashCommand for ExtensionSlashCommand { .await? .map_err(|e| anyhow!("{}", e))?; - anyhow::Ok(completions) + anyhow::Ok( + completions + .into_iter() + .map(|completion| ArgumentCompletion { + label: completion.clone(), + new_text: completion, + run_command: true, + }) + .collect(), + ) } .boxed() }