From 3efec1c848ea32ec72561712a34c9963bc1111a1 Mon Sep 17 00:00:00 2001 From: WangTongchao Date: Sun, 8 Nov 2026 02:53:04 +0800 Subject: [PATCH] feat(plugins): let a plugin keep a process running behind a handle (#2027) Closes #1826 API 1.3 adds `run-command` (one-shot with stdin, cwd and environment) and a handle-based `start` for a long-running process with incremental stdin/stdout/stderr, both gated by the existing `process:` grants. A plugin's environment entries are append-only: a key the child would already inherit (including the login-shell PATH) is refused with an error naming it, as are empty or malformed keys. Processes run in their own process group and are killed with their descendants when the handle is dropped, the plugin is disabled, unloaded or reclaimed, or the owning window closes; at most eight handles per plugin. Output is bounded and UTF-8 safe, stdin writes honour cancellation, and validation failures return typed errors instead of trapping the plugin. Verified on macOS; Windows/Linux not run. Co-authored-by: chaoes <26876656+chaoes@users.noreply.github.com> --- crates/ide-host/src/plugins/api.rs | 674 +++++++++++++++++- crates/ide-host/src/plugins/mod.rs | 3 + crates/ide-host/tests/plugins.rs | 99 ++- crates/ide-plugin/src/runtime.rs | 40 +- crates/ide-plugin/src/types.rs | 18 + crates/ide-plugin/src/wit_host.rs | 148 +++- .../tests/fixtures/commit/dist/plugin.wasm | Bin 97343 -> 97343 bytes .../fixtures/commit/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/endpoints/dist/plugin.wasm | Bin 78631 -> 78636 bytes .../endpoints/dist/plugin.wasm.sources | 2 +- .../fixtures/file-templates/dist/plugin.wasm | Bin 68044 -> 68048 bytes .../file-templates/dist/plugin.wasm.sources | 2 +- .../fixtures/grouped-views/dist/plugin.wasm | Bin 89160 -> 89160 bytes .../grouped-views/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/hello/dist/plugin.wasm | Bin 141555 -> 141555 bytes .../fixtures/hello/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/hover/dist/plugin.wasm | Bin 86790 -> 86785 bytes .../fixtures/hover/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/indexer/dist/plugin.wasm | Bin 79921 -> 79926 bytes .../fixtures/indexer/dist/plugin.wasm.sources | 2 +- .../fixtures/inlay-hints/dist/plugin.wasm | Bin 88686 -> 88681 bytes .../inlay-hints/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/jobs/dist/plugin.wasm | Bin 147771 -> 147785 bytes .../fixtures/jobs/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/misbehave/dist/plugin.wasm | Bin 123986 -> 136764 bytes .../misbehave/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/misbehave/plugin.toml | 37 + .../tests/fixtures/misbehave/src/lib.rs | 73 +- .../fixtures/path-events/dist/plugin.wasm | Bin 91105 -> 91105 bytes .../path-events/dist/plugin.wasm.sources | 2 +- .../fixtures/semantic-tokens/dist/plugin.wasm | Bin 88865 -> 88860 bytes .../semantic-tokens/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/storage/dist/plugin.wasm | Bin 71118 -> 71118 bytes .../fixtures/storage/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/tables/dist/plugin.wasm | Bin 116488 -> 116488 bytes .../fixtures/tables/dist/plugin.wasm.sources | 2 +- plugins/README.md | 12 + .../official/ai-completion/dist/plugin.wasm | Bin 169555 -> 169568 bytes .../ai-completion/dist/plugin.wasm.sources | 2 +- plugins/official/checkstyle/dist/plugin.wasm | Bin 166523 -> 166533 bytes .../checkstyle/dist/plugin.wasm.sources | 2 +- plugins/official/csv-editor/dist/plugin.wasm | Bin 130585 -> 130591 bytes .../csv-editor/dist/plugin.wasm.sources | 2 +- plugins/official/git-toolbox/dist/plugin.wasm | Bin 108493 -> 108493 bytes .../git-toolbox/dist/plugin.wasm.sources | 2 +- plugins/official/graphviz/dist/plugin.wasm | Bin 126110 -> 126110 bytes .../graphviz/dist/plugin.wasm.sources | 2 +- .../official/key-promoter/dist/plugin.wasm | Bin 97368 -> 97368 bytes .../key-promoter/dist/plugin.wasm.sources | 2 +- .../official/license-header/dist/plugin.wasm | Bin 150780 -> 150760 bytes .../license-header/dist/plugin.wasm.sources | 2 +- plugins/official/mybatis/dist/plugin.wasm | Bin 166698 -> 166698 bytes .../official/mybatis/dist/plugin.wasm.sources | 2 +- .../official/regex-tester/dist/plugin.wasm | Bin 932819 -> 932226 bytes .../regex-tester/dist/plugin.wasm.sources | 2 +- plugins/official/solidity/dist/plugin.wasm | Bin 105046 -> 105046 bytes .../solidity/dist/plugin.wasm.sources | 2 +- .../official/string-tools/dist/plugin.wasm | Bin 162800 -> 162800 bytes .../string-tools/dist/plugin.wasm.sources | 2 +- sdk/rust/openidea-plugin/src/host.rs | 140 ++++ sdk/rust/openidea-plugin/src/mock.rs | 219 +++++- sdk/rust/openidea-plugin/wit/io.wit | 54 +- wit/io.wit | 54 +- 63 files changed, 1549 insertions(+), 72 deletions(-) diff --git a/crates/ide-host/src/plugins/api.rs b/crates/ide-host/src/plugins/api.rs index 4cf8fdbeb..53e2bf18a 100644 --- a/crates/ide-host/src/plugins/api.rs +++ b/crates/ide-host/src/plugins/api.rs @@ -8,11 +8,13 @@ use ide_extension::Package; use ide_language::ProgramPin; use ide_plugin::{ CommitChange, CommitDraft, EventKind, HostApi, HostError, HttpRequest, HttpResponse, NotificationLevel, - ProcessOutput, Prompt, RepositoryStatus, StatusItem, + ProcessChunk, ProcessCommand, ProcessOutput, Prompt, RepositoryStatus, StatusItem, }; use parking_lot::{Mutex, RwLock}; use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::io::Read; use std::path::{Path, PathBuf}; +use std::process::{Child, ChildStdin, Command as StdCommand, Stdio}; use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{Duration, Instant}; @@ -124,6 +126,10 @@ impl SecretStore for FileSecretStore { const MAX_RESPONSE: usize = 8 << 20; /// Largest stdout / stderr kept from a process. const MAX_OUTPUT: usize = 4 << 20; +/// Most processes one plugin keeps running at once. +const MAX_PROCESSES: usize = 8; +/// Most output one stream of a handle holds until the plugin reads it. +const MAX_PENDING: usize = 1 << 20; /// Most files `find-files` walks before giving up. const MAX_WALK: usize = 200_000; @@ -176,6 +182,218 @@ enum Waiter { Commit(std::sync::mpsc::SyncSender), } +/// A process a plugin started and has not released (API 1.3 `process.start`). +/// Two reader threads copy its output into bounded buffers; the handle lives +/// as long as the plugin instance that started it. +struct Proc { + client: Option, + state: Mutex, +} + +struct ProcState { + child: Child, + stdin: Option, + /// What it printed and the plugin hasn't read yet. + stdout: Vec, + stderr: Vec, + /// Its exit status once it ended (a code, or -1 when a signal ended it). + status: Option, + /// Reader threads still copying its pipes into the buffers above. + readers: usize, +} + +/// Which pipe a reader thread drains. +#[derive(Clone, Copy)] +enum Stream { + Out, + Err, +} + +impl Proc { + /// Copies `pipe` into the matching buffer until it closes, keeping the last + /// [`MAX_PENDING`] bytes, then notes that the reader finished. + fn read(self: &Arc, mut pipe: impl Read + Send + 'static, stream: Stream) { + self.state.lock().readers += 1; + let this = Arc::clone(self); + std::thread::spawn(move || { + let mut buf = [0u8; 8 << 10]; + loop { + match pipe.read(&mut buf) { + Ok(0) => break, + Ok(n) => { + let mut state = this.state.lock(); + let out = match stream { + Stream::Out => &mut state.stdout, + Stream::Err => &mut state.stderr, + }; + out.extend_from_slice(&buf[..n]); + let over = out.len().saturating_sub(MAX_PENDING); + if over > 0 { + out.drain(..over); + let continuation = out.iter().take_while(|b| (**b & 0xC0) == 0x80).count(); + out.drain(..continuation); + } + } + Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, + Err(_) => break, + } + } + this.state.lock().readers -= 1; + }); + } + + /// Notes its exit status if it ended (dropping its stdin then). + fn reap(&self) -> Result<(), HostError> { + let mut state = self.state.lock(); + if state.status.is_none() { + match try_wait_process(&mut state.child) { + Ok(Some(status)) => { + state.status = Some(status.code().unwrap_or(-1)); + state.stdin = None; + } + Ok(None) => {} + Err(e) => return Err(failed(e)), + } + } + Ok(()) + } + + /// Ends it and reaps it, so its status is known right away; killing twice + /// does nothing. + fn kill(&self) { + let mut state = self.state.lock(); + state.stdin = None; + if state.status.is_none() { + ide_core::process::kill_tree(&mut state.child); + if let Ok(status) = state.child.wait() { + state.status = Some(status.code().unwrap_or(-1)); + } + } + } + + /// What it printed since the last call, each stream at most `max` bytes. + fn chunk(&self, max: usize) -> ProcessChunk { + let mut state = self.state.lock(); + let eof = state.readers == 0; + ProcessChunk { + stdout: take_text(&mut state.stdout, max, eof), + stderr: take_text(&mut state.stderr, max, eof), + status: state.status, + } + } +} + +/// Decodes complete characters within the byte budget, retaining partial UTF-8 +/// until the reader gets the rest (or EOF). Even a tiny budget makes progress. +fn take_text(buf: &mut Vec, max: usize, eof: bool) -> String { + let limit = max.clamp(1, MAX_PENDING); + let mut out = String::new(); + let mut consumed = 0; + while consumed < buf.len() && out.len() < limit { + let bytes = &buf[consumed..]; + let (valid, invalid) = match std::str::from_utf8(bytes) { + Ok(text) => (text.len(), None), + Err(e) => (e.valid_up_to(), Some(e.error_len())), + }; + let text = std::str::from_utf8(&bytes[..valid]).expect("valid prefix"); + let mut end = text.len().min(limit - out.len()); + while !text.is_char_boundary(end) { + end -= 1; + } + if end == 0 && out.is_empty() && !text.is_empty() { + end = text.chars().next().unwrap().len_utf8(); + } + out.push_str(&text[..end]); + consumed += end; + if end < valid { + break; + } + match invalid { + Some(len) if len.is_some() || eof => { + if !out.is_empty() && out.len() + '\u{fffd}'.len_utf8() > limit { + break; + } + out.push('\u{fffd}'); + consumed += len.unwrap_or(bytes.len() - valid); + } + _ => break, + } + } + buf.drain(..consumed); + out +} + +/// Ends descendants before reaping an exited leader, while its process id is +/// still reserved. Otherwise inherited pipes can keep a completed call waiting. +fn try_wait_process(child: &mut Child) -> std::io::Result> { + #[cfg(unix)] + { + use rustix::process::{Pid, WaitId, WaitIdOptions, waitid}; + let status = waitid( + WaitId::Pid(Pid::from_child(child)), + WaitIdOptions::EXITED | WaitIdOptions::NOHANG | WaitIdOptions::NOWAIT, + )?; + if status.is_none() { + return Ok(None); + } + ide_core::process::kill_tree(child); + child.try_wait() + } + #[cfg(not(unix))] + { + let status = child.try_wait()?; + if status.is_some() { + ide_core::process::kill_tree(child); + } + Ok(status) + } +} + +fn env_name_eq(a: &std::ffi::OsStr, b: &std::ffi::OsStr) -> bool { + #[cfg(windows)] + { + // Use the platform's Command key comparison, including non-ASCII names. + let mut probe = StdCommand::new("unused"); + probe.env(a, "").env(b, ""); + probe.get_envs().count() == 1 + } + #[cfg(not(windows))] + { + a == b + } +} + +/// Validates against the environment after `prepare`, then freezes that snapshot +/// so only new keys are added, including when the host environment changes later. +fn append_process_env(command: &mut StdCommand, env: &[(String, String)]) -> Result<(), HostError> { + if env.len() > 64 { + return Err(HostError::Invalid("at most 64 environment variables".into())); + } + let mut inherited: Vec<_> = std::env::vars_os().collect(); + for (key, value) in command.get_envs() { + inherited.retain(|(k, _)| !env_name_eq(k, key)); + if let Some(value) = value { + inherited.push((key.to_owned(), value.to_owned())); + } + } + for (key, value) in env { + if key.is_empty() || key.len() > 256 || key.contains(['=', '\0']) { + return Err(HostError::Invalid(format!("{key:?} is not an environment variable name"))); + } + if value.len() > 4096 || value.contains('\0') { + return Err(HostError::Invalid(format!("the value of {key:?} is too long or holds a NUL"))); + } + if inherited.iter().any(|(k, _)| env_name_eq(k, std::ffi::OsStr::new(key))) { + return Err(HostError::Invalid(format!( + "environment variable {key:?} already exists; only new variables may be added" + ))); + } + inherited.push((key.into(), value.into())); + } + command.env_clear().envs(inherited); + Ok(()) +} + /// State shared by the host and every plugin's [`PluginApi`]. pub(super) struct Shared { pub sink: Arc, @@ -184,6 +402,10 @@ pub(super) struct Shared { pub data_dir: PathBuf, tasks: Mutex>, next_task: AtomicU64, + /// (plugin, handle id) → the processes it started and has not released. + processes: Mutex>>, + next_process: AtomicU64, + process_shutdowns: Mutex>>, prompts: Mutex>, /// Plugins being stopped: their calls may not open new prompts (taken before `prompts`). prompts_blocked: Mutex>, @@ -223,6 +445,9 @@ impl Shared { data_dir, tasks: Mutex::default(), next_task: AtomicU64::new(1), + processes: Mutex::default(), + next_process: AtomicU64::new(1), + process_shutdowns: Mutex::default(), prompts: Mutex::default(), prompts_blocked: Mutex::default(), next_prompt: AtomicU64::new(1), @@ -295,6 +520,33 @@ impl Shared { self.prompts.lock().retain(|_, entry| entry.client != client); drop(closed); self.contexts.lock().retain(|(scope, ..), _| scope.as_deref() != Some(client)); + let mine: Vec<_> = { + let mut processes = self.processes.lock(); + let keys: Vec<_> = processes + .iter() + .filter(|(_, p)| p.client.as_deref() == Some(client)) + .map(|(key, _)| key.clone()) + .collect(); + keys.iter().filter_map(|key| processes.remove(key)).collect() + }; + if !mine.is_empty() { + // Window destruction runs on the UI thread; tree shutdown may wait. + let worker = std::thread::spawn(move || { + for proc in mine { + proc.kill(); + } + }); + let mut workers = self.process_shutdowns.lock(); + workers.retain(|worker| !worker.is_finished()); + workers.push(worker); + } + } + + pub fn finish_process_shutdowns(&self) { + let workers = std::mem::take(&mut *self.process_shutdowns.lock()); + for worker in workers { + let _ = worker.join(); + } } /// A window labelled `client` is being created (labels are reused after a close). @@ -305,6 +557,7 @@ impl Shared { /// Withdraws a plugin's run-time state (it stopped or failed). Its /// subscriptions go only with `subscriptions`, as they survive restarts. pub fn clear_plugin(&self, plugin: &str, subscriptions: bool) { + self.kill_processes(plugin); let removed: Vec = { let mut status = self.status.lock(); let ids: Vec = status.keys().filter(|(p, _)| p == plugin).map(|(_, id)| id.clone()).collect(); @@ -345,6 +598,19 @@ impl Shared { } } + /// Ends every process `plugin` started: its handles go with its instance + /// (it stopped, was disabled, or was reclaimed while idle). + pub fn kill_processes(&self, plugin: &str) { + let mine: Vec> = { + let mut processes = self.processes.lock(); + let keys: Vec<(String, u64)> = processes.keys().filter(|(p, _)| p == plugin).cloned().collect(); + keys.iter().filter_map(|k| processes.remove(k)).collect() + }; + for proc in mine { + proc.kill(); + } + } + /// The user cancelled a task in the UI. pub fn cancel_task(&self, id: u64) -> bool { let mut tasks = self.tasks.lock(); @@ -684,6 +950,23 @@ impl PluginApi { } } } + /// Where `cmd` runs: the project root it names, else the plugin's data + /// directory (created as needed). + fn spawn_dir(&self, cwd: Option<&Path>) -> PathBuf { + let dir = cwd.map(Path::to_path_buf).unwrap_or_else(|| self.shared.data_dir.join(self.id())); + let _ = std::fs::create_dir_all(&dir); + dir + } + + /// The process handle `id`, if this plugin started it and it is still open. + fn process(&self, id: u64) -> Result, HostError> { + self.shared + .processes + .lock() + .get(&(self.id().to_owned(), id)) + .cloned() + .ok_or_else(|| HostError::NotFound(format!("no process {id}: it was closed, or belongs to another plugin"))) + } } /// The changes `selection` commits, diffed repository by repository. @@ -1124,24 +1407,24 @@ impl HostApi for PluginApi { fn run_process( &self, - program: &str, - args: Vec, + cmd: ProcessCommand, cwd: Option<&Path>, stdin: Option, cancel: &CancellationToken, timeout: Duration, ) -> Result { - let binary = self.approved_program(program)?; - let cwd = cwd.map(Path::to_path_buf).unwrap_or_else(|| self.shared.data_dir.join(self.id())); - let _ = std::fs::create_dir_all(&cwd); - let mut cmd = std::process::Command::new(&binary); - ide_core::process::prepare(&mut cmd); - let mut child = cmd - .args(&args) + let binary = self.approved_program(&cmd.program)?; + let cwd = self.spawn_dir(cwd); + let mut command = StdCommand::new(&binary); + ide_core::process::prepare(&mut command); + append_process_env(&mut command, &cmd.env)?; + ide_core::process::own_group(&mut command); + let mut child = command + .args(&cmd.args) .current_dir(&cwd) - .stdin(if stdin.is_some() { std::process::Stdio::piped() } else { std::process::Stdio::null() }) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) + .stdin(if stdin.is_some() { Stdio::piped() } else { Stdio::null() }) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .spawn() .map_err(|e| HostError::Failed(format!("{}: {e}", binary.display())))?; if let (Some(input), Some(mut pipe)) = (stdin, child.stdin.take()) { @@ -1150,30 +1433,36 @@ impl HostApi for PluginApi { let _ = pipe.write_all(input.as_bytes()); }); } - let read = |pipe: Option>| { + let read = |pipe: Option>| { std::thread::spawn(move || { - use std::io::Read; let mut out = Vec::new(); if let Some(p) = pipe { - let _ = p.take(MAX_OUTPUT as u64).read_to_end(&mut out); + let mut p = p; + let _ = p.by_ref().take(MAX_OUTPUT as u64).read_to_end(&mut out); + let _ = std::io::copy(&mut p, &mut std::io::sink()); } - String::from_utf8_lossy(&out).into_owned() + let mut text = String::from_utf8_lossy(&out).into_owned(); + let mut end = text.len().min(MAX_OUTPUT); + while !text.is_char_boundary(end) { + end -= 1; + } + text.truncate(end); + text }) }; - let stdout = read(child.stdout.take().map(|p| Box::new(p) as Box)); - let stderr = read(child.stderr.take().map(|p| Box::new(p) as Box)); + let stdout = read(child.stdout.take().map(|p| Box::new(p) as Box)); + let stderr = read(child.stderr.take().map(|p| Box::new(p) as Box)); let deadline = Instant::now() + timeout; let status = loop { - if let Some(status) = child.try_wait().map_err(|e| HostError::Failed(e.to_string()))? { + if let Some(status) = try_wait_process(&mut child).map_err(|e| HostError::Failed(e.to_string()))? { break status; } if cancel.is_cancelled() || Instant::now() >= deadline { - let _ = child.kill(); - let _ = child.wait(); + ide_core::process::kill_tree(&mut child); return Err(if cancel.is_cancelled() { HostError::Cancelled } else { - HostError::Failed(format!("{program} timed out after {timeout:?}")) + HostError::Failed(format!("{} timed out after {timeout:?}", cmd.program)) }); } std::thread::sleep(Duration::from_millis(10)); @@ -1204,6 +1493,169 @@ impl HostApi for PluginApi { self.shared.sink.emit(None, PluginEvent::SubscriptionsChanged); } } + fn start_process(&self, cmd: ProcessCommand, cwd: Option<&Path>, client: Option<&str>) -> Result { + let binary = self.approved_program(&cmd.program)?; + let cwd = self.spawn_dir(cwd); + if client.is_some_and(|c| self.shared.closed_clients.lock().contains(c)) { + return Err(HostError::Cancelled); + } + { + let processes = self.shared.processes.lock(); + let running = processes.keys().filter(|(plugin, _)| plugin == self.id()).count(); + if running >= MAX_PROCESSES { + return Err(HostError::Invalid(format!( + "at most {MAX_PROCESSES} processes may run at once: release one first" + ))); + } + } + let mut command = StdCommand::new(&binary); + ide_core::process::prepare(&mut command); + append_process_env(&mut command, &cmd.env)?; + ide_core::process::own_group(&mut command); + let mut child = command + .args(&cmd.args) + .current_dir(&cwd) + // Always piped: `write` may come later, and a plugin that never + // writes closes it (or loses it when it releases the handle). + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| HostError::Failed(format!("{}: {e}", binary.display())))?; + let (stdout, stderr) = (child.stdout.take(), child.stderr.take()); + let proc = Arc::new(Proc { + client: client.map(str::to_owned), + state: Mutex::new(ProcState { + stdin: child.stdin.take(), + child, + stdout: Vec::new(), + stderr: Vec::new(), + status: None, + readers: 0, + }), + }); + if let Some(pipe) = stdout { + proc.read(pipe, Stream::Out); + } + if let Some(pipe) = stderr { + proc.read(pipe, Stream::Err); + } + let closed = self.shared.closed_clients.lock(); + if client.is_some_and(|c| closed.contains(c)) { + drop(closed); + proc.kill(); + return Err(HostError::Cancelled); + } + let id = self.shared.next_process.fetch_add(1, Ordering::Relaxed); + self.shared.processes.lock().insert((self.id().to_owned(), id), proc); + drop(closed); + Ok(id) + } + + fn poll_process(&self, id: u64, max: usize) -> Result { + let proc = self.process(id)?; + proc.reap()?; + Ok(proc.chunk(max)) + } + + fn write_process( + &self, + id: u64, + text: &str, + cancel: &CancellationToken, + timeout: Duration, + ) -> Result<(), HostError> { + let proc = self.process(id)?; + proc.reap()?; + let mut pipe = { + let mut state = proc.state.lock(); + if state.status.is_some() { + return Err(HostError::Failed("the process already ended".into())); + } + state.stdin.take().ok_or_else(|| HostError::Failed("its stdin is closed".into()))? + }; + let input = text.to_owned(); + let writer = proc.clone(); + let (tx, rx) = std::sync::mpsc::sync_channel(1); + std::thread::spawn(move || { + use std::io::Write; + let result = pipe.write_all(input.as_bytes()).and_then(|()| pipe.flush()); + let mut state = writer.state.lock(); + if state.status.is_none() { + state.stdin = Some(pipe); + } + let _ = tx.send(result); + }); + let deadline = Instant::now() + timeout; + loop { + if cancel.is_cancelled() || Instant::now() >= deadline { + proc.kill(); + return Err(if cancel.is_cancelled() { + HostError::Cancelled + } else { + HostError::Failed("stdin write timed out".into()) + }); + } + match rx.recv_timeout(Duration::from_millis(10)) { + Ok(result) => return result.map_err(|e| HostError::Failed(format!("stdin: {e}"))), + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {} + Err(_) => return Err(HostError::Failed("stdin writer stopped".into())), + } + } + } + + fn close_process_stdin(&self, id: u64) -> Result<(), HostError> { + let proc = self.process(id)?; + proc.reap()?; + let mut state = proc.state.lock(); + if state.status.is_some() { + return Err(HostError::Failed("the process already ended".into())); + } + state.stdin = None; + Ok(()) + } + + fn release_process(&self, id: u64) { + let proc = self.shared.processes.lock().remove(&(self.id().to_owned(), id)); + if let Some(proc) = proc { + proc.kill(); + } + } + + fn kill_process(&self, id: u64) -> Result<(), HostError> { + self.process(id)?.kill(); + Ok(()) + } + + fn wait_process( + &self, + id: u64, + max: usize, + timeout: Duration, + cancel: &CancellationToken, + ) -> Result { + let proc = self.process(id)?; + let deadline = Instant::now() + timeout; + loop { + proc.reap()?; + let (ended, readers) = { + let state = proc.state.lock(); + (state.status.is_some(), state.readers) + }; + // Its output is only complete once its pipes reached the end too. + if ended && readers == 0 { + break; + } + if cancel.is_cancelled() { + return Err(HostError::Cancelled); + } + if Instant::now() >= deadline { + break; + } + std::thread::sleep(Duration::from_millis(10)); + } + Ok(proc.chunk(max)) + } fn task_begin(&self, client: Option<&str>, title: String, cancellable: bool, cancel: CancellationToken) -> u64 { let id = self.shared.next_task.fetch_add(1, Ordering::Relaxed); @@ -1645,6 +2097,182 @@ mod tests { ); } + #[test] + fn process_environment_only_adds_variables() { + let tmp = tempfile::tempdir().unwrap(); + let binary = ide_core::process::find_executable( + "git", + &ide_core::process::user_path().or_else(|| std::env::var_os("PATH")).unwrap(), + ) + .expect("git on PATH"); + let pins: Arc>> = Arc::default(); + pins.write().insert(("acme.lint".into(), "process:git".into()), ProgramPin::approve(&binary).unwrap()); + let api = api(tmp.path(), pins); + let inherited = std::env::vars_os() + .find(|(key, _)| key != "PATH" && key.to_str().is_some_and(|key| !key.is_empty() && !key.contains('='))) + .unwrap() + .0 + .into_string() + .unwrap(); + for key in ["PATH".to_owned(), inherited] { + let cmd = ProcessCommand { + program: "git".into(), + env: vec![(key.clone(), "replacement".into())], + ..Default::default() + }; + let err = api + .run_process(cmd.clone(), None, None, &CancellationToken::new(), Duration::from_secs(5)) + .unwrap_err(); + assert!(matches!(err, HostError::Invalid(ref message) if message.contains(&key)), "{err}"); + let err = api.start_process(cmd, None, None).unwrap_err(); + assert!(matches!(err, HostError::Invalid(ref message) if message.contains(&key)), "{err}"); + } + } + + #[test] + fn process_environment_uses_prepared_values_and_windows_key_rules() { + let mut cmd = StdCommand::new("unused"); + ide_core::process::prepare(&mut cmd); + cmd.env("OPENIDEA_PREPARED_ENV", "login-shell"); + let err = append_process_env(&mut cmd, &[("OPENIDEA_PREPARED_ENV".into(), "override".into())]).unwrap_err(); + assert!(err.to_string().contains("OPENIDEA_PREPARED_ENV")); + for name in ["", "BAD=KEY", "BAD\0KEY"] { + assert!(append_process_env(&mut cmd, &[(name.into(), "value".into())]).is_err()); + } + for value in ["bad\0value".to_owned(), "x".repeat(4097)] { + assert!(append_process_env(&mut cmd, &[("OPENIDEA_NEW_ENV".into(), value)]).is_err()); + } + assert!( + append_process_env( + &mut cmd, + &[("OPENIDEA_NEW_ENV".into(), "a".into()), ("OPENIDEA_NEW_ENV".into(), "b".into())] + ) + .is_err() + ); + assert_eq!(env_name_eq("PATH".as_ref(), "Path".as_ref()), cfg!(windows)); + #[cfg(windows)] + { + assert!(append_process_env(&mut cmd, &[("pAtH".into(), "override".into())]).is_err()); + assert!(env_name_eq("Ä_KEY".as_ref(), "ä_key".as_ref())); + } + cmd.env_remove("OPENIDEA_REMOVED_ENV"); + append_process_env(&mut cmd, &[("OPENIDEA_REMOVED_ENV".into(), "added".into())]).unwrap(); + assert_eq!(cmd.get_envs().find(|(k, _)| *k == "OPENIDEA_REMOVED_ENV").unwrap().1, Some("added".as_ref())); + } + + #[test] + fn process_output_preserves_partial_utf8_and_bounds_replacement_text() { + let mut bytes = vec![0xe4, 0xb8]; + assert_eq!(take_text(&mut bytes, 1, false), ""); + bytes.extend_from_slice(&[0xad, b'x']); + assert_eq!(take_text(&mut bytes, 1, false), "中"); + assert_eq!(take_text(&mut bytes, 0, false), "x"); + let mut bytes = vec![0xff; MAX_PENDING]; + let text = take_text(&mut bytes, MAX_PENDING, true); + assert!(text.len() <= MAX_PENDING); + assert!(!bytes.is_empty()); + assert_eq!(take_text(&mut vec![0xe4, 0xb8], 8, true), "�"); + } + + #[cfg(unix)] + fn shell_api(dir: &Path) -> PluginApi { + let pins: Arc>> = Arc::default(); + pins.write() + .insert(("acme.lint".into(), "process:sh".into()), ProgramPin::approve(Path::new("/bin/sh")).unwrap()); + api(dir, pins) + } + + #[cfg(unix)] + fn shell(script: &str) -> ProcessCommand { + ProcessCommand { program: "sh".into(), args: vec!["-c".into(), script.into()], env: Vec::new() } + } + + #[cfg(unix)] + #[test] + fn process_handles_are_bounded_scoped_and_released() { + let tmp = tempfile::tempdir().unwrap(); + let api = shell_api(tmp.path()); + let mut ids = Vec::new(); + for _ in 0..MAX_PROCESSES { + ids.push(api.start_process(shell("exec cat"), None, Some("main")).unwrap()); + } + assert!(matches!(api.start_process(shell("exec cat"), None, None), Err(HostError::Invalid(_)))); + let proc = api.process(ids[0]).unwrap(); + api.release_process(ids[0]); + assert!(proc.state.lock().child.try_wait().unwrap().is_some()); + assert!(api.process(ids[0]).is_err()); + let other = api.start_process(shell("exec cat"), None, Some("other")).unwrap(); + api.shared.forget_client("main"); + assert!(api.process(ids[1]).is_err()); + assert!(api.process(other).is_ok()); + assert!(matches!(api.start_process(shell("exec cat"), None, Some("main")), Err(HostError::Cancelled))); + api.shared.kill_processes(api.id()); + assert!(api.shared.processes.lock().is_empty()); + } + + #[cfg(unix)] + #[test] + fn process_stdin_timeout_and_stop_do_not_deadlock() { + let tmp = tempfile::tempdir().unwrap(); + let api = Arc::new(shell_api(tmp.path())); + for stop in [false, true] { + let id = api.start_process(shell("exec sleep 60"), None, Some("main")).unwrap(); + let proc = api.process(id).unwrap(); + let writer = api.clone(); + let thread = std::thread::spawn(move || { + writer.write_process(id, &"x".repeat(1 << 20), &CancellationToken::new(), Duration::from_millis(150)) + }); + if stop { + std::thread::sleep(Duration::from_millis(30)); + api.shared.kill_processes(api.id()); + } + assert!(thread.join().unwrap().is_err()); + assert!(proc.state.lock().child.try_wait().unwrap().is_some()); + api.release_process(id); + } + } + + #[cfg(unix)] + #[test] + fn process_parent_exit_closes_descendant_pipes() { + let tmp = tempfile::tempdir().unwrap(); + let api = shell_api(tmp.path()); + let token = CancellationToken::new(); + let before = Instant::now(); + let out = + api.run_process(shell("sleep 60 & echo done"), None, None, &token, Duration::from_millis(300)).unwrap(); + assert_eq!(out.status, Some(0)); + assert_eq!(out.stdout.trim(), "done"); + assert!(before.elapsed() < Duration::from_secs(5)); + let id = api.start_process(shell("sleep 60 & echo done"), None, None).unwrap(); + let out = api.wait_process(id, MAX_PENDING, Duration::from_millis(300), &token).unwrap(); + assert_eq!(out.status, Some(0)); + assert_eq!(out.stdout.trim(), "done"); + assert_eq!(api.process(id).unwrap().state.lock().readers, 0); + api.release_process(id); + } + + #[cfg(unix)] + #[test] + fn process_output_is_drained_and_retention_is_bounded() { + let tmp = tempfile::tempdir().unwrap(); + let api = shell_api(tmp.path()); + let token = CancellationToken::new(); + let cmd = shell("head -c 5242880 /dev/zero; head -c 5242880 /dev/zero >&2"); + let out = api.run_process(cmd.clone(), None, None, &token, Duration::from_secs(5)).unwrap(); + assert_eq!(out.status, Some(0)); + assert_eq!((out.stdout.len(), out.stderr.len()), (MAX_OUTPUT, MAX_OUTPUT)); + let id = api.start_process(cmd, None, None).unwrap(); + let out = api.wait_process(id, MAX_PENDING, Duration::from_secs(5), &token).unwrap(); + assert_eq!(out.status, Some(0)); + assert_eq!((out.stdout.len(), out.stderr.len()), (MAX_PENDING, MAX_PENDING)); + let state = api.process(id).unwrap(); + let state = state.state.lock(); + assert!(state.stdout.is_empty() && state.stderr.is_empty()); + drop(state); + api.release_process(id); + } + #[test] fn programs_run_only_as_approved() { let tmp = tempfile::tempdir().unwrap(); diff --git a/crates/ide-host/src/plugins/mod.rs b/crates/ide-host/src/plugins/mod.rs index 8396d6d86..5d9b2dd60 100644 --- a/crates/ide-host/src/plugins/mod.rs +++ b/crates/ide-host/src/plugins/mod.rs @@ -1769,6 +1769,7 @@ impl PluginHost { for a in actors { a.stop(true); } + self.shared.finish_process_shutdowns(); } /// Periodic upkeep (every few seconds): reports crashes nobody waited @@ -2165,6 +2166,8 @@ impl PluginHost { *runtime = None; } actor.stop(false); + // Its handles go with its instance; an idle reclaim is one. + self.shared.kill_processes(plugin.id()); let mut meta = plugin.meta.lock(); if meta.status == PluginStatus::Active { meta.status = PluginStatus::Inactive; diff --git a/crates/ide-host/tests/plugins.rs b/crates/ide-host/tests/plugins.rs index d2b7393db..295effc30 100644 --- a/crates/ide-host/tests/plugins.rs +++ b/crates/ide-host/tests/plugins.rs @@ -551,6 +551,103 @@ fn fs_write_reaches_only_the_granted_paths() { assert_eq!((info.status, info.stats.crashes, info.stats.errors), (PluginStatus::Active, 0, 0), "{info:?}"); } +/// A program kept running behind a handle: the plugin writes its stdin, closes +/// it, and waits for the exit status and what the program printed. A one-off run +/// passes the environment it names. +#[test] +fn a_plugin_keeps_a_process_running_behind_a_handle() { + let f = fixture(Limits::default()); + f.install(&misbehave_pkg()); + f.host.set_capability("misbehave", "process:git", false).unwrap(); + assert!(f.run("misbehave", "envrun", Some("legacy")).unwrap_err().contains("process:git")); + for command in ["envrun", "envstart", "child"] { + let err = f.run("misbehave", command, None).unwrap_err(); + assert!(err.contains("process:git"), "{command}: {err}"); + } + // The fixture declares `process:git` as optional: it needs a program that + // reads stdin and reports its environment. Machines running these tests have + // git; without one, there is nothing to run and nothing to check. + if f.host.set_capability("misbehave", "process:git", true).is_err() { + return; + } + + let out = f.run("misbehave", "child", None).expect("the handle works").expect("a result"); + let (hash, status) = out.split_once(' ').unwrap_or_else(|| panic!("{out}")); + assert_eq!(hash.len(), 40, "a sha1 of what was written: {out}"); + assert!(hash.chars().all(|c| c.is_ascii_hexdigit()), "{out}"); + assert_eq!(status, "Some(0)", "it ended by itself: {out}"); + + assert!(f.run("misbehave", "envrun", Some("legacy")).unwrap().unwrap().starts_with("git version")); + let ident = f.run("misbehave", "envrun", None).expect("a run with extra environment").expect("a result"); + assert_eq!(ident.trim(), "added by the plugin"); + assert_eq!(f.run("misbehave", "envstart", None).unwrap().unwrap().trim(), "added by the plugin"); + for command in ["envdeny-run", "envdeny-start"] { + let inherited = std::env::vars().find(|(key, _)| key != "PATH" && !key.contains('=')).unwrap().0; + for key in ["PATH", inherited.as_str(), "", "BAD=KEY", "BAD\0KEY"] { + let err = f.run("misbehave", command, Some(key)).unwrap_err(); + assert!(err.contains("environment variable"), "{command} {key:?}: {err}"); + } + } + // Dropping a resource returns the slot; the limit is simultaneous handles. + for _ in 0..10 { + f.run("misbehave", "childkeep", None).unwrap(); + assert_eq!(f.run("misbehave", "childpoll", None).unwrap().as_deref(), Some("None")); + f.run("misbehave", "childdrop", None).unwrap(); + } + for _ in 0..8 { + f.run("misbehave", "childkeep", None).unwrap(); + } + assert!(f.run("misbehave", "childkeep", None).unwrap_err().contains("at most 8")); + f.run("misbehave", "childdrop", None).unwrap(); + f.run("misbehave", "childkeep", None).unwrap(); + f.host.forget_client("main"); + assert!(f.run("misbehave", "childpoll", None).unwrap_err().contains("no process")); + assert!(f.run("misbehave", "childkeep", None).is_err(), "late starts are cancelled"); + f.host.reopen_client("main"); + f.run("misbehave", "childdrop", None).unwrap(); + + let info = f.host.info_of("misbehave").unwrap(); + assert_eq!((info.status, info.stats.crashes, info.stats.errors), (PluginStatus::Active, 0, 0), "{info:?}"); +} + +#[cfg(unix)] +#[test] +fn plugin_processes_end_with_their_owner() { + for mode in ["disable", "unload", "shutdown", "reclaim", "trap", "window"] { + let f = fixture(Limits { idle_timeout: Some(Duration::from_millis(50)), ..Limits::default() }); + f.install(&misbehave_pkg()); + f.host.set_capability("misbehave", "process:git", true).unwrap(); + let marker = format!("--path={}/process-{mode}", f.project.display()); + let alive = || { + let ps = std::process::Command::new("ps").args(["-axo", "command="]).output().unwrap(); + assert!(ps.status.success()); + String::from_utf8_lossy(&ps.stdout).lines().any(|line| line.contains(&marker)) + }; + f.run("misbehave", "childkeep", marker.strip_prefix("--path=")).unwrap(); + assert!(alive(), "{mode}: the process started"); + match mode { + "disable" => f.host.set_enabled("misbehave", false).unwrap(), + "unload" => f.host.uninstall("misbehave").unwrap(), + "shutdown" => f.host.shutdown(), + "reclaim" => { + std::thread::sleep(Duration::from_millis(75)); + f.host.tick(); + assert_eq!(f.host.instances(), 0); + } + "trap" => { + assert!(f.run("misbehave", "panic", None).is_err()); + } + "window" => f.host.forget_client("main"), + _ => unreachable!(), + } + let deadline = Instant::now() + Duration::from_secs(5); + while alive() && Instant::now() < deadline { + std::thread::sleep(Duration::from_millis(10)); + } + assert!(!alive(), "{mode}: the process must end with its owner"); + } +} + /// A missing capability is an error the plugin can handle, not a trap. #[test] fn ungranted_capabilities_are_denied_not_trapped() { @@ -827,7 +924,7 @@ fn idle_instances_are_reclaimed_and_schedules_reactivate() { assert_eq!(f.host.instances(), 0); let info = f.host.info_of("misbehave").unwrap(); assert_eq!((info.status, info.stats.reclaims), (PluginStatus::Inactive, 1)); - assert_eq!(f.command_titles("misbehave").len(), 14, "contributions stay"); + assert_eq!(f.command_titles("misbehave").len(), 23, "contributions stay"); assert_eq!(f.run("misbehave", "echo", Some("y")).unwrap().as_deref(), Some("y")); assert_eq!(f.host.info_of("misbehave").unwrap().stats.activations, 2); diff --git a/crates/ide-plugin/src/runtime.rs b/crates/ide-plugin/src/runtime.rs index 38496f0bf..8206c78d8 100644 --- a/crates/ide-plugin/src/runtime.rs +++ b/crates/ide-plugin/src/runtime.rs @@ -8,8 +8,9 @@ use crate::types::{ PluginHover, PluginInlayHint, ProviderDocument, SemanticToken, }; use crate::types::{ - EventKind, HostError, HttpRequest, HttpResponse, Invocation, Limits, NotificationLevel, ProcessOutput, Prompt, - RepositoryStatus, StatusItem, SyntaxFold, SyntaxSymbol, SyntaxToken, WorkbenchEvent, + EventKind, HostError, HttpRequest, HttpResponse, Invocation, Limits, NotificationLevel, ProcessChunk, + ProcessCommand, ProcessOutput, Prompt, RepositoryStatus, StatusItem, SyntaxFold, SyntaxSymbol, SyntaxToken, + WorkbenchEvent, }; use crate::types::{ FieldValue, ServiceLog, ServiceLogToggle, ServicePane, ServiceTab, TableSort, View, ViewBody, ViewButton, @@ -167,11 +168,11 @@ pub trait HostApi: Send + Sync { /// Performs `request` (its host is granted), filling in secret placeholders. fn fetch(&self, request: HttpRequest, cancel: &CancellationToken) -> Result; fn secret_is_set(&self, name: &str) -> bool; - /// Runs the approved file of `process:`. + /// Runs the approved file of `process:` with `cmd.env` added to + /// the inherited environment, and waits. fn run_process( &self, - program: &str, - args: Vec, + cmd: ProcessCommand, cwd: Option<&Path>, stdin: Option, cancel: &CancellationToken, @@ -182,6 +183,35 @@ pub trait HostApi: Send + Sync { /// Delivers `kind` (`file-opened`, `file-saved`) only for paths matching `globs`. fn subscribe_paths(&self, kind: &str, globs: Vec) -> Result<(), HostError>; fn unsubscribe(&self, kind: &str); + /// Starts `cmd` and returns the handle to it without waiting. + fn start_process(&self, cmd: ProcessCommand, cwd: Option<&Path>, client: Option<&str>) -> Result; + /// What process `id` printed since the last call, each stream at most + /// `max` bytes (the rest waits for the next call), and its status once it + /// ended. + fn poll_process(&self, id: u64, max: usize) -> Result; + /// Writes `text` to the stdin of process `id`. + fn write_process( + &self, + id: u64, + text: &str, + cancel: &CancellationToken, + timeout: Duration, + ) -> Result<(), HostError>; + /// Closes the stdin of process `id`. + fn close_process_stdin(&self, id: u64) -> Result<(), HostError>; + /// Ends process `id`; not an error if it already ended. + fn kill_process(&self, id: u64) -> Result<(), HostError>; + /// Forgets the handle and ends its process if it still runs. + fn release_process(&self, id: u64); + /// Waits up to `timeout` for process `id` to end (`ZERO` only checks) and + /// returns what it printed meanwhile with its status once it ended. + fn wait_process( + &self, + id: u64, + max: usize, + timeout: Duration, + cancel: &CancellationToken, + ) -> Result; /// Starts a progress indicator; cancelling it from the UI cancels `cancel`. fn task_begin(&self, client: Option<&str>, title: String, cancellable: bool, cancel: CancellationToken) -> u64; fn task_report(&self, id: u64, done: u32, total: Option, message: Option); diff --git a/crates/ide-plugin/src/types.rs b/crates/ide-plugin/src/types.rs index d12365203..5e518edbb 100644 --- a/crates/ide-plugin/src/types.rs +++ b/crates/ide-plugin/src/types.rs @@ -352,6 +352,24 @@ pub struct ProcessOutput { pub stderr: String, } +/// What to run: the approved file of `process:`, its arguments and +/// the environment to add to the inherited one (WIT `process.command`). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ProcessCommand { + pub program: String, + pub args: Vec, + pub env: Vec<(String, String)>, +} + +/// What a kept-running program printed since the last read, and its exit +/// status once it ended (WIT `process.chunk`). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ProcessChunk { + pub stdout: String, + pub stderr: String, + pub status: Option, +} + /// Where `net:` requests may go: the URL's host when it is `https`, or /// `http` to the loopback interface. pub fn net_host(url: &str) -> Result { diff --git a/crates/ide-plugin/src/wit_host.rs b/crates/ide-plugin/src/wit_host.rs index 607558473..d1c69a1fa 100644 --- a/crates/ide-plugin/src/wit_host.rs +++ b/crates/ide-plugin/src/wit_host.rs @@ -5,7 +5,8 @@ use crate::runtime::{MAX_TITLE, StoreData, clip}; use crate::sandbox; use crate::types::{ - EditorContext, EventKind, HostError, HttpRequest, NotificationLevel, PickItem, Prompt, StatusItem, net_host, + EditorContext, EventKind, HostError, HttpRequest, NotificationLevel, PickItem, ProcessChunk, ProcessCommand, + Prompt, StatusItem, net_host, }; use ide_core::ipc_path::to_ipc; use ide_extension::Capability; @@ -23,6 +24,7 @@ pub(crate) mod bindings { "openidea:plugin/editor.text-editor": super::EditorRes, "openidea:plugin/invocation.invocation": super::InvocationRes, "openidea:plugin/tasks.task": super::TaskRes, + "openidea:plugin/process.child": super::ChildRes, }, }); } @@ -124,6 +126,19 @@ pub struct ProjectRes { root: PathBuf, } +/// A program a plugin started behind a handle (`process.start`); dropping the +/// handle ends it if it still runs. +pub struct ChildRes { + id: u64, + host: std::sync::Arc, +} + +impl Drop for ChildRes { + fn drop(&mut self) { + self.host.release_process(self.id); + } +} + /// The invoking window's focused editor. pub struct EditorRes { ctx: EditorContext, @@ -143,6 +158,15 @@ const MAX_FIND: u32 = 10_000; const MAX_STATUS_TEXT: usize = 128; const MAX_PICK_ITEMS: usize = 5000; const MAX_PICK_KEY: usize = 256; +const MAX_ARGS: usize = 256; +const MAX_ARG_BYTES: usize = 4096; +const MAX_ENV: usize = 64; +const MAX_ENV_NAME: usize = 256; +const MAX_ENV_VALUE: usize = 4096; +/// Most output one `poll` / `wait` hands out, per stream. +const MAX_CHUNK: usize = 1 << 20; +/// Most text one `write` puts into a process's stdin. +const MAX_STDIN_BYTES: usize = 1 << 20; type Fallible = wasmtime::Result>; @@ -933,6 +957,45 @@ impl secrets::Host for StoreData { } } +impl StoreData { + /// The `command` of `process.run-command` / `process.start`, checked: the + /// program is the one the user approved, the sizes stay in the limits. + fn command(&self, cmd: process::Command) -> Result { + self.require(Capability::Process(cmd.program.clone()))?; + if cmd.args.len() > MAX_ARGS || cmd.args.iter().any(|a| a.len() > MAX_ARG_BYTES) { + return Err(wit::Error::Invalid(format!("at most {MAX_ARGS} arguments of at most {MAX_ARG_BYTES} bytes"))); + } + if cmd.env.len() > MAX_ENV { + return Err(wit::Error::Invalid(format!("at most {MAX_ENV} environment variables"))); + } + for (name, value) in &cmd.env { + if name.is_empty() || name.len() > MAX_ENV_NAME || name.contains(['=', '\0']) { + return Err(wit::Error::Invalid(format!("{name:?} is not an environment variable name"))); + } + if value.len() > MAX_ENV_VALUE || value.contains('\0') { + return Err(wit::Error::Invalid(format!("the value of {name:?} is too long or holds a NUL"))); + } + } + Ok(ProcessCommand { program: cmd.program, args: cmd.args, env: cmd.env }) + } + + /// The project a call runs in, when it named one. + fn cwd_of(&self, cwd: Option>) -> wasmtime::Result> { + Ok(match &cwd { + Some(p) => Some(self.table.get(p)?.root.clone()), + None => None, + }) + } +} + +fn to_chunk(chunk: ProcessChunk) -> process::Chunk { + process::Chunk { stdout: chunk.stdout, stderr: chunk.stderr, status: chunk.status } +} + +fn to_output(output: crate::types::ProcessOutput) -> process::Output { + process::Output { status: output.status, stdout: output.stdout, stderr: output.stderr } +} + impl process::Host for StoreData { fn run( &mut self, @@ -941,20 +1004,81 @@ impl process::Host for StoreData { cwd: Option>, stdin: Option, ) -> Fallible { - if let Err(e) = self.require(Capability::Process(program.clone())) { - return Ok(Err(e)); - } - let cwd = match &cwd { - Some(p) => Some(self.table.get(p)?.root.clone()), - None => None, + let cmd = match self.command(process::Command { program, args, env: Vec::new() }) { + Ok(cmd) => cmd, + Err(e) => return Ok(Err(e)), }; + let cwd = self.cwd_of(cwd)?; let token = self.cancel.token(); let timeout = self.remaining(); - Ok(self - .host - .run_process(&program, args, cwd.as_deref(), stdin, &token, timeout) - .map(|o| process::Output { status: o.status, stdout: o.stdout, stderr: o.stderr }) - .map_err(to_wit)) + Ok(self.host.run_process(cmd, cwd.as_deref(), stdin, &token, timeout).map(to_output).map_err(to_wit)) + } + + fn run_command( + &mut self, + cmd: process::Command, + cwd: Option>, + stdin: Option, + ) -> Fallible { + let cmd = match self.command(cmd) { + Ok(cmd) => cmd, + Err(e) => return Ok(Err(e)), + }; + let cwd = self.cwd_of(cwd)?; + let token = self.cancel.token(); + let timeout = self.remaining(); + Ok(self.host.run_process(cmd, cwd.as_deref(), stdin, &token, timeout).map(to_output).map_err(to_wit)) + } + + fn start(&mut self, cmd: process::Command, cwd: Option>) -> Fallible> { + let cmd = match self.command(cmd) { + Ok(cmd) => cmd, + Err(e) => return Ok(Err(e)), + }; + let cwd = self.cwd_of(cwd)?; + let id = match self.host.start_process(cmd, cwd.as_deref(), self.invocation.client.as_deref()) { + Ok(id) => id, + Err(e) => return Ok(Err(to_wit(e))), + }; + self.push(ChildRes { id, host: self.host.clone() }).map(Ok) + } +} + +impl process::HostChild for StoreData { + fn poll(&mut self, this: Resource, max_bytes: u32) -> Fallible { + let id = self.table.get(&this)?.id; + let max = (max_bytes as usize).min(MAX_CHUNK); + Ok(self.host.poll_process(id, max).map(to_chunk).map_err(to_wit)) + } + + fn wait(&mut self, this: Resource, timeout_ms: u32) -> Fallible { + let id = self.table.get(&this)?.id; + let timeout = Duration::from_millis(u64::from(timeout_ms)).min(self.remaining()); + let token = self.cancel.token(); + Ok(self.host.wait_process(id, MAX_CHUNK, timeout, &token).map(to_chunk).map_err(to_wit)) + } + + fn write(&mut self, this: Resource, text: String) -> Fallible<()> { + if text.len() > MAX_STDIN_BYTES { + return Ok(Err(wit::Error::Invalid(format!("at most {MAX_STDIN_BYTES} bytes at a time")))); + } + let id = self.table.get(&this)?.id; + Ok(self.host.write_process(id, &text, &self.cancel.token(), self.remaining()).map_err(to_wit)) + } + + fn close_stdin(&mut self, this: Resource) -> Fallible<()> { + let id = self.table.get(&this)?.id; + Ok(self.host.close_process_stdin(id).map_err(to_wit)) + } + + fn kill(&mut self, this: Resource) -> Fallible<()> { + let id = self.table.get(&this)?.id; + Ok(self.host.kill_process(id).map_err(to_wit)) + } + + fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + self.table.delete(rep)?; + Ok(()) } } diff --git a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm index 49e1375530a9d39b1fb94ab03406cb6c64b3e0b1..30247799c67c7b1dc5b4e4c71968688079e2ec80 100644 GIT binary patch delta 195 zcmdo0gLVH8)(wKvQs>wF>~DWN=UGmx0<$JFhtrlCYy^{O|}z>ms*IH;9X4+hX<%eL2vj)o|mv2?Pud^S)~yF98QRF?r2d2Oyy2s$mW{L4Y36SfBu_O3Y*A-y~Nz~+0eJ>3Co CY;mptCIcJGnB$6j-{Dlls@ zb2#lge)r@0FI@*CH*XhdU}QW1R5%Hbq9aazcgxF7UM?&-xmz|Gx8g~1MT}D? z7t5iT$B1I-WO?~)pqg%Z6g8+01FG?w%q*(7d69zQX#sb~)$VU3uXFE9HvhLsyXofU I$FDuz0e3rd*Z=?k diff --git a/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm.sources index 3bfe310f1..99bb14143 100644 --- a/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm.sources @@ -1 +1 @@ -892b7ca523ef251eb626aece060acd54258afaf8204fe02713b9e57b5ca9a67e +6c2a59bf732acebd74ad4d05824be3aafbd501964be01205ca1e0d5a6135d113 diff --git a/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm index 22d911225a4cfc0553a962c3c25ed75eb4292a21..84917b1f06b6176303a650b0d2281d29c0bd2d94 100644 GIT binary patch delta 211 zcmX>zndQP{mJP)mjMF!laAGhix#2uLNDVVGv zF1C51)L9Hg7RaVl%GERBGi9@|A}b?`0!CDKO!iUsnp~^21w{?ITYZ$jz7@E5zJ2;N WnT>N(I%j=mP_CQ4Jy4MGyB7e%O?HI< delta 237 zcmX@HgZ0D?)(x!U(p#=AUp0UIsta@LTNRi!nK_&`9@@U5|L}nu{}?tah&wRjQ!rUU zTx|11sk0c0ERap9l&fdLXUb+_MOH=>1&pZfnCzqMHMv%43yKcVA@!>Jy4MGyB7eWr*qx_ diff --git a/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm.sources index a89462c27..a49e9bfbe 100644 --- a/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm.sources @@ -1 +1 @@ -6981c76c96e2e7ab086be65847010bb22cb012c259cdec075b2285253c73f637 +97f7f6fe965e95510d5556feb40329c312e5430e631479be6c9646046a888478 diff --git a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm index 9f94b36dedc21fb27b7e7156e707bc576fbc4b12..aa239bbee654cdb2af09cc95a0c049cbf326ab93 100644 GIT binary patch delta 397 zcmex-k>m44jt$M?9DK~2%#0jNOq;vKTLl=`P8QUNWL&#BTSJ;@^9udzOpNO$HyG(L zuG_rcsF!JTn-&i<^9+1HM!1AY;&{23`WLPlVzMLfaFxC?|2Md z>MX~GCM|(v=N-?ztZ356PTJ)E0L3kgs4iX|=!7DT>Y>T218p`t1wZ6sTr*iEE(l1L z$F(r7p8O}yn{oAKkNEo`K#s;1KgQL~m44jt$M?92`tce9WB8jGMc~TLl<5OcvCLWZbYhTSJ;@vy=f3Gvn&X>c+bi z)-*IUFeorPGMX_-FgYkNYcM4^*1Pe7gdQ+t39OmSXi~tqW^$3qsm*O#*O?eMPL9*n zW8Ap8SGSjGvyG`HqvWm+yXWpY)6>S%s=%zt%;7X){r^QXSDaYvy}8aznS*iF=0$d! z@Ti(xXC}6}*JaAaJ=VWJTQ5IU>@VI$^1f7B^&N+I&*7!?}ma_1r|+a38$|O zlNQZbvwYpE&HIE>IT^1_=9d$nY%SA-tdLognFC4Tbs2L;#<@U+lW;4Vyj!lAaoS{k zc@#B_C@V|$^1f7r6zWqpTGI&^PT?~S`}C{nI)Y1 zr~Y_*_sOe&J)8FmrE)S}pUf*KKG{m92}LEdCNl?;%BwQwjEwVuDktGlHF<|zG1GLW z$qzI|Cco=qo9r%Mj-r|o#mvd?6}%@aDJU{d2O29k*-Kt$a-V_+iUu@CyjN&=ARu_; Zv$6KP;<|pWeRow?|Lxgc&&b#m2mlcTd;I_a diff --git a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources index 42b5b3577..be93a9417 100644 --- a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources @@ -1 +1 @@ -00293224c09af5a08ea8862e4fd6263ea36f500750752b428f8e6ff61747a524 +a985162e44066c6bf6c93d01af1b01d40d305c6be43072d4f33d4f1f19a4dee0 diff --git a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm index 5c3d36db5cef70abafec3afca029df7f9b3812cf..f70d375deca726da59962e973a31a421b6008e0a 100644 GIT binary patch delta 248 zcmdn^fo0nVmJRDU81HW0!10uW@xbJ1f_aSBCchIFpDZKPB=zFk$|-ZUZrsD$s=%zt z%;B`^bZ@Ti(RQ>2J-`(z$be2O-27nR^-oHqG_9EvJN zR0}6p%Vz@>y%Uz3{7z11vY~y*A%bXt^SgQQdWD<RZlXamTrDNadW}f GgH8bcn0yET delta 250 zcmdn?fo0DFKhlRWLFPJ|jFE8;P~jxpiYCt%DPr6z#-1b*g5%xU>@Vk$>u^+C1?Jg(f45clox!h3M`t;5>5}d z?$|hQ!oS0Ho1Y0yU}L;GIZ;}Ca-U=qvO;D}W)38UT2f|=jB|hrC*f8!nOCNWaq8p- z85A{)C@VE$>u^+rB1K^)zvv?$^PB-tqLrf%o0vt z-XHt+`hRb0&gQ2=6WAE9O^%lqpWG|igrbsJlbHiarG}IlBja44%1L-sP3Dp*Vw^U) zRt8lOBZ^Uz?d7~C7s)CD6~;@;O`a{IGx?sZ2Od*5+siF{DR6pv+{6bH>UMwGesSMF ObFMerKd>`a1_1z%j(VB^ diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources index 3290bbb05..951d0bc92 100644 --- a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources @@ -1 +1 @@ -ad8fb651c09a49be8254bfe5fbaf7463f80e23ec712038e78141e3e32abb93eb +9a3d097559f6685e82024bd84cb0396875762b7136636fdc9bd11cb41875373a diff --git a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm index 07cb72bd31ab41425991b58689e389773c5fd0f1..640b15a49b8d4b6c4fcb7204dddcc431d71541f7 100644 GIT binary patch delta 7148 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 Uebfu1>mJCyz1|zJ7LBw11L^h_;{X5v delta 7147 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 diff --git a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources index e265a35ae..d2a7ba675 100644 --- a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources @@ -1 +1 @@ -7fde1c70e9e17693144c7229e53dac200f18c281e0bc83fffc21f3d18671d1be +6ca0c03e31e0cd67192bb86c93b1feb294fec79cf432ceb57b9cdc3e42eb6b02 diff --git a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm index d6136a572a94a061db416e017ba97e8391531be4..ea3a85795bdb1fc6d8ac86f2ed9eee0ef31d91d2 100644 GIT binary patch delta 46570 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�UZKyatX{fdAIVy`a`ntf}HQmi;bPo;S zWF#E!BOgL@+Gj6bfWwt$cDJ-I9IDNnGpB{`a5zvi?z)C`s)3e;UCnmkE>CB3N88NV z&5byuNgTN3lG!>&cDh{ME!}PQu}wpL9i7dm;)tl`IWbprHG>-EoHMg~=Fs_rJDtw9 zmW9n{I$LI=N>6utds}x)N0+;MX6M}I?lGcKS!hsN4ZT+RMjAWlQANSwK72eBJ%-yk z&27!ym-mB{PtXqgS8DN~-hv|uq1Wz-e&0QFN_Iu|IaOQb1|#5qHX zGi!Ex$C)+V?KQL8S~_O6&+MF|xH1@04NjcGIa%$UXDV)=h*&Ul;hBoZ?IExk7)=cl zkvzRW1_k)d%c<4rGGBBSA>SfRXevF^I<#mu_s`^JFS5O>xWFN?-_^b_HHBVJ= zCV`_EQfFPd36AEVxi%ag5OS9~BqW%R41S11ars*op4L8FFayJ!rXEY=Sp6s!JCuw< z3&*+SSo#PATU9BIAB|IWe$KqLSEnV|)6i>bl9flx7hdgjFTTs-3fZ1BGYkManeS|umNVR_T zVj)+_w?5xQCD{dTKP3+9;!M-6j4f1L9F|9Xc{|$MAU$o(oOGnSmk z)mv>_u=-WVF-I#!PR^_p@8>`+E`8Mg4)fxKqunE%5^HE1MqZM=VF)Rqa%;zzly7C+ zPlNT6k`p?cyV@6b&Tg)m)7jo}QrhuhN~tyTe)u@cibWBRI95tQd7s+Xq3B6Kz6e%! z_IKVF<8qAJ2P8Fnipo7vw>a<1s(*lTJ;$mQh%Ymj6on6y)hd1B{m?K}%|l`)WE7{d zoz~LXzK|7%QsMT2T-na%Gg`X6F5E)b`H@jXD1F@-h|;VL58%6Dm2UK;l3~5}0QK?o z6Azh|<3Z{Zs7ln*Xw^SRgELT?OBCWzSL~5c|&2Yn(eddwoaqjYNn5F$ zg4UuOHOE@E73)YLb5BumI6EWf^qE~PBcU5=nPg3MgBk|aWoKGDwo*P}=Qd6Nvp(2L znO0~Uj(EyWp8f@>ITD=aQUlh|ZB%HrbDlhLA{dhM#d%-`xV-_R3x(YanisIL0WFfl zi9)tkqj1`(PhNDIf+NBdXRYPyrIP75K@9f_4%&bUiYq*k%WtP}36cs>#0TtnXaa>2 z3Q;5lCEzo9smvXc_t*4N@$ngU5#|y~>O@f{(@qN@&1;|OCK5C|Aqxp=n#L2!SrUHM zBNPc`+gZywD{H}k$dV(oG(SR>?p%5Q<0EiC=UI_QsVbHaWg*(_i7PZws=x-u?Mvz~ zkrcL*n86(>I!z=N+KJpqM^eX$w4#(Wx2@zdaK@ZSEEb8@@<*t;Pl=t8rOhXFke6>u zaa4t~)?JC)5UYl$_12^03Y2jRbdHpYB3CF1uLm*ZoYY_BXRT2$B6W_;MP%0A0xl52 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 Ktw6q7|GxlN%G)*o diff --git a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources index 43a3ab2fd..fb352bce8 100644 --- a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources @@ -1 +1 @@ -47623924eee80c6d1aa9dcc22825ea70b92e1b1a41ee5741f20d4d20cdf49eb3 +0ec5c31f6b9b221b0b7be58f73d5c94703e8c14ada610d8ad12f84f63bf5fe97 diff --git a/crates/ide-plugin/tests/fixtures/misbehave/plugin.toml b/crates/ide-plugin/tests/fixtures/misbehave/plugin.toml index 603b77c3d..66d85d8ce 100644 --- a/crates/ide-plugin/tests/fixtures/misbehave/plugin.toml +++ b/crates/ide-plugin/tests/fixtures/misbehave/plugin.toml @@ -8,6 +8,7 @@ activation = ["on-command:*"] [capabilities] required = ["project.read"] +optional = ["process:git"] [[contributes.commands]] id = "echo" @@ -64,3 +65,39 @@ title = "subscribe" [[contributes.commands]] id = "diagnose" title = "diagnose" + +[[contributes.commands]] +id = "child" +title = "child" + +[[contributes.commands]] +id = "envrun" +title = "envrun" + +[[contributes.commands]] +id = "envstart" +title = "envstart" + +[[contributes.commands]] +id = "envdeny-run" +title = "envdeny-run" + +[[contributes.commands]] +id = "envdeny-start" +title = "envdeny-start" + +[[contributes.commands]] +id = "childkeep" +title = "childkeep" + +[[contributes.commands]] +id = "childpoll" +title = "childpoll" + +[[contributes.commands]] +id = "childdrop" +title = "childdrop" + +[[contributes.commands]] +id = "childtrap" +title = "childtrap" diff --git a/crates/ide-plugin/tests/fixtures/misbehave/src/lib.rs b/crates/ide-plugin/tests/fixtures/misbehave/src/lib.rs index 843d83d4b..e274a24f6 100644 --- a/crates/ide-plugin/tests/fixtures/misbehave/src/lib.rs +++ b/crates/ide-plugin/tests/fixtures/misbehave/src/lib.rs @@ -20,10 +20,14 @@ use exports::openidea::plugin::providers::{ }; use exports::openidea::plugin::syntax_provider::{self, Fold, Symbol, Token}; use exports::openidea::plugin::{commands, event_handler, lifecycle}; -use openidea::plugin::{invocation::Invocation, types}; +use openidea::plugin::{invocation::Invocation, process, types}; struct Misbehave; +thread_local! { + static CHILDREN: std::cell::RefCell> = const { std::cell::RefCell::new(Vec::new()) }; +} + fn failed(message: &str) -> types::Error { types::Error::Failed(message.into()) } @@ -92,6 +96,73 @@ impl commands::Guest for Misbehave { let project = ctx.project().ok_or_else(|| failed("no project is open"))?; project.write_file(&arg.unwrap_or_default(), "written inside the project\n").map(|()| None) } + + // API 1.3 `process.start`: a program kept running behind a handle. + "child" => { + let cmd = process::Command { + program: "git".into(), + args: vec!["hash-object".into(), "--stdin".into()], + env: Vec::new(), + }; + let child = process::start(&cmd, None)?; + child.write("hello\n")?; + child.close_stdin()?; + let chunk = child.wait(30_000)?; + child.kill()?; + Ok(Some(format!("{} {:?}", chunk.stdout.trim(), chunk.status))) + } + "childkeep" | "childtrap" => { + let cmd = process::Command { + program: "git".into(), + args: vec![ + "hash-object".into(), + "--stdin".into(), + format!("--path={}", arg.unwrap_or_else(|| "openidea-process-2027".into())), + ], + env: Vec::new(), + }; + let child = process::start(&cmd, None)?; + CHILDREN.with(|children| children.borrow_mut().push(child)); + if id == "childtrap" { + panic!("trap with a live child"); + } + Ok(Some("running".into())) + } + "childpoll" => CHILDREN.with(|children| { + let children = children.borrow(); + let child = children.last().ok_or_else(|| failed("no child"))?; + Ok(Some(format!("{:?}", child.poll(100)?.status))) + }), + "childdrop" => { + CHILDREN.with(|children| children.borrow_mut().clear()); + Ok(None) + } + "envrun" | "envstart" | "envdeny-run" | "envdeny-start" => { + if arg.as_deref() == Some("legacy") { + return Ok(Some(process::run("git", &["--version".into()], None, None)?.stdout)); + } + let env = if id.starts_with("envdeny") { + vec![(arg.unwrap_or_else(|| "PATH".into()), "replacement".into())] + } else { + vec![("OPENIDEA_PROCESS_TEST_VALUE".into(), "added by the plugin".into())] + }; + let cmd = process::Command { + program: "git".into(), + args: vec![ + "--config-env=openidea.test=OPENIDEA_PROCESS_TEST_VALUE".into(), + "config".into(), + "--get".into(), + "openidea.test".into(), + ], + env, + }; + if id.ends_with("start") { + let child = process::start(&cmd, None)?; + Ok(Some(child.wait(30_000)?.stdout)) + } else { + Ok(Some(process::run_command(&cmd, None, None)?.stdout)) + } + } // `editor.write` is not granted: an error, not a trap. "insert" => { let editor = ctx.editor().ok_or_else(|| failed("no editor"))?; diff --git a/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm index 40456de8d6c34e0fecf9820f47345545c40d907e..38d968683c2499fafbef65195ac86747101489f8 100644 GIT binary patch delta 190 zcmaEOob};x)(tyFCEJc4zPSC$t>X!;3M`t;5>8Y1HJ*Lda^Qu)=1Zc6OvuvAn#>$X z(lQd_tazk-WF451r5RClPu`>GF!`y%K4cYW=I>El_e!Acu|d`1to{e0@e5X}F=vY&+NVqSv8Sfkl&9!s%T9oz@f^?xMA`&!92!;lY6DaC&vg?OLgB~b7kYo#YpyPN^bILX`RUevL5(MjFR!%yjS+_OM!0PZYl3xkE84+rn8^y%P8Nz Jik-1M7yv)db~FG0 delta 265 zcmbQUjdkHR)(z`9824@7z@f^?xOMV1!92!8lNU-@O|BP;ke>7Vc;CmaoyTX?w<<7e zGIKbs+yDB*_8)I2Y_8w@QD{9Y<8`3YnRu0jNSZM+&I2kFpS(z_8lTe1=F&wh)0r5V zCl}6;n0!}8fASY;4-^fIs7{!iC+jtNfs7*Kbg=O^W$+rmIZsyomB5_87X`zg^vzgz SU#jl#cE!!xm$EaK2Lk}mVuEr2 diff --git a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources index 155ac2426..bfe0396ca 100644 --- a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources @@ -1 +1 @@ -94c6ed6a516973e8ab90a0c550ea700f795eb61f1941c514c0d033db09321a87 +07001ff22cd33d5ae6f96b584707e66d31f2f93423cf72a993e6f11c9f0ec319 diff --git a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm index d42b64cee5219b46b6525940474b2b80bfc44acf..9c2511c366d7aa169e11e088473577fdda96724f 100644 GIT binary patch delta 166 zcmX@Nn&sSTmJQWH(jT7h?tA+5_vKyntqRPV%p6Yp@7~#Ud-1#*j~F&j6DniDt3X|x zl@Ub&BdQ6L6C}MRPn6h#q6XcT1WEIK0v|q~3)FkReAj-x@a?jd8W7aQS{0ZznK_(#FU>p9@%!*{s@1Hx1y diff --git a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources index a8db144e6..8ffef6179 100644 --- a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources @@ -1 +1 @@ -34a1df80de1db3a04363f81b8f3501cb59dc0aee9a0754912b84a34c76ea77d0 +ae9cb4a106aa8fd46662dba07acd0daba2927b7fe53e544cd95348954505bc36 diff --git a/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm index fe07409174910b513e2c7c96a8fcf2f1c5530565..fc5b069fe62fdd4c4c355c0f440e3f88fe96bc2b 100644 GIT binary patch delta 779 zcmaJj`iPeO9h_H-aOf!6#<{)~oAjF58ZY3m3 zb`mv11{KwovvnRsE28wFhYAgP@Tot(#3(n~l2oX^JE_=<-^1^m@BGepe&-y%ey^_I zt9vvd88byuWB?2pV>m6rjP6{zVDM4PD`V%TQd0a3bWx&ymX>+bwt*W*6E70{7qq9*f8 z#-A2gt|rk+3zh**v9;8)nd11~niLvR+m4l0q|m8TVXW*3d91CB^2+DZxbBB48JBD_ z{;q0)32dn@gb;OC-{pOvY_HG-RaF&?;Irq@d3zO<#k9K&(%5CqGRM5&GV_os7_tRf zkdo=L;4r$RAZ97;EUcox*sk9owi&RluNXfm`7Eg7!IFG>RwDsC#z9{b$B6F`o~!NV z8~jr1%!d2a&^W{30cD(wZs+Z+I@kG%f3(j+7^gc9aDTPqxp6~LbeJ{8h|h-cv!{7- zV`tGnO5qgsbnecChve_F0|aRC0{;P%^zPCtfO|C74SMprGXcKQsOL#~av@K93%JB$ z?;OYRzB90lzxpbT%gOl#O>Pa ksqkhUaML`t^j3 zu|JY%RFbjsEA>2x7PdYldkGAB=(z}yYSS4g`p~&Es=fGsIOlMF=lsq&KmJ{^woBHI zj|zH@UMA1;035Js!KqUc^t4*s5Y~7o!PUJ(YUAASY%&>}pDqfBd?}xCbaNR)?_P|^ zUYs&M0*GQ$?jhKwYAnyMfha3CRlrYn#kTS8Q)V$rq`v;t9Fr=@YD633^)30KQvN{Y{S~zyLTQp>=M3oTr`~Boy zdTA@nGOL(i&tab&C*Ua?aOSem{sK;jvPNeuEjr-LS9b%bnI&eNv_{pGdSVmVqOG}b z{0~#?eax?tL{8*{!BmQKsU=|o&mDYB_c5KllCG?_yMZK=9Y&mW?gAKM;fROqQsfxB zSk*x~E>^W>!wA;9XE+!|-O2cFYF=;MpsruuH?-SyTMfnQZ7=m(H}I}vx`7zVS@Oau zDt5OQ{)MarVP|_$COpH3J_o=Ue!fEgU=lxGn+F)i{tn0>RjP^dLHAp-=YyxU-=6Vc&HJlAM=l`6}}tjvP9 z?!4k4&W_6-T|A`|@3JxQnsGty1xPR{>><0}T8_!^J%HtuLd&=mspVjWRrEB%3Wj<* v6=vPt9f}ir^&BKIzwa=RYwG{YACE`OcK`!-cC!A$dSlsH99 diff --git a/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm.sources index 240f79053..8830ecac1 100644 --- a/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm.sources @@ -1 +1 @@ -c6698547bcda49a08bacd6d0876af74ae901d4c32e3c32b014a7679fd96f32c0 +8a79f2899d88773d7c33175136960727b5ad0aefc5ce5eb363fd20dc4990c60e diff --git a/plugins/README.md b/plugins/README.md index c351f66f9..bb1854036 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -442,6 +442,8 @@ then run `openidea-plugin check`. | `host::publish_diagnostics(path, items)` | `diagnostics.publish` | Replaces this plugin's problems for the file (editor squiggles and the Problems view); an empty list clears them. | | `host::fetch(request)` → `{status, headers, body}` | `net.fetch` | See _Network_. | | `Project::run_process(program, args, stdin)` → `{code, stdout, stderr}` | `process.run` | Only the pinned program of a `process:` grant; runs in the project root of a trusted project; 4 MiB of output; killed when the call is cancelled or times out. | +| `host::run_command(cmd, project, stdin)` → `{code, stdout, stderr}` | `process.run-command` | API 1.3: same limits as `run_process`, with append-only environment variables; needs the pinned `process:` grant. | +| `host::start(cmd, project)` → `Child` | `process.start` | API 1.3: `poll(max_bytes)`, `write(text)`, `close_stdin()`, `wait(timeout_ms)`, `kill()`; at most 8 handles; dropping one kills its process. | | `host::refresh_view(view)` | `views.refresh` | The view (or custom editors) render again; call it when the content changed. | | `host::reveal_view(view)` | `views.reveal` | Brings one of your declared `[[contributes.views]]` to the front in the calling window. | | `host::post_view_message(view, text)` | `views.post-message` | To the script of the view's `html` body. | @@ -449,6 +451,16 @@ then run `openidea-plugin check`. | `host::open_location(path, line, column)` | `views.open-location` | Opens a file inside the project in an editor of the invoking window, the caret at `line`/`column` (0-based, UTF-16): a view's links to code. Needs `project.read`. | | `host::setting(key)`, `setting_bool`, `setting_string`, `setting_number` | `settings.get` | The user's value (project over global) or the schema's default. | +Both process APIs only add environment variables. A key already in the prepared child +environment (including the login-shell `PATH`) fails with an error naming the key; +variables are never replaced or removed. Names compare case-insensitively on Windows. +At most 64 entries are allowed: nonempty names up to 256 bytes without `=` or NUL, +and values up to 4 KiB without NUL. The approved file still decides what runs. + +Unpolled child output keeps the last 1 MiB per stream; each read is capped at 1 MiB +and preserves complete characters. Disabling, unloading, reclaiming or stopping a +plugin ends its children, as does closing the window that started them. + `run-command` receives an optional string argument: `executeAction("plugin..", "arg")` from another feature, or nothing when run from Search Everywhere / a shortcut. `ctx.selected_paths()` lists the files and folders a Project view or editor tab menu ran the command on (the argument is the first of them); it is empty otherwise. No capability is needed. diff --git a/plugins/official/ai-completion/dist/plugin.wasm b/plugins/official/ai-completion/dist/plugin.wasm index 23c24508355f8d8ed7f11183ab23b7024e6bcb23..45092d655907f93b9726acc97a3369d1aafbda70 100644 GIT binary patch delta 14239 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 delta 14235 zcmcJ02UJx@*Y@4#ToAkCdzTdyr_b=9(J7v$FJ$rVWJ%_7n3!Ye8aN|Q0Wh6c_ zRkoMc8dtNDt`%8J*EHr=xKd(^!VM&O6jflm-Mx9tXi4G4`biE(LmY*nD~L}UB{>-l zqp6bGN*XB@G#bWet$0t9JXQxg6el@JhH-3{dnApI_b4GxpdjwTq$0f0K&gn)Fp)~L zO+^A}60>?*RHTw>Z18LQ_y$1gfc2o?Y{-K+^zHj;6D|o?-F~3c@rqpEg=5C>eg` zP`$;;(Q z@(OvSd|p~0&zD!r@$z(esyt1;5F<^If0q}^i{(}FSb2;*TW0c1d6pb2PnOS0zsX#l zBPYo7yIjyJBA*UM||YkMW6Po4eZ;H>0;F=k_+>{)&5sPqd5c3+$l$r=}( z>bzkzpbaLg(dwkcu?I!{Xp@f7zgP)+%W4-3rB`f7u{avXJ{D`}e0&t(&34u_idHgM z>*B{LjfI#8(o43?{JmG!Xsm?MT#yG?5x>cRTjIB}%x~5cL$q>$ z4Y!oD+>8UHv%zezN@0?i@;8>wnj-2M?1-g#h>FlEo9#+V=_JYwQa0uyA!~+4cq9L4 z`h)H852D+wVTrsOWv;Rkwo+WcoHYnMtNIoc!NL;Bw zZtPPf6YE_fm?@=P@*MQx3N>>dv>Yfq=T)Jc) z?4wHO;Rvwj7NgwQeF9tLhn>k}i+lzlLN$kfX{Dr%blDY*l*4 zK9#+Xp8Ms#K~I^`0X~{wYQknR7qDg+q62_2)?oUZ?F{`P5Apw*>to7`V_jap~TH(vqU+&Ef_r(rhR#8LTlig-yv}y})ELP39dB zP!V>yN*GN_EE3+`NRty|zFJF^&Vs9zpv=T3)xIPUG5MR~ke9XJd|y!`g>+I3R<~#i z=~#0*&3WkyiyT2LaZ6+XDaV9pnWfT_lb@X#6Pe4m-j2RMhe-}-onTJYFF9tfkoc^+ zGbtzY&Nzh`7CN0+;ad|aXY!7nRk4NZ*v_#&QFF18f;Fov=ko$yU~OxDYi2@=oXBK0 zn(eJdv#iV%9To>?gW8H?DK!J=BD+thY&-&NAu- z>U+!*GIjOONHR51_1Ef<-tXC8tyjdRVTg=}H{iY2q<&=6uUTIKIl=kE}$qzLya z&e#pX4P$TW`-j?ugqmFRgU_{Ffjp;sw8mxS9jn_QQa{)n?S%0D>&!e1%R!i5w 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 XvGIPu^<2?=n4WkTjm0-4F%|v>B%}=- diff --git a/plugins/official/checkstyle/dist/plugin.wasm.sources b/plugins/official/checkstyle/dist/plugin.wasm.sources index 644823b61..2e7d19917 100644 --- a/plugins/official/checkstyle/dist/plugin.wasm.sources +++ b/plugins/official/checkstyle/dist/plugin.wasm.sources @@ -1 +1 @@ -d23b9b067928d6e7648a4989b09e201cade971a67ab769b4d583a274f4e071ac +31f1d88c7e88553eed145fd170485c10e19967eccbf2021ec71fca862d05e952 diff --git a/plugins/official/csv-editor/dist/plugin.wasm b/plugins/official/csv-editor/dist/plugin.wasm index 7fb1195c21496abdd350902a3c4b506570bc6ecc..f631fc1d820f4069cb7c02e8806ce874ccc7c570 100644 GIT binary patch delta 315 zcmbRFhkgDZ_6>7*8P{)~%WKWecx3WTi9D8t%#6&_^`scZCJQLDOcqmCn%pi~F7;z= z!>>7;XRfGeRbbX+mT-D-ta<8^r-%MsW|(|lS$(pd)M~~BlV3?4LQ}|rq;T_5X+w6F z!_17VFyoh@sYW(-^Lgb=Hpat~cj+ihj@0TwQ;BTu7*8CPze%WKWexNq`Ji9D7C%#6&F|0qdKj?|K$+#*>eIpxmDS2t%I z_|Dv_z^utE;k0JOq<4Ey9_m(@EFpD?asK4jQissga3IudJ}zy@&Uk3@Po@26ick$w z-~3bAi4CZ5zmCG>7_A;O#YiSjX40->+&#HbdkwN0Mong9HSRj)KsEbyP}BfjkE+J2 l`MmD-^SX?&xdI}2L358Xyl2*#a^$Wzm$<@qo7s$ylK^5Wh4ugd diff --git a/plugins/official/csv-editor/dist/plugin.wasm.sources b/plugins/official/csv-editor/dist/plugin.wasm.sources index e291976cd..69914ed1b 100644 --- a/plugins/official/csv-editor/dist/plugin.wasm.sources +++ b/plugins/official/csv-editor/dist/plugin.wasm.sources @@ -1 +1 @@ -a49ab90f826a214d7de7058164f1fd886d9b762482c94953ee07495833742f92 +6a2db880c49b15d7ce5ba7fda69ac8d1cac04f0412d42b6922550594ef94a7c5 diff --git a/plugins/official/git-toolbox/dist/plugin.wasm b/plugins/official/git-toolbox/dist/plugin.wasm index 25469c2f402ad44fa50810c6cca64f5e2a148f7b..bfe8a222e824f514e6705c0f577ae7e77f5975c3 100644 GIT binary patch delta 163 zcmX?mp6%>;whhLLk}E%5oHU`Q<#bT10<$JFhtq^*2TnX(IeD_}W*^15%y^`kR9P93 zr5RClPgYR-hb)U~Hc-~SxlDa~nL4A7jDTik2vefKv`YfQ0+N2dYPQ=iIWzuF1_0yQ BMtuMP delta 163 zcmX?mp6%>;whhLLk`q==c=&hv>i0IS3e1|!98Ra2Z>?PVcj6Yy%|42Anej+7sj@O6 zOEaSAo~)qu4_Ow~Y@n=tbD8?~GId5D8G#8RDtGp;xo2~#(Jb_pdxquqOU{hHlL7ub BOsfC@ diff --git a/plugins/official/git-toolbox/dist/plugin.wasm.sources b/plugins/official/git-toolbox/dist/plugin.wasm.sources index b24ebe75c..a5f6a09d1 100644 --- a/plugins/official/git-toolbox/dist/plugin.wasm.sources +++ b/plugins/official/git-toolbox/dist/plugin.wasm.sources @@ -1 +1 @@ -29f7f3bd8b35edf079fdac3c4cc67c5b5ffd57c999064a9ee55793f4f22b6c6b +9299f10c517ee5582312f0f14871e21d36050ee8d621df31d7e6a4126c4ea37e diff --git a/plugins/official/graphviz/dist/plugin.wasm b/plugins/official/graphviz/dist/plugin.wasm index d10ee67e34fb8a03180a4b80532c5cb924a776bb..bc64f8f58bf6611fed58672145c2122530e63ec5 100644 GIT binary patch delta 235 zcmbPtlYQPz_6=!LQhN?|-u!!G_0ew(tqRPV%o0wGXOH&$>o|VJY;%KDA|tvy2a^16 zX(uLhc?*R6Lm;8 bNl?0dd|g*rW7@=r?lLRQw(ss@e4GdXTK9B2 delta 235 zcmbPtlYQPz_6=!LQrqS)y!qhC|MzbgS{0ZznI)XAZ1{5L@|*uRJT^B-B{HJRb0Eq8 zmUd!7m$yL3Pi~M>-&`k`!iKH}*^ECbtf+P{YBD3qPnK5uhetb5+N-%%eS57sqh7MW bHh&Fnwg2~XLL}x|r~cn~V*Bnc#>a^O+u40J diff --git a/plugins/official/graphviz/dist/plugin.wasm.sources b/plugins/official/graphviz/dist/plugin.wasm.sources index 872be9822..19c15d609 100644 --- a/plugins/official/graphviz/dist/plugin.wasm.sources +++ b/plugins/official/graphviz/dist/plugin.wasm.sources @@ -1 +1 @@ -a008cd053e851f86be07576b21c0a0a763cb8e239e7bba03127de0d7c5ab3a4b +1f86f15f5e864ec9a91cb31379047eafd4ae8c07ffacb5e1c85955ee07873342 diff --git a/plugins/official/key-promoter/dist/plugin.wasm b/plugins/official/key-promoter/dist/plugin.wasm index 6e8cf42bfd595dd2743596e41898473f89dc4286..7de6bde145f3422d833ec867f6d819d0ccf18eeb 100644 GIT binary patch delta 163 zcmccdgZ0J_)(yNek~8n0Ty}i+vYzZ#1!hfV4yVcMroP>{@OtmB&1y1I9C)N7RoEDj zr5RClPoALWFnNdSeq<~L7J+de^!(S})IX8xs;J2NC+3x_Y2<&KrE+I~lqu_g!r DVxLVn delta 163 zcmccdgZ0J_)(yNelHH4Yf1Ta6^;&wX0<$JFhtu?q32*m4z4ZIzW;Gcp4m{G4Dr}6% z(u^p&Cr?mwn7l)EKe7r`^P4BAZJ(gVXu~Yf9q4@j%GEW`#aWiz)%N~gxc!bMV@(hM Djww<> diff --git a/plugins/official/key-promoter/dist/plugin.wasm.sources b/plugins/official/key-promoter/dist/plugin.wasm.sources index eda0ec3d2..9c0e26954 100644 --- a/plugins/official/key-promoter/dist/plugin.wasm.sources +++ b/plugins/official/key-promoter/dist/plugin.wasm.sources @@ -1 +1 @@ -a598e79d3e15b7ce4c5fef6b0af56d7d17e572bc1736806bbfa93349126dfb33 +540e60e34b752eb201ef9cacea44066b4f59a4e36c3d1a468bf208223569a628 diff --git a/plugins/official/license-header/dist/plugin.wasm b/plugins/official/license-header/dist/plugin.wasm index b6f0a21d21eb695685bf8fd66539a83626cc9de6..840128652373748f52a7d3e6761b73a0046a6081 100644 GIT binary patch delta 671 zcmb7=O(=tL9LJyE|Ct$6dn~fCcw~&sP+sz~M-PQKSrR#s!fsqS%*(tU#4jWc;zAVq zD=#@X$lGDl^0s9Z4jf$M=4BpEo0439hhLxH_vQCC74k3@vf8YIecGbZ6P#D`cppBC zi{UY#o6JllI@+t=l?OB0P%Qw^O6`OkPU&r&Lk8=(rXSZH`4F8O%S&~3UY5dYinr!I z&X?}G?>n#|u^)E-WUBEgsR$k=E(()O3D|6?|6P-M4cja%V7w5EPQ4K$(=u>c(D)y) zDXbzOgc-YyA{^kJapngX7}kuNX*F<#D%0!_&QFgXn>LB`fSydROnup9voU~@ z>a3ibw&x51oYD8(Y632_6w=XRF8B9q z4AJM`^@Bvt$^%dMpk$)>8e!Z89rmmc7{CH=1$bzmw-NCDS|8f?t$Yl?VwF3J7T?^) gf}JLQWVyH_U(@r>T8%qz^kzF+dHA}~dIH$_31?a*F#rGn delta 662 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 delta 36655 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 delta 271 zcmcb%h3(oFwhiJUQfFo^SomS}eFcp*KRfxxyXt~ezKv6 z$Yuj6X?8+tDiwcW*du{tPp?@NBOWt0Gg`2r$}^(4bF!l4e>~cO(%#LjR@+;x7*8?_ boZ-=6iT6#cw&q9+)s1#qyFJd1@kcZOTA6=D diff --git a/plugins/official/solidity/dist/plugin.wasm.sources b/plugins/official/solidity/dist/plugin.wasm.sources index fbc066fed..44490bdd2 100644 --- a/plugins/official/solidity/dist/plugin.wasm.sources +++ b/plugins/official/solidity/dist/plugin.wasm.sources @@ -1 +1 @@ -fc5e1ed12c0238b7d9bc0621e41beb89bd62191fe0f30b8c66554207cde9cdf7 +8f4c3af7f1f0f6e2001ce161d89e2b83ef700480d6fd5517f46e90437f74639e diff --git a/plugins/official/string-tools/dist/plugin.wasm b/plugins/official/string-tools/dist/plugin.wasm index 6247cdcc194fbf76a6f008fe6d914b8995dd4af0..e67da1f75e31f3c7ddbdb3d250fc2e254772e1c8 100644 GIT binary patch delta 293 zcmezHo%6$Y&J9lmSr|o`L^r<_tPz)bz32ap#m6UZILFYcz^uv4;na2G(}BaYw%>TT zxnA=(C*$qO)yCP34>mtHp2Eg>cXN%^Oh$Zq^ljKs$n{2t;U81hRKBw#5d3Vd0dQXH{-IBpPoE>kIA?l_0HXrqo$ajx zj8_`+&+zoDXJ0xyd8ji delta 293 zcmezHo%6$Y&J9lmSwxvc88^QatPz)LoWA(-<}D}Ybl0~kFl#b%IKAv>U)?ir*~71! z>otFKGTxe8ZJf<`fAe$WDQt{)HrH6qWW=XO--ZoUo)N{&$^N$g@n{E1dvD%s$2N!Y z;pSbpj?1w=Y;0&?m|XZkeDmC&$Hk^A3Nk7%-rnvc$asZO>Mlr5fz?qUOMy{=Nr6>^ zi33FN2;AFlAk4UwiSfbqYod%vj7+;3r#FN!icDX(pOJfds{rGi?H1yUey;@@PiI&y XR, + stdin: Option<&str>, + ) -> Result { + let c = wit::process::Command { program: cmd.program.clone(), args: cmd.args.clone(), env: cmd.env.clone() }; + wit::process::run_command(&c, cwd, stdin) + .map(|o| ProcessOutput { status: o.status, stdout: o.stdout, stderr: o.stderr }) + .map_err(error) + } + + pub fn start(cmd: &crate::host::Command, cwd: Option<&ProjectRes>) -> Result { + let c = wit::process::Command { program: cmd.program.clone(), args: cmd.args.clone(), env: cmd.env.clone() }; + wit::process::start(&c, cwd).map_err(error) + } + + pub fn child_poll(child: &ChildRes, max: u32) -> Result { + child.poll(max).map(chunk_in).map_err(error) + } + + pub fn child_write(child: &ChildRes, text: &str) -> Result<(), Error> { + child.write(text).map_err(error) + } + + pub fn child_close_stdin(child: &ChildRes) -> Result<(), Error> { + child.close_stdin().map_err(error) + } + + pub fn child_kill(child: &ChildRes) -> Result<(), Error> { + child.kill().map_err(error) + } + + pub fn child_wait(child: &ChildRes, timeout_ms: u32) -> Result { + child.wait(timeout_ms).map(chunk_in).map_err(error) + } + + fn chunk_in(c: wit::process::Chunk) -> crate::host::Chunk { + crate::host::Chunk { stdout: c.stdout, stderr: c.stderr, status: c.status } + } + pub fn range_from(r: wit::types::Range) -> Range { range_in(r) } @@ -664,6 +706,104 @@ pub fn run_process(program: &str, args: &[String], stdin: Option<&str>) -> Resul imp::run_process(program, args, None, stdin) } +/// A program with its arguments and the environment added to the inherited one. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Command { + pub program: String, + pub args: Vec, + /// Variables added to the inherited environment: at most 64, names at most + /// 256 bytes, values at most 4 KiB, no NUL or '=' in names, no NUL in values. + /// Existing names in the prepared child environment (including login-shell + /// PATH) fail with an error naming the key. Windows names compare without + /// case. Variables are never replaced or removed. + pub env: Vec<(String, String)>, +} + +impl Command { + /// Selects the program approved by a `process:` grant. + pub fn new(program: &str) -> Self { + Self { program: program.to_owned(), args: Vec::new(), env: Vec::new() } + } + + /// Appends one argument. + pub fn arg(mut self, arg: &str) -> Self { + self.args.push(arg.to_owned()); + self + } + + /// Appends arguments in order. + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + S: AsRef, + { + self.args.extend(args.into_iter().map(|a| a.as_ref().to_owned())); + self + } + + /// Adds a variable; an inherited or duplicate name is an error at spawn. + pub fn env(mut self, name: &str, value: &str) -> Self { + self.env.push((name.to_owned(), value.to_owned())); + self + } +} + +/// What a kept-running program printed since the last read, and its exit status +/// once it ended. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Chunk { + pub stdout: String, + pub stderr: String, + pub status: Option, +} + +/// A program kept running behind a handle ([`start`]). Dropping it ends the +/// program, as the host does when the plugin instance goes away. +pub struct Child { + inner: imp::ChildRes, +} + +impl Child { + /// What it printed since the last read, up to 1 MiB per stream, or one + /// whole character when `max_bytes` is smaller; + /// `status` is `Some` once it ended. + pub fn poll(&self, max_bytes: u32) -> Result { + imp::child_poll(&self.inner, max_bytes) + } + + /// Writes `text` to its stdin. + pub fn write(&self, text: &str) -> Result<(), Error> { + imp::child_write(&self.inner, text) + } + + /// Closes its stdin, so a program reading it sees the end. + pub fn close_stdin(&self) -> Result<(), Error> { + imp::child_close_stdin(&self.inner) + } + + /// Ends it; `status` becomes known right away (-1 when a signal ended it). + pub fn kill(&self) -> Result<(), Error> { + imp::child_kill(&self.inner) + } + + /// Waits up to `timeout_ms` for it to end (0 only checks), then reads it. + pub fn wait(&self, timeout_ms: u32) -> Result { + imp::child_wait(&self.inner, timeout_ms) + } +} + +/// Runs a [`Command`] to its end (API 1.3). Its environment only adds new +/// variables; an existing key fails with an error naming it. +pub fn run_command(cmd: &Command, project: Option<&Project>, stdin: Option<&str>) -> Result { + imp::run_command(cmd, project.map(|p| &p.inner), stdin) +} + +/// Starts a [`Command`] and keeps it running behind a [`Child`] (at most 8 per +/// plugin instance, API 1.3). Closing the invoking window ends its processes. +pub fn start(cmd: &Command, project: Option<&Project>) -> Result { + imp::start(cmd, project.map(|p| &p.inner)).map(|inner| Child { inner }) +} + /// Projects open in any window. Needs `project.read`. pub fn projects() -> Result, Error> { Ok(imp::projects()?.into_iter().map(|inner| Project { inner }).collect()) diff --git a/sdk/rust/openidea-plugin/src/mock.rs b/sdk/rust/openidea-plugin/src/mock.rs index 3195dd501..61730e48d 100644 --- a/sdk/rust/openidea-plugin/src/mock.rs +++ b/sdk/rust/openidea-plugin/src/mock.rs @@ -44,10 +44,29 @@ type ProcessHandler = Box) -> Result, + /// Variables added to the inherited environment. + pub env: Vec<(String, String)>, /// The project root it ran in (`None`: the data directory). pub cwd: Option, } +/// A program a plugin started and keeps running behind a handle. +#[derive(Debug)] +#[allow(dead_code)] // recorded so a test can see what a handle was started with +struct ChildState { + program: String, + args: Vec, + env: Vec<(String, String)>, + cwd: Option, + /// What `write` put into its stdin. + stdin: String, + stdin_closed: bool, + /// What it printed and the plugin has not read yet. + stdout: String, + stderr: String, + status: Option, +} + struct Editor { path: String, language: String, @@ -95,6 +114,9 @@ struct State { requests: Vec, processes: BTreeMap, runs: Vec, + /// The programs `start`ed, by handle id. + children: BTreeMap, + next_child: u64, refreshed: Vec, view_messages: Vec<(String, String)>, terminals: Vec, @@ -111,6 +133,9 @@ pub struct MockTerminal { pub args: Vec, } +/// At most 8 programs a plugin may keep running, as the host allows. +const MAX_CHILDREN: usize = 8; + thread_local! { static CURRENT: RefCell>>> = const { RefCell::new(None) }; } @@ -1185,7 +1210,12 @@ pub mod current { ) -> Result { with(|s| { require(s, &format!("process:{program}"))?; - s.runs.push(ProcessRun { program: program.into(), args: args.to_vec(), cwd: cwd.map(|p| p.root.clone()) }); + s.runs.push(ProcessRun { + program: program.into(), + args: args.to_vec(), + env: Vec::new(), + cwd: cwd.map(|p| p.root.clone()), + }); match s.processes.get(program) { Some(handler) => handler(args, stdin), None => Err(Error::NotFound(format!("{program} (MockHost::on_process)"))), @@ -1193,6 +1223,174 @@ pub mod current { }) } + pub struct ChildRes { + id: u64, + owner: Rc>, + } + + impl Drop for ChildRes { + fn drop(&mut self) { + self.owner.borrow_mut().children.remove(&self.id); + } + } + + fn command_env(cmd: &crate::host::Command) -> Result<(), Error> { + if cmd.env.len() > 64 { + return Err(Error::Invalid("at most 64 environment variables".into())); + } + let mut names: Vec<&str> = Vec::new(); + for (key, value) in &cmd.env { + if key.is_empty() + || key.len() > 256 + || key.contains(['=', '\0']) + || value.len() > 4096 + || value.contains('\0') + { + return Err(Error::Invalid(format!("invalid environment variable {key:?}"))); + } + if names.iter().any(|name| if cfg!(windows) { name.eq_ignore_ascii_case(key) } else { *name == key }) { + return Err(Error::Invalid(format!("environment variable {key:?} already exists"))); + } + names.push(key); + } + Ok(()) + } + + /// Runs a [`Command`]: like `run_process`, with its environment in front of + /// what the program printed (the mock machine has none of its own, so this + /// is how a test sees the variables arrived). + pub fn run_command( + cmd: &crate::host::Command, + cwd: Option<&ProjectRes>, + stdin: Option<&str>, + ) -> Result { + with(|s| { + require(s, &format!("process:{}", cmd.program))?; + command_env(cmd)?; + s.runs.push(ProcessRun { + program: cmd.program.clone(), + args: cmd.args.clone(), + env: cmd.env.clone(), + cwd: cwd.map(|p| p.root.clone()), + }); + let Some(handler) = s.processes.get(&cmd.program) else { + return Err(Error::NotFound(format!("{} (MockHost::on_process)", cmd.program))); + }; + let mut out = handler(&cmd.args, stdin)?; + let env: String = cmd.env.iter().map(|(k, v)| format!("{k}={v}\n")).collect(); + out.stdout.insert_str(0, &env); + Ok(out) + }) + } + + /// Starts a [`Command`] and returns its handle (at most 8, as the host allows). + pub fn start(cmd: &crate::host::Command, cwd: Option<&ProjectRes>) -> Result { + with(|s| { + require(s, &format!("process:{}", cmd.program))?; + command_env(cmd)?; + if s.children.len() >= MAX_CHILDREN { + return Err(Error::Invalid(format!( + "{}: too many processes (at most {MAX_CHILDREN} at a time)", + cmd.program + ))); + } + s.next_child += 1; + let id = s.next_child; + let child = ChildState { + program: cmd.program.clone(), + args: cmd.args.clone(), + env: cmd.env.clone(), + cwd: cwd.map(|p| p.root.clone()), + stdin: String::new(), + stdin_closed: false, + stdout: format!("started: {}\n", cmd.program), + stderr: String::new(), + status: None, + }; + s.children.insert(id, child); + let owner = CURRENT.with(|c| c.borrow().as_ref().unwrap().clone()); + Ok(ChildRes { id, owner }) + }) + } + + fn child_at(s: &mut State, id: u64) -> Result<&mut ChildState, Error> { + s.children.get_mut(&id).ok_or_else(|| Error::NotFound(format!("{id} (no such process handle)"))) + } + + /// Up to `max` bytes from the front of `buf`, cut at a character boundary. + fn take(buf: &mut String, max: u32) -> String { + let mut cut = (max.max(1) as usize).min(1 << 20).min(buf.len()); + while !buf.is_char_boundary(cut) { + cut -= 1; + } + if cut == 0 && !buf.is_empty() { + cut = buf.chars().next().unwrap().len_utf8(); + } + buf.drain(..cut).collect() + } + + pub fn child_poll(child: &ChildRes, max_bytes: u32) -> Result { + let id = child.id; + with(|s| { + let c = child_at(s, id)?; + Ok(crate::host::Chunk { + stdout: take(&mut c.stdout, max_bytes), + stderr: take(&mut c.stderr, max_bytes), + status: c.status, + }) + }) + } + + pub fn child_write(child: &ChildRes, text: &str) -> Result<(), Error> { + let id = child.id; + with(|s| { + let c = child_at(s, id)?; + if c.status.is_some() { + return Err(Error::Invalid(format!("{id}: the process has ended"))); + } + if c.stdin_closed { + return Err(Error::Invalid(format!("{id}: the input is closed"))); + } + c.stdin.push_str(text); + c.stdout.push_str(&format!("echo: {text}")); + Ok(()) + }) + } + + pub fn child_close_stdin(child: &ChildRes) -> Result<(), Error> { + let id = child.id; + with(|s| { + child_at(s, id)?.stdin_closed = true; + Ok(()) + }) + } + + pub fn child_kill(child: &ChildRes) -> Result<(), Error> { + let id = child.id; + with(|s| { + let c = child_at(s, id)?; + if c.status.is_none() { + c.status = Some(-1); + } + Ok(()) + }) + } + + pub fn child_wait(child: &ChildRes, timeout_ms: u32) -> Result { + let id = child.id; + with(|s| { + let c = child_at(s, id)?; + if timeout_ms > 0 && c.status.is_none() { + c.status = Some(0); + } + Ok(crate::host::Chunk { + stdout: std::mem::take(&mut c.stdout), + stderr: std::mem::take(&mut c.stderr), + status: c.status, + }) + }) + } + pub fn begin_task(title: &str, _cancellable: bool) -> TaskRes { with(|s| { s.tasks.push(MockTask { title: title.into(), ..MockTask::default() }); @@ -1410,6 +1608,25 @@ mod tests { assert_eq!(host::copy_to_clipboard("x"), Err(Error::Denied("clipboard".into()))); } + #[test] + fn child_resources_return_slots_and_zero_timeout_only_checks() { + let _mock = MockHost::install().with_capabilities(&["process:git"]); + let command = host::Command::new("git").env("NEW_KEY", "new"); + for _ in 0..10 { + let child = host::start(&command, None).unwrap(); + assert_eq!(child.wait(0).unwrap().status, None); + child.write("中").unwrap(); + assert_eq!(child.poll(0).unwrap().stdout, "e"); + child.kill().unwrap(); + assert_eq!(child.poll(100).unwrap().status, Some(-1)); + } + let children: Vec<_> = (0..8).map(|_| host::start(&command, None).unwrap()).collect(); + assert!(matches!(host::start(&command, None), Err(Error::Invalid(_)))); + drop(children); + assert!(host::start(&command, None).is_ok()); + assert!(matches!(host::start(&command.clone().env("NEW_KEY", "override"), None), Err(Error::Invalid(_)))); + } + #[test] fn network_secrets_processes_and_files() { let mock = MockHost::install(); diff --git a/sdk/rust/openidea-plugin/wit/io.wit b/sdk/rust/openidea-plugin/wit/io.wit index 97111a625..cffd883be 100644 --- a/sdk/rust/openidea-plugin/wit/io.wit +++ b/sdk/rust/openidea-plugin/wit/io.wit @@ -75,8 +75,10 @@ interface secrets { is-set: func(name: string) -> bool; } -/// One-off runs of programs granted with `process:` (the file the -/// user approved, checked before every run). +/// Runs of programs granted with `process:` (the file the user +/// approved, checked before every run): once and awaited with `run`, once +/// with an environment with `run-command`, or kept running behind a `child` +/// handle started with `start`. interface process { use types.{error}; use workspace.{project}; @@ -89,8 +91,56 @@ interface process { stderr: string, } + /// What to run (API 1.3). `env` only adds variables: an existing name in + /// the prepared child environment (including the login-shell PATH) is an + /// error naming that key; nothing is replaced or removed. Names compare + /// case-insensitively on Windows. At most 64 entries, nonempty names of + /// at most 256 bytes without '=' or NUL, values at most 4 KiB without NUL. + record command { + program: string, + args: list, + env: list>, + } + + /// What a kept-running program printed since the last call, and its exit + /// code once it ended (`none` while it still runs). + record chunk { + /// Up to 1 MiB per stream; unpolled output keeps the last 1 MiB. + /// `poll` takes up to `max-bytes`, or one whole character when that + /// limit is smaller. Incomplete characters wait for the next read. + stdout: string, + stderr: string, + status: option, + } + + /// A program left running. Dropping the handle kills it if it still + /// runs, so a plugin cannot leak one; a plugin has at most 8 at a time + /// (the ninth `start` is `invalid`). They also end with the plugin: + /// stopping, disabling or reclaiming it kills what it started. + resource child { + /// What it printed since the last call, and its status once it ends. + poll: func(max-bytes: u32) -> result; + /// Waits up to `timeout-ms` for it to end (0 only checks), then + /// reports what it printed since the last call. + wait: func(timeout-ms: u32) -> result; + /// Writes to its stdin; fails once it ended or stdin was closed. + write: func(text: string) -> result<_, error>; + /// Closes stdin; fails once the program ended. + close-stdin: func() -> result<_, error>; + /// Kills it and reaps it, so its status is available afterwards. + kill: func() -> result<_, error>; + } + /// Runs `program` with `args` in `cwd`'s root (else the plugin's data /// directory) and waits; cancelled with the task or call. Call it inside /// a task for anything slow. run: func(program: string, args: list, cwd: option>, stdin: option) -> result; + + /// The same as `run`, with append-only environment variables (API 1.3). + run-command: func(cmd: command, cwd: option>, stdin: option) -> result; + + /// Starts `cmd` with piped stdin and stdout/stderr, and returns the + /// handle to it without waiting (API 1.3). Closing the invoking window + /// also ends its processes. + start: func(cmd: command, cwd: option>) -> result; } diff --git a/wit/io.wit b/wit/io.wit index 97111a625..cffd883be 100644 --- a/wit/io.wit +++ b/wit/io.wit @@ -75,8 +75,10 @@ interface secrets { is-set: func(name: string) -> bool; } -/// One-off runs of programs granted with `process:` (the file the -/// user approved, checked before every run). +/// Runs of programs granted with `process:` (the file the user +/// approved, checked before every run): once and awaited with `run`, once +/// with an environment with `run-command`, or kept running behind a `child` +/// handle started with `start`. interface process { use types.{error}; use workspace.{project}; @@ -89,8 +91,56 @@ interface process { stderr: string, } + /// What to run (API 1.3). `env` only adds variables: an existing name in + /// the prepared child environment (including the login-shell PATH) is an + /// error naming that key; nothing is replaced or removed. Names compare + /// case-insensitively on Windows. At most 64 entries, nonempty names of + /// at most 256 bytes without '=' or NUL, values at most 4 KiB without NUL. + record command { + program: string, + args: list, + env: list>, + } + + /// What a kept-running program printed since the last call, and its exit + /// code once it ended (`none` while it still runs). + record chunk { + /// Up to 1 MiB per stream; unpolled output keeps the last 1 MiB. + /// `poll` takes up to `max-bytes`, or one whole character when that + /// limit is smaller. Incomplete characters wait for the next read. + stdout: string, + stderr: string, + status: option, + } + + /// A program left running. Dropping the handle kills it if it still + /// runs, so a plugin cannot leak one; a plugin has at most 8 at a time + /// (the ninth `start` is `invalid`). They also end with the plugin: + /// stopping, disabling or reclaiming it kills what it started. + resource child { + /// What it printed since the last call, and its status once it ends. + poll: func(max-bytes: u32) -> result; + /// Waits up to `timeout-ms` for it to end (0 only checks), then + /// reports what it printed since the last call. + wait: func(timeout-ms: u32) -> result; + /// Writes to its stdin; fails once it ended or stdin was closed. + write: func(text: string) -> result<_, error>; + /// Closes stdin; fails once the program ended. + close-stdin: func() -> result<_, error>; + /// Kills it and reaps it, so its status is available afterwards. + kill: func() -> result<_, error>; + } + /// Runs `program` with `args` in `cwd`'s root (else the plugin's data /// directory) and waits; cancelled with the task or call. Call it inside /// a task for anything slow. run: func(program: string, args: list, cwd: option>, stdin: option) -> result; + + /// The same as `run`, with append-only environment variables (API 1.3). + run-command: func(cmd: command, cwd: option>, stdin: option) -> result; + + /// Starts `cmd` with piped stdin and stdout/stderr, and returns the + /// handle to it without waiting (API 1.3). Closing the invoking window + /// also ends its processes. + start: func(cmd: command, cwd: option>) -> result; }