zed_extension_api: Add simple process API (#25399)

This PR adds a simple API for working with processes to the extension
API.

The API is designed to mirror Rust's
[`std::process::Command`](https://doc.rust-lang.org/std/process/struct.Command.html).

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-02-22 13:05:36 -05:00 committed by GitHub
parent ec4d9ec111
commit 4d106a4b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 109 additions and 14 deletions

View file

@ -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;

View file

@ -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<String>) -> Self {
Self {
command: program.into(),
args: Vec::new(),
env: Vec::new(),
}
}
pub fn arg(mut self, arg: impl Into<String>) -> Self {
self.args.push(arg.into());
self
}
pub fn args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.args.extend(args.into_iter().map(Into::into));
self
}
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.push((key.into(), value.into()));
self
}
pub fn envs(
mut self,
envs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
self.env.extend(
envs.into_iter()
.map(|(key, value)| (key.into(), value.into())),
);
self
}
pub fn output(&mut self) -> Result<Output, String> {
process::run_command(self)
}
}

View file

@ -6,4 +6,7 @@ interface common {
/// The end of the range (exclusive).
end: u32,
}
/// A list of environment variables.
type env-vars = list<tuple<string, string>>;
}

View file

@ -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<tuple<string, string>>;
/// A command.
record command {
/// The command to execute.
command: string,
/// The arguments to pass to the command.
args: list<string>,
/// The environment variables to set for the command.
env: env-vars,
}
/// A Zed worktree.
resource worktree {
/// Returns the ID of the worktree.

View file

@ -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<string>,
/// 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<s32>,
/// The data that the process wrote to stdout.
stdout: list<u8>,
/// The data that the process wrote to stderr.
stderr: list<u8>,
}
/// 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<output, string>;
}

View file

@ -576,6 +576,35 @@ impl platform::Host for WasmState {
}
}
impl From<std::process::Output> 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<Result<process::Output, String>> {
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 {}