From ad4e52842cc716c9e945fdc4562b1381543b21b0 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 18 Jun 2024 17:28:01 -0400 Subject: [PATCH] Make slash commands defined in extensions return `SlashCommandOutput` (#13237) This PR extends the interface for slash commands defined in extensions to have them return `SlashCommandOutput`. This allows for slash commands to return multiple output sections for a single piece of generated text. Note that we don't allow specifying the icon to display in the placeholder, as we don't want to commit to that in our API at the moment. Release Notes: - N/A --- .../extension/src/extension_slash_command.rs | 28 +++++++++---------- crates/extension/src/wasm_host/wit.rs | 9 ++++-- .../src/wasm_host/wit/since_v0_0_7.rs | 3 ++ crates/extension_api/src/extension_api.rs | 8 +++--- .../extension_api/wit/since_v0.0.7/common.wit | 9 ++++++ .../wit/since_v0.0.7/extension.wit | 13 ++------- .../wit/since_v0.0.7/slash-command.wit | 18 ++++++++++++ extensions/gleam/src/gleam.rs | 23 ++++++++++----- 8 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 crates/extension_api/wit/since_v0.0.7/common.wit diff --git a/crates/extension/src/extension_slash_command.rs b/crates/extension/src/extension_slash_command.rs index 4f555f94ab..23de82174c 100644 --- a/crates/extension/src/extension_slash_command.rs +++ b/crates/extension/src/extension_slash_command.rs @@ -52,11 +52,9 @@ impl SlashCommand for ExtensionSlashCommand { delegate: Arc, cx: &mut WindowContext, ) -> Task> { - let command_name = SharedString::from(self.command.name.clone()); let argument = argument.map(|arg| arg.to_string()); - let text = cx.background_executor().spawn(async move { - let output = self - .extension + let output = cx.background_executor().spawn(async move { + self.extension .call({ let this = self.clone(); move |extension, store| { @@ -77,19 +75,21 @@ impl SlashCommand for ExtensionSlashCommand { .boxed() } }) - .await?; - output.ok_or_else(|| anyhow!("no output from command: {}", self.command.name)) + .await }); cx.foreground_executor().spawn(async move { - let text = text.await?; - let range = 0..text.len(); + let output = output.await?; Ok(SlashCommandOutput { - text, - sections: vec![SlashCommandOutputSection { - range, - icon: IconName::Code, - label: command_name, - }], + text: output.text, + sections: output + .sections + .into_iter() + .map(|section| SlashCommandOutputSection { + range: section.range.into(), + icon: IconName::Code, + label: section.label.into(), + }) + .collect(), run_commands_in_text: false, }) }) diff --git a/crates/extension/src/wasm_host/wit.rs b/crates/extension/src/wasm_host/wit.rs index 57a0e140a3..9963840b14 100644 --- a/crates/extension/src/wasm_host/wit.rs +++ b/crates/extension/src/wasm_host/wit.rs @@ -6,7 +6,7 @@ use release_channel::ReleaseChannel; use since_v0_0_7 as latest; use super::{wasm_engine, WasmState}; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use language::{LanguageServerName, LspAdapterDelegate}; use semantic_version::SemanticVersion; use std::{ops::RangeInclusive, sync::Arc}; @@ -19,6 +19,7 @@ use wasmtime::{ pub use latest::CodeLabelSpanLiteral; pub use latest::{ zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind}, + zed::extension::slash_command::SlashCommandOutput, CodeLabel, CodeLabelSpan, Command, Range, SlashCommand, }; pub use since_v0_0_4::LanguageServerConfig; @@ -262,13 +263,15 @@ impl Extension { command: &SlashCommand, argument: Option<&str>, resource: Resource>, - ) -> Result, String>> { + ) -> Result> { match self { Extension::V007(ext) => { ext.call_run_slash_command(store, command, argument, resource) .await } - Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(None)), + Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => { + Err(anyhow!("`run_slash_command` not available prior to v0.0.7")) + } } } } diff --git a/crates/extension/src/wasm_host/wit/since_v0_0_7.rs b/crates/extension/src/wasm_host/wit/since_v0_0_7.rs index 35874b6cd6..08a2993f57 100644 --- a/crates/extension/src/wasm_host/wit/since_v0_0_7.rs +++ b/crates/extension/src/wasm_host/wit/since_v0_0_7.rs @@ -98,6 +98,9 @@ impl HostWorktree for WasmState { } } +#[async_trait] +impl common::Host for WasmState {} + #[async_trait] impl nodejs::Host for WasmState { async fn node_binary_path(&mut self) -> wasmtime::Result> { diff --git a/crates/extension_api/src/extension_api.rs b/crates/extension_api/src/extension_api.rs index e117b2b127..a15826f2c1 100644 --- a/crates/extension_api/src/extension_api.rs +++ b/crates/extension_api/src/extension_api.rs @@ -24,7 +24,7 @@ pub use wit::{ npm_package_latest_version, }, zed::extension::platform::{current_platform, Architecture, Os}, - zed::extension::slash_command::SlashCommand, + zed::extension::slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection}, CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars, LanguageServerInstallationStatus, Range, Worktree, }; @@ -114,8 +114,8 @@ pub trait Extension: Send + Sync { _command: SlashCommand, _argument: Option, _worktree: &Worktree, - ) -> Result, String> { - Ok(None) + ) -> Result { + Err("`run_slash_command` not implemented".to_string()) } } @@ -229,7 +229,7 @@ impl wit::Guest for Component { command: SlashCommand, argument: Option, worktree: &Worktree, - ) -> Result, String> { + ) -> Result { extension().run_slash_command(command, argument, worktree) } } diff --git a/crates/extension_api/wit/since_v0.0.7/common.wit b/crates/extension_api/wit/since_v0.0.7/common.wit new file mode 100644 index 0000000000..c4f321f4c7 --- /dev/null +++ b/crates/extension_api/wit/since_v0.0.7/common.wit @@ -0,0 +1,9 @@ +interface common { + /// A (half-open) range (`[start, end)`). + record range { + /// The start of the range (inclusive). + start: u32, + /// The end of the range (exclusive). + end: u32, + } +} diff --git a/crates/extension_api/wit/since_v0.0.7/extension.wit b/crates/extension_api/wit/since_v0.0.7/extension.wit index 6758e5f691..253011194f 100644 --- a/crates/extension_api/wit/since_v0.0.7/extension.wit +++ b/crates/extension_api/wit/since_v0.0.7/extension.wit @@ -5,8 +5,9 @@ world extension { import platform; import nodejs; + use common.{range}; use lsp.{completion, symbol}; - use slash-command.{slash-command}; + use slash-command.{slash-command, slash-command-output}; /// Initializes the extension. export init-extension: func(); @@ -118,17 +119,9 @@ world extension { highlight-name: option, } - /// A (half-open) range (`[start, end)`). - record range { - /// The start of the range (inclusive). - start: u32, - /// The end of the range (exclusive). - end: u32, - } - export labels-for-completions: func(language-server-id: string, completions: list) -> result>, string>; export labels-for-symbols: func(language-server-id: string, symbols: list) -> result>, string>; /// Runs the provided slash command. - export run-slash-command: func(command: slash-command, argument: option, worktree: borrow) -> result, string>; + export run-slash-command: func(command: slash-command, argument: option, worktree: borrow) -> result; } diff --git a/crates/extension_api/wit/since_v0.0.7/slash-command.wit b/crates/extension_api/wit/since_v0.0.7/slash-command.wit index 1be04f3319..17d4a61552 100644 --- a/crates/extension_api/wit/since_v0.0.7/slash-command.wit +++ b/crates/extension_api/wit/since_v0.0.7/slash-command.wit @@ -1,4 +1,6 @@ interface slash-command { + use common.{range}; + /// A slash command for use in the Assistant. record slash-command { /// The name of the slash command. @@ -10,4 +12,20 @@ interface slash-command { /// Whether this slash command requires an argument. requires-argument: bool, } + + /// The output of a slash command. + record slash-command-output { + /// The text produced by the slash command. + text: string, + /// The list of sections to show in the slash command placeholder. + sections: list, + } + + /// A section in the slash command output. + record slash-command-output-section { + /// The range this section occupies. + range: range, + /// The label to display in the placeholder for this section. + label: string, + } } diff --git a/extensions/gleam/src/gleam.rs b/extensions/gleam/src/gleam.rs index d75ac8bc25..1671fdf400 100644 --- a/extensions/gleam/src/gleam.rs +++ b/extensions/gleam/src/gleam.rs @@ -1,6 +1,9 @@ use std::fs; use zed::lsp::CompletionKind; -use zed::{CodeLabel, CodeLabelSpan, LanguageServerId, SlashCommand}; +use zed::{ + CodeLabel, CodeLabelSpan, LanguageServerId, SlashCommand, SlashCommandOutput, + SlashCommandOutputSection, +}; use zed_extension_api::{self as zed, Result}; struct GleamExtension { @@ -148,18 +151,24 @@ impl zed::Extension for GleamExtension { command: SlashCommand, _argument: Option, worktree: &zed::Worktree, - ) -> Result, String> { + ) -> Result { match command.name.as_str() { "gleam-project" => { - let mut message = String::new(); - message.push_str("You are in a Gleam project.\n"); + let mut text = String::new(); + text.push_str("You are in a Gleam project.\n"); if let Some(gleam_toml) = worktree.read_text_file("gleam.toml").ok() { - message.push_str("The `gleam.toml` is as follows:\n"); - message.push_str(&gleam_toml); + text.push_str("The `gleam.toml` is as follows:\n"); + text.push_str(&gleam_toml); } - Ok(Some(message)) + Ok(SlashCommandOutput { + sections: vec![SlashCommandOutputSection { + range: (0..text.len()).into(), + label: "gleam-project".to_string(), + }], + text, + }) } command => Err(format!("unknown slash command: \"{command}\"")), }