docs: Add docs for defining slash commands in extensions (#16303)

Marshall Bowers created

This PR adds docs for defining slash commands within extensions.

Release Notes:

- N/A

Change summary

docs/src/SUMMARY.md                          |   1 
docs/src/extensions.md                       |   1 
docs/src/extensions/developing-extensions.md |   5 
docs/src/extensions/slash-commands.md        | 135 ++++++++++++++++++++++
4 files changed, 138 insertions(+), 4 deletions(-)

Detailed changes

docs/src/SUMMARY.md 🔗

@@ -38,6 +38,7 @@
 - [Developing Extensions](./extensions/developing-extensions.md)
 - [Language Extensions](./extensions/languages.md)
 - [Theme Extensions](./extensions/themes.md)
+- [Slash Commands](./extensions/slash-commands.md)
 
 # Language Support
 

docs/src/extensions.md 🔗

@@ -6,3 +6,4 @@ Zed lets you add new functionality using user-defined extensions.
 - [Developing Extensions](./extensions/developing-extensions.md)
   - [Developing Language Extensions](./extensions/languages.md)
   - [Developing Themes](./extensions/themes.md)
+  - [Developing Slash Commands](./extensions/slash-commands.md)

docs/src/extensions/developing-extensions.md 🔗

@@ -6,6 +6,7 @@ Extensions can add the following capabilities to Zed:
 
 - [Languages](./languages.md)
 - [Themes](./themes.md)
+- [Slash Commands](./slash-commands.md)
 
 ## Directory Structure of a Zed Extension
 
@@ -22,10 +23,6 @@ description = "My cool extension"
 repository = "https://github.com/your-name/my-zed-extension"
 ```
 
-<!--
-TBD: Document `slash_commands`, `indexed_docs_providers` (see: extensions/gleam/extension.toml)
--->
-
 In addition to this, there are several other optional files and directories that can be used to add functionality to a Zed extension. An example directory structure of an extension that provides all capabilities is as follows:
 
 ```

docs/src/extensions/slash-commands.md 🔗

@@ -0,0 +1,135 @@
+# Slash Commands
+
+Extensions may provide slash commands for use in the Assistant.
+
+## Defining slash commands
+
+A given extension may provide one or more slash commands. Each slash command must be registered in the `extension.toml`.
+
+For example, here is an extension that provides two slash commands: `/echo` and `/pick-one`:
+
+```toml
+[slash_commands.echo]
+description = "echoes the provided input"
+requires_argument = true
+tooltip_text = ""
+
+[slash_commands.pick-one]
+description = "pick one of three options"
+requires_argument = true
+tooltip_text = ""
+```
+
+Each slash command may define the following properties:
+
+- `description`: A description of the slash command that will be shown when completing available commands.
+- `requires_argument`: Indicates whether a slash command requires at least one argument to run.
+- `tooltip_text`: Currently unused.
+
+## Implementing slash command behavior
+
+To implement behavior for your slash commands, implement `run_slash_command` for your extension.
+
+This method accepts the slash command that will be run, the list of arguments passed to it, and an optional `Worktree`.
+
+This method returns `SlashCommandOutput`, which contains the textual output of the command in the `text` field. The output may also define `SlashCommandOutputSection`s that contain ranges into the output. These sections are then rendered as creases in the Assistant's context editor.
+
+Your extension should `match` on the command name (without the leading `/`) and then execute behavior accordingly:
+
+```rs
+impl zed::Extension for MyExtension {
+    fn run_slash_command(
+        &self,
+        command: SlashCommand,
+        args: Vec<String>,
+        _worktree: Option<&Worktree>,
+    ) -> Result<SlashCommandOutput, String> {
+        match command.name.as_str() {
+            "echo" => {
+                if args.is_empty() {
+                    return Err("nothing to echo".to_string());
+                }
+
+                let text = args.join(" ");
+
+                Ok(SlashCommandOutput {
+                    sections: vec![SlashCommandOutputSection {
+                        range: (0..text.len()).into(),
+                        label: "Echo".to_string(),
+                    }],
+                    text,
+                })
+            }
+            "pick-one" => {
+                let Some(selection) = args.first() else {
+                    return Err("no option selected".to_string());
+                };
+
+                match selection.as_str() {
+                    "option-1" | "option-2" | "option-3" => {}
+                    invalid_option => {
+                        return Err(format!("{invalid_option} is not a valid option"));
+                    }
+                }
+
+                let text = format!("You chose {selection}.");
+
+                Ok(SlashCommandOutput {
+                    sections: vec![SlashCommandOutputSection {
+                        range: (0..text.len()).into(),
+                        label: format!("Pick One: {selection}"),
+                    }],
+                    text,
+                })
+            }
+            command => Err(format!("unknown slash command: \"{command}\"")),
+        }
+    }
+}
+```
+
+## Auto-completing slash command arguments
+
+For slash commands that have arguments, you may also choose to implement `complete_slash_command_argument` to provide completions for your slash commands.
+
+This method accepts the slash command that will be run and the list of arguments passed to it. It returns a list of `SlashCommandArgumentCompletion`s that will be shown in the completion menu.
+
+A `SlashCommandArgumentCompletion` consists of the following properties:
+
+- `label`: The label that will be shown in the completion menu.
+- `new_text`: The text that will be inserted when the completion is accepted.
+- `run_command`: Whether the slash command will be run when the completion is accepted.
+
+Once again, your extension should `match` on the command name (without the leading `/`) and return the desired argument completions:
+
+```rs
+impl zed::Extension for MyExtension {
+    fn complete_slash_command_argument(
+        &self,
+        command: SlashCommand,
+        _args: Vec<String>,
+    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
+        match command.name.as_str() {
+            "echo" => Ok(vec![]),
+            "pick-one" => Ok(vec![
+                SlashCommandArgumentCompletion {
+                    label: "Option One".to_string(),
+                    new_text: "option-1".to_string(),
+                    run_command: true,
+                },
+                SlashCommandArgumentCompletion {
+                    label: "Option Two".to_string(),
+                    new_text: "option-2".to_string(),
+                    run_command: true,
+                },
+                SlashCommandArgumentCompletion {
+                    label: "Option Three".to_string(),
+                    new_text: "option-3".to_string(),
+                    run_command: true,
+                },
+            ]),
+            command => Err(format!("unknown slash command: \"{command}\"")),
+        }
+    }
+}
+```