Add example extension to showcase slash commands (#16300)

Marshall Bowers created

This PR adds an example extension to showcase how to write slash
commands in an extension.

Release Notes:

- N/A

Change summary

Cargo.lock                                                      |  7 
Cargo.toml                                                      |  1 
extensions/slash-commands-example/Cargo.toml                    | 16 
extensions/slash-commands-example/LICENSE-APACHE                |  1 
extensions/slash-commands-example/README.md                     |  3 
extensions/slash-commands-example/extension.toml                | 17 
extensions/slash-commands-example/src/slash_commands_example.rs | 90 +++
7 files changed, 135 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -9942,6 +9942,13 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "slash_commands_example"
+version = "0.1.0"
+dependencies = [
+ "zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "slice-group-by"
 version = "0.3.1"

Cargo.toml 🔗

@@ -149,6 +149,7 @@ members = [
     "extensions/purescript",
     "extensions/ruff",
     "extensions/ruby",
+    "extensions/slash-commands-example",
     "extensions/snippets",
     "extensions/svelte",
     "extensions/terraform",

extensions/slash-commands-example/Cargo.toml 🔗

@@ -0,0 +1,16 @@
+[package]
+name = "slash_commands_example"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/slash_commands_example.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = "0.1.0"

extensions/slash-commands-example/extension.toml 🔗

@@ -0,0 +1,17 @@
+id = "slash-commands-example"
+name = "Slash Commands Example"
+description = "An example extension showcasing slash commands."
+version = "0.1.0"
+schema_version = 1
+authors = ["Zed Industries <hi@zed.dev>"]
+repository = "https://github.com/zed-industries/zed"
+
+[slash_commands.echo]
+description = "echoes the provided input"
+requires_argument = true
+tooltip_text = "Echoes the provided input"
+
+[slash_commands.pick-one]
+description = "pick one of three options"
+requires_argument = true
+tooltip_text = "Pick one of three options"

extensions/slash-commands-example/src/slash_commands_example.rs 🔗

@@ -0,0 +1,90 @@
+use zed_extension_api::{
+    self as zed, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput,
+    SlashCommandOutputSection, Worktree,
+};
+
+struct SlashCommandsExampleExtension;
+
+impl zed::Extension for SlashCommandsExampleExtension {
+    fn new() -> Self {
+        SlashCommandsExampleExtension
+    }
+
+    fn complete_slash_command_argument(
+        &self,
+        command: SlashCommand,
+        _args: Vec<String>,
+    ) -> Result<Vec<zed_extension_api::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}\"")),
+        }
+    }
+
+    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}\"")),
+        }
+    }
+}
+
+zed::register_extension!(SlashCommandsExampleExtension);