Allow completing slash command arguments from extensions (#13240)

Marshall Bowers created

This PR extends the extension API with support for completing slash
command arguments for slash commands defined in extensions.

Release Notes:

- N/A

Change summary

crates/assistant/src/slash_command/active_command.rs          |  5 
crates/assistant/src/slash_command/default_command.rs         |  2 
crates/assistant/src/slash_command/diagnostics_command.rs     |  2 
crates/assistant/src/slash_command/fetch_command.rs           |  2 
crates/assistant/src/slash_command/file_command.rs            |  2 
crates/assistant/src/slash_command/now_command.rs             |  2 
crates/assistant/src/slash_command/project_command.rs         |  2 
crates/assistant/src/slash_command/prompt_command.rs          |  2 
crates/assistant/src/slash_command/rustdoc_command.rs         |  2 
crates/assistant/src/slash_command/search_command.rs          |  2 
crates/assistant/src/slash_command/tabs_command.rs            |  2 
crates/assistant_slash_command/src/assistant_slash_command.rs |  2 
crates/extension/src/extension_slash_command.rs               | 29 ++++
crates/extension/src/wasm_host/wit.rs                         | 15 ++
crates/extension_api/src/extension_api.rs                     | 18 ++
crates/extension_api/wit/since_v0.0.7/extension.wit           |  5 
extensions/gleam/src/gleam.rs                                 | 15 ++
17 files changed, 90 insertions(+), 19 deletions(-)

Detailed changes

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

@@ -6,6 +6,7 @@ use anyhow::{anyhow, Result};
 use editor::Editor;
 use gpui::{AppContext, Task, WeakView};
 use language::LspAdapterDelegate;
+use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 use ui::WindowContext;
 use workspace::Workspace;
@@ -26,9 +27,9 @@ impl SlashCommand for ActiveSlashCommand {
     }
 
     fn complete_argument(
-        &self,
+        self: Arc<Self>,
         _query: String,
-        _cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
+        _cancel: Arc<AtomicBool>,
         _workspace: Option<WeakView<Workspace>>,
         _cx: &mut AppContext,
     ) -> Task<Result<Vec<String>>> {

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

@@ -31,7 +31,7 @@ impl SlashCommand for TabsSlashCommand {
     }
 
     fn complete_argument(
-        &self,
+        self: Arc<Self>,
         _query: String,
         _cancel: Arc<std::sync::atomic::AtomicBool>,
         _workspace: Option<WeakView<Workspace>>,

crates/assistant_slash_command/src/assistant_slash_command.rs 🔗

@@ -23,7 +23,7 @@ pub trait SlashCommand: 'static + Send + Sync {
     fn description(&self) -> String;
     fn menu_text(&self) -> String;
     fn complete_argument(
-        &self,
+        self: Arc<Self>,
         query: String,
         cancel: Arc<AtomicBool>,
         workspace: Option<WeakView<Workspace>>,

crates/extension/src/extension_slash_command.rs 🔗

@@ -36,13 +36,34 @@ impl SlashCommand for ExtensionSlashCommand {
     }
 
     fn complete_argument(
-        &self,
-        _query: String,
+        self: Arc<Self>,
+        query: String,
         _cancel: Arc<AtomicBool>,
         _workspace: Option<WeakView<Workspace>>,
-        _cx: &mut AppContext,
+        cx: &mut AppContext,
     ) -> Task<Result<Vec<String>>> {
-        Task::ready(Ok(Vec::new()))
+        cx.background_executor().spawn(async move {
+            self.extension
+                .call({
+                    let this = self.clone();
+                    move |extension, store| {
+                        async move {
+                            let completions = extension
+                                .call_complete_slash_command_argument(
+                                    store,
+                                    &this.command,
+                                    query.as_ref(),
+                                )
+                                .await?
+                                .map_err(|e| anyhow!("{}", e))?;
+
+                            anyhow::Ok(completions)
+                        }
+                        .boxed()
+                    }
+                })
+                .await
+        })
     }
 
     fn run(

crates/extension/src/wasm_host/wit.rs 🔗

@@ -257,6 +257,21 @@ impl Extension {
         }
     }
 
+    pub async fn call_complete_slash_command_argument(
+        &self,
+        store: &mut Store<WasmState>,
+        command: &SlashCommand,
+        query: &str,
+    ) -> Result<Result<Vec<String>, String>> {
+        match self {
+            Extension::V007(ext) => {
+                ext.call_complete_slash_command_argument(store, command, query)
+                    .await
+            }
+            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
+        }
+    }
+
     pub async fn call_run_slash_command(
         &self,
         store: &mut Store<WasmState>,

crates/extension_api/src/extension_api.rs 🔗

@@ -108,7 +108,16 @@ pub trait Extension: Send + Sync {
         None
     }
 
-    /// Runs the given slash command.
+    /// Returns the completions that should be shown when completing the provided slash command with the given query.
+    fn complete_slash_command_argument(
+        &self,
+        _command: SlashCommand,
+        _query: String,
+    ) -> Result<Vec<String>, String> {
+        Ok(Vec::new())
+    }
+
+    /// Returns the output from running the provided slash command.
     fn run_slash_command(
         &self,
         _command: SlashCommand,
@@ -225,6 +234,13 @@ impl wit::Guest for Component {
         Ok(labels)
     }
 
+    fn complete_slash_command_argument(
+        command: SlashCommand,
+        query: String,
+    ) -> Result<Vec<String>, String> {
+        extension().complete_slash_command_argument(command, query)
+    }
+
     fn run_slash_command(
         command: SlashCommand,
         argument: Option<String>,

crates/extension_api/wit/since_v0.0.7/extension.wit 🔗

@@ -122,6 +122,9 @@ world extension {
     export labels-for-completions: func(language-server-id: string, completions: list<completion>) -> result<list<option<code-label>>, string>;
     export labels-for-symbols: func(language-server-id: string, symbols: list<symbol>) -> result<list<option<code-label>>, string>;
 
-    /// Runs the provided slash command.
+    /// Returns the completions that should be shown when completing the provided slash command with the given query.
+    export complete-slash-command-argument: func(command: slash-command, query: string) -> result<list<string>, string>;
+
+    /// Returns the output from running the provided slash command.
     export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<slash-command-output, string>;
 }

extensions/gleam/src/gleam.rs 🔗

@@ -146,6 +146,21 @@ impl zed::Extension for GleamExtension {
         })
     }
 
+    fn complete_slash_command_argument(
+        &self,
+        command: SlashCommand,
+        _query: String,
+    ) -> Result<Vec<String>, String> {
+        match command.name.as_str() {
+            "gleam-project" => Ok(vec![
+                "apple".to_string(),
+                "banana".to_string(),
+                "cherry".to_string(),
+            ]),
+            _ => Ok(Vec::new()),
+        }
+    }
+
     fn run_slash_command(
         &self,
         command: SlashCommand,