From 8df21f7bcd4d0f98968d5d23a1643c1c6f90842d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 14 Aug 2024 19:36:55 +0300 Subject: [PATCH] Fix slash command argument completion bugs (#16233) Release Notes: - N/A --------- Co-authored-by: Mikayla Maki --- crates/assistant/src/slash_command.rs | 72 +++++++++++++------ .../src/slash_command/diagnostics_command.rs | 1 + .../src/slash_command/docs_command.rs | 5 ++ .../src/slash_command/file_command.rs | 3 +- .../src/slash_command/prompt_command.rs | 8 ++- .../src/slash_command/tab_command.rs | 2 + .../src/assistant_slash_command.rs | 2 + .../extension/src/extension_slash_command.rs | 1 + 8 files changed, 67 insertions(+), 27 deletions(-) diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 25f0e4e89b..1d305cffb0 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -150,6 +150,7 @@ impl SlashCommandCompletionProvider { arguments: &[String], command_range: Range, argument_range: Range, + last_argument_range: Range, cx: &mut WindowContext, ) -> Task>> { let new_cancel_flag = Arc::new(AtomicBool::new(false)); @@ -180,24 +181,30 @@ impl SlashCommandCompletionProvider { .map(|(editor, workspace)| { Arc::new({ let mut completed_arguments = arguments.clone(); - completed_arguments.pop(); + if new_argument.replace_previous_arguments { + completed_arguments.clear(); + } else { + completed_arguments.pop(); + } completed_arguments.push(new_argument.new_text.clone()); let command_range = command_range.clone(); let command_name = command_name.clone(); - move |_: CompletionIntent, cx: &mut WindowContext| { - editor - .update(cx, |editor, cx| { - editor.run_command( - command_range.clone(), - &command_name, - &completed_arguments, - true, - workspace.clone(), - cx, - ); - }) - .ok(); + move |intent: CompletionIntent, cx: &mut WindowContext| { + if intent.is_complete() { + editor + .update(cx, |editor, cx| { + editor.run_command( + command_range.clone(), + &command_name, + &completed_arguments, + true, + workspace.clone(), + cx, + ); + }) + .ok(); + } } }) as Arc<_> }) @@ -211,7 +218,11 @@ impl SlashCommandCompletionProvider { } project::Completion { - old_range: argument_range.clone(), + old_range: if new_argument.replace_previous_arguments { + argument_range.clone() + } else { + last_argument_range.clone() + }, label: new_argument.label, new_text, documentation: None, @@ -237,7 +248,7 @@ impl CompletionProvider for SlashCommandCompletionProvider { _: editor::CompletionContext, cx: &mut ViewContext, ) -> Task>> { - let Some((name, arguments, command_range, argument_range)) = + let Some((name, arguments, command_range, last_argument_range)) = buffer.update(cx, |buffer, _cx| { let position = buffer_position.to_point(buffer); let line_start = Point::new(position.row, 0); @@ -254,31 +265,46 @@ impl CompletionProvider for SlashCommandCompletionProvider { ..buffer.anchor_after(command_range_end); let name = line[call.name.clone()].to_string(); - let (arguments, argument_range) = if let Some(argument) = call.arguments.last() { - let start = + let (arguments, last_argument_range) = if let Some(argument) = call.arguments.last() + { + let last_arg_start = buffer.anchor_after(Point::new(position.row, argument.start as u32)); + let first_arg_start = call.arguments.first().expect("we have the last element"); + let first_arg_start = + buffer.anchor_after(Point::new(position.row, first_arg_start.start as u32)); let arguments = call .arguments .iter() .filter_map(|argument| Some(line.get(argument.clone())?.to_string())) .collect::>(); - (Some(arguments), start..buffer_position) + let argument_range = first_arg_start..buffer_position; + ( + Some((arguments, argument_range)), + last_arg_start..buffer_position, + ) } else { let start = buffer.anchor_after(Point::new(position.row, call.name.start as u32)); (None, start..buffer_position) }; - Some((name, arguments, command_range, argument_range)) + Some((name, arguments, command_range, last_argument_range)) }) else { return Task::ready(Ok(Vec::new())); }; - if let Some(arguments) = arguments { - self.complete_command_argument(&name, &arguments, command_range, argument_range, cx) + if let Some((arguments, argument_range)) = arguments { + self.complete_command_argument( + &name, + &arguments, + command_range, + argument_range, + last_argument_range, + cx, + ) } else { - self.complete_command_name(&name, command_range, argument_range, cx) + self.complete_command_name(&name, command_range, last_argument_range, cx) } } diff --git a/crates/assistant/src/slash_command/diagnostics_command.rs b/crates/assistant/src/slash_command/diagnostics_command.rs index 01aefc5d2f..a23d0a098b 100644 --- a/crates/assistant/src/slash_command/diagnostics_command.rs +++ b/crates/assistant/src/slash_command/diagnostics_command.rs @@ -150,6 +150,7 @@ impl SlashCommand for DiagnosticsSlashCommand { label: completion.clone().into(), new_text: completion, run_command: true, + replace_previous_arguments: false, }) .collect()) }) diff --git a/crates/assistant/src/slash_command/docs_command.rs b/crates/assistant/src/slash_command/docs_command.rs index 4279487664..370805b090 100644 --- a/crates/assistant/src/slash_command/docs_command.rs +++ b/crates/assistant/src/slash_command/docs_command.rs @@ -182,6 +182,7 @@ impl SlashCommand for DocsSlashCommand { label: item.clone().into(), new_text: item.to_string(), run_command: true, + replace_previous_arguments: false, }) .collect() } @@ -194,6 +195,7 @@ impl SlashCommand for DocsSlashCommand { label: "No available docs providers.".into(), new_text: String::new(), run_command: false, + replace_previous_arguments: false, }]); } @@ -203,6 +205,7 @@ impl SlashCommand for DocsSlashCommand { label: provider.to_string().into(), new_text: provider.to_string(), run_command: false, + replace_previous_arguments: false, }) .collect()) } @@ -234,6 +237,7 @@ impl SlashCommand for DocsSlashCommand { label: format!("{package_name} (unindexed)").into(), new_text: format!("{package_name}"), run_command: true, + replace_previous_arguments: false, }) .collect::>(); items.extend(workspace_crate_completions); @@ -247,6 +251,7 @@ impl SlashCommand for DocsSlashCommand { .into(), new_text: provider.to_string(), run_command: false, + replace_previous_arguments: false, }]); } diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index 4ed38e91ef..e73fb158df 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -164,7 +164,8 @@ impl SlashCommand for FileSlashCommand { Some(ArgumentCompletion { label, new_text: text, - run_command: false, + run_command: true, + replace_previous_arguments: false, }) }) .collect()) diff --git a/crates/assistant/src/slash_command/prompt_command.rs b/crates/assistant/src/slash_command/prompt_command.rs index 1a48d94d7a..783d120dad 100644 --- a/crates/assistant/src/slash_command/prompt_command.rs +++ b/crates/assistant/src/slash_command/prompt_command.rs @@ -35,7 +35,7 @@ impl SlashCommand for PromptSlashCommand { cx: &mut WindowContext, ) -> Task>> { let store = PromptStore::global(cx); - let query = arguments.last().cloned().unwrap_or_default(); + let query = arguments.to_owned().join(" "); cx.background_executor().spawn(async move { let prompts = store.await?.search(query).await; Ok(prompts @@ -46,6 +46,7 @@ impl SlashCommand for PromptSlashCommand { label: prompt_title.clone().into(), new_text: prompt_title, run_command: true, + replace_previous_arguments: true, }) }) .collect()) @@ -59,12 +60,13 @@ impl SlashCommand for PromptSlashCommand { _delegate: Option>, cx: &mut WindowContext, ) -> Task> { - let Some(title) = arguments.first() else { + let title = arguments.to_owned().join(" "); + if title.trim().is_empty() { return Task::ready(Err(anyhow!("missing prompt name"))); }; let store = PromptStore::global(cx); - let title = SharedString::from(title.to_string()); + let title = SharedString::from(title.clone()); let prompt = cx.background_executor().spawn({ let title = title.clone(); async move { diff --git a/crates/assistant/src/slash_command/tab_command.rs b/crates/assistant/src/slash_command/tab_command.rs index 6bc96ce34b..db9cd76cdc 100644 --- a/crates/assistant/src/slash_command/tab_command.rs +++ b/crates/assistant/src/slash_command/tab_command.rs @@ -76,6 +76,7 @@ impl SlashCommand for TabSlashCommand { Some(ArgumentCompletion { label: path_string.clone().into(), new_text: path_string, + replace_previous_arguments: false, run_command, }) }); @@ -83,6 +84,7 @@ impl SlashCommand for TabSlashCommand { Ok(Some(ArgumentCompletion { label: ALL_TABS_COMPLETION_ITEM.into(), new_text: ALL_TABS_COMPLETION_ITEM.to_owned(), + replace_previous_arguments: false, run_command: true, }) .into_iter() diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index b37e7a1c33..00671338df 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -23,6 +23,8 @@ pub struct ArgumentCompletion { pub new_text: String, /// Whether the command should be run when accepting this completion. pub run_command: bool, + /// Whether to replace the all arguments, or whether to treat this as an independent argument. + pub replace_previous_arguments: bool, } pub trait SlashCommand: 'static + Send + Sync { diff --git a/crates/extension/src/extension_slash_command.rs b/crates/extension/src/extension_slash_command.rs index fffc9307f8..d0ec466767 100644 --- a/crates/extension/src/extension_slash_command.rs +++ b/crates/extension/src/extension_slash_command.rs @@ -66,6 +66,7 @@ impl SlashCommand for ExtensionSlashCommand { .map(|completion| ArgumentCompletion { label: completion.label.into(), new_text: completion.new_text, + replace_previous_arguments: false, run_command: completion.run_command, }) .collect(),