diff --git a/crates/extension_api/src/extension_api.rs b/crates/extension_api/src/extension_api.rs index 4868f8e4d1..06793abac1 100644 --- a/crates/extension_api/src/extension_api.rs +++ b/crates/extension_api/src/extension_api.rs @@ -1,6 +1,7 @@ //! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust. pub mod http_client; +pub mod process; pub mod settings; use core::fmt; diff --git a/crates/extension_api/src/process.rs b/crates/extension_api/src/process.rs new file mode 100644 index 0000000000..1068fd9c17 --- /dev/null +++ b/crates/extension_api/src/process.rs @@ -0,0 +1,44 @@ +//! A module for working with processes. + +use crate::wit::zed::extension::process; +pub use crate::wit::zed::extension::process::{Command, Output}; + +impl Command { + pub fn new(program: impl Into) -> Self { + Self { + command: program.into(), + args: Vec::new(), + env: Vec::new(), + } + } + + pub fn arg(mut self, arg: impl Into) -> Self { + self.args.push(arg.into()); + self + } + + pub fn args(mut self, args: impl IntoIterator>) -> Self { + self.args.extend(args.into_iter().map(Into::into)); + self + } + + pub fn env(mut self, key: impl Into, value: impl Into) -> Self { + self.env.push((key.into(), value.into())); + self + } + + pub fn envs( + mut self, + envs: impl IntoIterator, impl Into)>, + ) -> Self { + self.env.extend( + envs.into_iter() + .map(|(key, value)| (key.into(), value.into())), + ); + self + } + + pub fn output(&mut self) -> Result { + process::run_command(self) + } +} diff --git a/crates/extension_api/wit/since_v0.3.0/common.wit b/crates/extension_api/wit/since_v0.3.0/common.wit index c4f321f4c7..139e7ba0ca 100644 --- a/crates/extension_api/wit/since_v0.3.0/common.wit +++ b/crates/extension_api/wit/since_v0.3.0/common.wit @@ -6,4 +6,7 @@ interface common { /// The end of the range (exclusive). end: u32, } + + /// A list of environment variables. + type env-vars = list>; } diff --git a/crates/extension_api/wit/since_v0.3.0/extension.wit b/crates/extension_api/wit/since_v0.3.0/extension.wit index 3e54c5be89..95aaec5469 100644 --- a/crates/extension_api/wit/since_v0.3.0/extension.wit +++ b/crates/extension_api/wit/since_v0.3.0/extension.wit @@ -4,10 +4,12 @@ world extension { import github; import http-client; import platform; + import process; import nodejs; - use common.{range}; + use common.{env-vars, range}; use lsp.{completion, symbol}; + use process.{command}; use slash-command.{slash-command, slash-command-argument-completion, slash-command-output}; /// Initializes the extension. @@ -56,19 +58,6 @@ world extension { /// Updates the installation status for the given language server. import set-language-server-installation-status: func(language-server-name: string, status: language-server-installation-status); - /// A list of environment variables. - type env-vars = list>; - - /// A command. - record command { - /// The command to execute. - command: string, - /// The arguments to pass to the command. - args: list, - /// The environment variables to set for the command. - env: env-vars, - } - /// A Zed worktree. resource worktree { /// Returns the ID of the worktree. diff --git a/crates/extension_api/wit/since_v0.3.0/process.wit b/crates/extension_api/wit/since_v0.3.0/process.wit new file mode 100644 index 0000000000..d9a5728a3d --- /dev/null +++ b/crates/extension_api/wit/since_v0.3.0/process.wit @@ -0,0 +1,29 @@ +interface process { + use common.{env-vars}; + + /// A command. + record command { + /// The command to execute. + command: string, + /// The arguments to pass to the command. + args: list, + /// The environment variables to set for the command. + env: env-vars, + } + + /// The output of a finished process. + record output { + /// The status (exit code) of the process. + /// + /// On Unix, this will be `None` if the process was terminated by a signal. + status: option, + /// The data that the process wrote to stdout. + stdout: list, + /// The data that the process wrote to stderr. + stderr: list, + } + + /// Executes the given command as a child process, waiting for it to finish + /// and collecting all of its output. + run-command: func(command: command) -> result; +} diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs index 654438d541..b634134a6e 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs @@ -576,6 +576,35 @@ impl platform::Host for WasmState { } } +impl From for process::Output { + fn from(output: std::process::Output) -> Self { + Self { + status: output.status.code(), + stdout: output.stdout, + stderr: output.stderr, + } + } +} + +impl process::Host for WasmState { + async fn run_command( + &mut self, + command: process::Command, + ) -> wasmtime::Result> { + maybe!(async { + let output = util::command::new_smol_command(command.command.as_str()) + .args(&command.args) + .envs(command.env) + .output() + .await?; + + Ok(output.into()) + }) + .await + .to_wasmtime_result() + } +} + #[async_trait] impl slash_command::Host for WasmState {}