Run slash commands both on enter and on argument completion that requires it (#16283)

Kirill Bulatov created

Release Notes:

- N/A

Change summary

crates/assistant/src/assistant_panel.rs                       |  6 
crates/assistant/src/slash_command.rs                         | 36 ++--
crates/assistant/src/slash_command/diagnostics_command.rs     |  4 
crates/assistant/src/slash_command/tab_command.rs             | 15 +
crates/assistant/src/slash_command/terminal_command.rs        |  4 
crates/assistant_slash_command/src/assistant_slash_command.rs |  3 
crates/collab_ui/src/chat_panel/message_editor.rs             |  1 
crates/editor/src/editor.rs                                   | 17 +
crates/project/src/project.rs                                 |  9 
9 files changed, 61 insertions(+), 34 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -2081,7 +2081,7 @@ impl ContextEditor {
                     }
 
                     editor.insert(&format!("/{name}"), cx);
-                    if command.requires_argument() {
+                    if command.accepts_arguments() {
                         editor.insert(" ", cx);
                         editor.show_completions(&ShowCompletions::default(), cx);
                     }
@@ -2094,6 +2094,10 @@ impl ContextEditor {
     }
 
     pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
+        if self.editor.read(cx).has_active_completions_menu() {
+            return;
+        }
+
         let selections = self.editor.read(cx).selections.disjoint_anchors();
         let mut commands_by_range = HashMap::default();
         let workspace = self.workspace.clone();

crates/assistant/src/slash_command.rs 🔗

@@ -97,20 +97,25 @@ impl SlashCommandCompletionProvider {
                         let command = commands.command(&mat.string)?;
                         let mut new_text = mat.string.clone();
                         let requires_argument = command.requires_argument();
-                        if requires_argument {
+                        let accepts_arguments = command.accepts_arguments();
+                        if requires_argument || accepts_arguments {
                             new_text.push(' ');
                         }
 
-                        let confirm = editor.clone().zip(workspace.clone()).and_then(
-                            |(editor, workspace)| {
-                                (!requires_argument).then(|| {
+                        let confirm =
+                            editor
+                                .clone()
+                                .zip(workspace.clone())
+                                .map(|(editor, workspace)| {
                                     let command_name = mat.string.clone();
                                     let command_range = command_range.clone();
                                     let editor = editor.clone();
                                     let workspace = workspace.clone();
                                     Arc::new(
                                         move |intent: CompletionIntent, cx: &mut WindowContext| {
-                                            if intent.is_complete() {
+                                            if !requires_argument
+                                                && (!accepts_arguments || intent.is_complete())
+                                            {
                                                 editor
                                                     .update(cx, |editor, cx| {
                                                         editor.run_command(
@@ -123,12 +128,13 @@ impl SlashCommandCompletionProvider {
                                                         );
                                                     })
                                                     .ok();
+                                                false
+                                            } else {
+                                                requires_argument || accepts_arguments
                                             }
                                         },
                                     ) as Arc<_>
-                                })
-                            },
-                        );
+                                });
                         Some(project::Completion {
                             old_range: name_range.clone(),
                             documentation: Some(Documentation::SingleLine(command.description())),
@@ -136,7 +142,6 @@ impl SlashCommandCompletionProvider {
                             label: command.label(cx),
                             server_id: LanguageServerId(0),
                             lsp_completion: Default::default(),
-                            show_new_completions_on_confirm: requires_argument,
                             confirm,
                         })
                     })
@@ -175,7 +180,7 @@ impl SlashCommandCompletionProvider {
                     .await?
                     .into_iter()
                     .map(|new_argument| {
-                        let confirm = if new_argument.run_command {
+                        let confirm =
                             editor
                                 .clone()
                                 .zip(workspace.clone())
@@ -192,7 +197,7 @@ impl SlashCommandCompletionProvider {
                                         let command_range = command_range.clone();
                                         let command_name = command_name.clone();
                                         move |intent: CompletionIntent, cx: &mut WindowContext| {
-                                            if intent.is_complete() {
+                                            if new_argument.run_command || intent.is_complete() {
                                                 editor
                                                     .update(cx, |editor, cx| {
                                                         editor.run_command(
@@ -205,13 +210,13 @@ impl SlashCommandCompletionProvider {
                                                         );
                                                     })
                                                     .ok();
+                                                false
+                                            } else {
+                                                !new_argument.run_command
                                             }
                                         }
                                     }) as Arc<_>
-                                })
-                        } else {
-                            None
-                        };
+                                });
 
                         let mut new_text = new_argument.new_text.clone();
                         if !new_argument.run_command {
@@ -229,7 +234,6 @@ impl SlashCommandCompletionProvider {
                             documentation: None,
                             server_id: LanguageServerId(0),
                             lsp_completion: Default::default(),
-                            show_new_completions_on_confirm: !new_argument.run_command,
                             confirm,
                         }
                     })

crates/assistant/src/slash_command/tab_command.rs 🔗

@@ -39,6 +39,10 @@ impl SlashCommand for TabSlashCommand {
         false
     }
 
+    fn accepts_arguments(&self) -> bool {
+        true
+    }
+
     fn complete_argument(
         self: Arc<Self>,
         arguments: &[String],
@@ -94,15 +98,16 @@ impl SlashCommand for TabSlashCommand {
                 })
             });
 
-            let active_item_completion = active_item_path.as_deref().map(|active_item_path| {
-                let path_string = active_item_path.to_string_lossy().to_string();
-                ArgumentCompletion {
+            let active_item_completion = active_item_path
+                .as_deref()
+                .map(|active_item_path| active_item_path.to_string_lossy().to_string())
+                .filter(|path_string| !argument_set.contains(path_string))
+                .map(|path_string| ArgumentCompletion {
                     label: path_string.clone().into(),
                     new_text: path_string,
                     replace_previous_arguments: false,
                     run_command,
-                }
-            });
+                });
 
             Ok(active_item_completion
                 .into_iter()

crates/assistant_slash_command/src/assistant_slash_command.rs 🔗

@@ -42,6 +42,9 @@ pub trait SlashCommand: 'static + Send + Sync {
         cx: &mut WindowContext,
     ) -> Task<Result<Vec<ArgumentCompletion>>>;
     fn requires_argument(&self) -> bool;
+    fn accepts_arguments(&self) -> bool {
+        self.requires_argument()
+    }
     fn run(
         self: Arc<Self>,
         arguments: &[String],

crates/collab_ui/src/chat_panel/message_editor.rs 🔗

@@ -314,7 +314,6 @@ impl MessageEditor {
                     server_id: LanguageServerId(0), // TODO: Make this optional or something?
                     lsp_completion: Default::default(), // TODO: Make this optional or something?
                     confirm: None,
-                    show_new_completions_on_confirm: false,
                 }
             })
             .collect()

crates/editor/src/editor.rs 🔗

@@ -4379,11 +4379,11 @@ impl Editor {
             this.refresh_inline_completion(true, cx);
         });
 
-        if let Some(confirm) = completion.confirm.as_ref() {
-            (confirm)(intent, cx);
-        }
-
-        if completion.show_new_completions_on_confirm {
+        let show_new_completions_on_confirm = completion
+            .confirm
+            .as_ref()
+            .map_or(false, |confirm| confirm(intent, cx));
+        if show_new_completions_on_confirm {
             self.show_completions(&ShowCompletions { trigger: None }, cx);
         }
 
@@ -11926,6 +11926,12 @@ impl Editor {
         let bounds = self.last_bounds?;
         Some(element::gutter_bounds(bounds, self.gutter_dimensions))
     }
+
+    pub fn has_active_completions_menu(&self) -> bool {
+        self.context_menu.read().as_ref().map_or(false, |menu| {
+            menu.visible() && matches!(menu, ContextMenu::Completions(_))
+        })
+    }
 }
 
 fn hunks_for_selections(
@@ -12141,7 +12147,6 @@ fn snippet_completions(
                     ..Default::default()
                 },
                 confirm: None,
-                show_new_completions_on_confirm: false,
             })
         })
         .collect()

crates/project/src/project.rs 🔗

@@ -450,9 +450,10 @@ pub struct Completion {
     /// The raw completion provided by the language server.
     pub lsp_completion: lsp::CompletionItem,
     /// An optional callback to invoke when this completion is confirmed.
-    pub confirm: Option<Arc<dyn Send + Sync + Fn(CompletionIntent, &mut WindowContext)>>,
-    /// If true, the editor will show a new completion menu after this completion is confirmed.
-    pub show_new_completions_on_confirm: bool,
+    /// Returns, whether new completions should be retriggered after the current one.
+    /// If `true` is returned, the editor will show a new completion menu after this completion is confirmed.
+    /// if no confirmation is provided or `false` is returned, the completion will be committed.
+    pub confirm: Option<Arc<dyn Send + Sync + Fn(CompletionIntent, &mut WindowContext) -> bool>>,
 }
 
 impl std::fmt::Debug for Completion {
@@ -9128,7 +9129,6 @@ impl Project {
                         filter_range: Default::default(),
                     },
                     confirm: None,
-                    show_new_completions_on_confirm: false,
                 },
                 false,
                 cx,
@@ -10765,7 +10765,6 @@ async fn populate_labels_for_completions(
             documentation,
             lsp_completion,
             confirm: None,
-            show_new_completions_on_confirm: false,
         })
     }
 }