Detailed changes
@@ -26,7 +26,7 @@ use std::{
sync::Arc,
time::{Duration, SystemTime},
};
-use util::command::new_smol_command;
+use util::command::new_command;
use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
@@ -127,7 +127,7 @@ impl Drop for MacOsUnmounter<'_> {
let mount_path = mem::take(&mut self.mount_path);
self.background_executor
.spawn(async move {
- let unmount_output = new_smol_command("hdiutil")
+ let unmount_output = new_command("hdiutil")
.args(["detach", "-force"])
.arg(&mount_path)
.output()
@@ -902,7 +902,7 @@ async fn install_release_linux(
.await
.context("failed to create directory into which to extract update")?;
- let output = new_smol_command("tar")
+ let output = new_command("tar")
.arg("-xzf")
.arg(&downloaded_tar_gz)
.arg("-C")
@@ -937,7 +937,7 @@ async fn install_release_linux(
to = PathBuf::from(prefix);
}
- let output = new_smol_command("rsync")
+ let output = new_command("rsync")
.args(["-av", "--delete"])
.arg(&from)
.arg(&to)
@@ -969,7 +969,7 @@ async fn install_release_macos(
let mut mounted_app_path: OsString = mount_path.join(running_app_filename).into();
mounted_app_path.push("/");
- let output = new_smol_command("hdiutil")
+ let output = new_command("hdiutil")
.args(["attach", "-nobrowse"])
.arg(&downloaded_dmg)
.arg("-mountroot")
@@ -989,7 +989,7 @@ async fn install_release_macos(
background_executor: cx.background_executor(),
};
- let output = new_smol_command("rsync")
+ let output = new_command("rsync")
.args(["-av", "--delete"])
.arg(&mounted_app_path)
.arg(&running_app_path)
@@ -1020,7 +1020,7 @@ async fn cleanup_windows() -> Result<()> {
}
async fn install_release_windows(downloaded_installer: PathBuf) -> Result<Option<PathBuf>> {
- let output = new_smol_command(downloaded_installer)
+ let output = new_command(downloaded_installer)
.arg("/verysilent")
.arg("/update=true")
.arg("!desktopicon")
@@ -1058,7 +1058,7 @@ pub async fn finalize_auto_update_on_quit() {
.parent()
.map(|p| p.join("tools").join("auto_update_helper.exe"))
{
- let mut command = util::command::new_smol_command(helper);
+ let mut command = util::command::new_command(helper);
command.arg("--launch");
command.arg("false");
if let Ok(mut cmd) = command.spawn() {
@@ -1402,7 +1402,7 @@ async fn ensure_node_version_for_copilot(node_path: &Path) -> anyhow::Result<()>
log::info!("Checking Node.js version for Copilot at: {:?}", node_path);
- let output = util::command::new_smol_command(node_path)
+ let output = util::command::new_command(node_path)
.arg("--version")
.output()
.await
@@ -429,7 +429,7 @@ impl DebugAdapter for GoDebugAdapter {
let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
- let install_output = util::command::new_smol_command(&go)
+ let install_output = util::command::new_command(&go)
.env("GO111MODULE", "on")
.env("GOBIN", &adapter_path)
.args(&["install", "github.com/go-delve/delve/cmd/dlv@latest"])
@@ -20,7 +20,7 @@ use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
-use util::command::new_smol_command;
+use util::command::new_command;
use util::{ResultExt, paths::PathStyle, rel_path::RelPath};
enum DebugpyLaunchMode<'a> {
@@ -121,7 +121,7 @@ impl PythonDebugAdapter {
std::fs::create_dir_all(&download_dir)?;
let venv_python = self.base_venv_path(toolchain, delegate).await?;
- let installation_succeeded = util::command::new_smol_command(venv_python.as_ref())
+ let installation_succeeded = util::command::new_command(venv_python.as_ref())
.args([
"-m",
"pip",
@@ -259,7 +259,7 @@ impl PythonDebugAdapter {
};
let debug_adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref());
- let output = util::command::new_smol_command(&base_python)
+ let output = util::command::new_command(&base_python)
.args(["-m", "venv", "zed_base_venv"])
.current_dir(
&debug_adapter_path,
@@ -308,7 +308,7 @@ impl PythonDebugAdapter {
// Try to detect situations where `python3` exists but is not a real Python interpreter.
// Notably, on fresh Windows installs, `python3` is a shim that opens the Microsoft Store app
// when run with no arguments, and just fails otherwise.
- let Some(output) = new_smol_command(&path)
+ let Some(output) = new_command(&path)
.args(["-c", "print(1 + 2)"])
.output()
.await
@@ -7,7 +7,8 @@ use std::{
use node_runtime::NodeRuntime;
use serde::Deserialize;
use settings::DevContainerConnection;
-use smol::{fs, process::Command};
+use smol::fs;
+use util::command::Command;
use util::rel_path::RelPath;
use workspace::Workspace;
use worktree::Snapshot;
@@ -73,13 +74,12 @@ pub(crate) struct DevContainerCli {
impl DevContainerCli {
fn command(&self, use_podman: bool) -> Command {
let mut command = if let Some(node_runtime_path) = &self.node_runtime_path {
- let mut command = util::command::new_smol_command(
- node_runtime_path.as_os_str().display().to_string(),
- );
+ let mut command =
+ util::command::new_command(node_runtime_path.as_os_str().display().to_string());
command.arg(self.path.display().to_string());
command
} else {
- util::command::new_smol_command(self.path.display().to_string())
+ util::command::new_command(self.path.display().to_string())
};
if use_podman {
@@ -297,9 +297,9 @@ fn dev_container_script() -> String {
async fn check_for_docker(use_podman: bool) -> Result<(), DevContainerError> {
let mut command = if use_podman {
- util::command::new_smol_command("podman")
+ util::command::new_command("podman")
} else {
- util::command::new_smol_command("docker")
+ util::command::new_command("docker")
};
command.arg("--version");
@@ -315,7 +315,7 @@ async fn check_for_docker(use_podman: bool) -> Result<(), DevContainerError> {
pub(crate) async fn ensure_devcontainer_cli(
node_runtime: &NodeRuntime,
) -> Result<DevContainerCli, DevContainerError> {
- let mut command = util::command::new_smol_command(&dev_container_cli());
+ let mut command = util::command::new_command(&dev_container_cli());
command.arg("--version");
if let Err(e) = command.output().await {
@@ -340,7 +340,7 @@ pub(crate) async fn ensure_devcontainer_cli(
);
let mut command =
- util::command::new_smol_command(node_runtime_path.as_os_str().display().to_string());
+ util::command::new_command(node_runtime_path.as_os_str().display().to_string());
command.arg(datadir_cli_path.display().to_string());
command.arg("--version");
@@ -385,7 +385,7 @@ pub(crate) async fn ensure_devcontainer_cli(
};
let mut command =
- util::command::new_smol_command(node_runtime_path.as_os_str().display().to_string());
+ util::command::new_command(node_runtime_path.as_os_str().display().to_string());
command.arg(datadir_cli_path.display().to_string());
command.arg("--version");
if let Err(e) = command.output().await {
@@ -26,7 +26,7 @@ use std::{
time::Duration,
};
use unindent::Unindent as _;
-use util::{ResultExt as _, command::new_smol_command, markdown::MarkdownCodeBlock};
+use util::{ResultExt as _, command::new_command, markdown::MarkdownCodeBlock};
use crate::{
AgentAppState, ToolMetrics,
@@ -1072,7 +1072,7 @@ pub fn repo_path_for_url(repos_dir: &Path, repo_url: &str) -> PathBuf {
}
pub async fn run_git(repo_path: &Path, args: &[&str]) -> Result<String> {
- let output = new_smol_command("git")
+ let output = new_command("git")
.current_dir(repo_path)
.args(args)
.output()
@@ -11,10 +11,10 @@ use serde::Deserialize;
use std::{
env, fs, mem,
path::{Path, PathBuf},
- process::Stdio,
str::FromStr,
sync::Arc,
};
+use util::command::Stdio;
use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _};
use wasmparser::Parser;
@@ -157,7 +157,7 @@ impl ExtensionBuilder {
"compiling Rust crate for extension {}",
extension_dir.display()
);
- let output = util::command::new_smol_command("cargo")
+ let output = util::command::new_command("cargo")
.args(["build", "--target", RUST_TARGET])
.args(options.release.then_some("--release"))
.arg("--target-dir")
@@ -263,7 +263,7 @@ impl ExtensionBuilder {
);
} else {
log::info!("compiling {grammar_name} parser");
- let clang_output = util::command::new_smol_command(&clang_path)
+ let clang_output = util::command::new_command(&clang_path)
.args(["-fPIC", "-shared", "-Os"])
.arg(format!("-Wl,--export=tree_sitter_{grammar_name}"))
.arg("-o")
@@ -292,7 +292,7 @@ impl ExtensionBuilder {
let git_dir = directory.join(".git");
if directory.exists() {
- let remotes_output = util::command::new_smol_command("git")
+ let remotes_output = util::command::new_command("git")
.arg("--git-dir")
.arg(&git_dir)
.args(["remote", "-v"])
@@ -316,7 +316,7 @@ impl ExtensionBuilder {
fs::create_dir_all(directory).with_context(|| {
format!("failed to create grammar directory {}", directory.display(),)
})?;
- let init_output = util::command::new_smol_command("git")
+ let init_output = util::command::new_command("git")
.arg("init")
.current_dir(directory)
.output()
@@ -328,7 +328,7 @@ impl ExtensionBuilder {
);
}
- let remote_add_output = util::command::new_smol_command("git")
+ let remote_add_output = util::command::new_command("git")
.arg("--git-dir")
.arg(&git_dir)
.args(["remote", "add", "origin", url])
@@ -343,7 +343,7 @@ impl ExtensionBuilder {
}
}
- let fetch_output = util::command::new_smol_command("git")
+ let fetch_output = util::command::new_command("git")
.arg("--git-dir")
.arg(&git_dir)
.args(["fetch", "--depth", "1", "origin", rev])
@@ -351,7 +351,7 @@ impl ExtensionBuilder {
.await
.context("failed to execute `git fetch`")?;
- let checkout_output = util::command::new_smol_command("git")
+ let checkout_output = util::command::new_command("git")
.arg("--git-dir")
.arg(&git_dir)
.args(["checkout", rev])
@@ -379,7 +379,7 @@ impl ExtensionBuilder {
}
async fn install_rust_wasm_target_if_needed(&self) -> Result<()> {
- let rustc_output = util::command::new_smol_command("rustc")
+ let rustc_output = util::command::new_command("rustc")
.arg("--print")
.arg("sysroot")
.output()
@@ -397,7 +397,7 @@ impl ExtensionBuilder {
return Ok(());
}
- let output = util::command::new_smol_command("rustup")
+ let output = util::command::new_command("rustup")
.args(["target", "add", RUST_TARGET])
.stderr(Stdio::piped())
.stdout(Stdio::inherit())
@@ -452,7 +452,7 @@ impl ExtensionBuilder {
log::info!("un-tarring wasi-sdk to {}", tar_out_dir.display());
// Shell out to tar to extract the archive
- let tar_output = util::command::new_smol_command("tar")
+ let tar_output = util::command::new_command("tar")
.arg("-xzf")
.arg(&tar_gz_path)
.arg("-C")
@@ -868,7 +868,7 @@ impl process::Host for WasmState {
self.capability_granter
.grant_exec(&command.command, &command.args)?;
- let output = util::command::new_smol_command(command.command.as_str())
+ let output = util::command::new_command(command.command.as_str())
.args(&command.args)
.envs(command.env)
.output()
@@ -15,7 +15,7 @@ use gpui::Global;
use gpui::ReadGlobal as _;
use gpui::SharedString;
use std::borrow::Cow;
-use util::command::new_smol_command;
+use util::command::new_command;
#[cfg(unix)]
use std::os::fd::{AsFd, AsRawFd};
@@ -516,7 +516,7 @@ impl Fs for RealFs {
#[cfg(windows)]
if smol::fs::metadata(&target).await?.is_dir() {
- let status = new_smol_command("cmd")
+ let status = new_command("cmd")
.args(["/C", "mklink", "/J"])
.args([path, target.as_path()])
.status()
@@ -1057,7 +1057,7 @@ impl Fs for RealFs {
abs_work_directory_path: &Path,
fallback_branch_name: String,
) -> Result<()> {
- let config = new_smol_command("git")
+ let config = new_command("git")
.current_dir(abs_work_directory_path)
.args(&["config", "--global", "--get", "init.defaultBranch"])
.output()
@@ -1071,7 +1071,7 @@ impl Fs for RealFs {
branch_name = Cow::Borrowed(fallback_branch_name.as_str());
}
- new_smol_command("git")
+ new_command("git")
.current_dir(abs_work_directory_path)
.args(&["init", "-b"])
.arg(branch_name.trim())
@@ -1091,7 +1091,7 @@ impl Fs for RealFs {
let _job_tracker = JobTracker::new(job_info, self.job_event_subscribers.clone());
- let output = new_smol_command("git")
+ let output = new_command("git")
.current_dir(abs_work_directory)
.args(&["clone", repo_url])
.output()
@@ -5,12 +5,12 @@ use anyhow::{Context as _, Result};
use collections::{HashMap, HashSet};
use futures::AsyncWriteExt;
use serde::{Deserialize, Serialize};
-use std::process::Stdio;
use std::{ops::Range, path::Path};
use text::{LineEnding, Rope};
use time::OffsetDateTime;
use time::UtcOffset;
use time::macros::format_description;
+use util::command::Stdio;
pub use git2 as libgit;
@@ -61,7 +61,7 @@ async fn run_git_blame(
let mut child = {
let span = ztracing::debug_span!("spawning git-blame command", path = path.as_unix_str());
let _enter = span.enter();
- util::command::new_smol_command(git_binary)
+ util::command::new_command(git_binary)
.current_dir(working_directory)
.arg("blame")
.arg("--incremental")
@@ -80,16 +80,15 @@ pub async fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result<Hash
async fn get_messages_impl(working_directory: &Path, shas: &[Oid]) -> Result<Vec<String>> {
const MARKER: &str = "<MARKER>";
- let mut cmd = util::command::new_smol_command("git");
- cmd.current_dir(working_directory)
+ let output = util::command::new_command("git")
+ .current_dir(working_directory)
.arg("show")
.arg("-s")
.arg(format!("--format=%B{}", MARKER))
- .args(shas.iter().map(ToString::to_string));
- let output = cmd
+ .args(shas.iter().map(ToString::to_string))
.output()
.await
- .with_context(|| format!("starting git blame process: {:?}", cmd))?;
+ .context("starting git show process")?;
anyhow::ensure!(
output.status.success(),
"'git show' failed with error {:?}",
@@ -21,7 +21,7 @@ use text::LineEnding;
use std::collections::HashSet;
use std::ffi::{OsStr, OsString};
-use std::process::{ExitStatus, Stdio};
+use std::process::ExitStatus;
use std::str::FromStr;
use std::{
cmp::Ordering,
@@ -31,7 +31,7 @@ use std::{
};
use sum_tree::MapSeekTarget;
use thiserror::Error;
-use util::command::new_smol_command;
+use util::command::{Stdio, new_command};
use util::paths::PathStyle;
use util::rel_path::RelPath;
use util::{ResultExt, paths};
@@ -954,7 +954,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
let working_directory = working_directory?;
- let output = new_smol_command(git_binary_path)
+ let output = new_command(git_binary_path)
.current_dir(&working_directory)
.args([
"--no-optional-locks",
@@ -993,7 +993,7 @@ impl GitRepository for RealGitRepository {
};
let git_binary_path = self.any_git_binary_path.clone();
cx.background_spawn(async move {
- let show_output = util::command::new_smol_command(&git_binary_path)
+ let show_output = util::command::new_command(&git_binary_path)
.current_dir(&working_directory)
.args([
"--no-optional-locks",
@@ -1016,7 +1016,7 @@ impl GitRepository for RealGitRepository {
let changes = parse_git_diff_name_status(&show_stdout);
let parent_sha = format!("{}^", commit);
- let mut cat_file_process = util::command::new_smol_command(&git_binary_path)
+ let mut cat_file_process = util::command::new_command(&git_binary_path)
.current_dir(&working_directory)
.args(["--no-optional-locks", "cat-file", "--batch=%(objectsize)"])
.stdin(Stdio::piped())
@@ -1133,7 +1133,7 @@ impl GitRepository for RealGitRepository {
ResetMode::Soft => "--soft",
};
- let output = new_smol_command(&self.any_git_binary_path)
+ let output = new_command(&self.any_git_binary_path)
.envs(env.iter())
.current_dir(&working_directory?)
.args(["reset", mode_flag, &commit])
@@ -1162,7 +1162,7 @@ impl GitRepository for RealGitRepository {
return Ok(());
}
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory?)
.envs(env.iter())
.args(["checkout", &commit, "--"])
@@ -1260,7 +1260,7 @@ impl GitRepository for RealGitRepository {
let mode = if is_executable { "100755" } else { "100644" };
if let Some(content) = content {
- let mut child = new_smol_command(&git_binary_path)
+ let mut child = new_command(&git_binary_path)
.current_dir(&working_directory)
.envs(env.iter())
.args(["hash-object", "-w", "--stdin"])
@@ -1276,7 +1276,7 @@ impl GitRepository for RealGitRepository {
log::debug!("indexing SHA: {sha}, path {path:?}");
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.envs(env.iter())
.args(["update-index", "--add", "--cacheinfo", mode, sha])
@@ -1291,7 +1291,7 @@ impl GitRepository for RealGitRepository {
);
} else {
log::debug!("removing path {path:?} from the index");
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.envs(env.iter())
.args(["update-index", "--force-remove"])
@@ -1328,7 +1328,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
let working_directory = working_directory?;
- let mut process = new_smol_command(&git_binary_path)
+ let mut process = new_command(&git_binary_path)
.current_dir(&working_directory)
.args([
"--no-optional-locks",
@@ -1391,7 +1391,7 @@ impl GitRepository for RealGitRepository {
let args = git_status_args(path_prefixes);
log::debug!("Checking for git status in {path_prefixes:?}");
self.executor.spawn(async move {
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(working_directory)
.args(args)
.output()
@@ -1434,7 +1434,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(working_directory)
.args(args)
.output()
@@ -1455,7 +1455,7 @@ impl GitRepository for RealGitRepository {
let working_directory = self.working_directory();
self.executor
.spawn(async move {
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(working_directory?)
.args(&["stash", "list", "--pretty=format:%gd%x00%H%x00%ct%x00%s"])
.output()
@@ -1496,7 +1496,7 @@ impl GitRepository for RealGitRepository {
&fields,
];
let working_directory = working_directory?;
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.args(args)
.output()
@@ -1514,7 +1514,7 @@ impl GitRepository for RealGitRepository {
if branches.is_empty() {
let args = vec!["symbolic-ref", "--quiet", "HEAD"];
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.args(args)
.output()
@@ -1544,7 +1544,7 @@ impl GitRepository for RealGitRepository {
let working_directory = self.working_directory();
self.executor
.spawn(async move {
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(working_directory?)
.args(&["--no-optional-locks", "worktree", "list", "--porcelain"])
.output()
@@ -1584,7 +1584,7 @@ impl GitRepository for RealGitRepository {
}
self.executor
.spawn(async move {
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(working_directory?)
.args(args)
.output()
@@ -1768,7 +1768,7 @@ impl GitRepository for RealGitRepository {
args.push("--");
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.args(&args)
.arg(path.as_unix_str())
@@ -1824,7 +1824,7 @@ impl GitRepository for RealGitRepository {
DiffType::HeadToWorktree => None,
};
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory?)
.args(["diff"])
.args(args)
@@ -1851,7 +1851,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
if !paths.is_empty() {
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory?)
.envs(env.iter())
.args(["update-index", "--add", "--remove", "--"])
@@ -1880,7 +1880,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
if !paths.is_empty() {
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory?)
.envs(env.iter())
.args(["reset", "--quiet", "--"])
@@ -1908,7 +1908,7 @@ impl GitRepository for RealGitRepository {
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
- let mut cmd = new_smol_command(&git_binary_path);
+ let mut cmd = new_command(&git_binary_path);
cmd.current_dir(&working_directory?)
.envs(env.iter())
.args(["stash", "push", "--quiet"])
@@ -1937,7 +1937,7 @@ impl GitRepository for RealGitRepository {
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
- let mut cmd = new_smol_command(git_binary_path);
+ let mut cmd = new_command(git_binary_path);
let mut args = vec!["stash".to_string(), "pop".to_string()];
if let Some(index) = index {
args.push(format!("stash@{{{}}}", index));
@@ -1967,7 +1967,7 @@ impl GitRepository for RealGitRepository {
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
- let mut cmd = new_smol_command(git_binary_path);
+ let mut cmd = new_command(git_binary_path);
let mut args = vec!["stash".to_string(), "apply".to_string()];
if let Some(index) = index {
args.push(format!("stash@{{{}}}", index));
@@ -1997,7 +1997,7 @@ impl GitRepository for RealGitRepository {
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
- let mut cmd = new_smol_command(git_binary_path);
+ let mut cmd = new_command(git_binary_path);
let mut args = vec!["stash".to_string(), "drop".to_string()];
if let Some(index) = index {
args.push(format!("stash@{{{}}}", index));
@@ -2032,15 +2032,15 @@ impl GitRepository for RealGitRepository {
// Note: Do not spawn this command on the background thread, it might pop open the credential helper
// which we want to block on.
async move {
- let mut cmd = new_smol_command(git_binary_path);
+ let mut cmd = new_command(git_binary_path);
cmd.current_dir(&working_directory?)
.envs(env.iter())
.args(["commit", "--quiet", "-m"])
.arg(&message.to_string())
.arg("--cleanup=strip")
.arg("--no-verify")
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped());
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
if options.amend {
cmd.arg("--amend");
@@ -2079,7 +2079,7 @@ impl GitRepository for RealGitRepository {
async move {
let git_binary_path = git_binary_path.context("git not found on $PATH, can't push")?;
let working_directory = working_directory?;
- let mut command = new_smol_command(git_binary_path);
+ let mut command = new_command(git_binary_path);
command
.envs(env.iter())
.current_dir(&working_directory)
@@ -2090,9 +2090,9 @@ impl GitRepository for RealGitRepository {
}))
.arg(remote_name)
.arg(format!("{}:{}", branch_name, remote_branch_name))
- .stdin(smol::process::Stdio::null())
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped());
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
run_git_command(env, ask_pass, command, executor).await
}
@@ -2115,7 +2115,7 @@ impl GitRepository for RealGitRepository {
// which we want to block on.
async move {
let git_binary_path = git_binary_path.context("git not found on $PATH, can't pull")?;
- let mut command = new_smol_command(git_binary_path);
+ let mut command = new_command(git_binary_path);
command
.envs(env.iter())
.current_dir(&working_directory?)
@@ -2128,8 +2128,8 @@ impl GitRepository for RealGitRepository {
command
.arg(remote_name)
.args(branch_name)
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped());
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
run_git_command(env, ask_pass, command, executor).await
}
@@ -2151,13 +2151,13 @@ impl GitRepository for RealGitRepository {
// which we want to block on.
async move {
let git_binary_path = git_binary_path.context("git not found on $PATH, can't fetch")?;
- let mut command = new_smol_command(git_binary_path);
+ let mut command = new_command(git_binary_path);
command
.envs(env.iter())
.current_dir(&working_directory?)
.args(["fetch", &remote_name])
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped());
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
run_git_command(env, ask_pass, command, executor).await
}
@@ -2170,7 +2170,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
let working_directory = working_directory?;
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.args(["rev-parse", "--abbrev-ref"])
.arg(format!("{branch}@{{push}}"))
@@ -2197,7 +2197,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
let working_directory = working_directory?;
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.args(["config", "--get"])
.arg(format!("branch.{branch}.remote"))
@@ -2221,7 +2221,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
let working_directory = working_directory?;
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.args(["remote", "-v"])
.output()
@@ -2280,7 +2280,7 @@ impl GitRepository for RealGitRepository {
.spawn(async move {
let working_directory = working_directory?;
let git_cmd = async |args: &[&str]| -> Result<String> {
- let output = new_smol_command(&git_binary_path)
+ let output = new_command(&git_binary_path)
.current_dir(&working_directory)
.args(args)
.output()
@@ -2540,7 +2540,7 @@ impl GitRepository for RealGitRepository {
{
let hook_abs_path = repository.lock().path().join("hooks").join(hook.as_str());
if hook_abs_path.is_file() {
- let output = new_smol_command(&hook_abs_path)
+ let output = new_command(&hook_abs_path)
.envs(env.iter())
.current_dir(&working_directory)
.output()
@@ -2706,8 +2706,8 @@ async fn run_commit_data_reader(
Ok(())
}
-async fn read_single_commit_response(
- stdout: &mut BufReader<smol::process::ChildStdout>,
+async fn read_single_commit_response<R: smol::io::AsyncBufRead + Unpin>(
+ stdout: &mut R,
sha: &Oid,
) -> Result<GraphCommitData> {
let mut header_bytes = Vec::new();
@@ -2964,11 +2964,11 @@ impl GitBinary {
Ok(String::from_utf8(output.stdout)?)
}
- fn build_command<S>(&self, args: impl IntoIterator<Item = S>) -> smol::process::Command
+ fn build_command<S>(&self, args: impl IntoIterator<Item = S>) -> util::command::Command
where
S: AsRef<OsStr>,
{
- let mut command = new_smol_command(&self.git_binary_path);
+ let mut command = new_command(&self.git_binary_path);
command.current_dir(&self.working_directory);
command.args(args);
if let Some(index_file_path) = self.index_file_path.as_ref() {
@@ -2990,7 +2990,7 @@ struct GitBinaryCommandError {
async fn run_git_command(
env: Arc<HashMap<String, String>>,
ask_pass: AskPassDelegate,
- mut command: smol::process::Command,
+ mut command: util::command::Command,
executor: BackgroundExecutor,
) -> Result<RemoteCommandOutput> {
if env.contains_key("GIT_ASKPASS") {
@@ -3019,7 +3019,7 @@ async fn run_git_command(
async fn run_askpass_command(
mut ask_pass: AskPassSession,
- git_process: smol::process::Child,
+ git_process: util::command::Child,
) -> anyhow::Result<RemoteCommandOutput> {
select_biased! {
result = ask_pass.run().fuse() => {
@@ -17,7 +17,7 @@ use anyhow::{Context as _, anyhow};
use calloop::LoopSignal;
use futures::channel::oneshot;
use util::ResultExt as _;
-use util::command::{new_smol_command, new_std_command};
+use util::command::{new_command, new_std_command};
#[cfg(any(feature = "wayland", feature = "x11"))]
use xkbcommon::xkb::{self, Keycode, Keysym, State};
@@ -475,7 +475,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let path = path.to_owned();
self.background_executor()
.spawn(async move {
- let _ = new_smol_command("xdg-open")
+ let _ = new_command("xdg-open")
.arg(path)
.spawn()
.context("invoking xdg-open")
@@ -54,7 +54,7 @@ use std::{
};
use util::{
ResultExt,
- command::{new_smol_command, new_std_command},
+ command::{new_command, new_std_command},
};
#[allow(non_upper_case_globals)]
@@ -848,7 +848,7 @@ impl Platform for MacPlatform {
.lock()
.background_executor
.spawn(async move {
- if let Some(mut child) = new_smol_command("open")
+ if let Some(mut child) = new_command("open")
.arg(path)
.spawn()
.context("invoking open command")
@@ -2,7 +2,7 @@ use anyhow::{Context as _, anyhow};
use gpui::{App, DivInspectorState, Inspector, InspectorElementId, IntoElement, Window};
use std::{cell::OnceCell, path::Path, sync::Arc};
use ui::{Label, Tooltip, prelude::*, utils::platform_title_bar_height};
-use util::{ResultExt as _, command::new_smol_command};
+use util::{ResultExt as _, command::new_command};
use workspace::AppState;
use crate::div_inspector::DivInspector;
@@ -169,7 +169,7 @@ async fn open_zed_source_location(
location.column()
);
- let output = new_smol_command("zed")
+ let output = new_command("zed")
.arg(&path_arg)
.output()
.await
@@ -111,7 +111,7 @@ impl LspInstaller for GoLspAdapter {
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());
- let go_version_output = util::command::new_smol_command(&go)
+ let go_version_output = util::command::new_command(&go)
.args(["version"])
.output()
.await
@@ -140,7 +140,7 @@ impl LspInstaller for GoLspAdapter {
let gobin_dir = container_dir.join("gobin");
fs::create_dir_all(&gobin_dir).await?;
- let install_output = util::command::new_smol_command(go)
+ let install_output = util::command::new_command(go)
.env("GO111MODULE", "on")
.env("GOBIN", &gobin_dir)
.args(["install", "golang.org/x/tools/gopls@latest"])
@@ -159,7 +159,7 @@ impl LspInstaller for GoLspAdapter {
}
let installed_binary_path = gobin_dir.join(BINARY);
- let version_output = util::command::new_smol_command(&installed_binary_path)
+ let version_output = util::command::new_command(&installed_binary_path)
.arg("version")
.output()
.await
@@ -30,9 +30,9 @@ use terminal::terminal_settings::TerminalSettings;
use smol::lock::OnceCell;
use std::cmp::{Ordering, Reverse};
use std::env::consts;
-use std::process::Stdio;
+use util::command::Stdio;
-use util::command::new_smol_command;
+use util::command::new_command;
use util::fs::{make_file_executable, remove_matching};
use util::paths::PathStyle;
use util::rel_path::RelPath;
@@ -1620,7 +1620,7 @@ impl PyLspAdapter {
let mut path = PathBuf::from(work_dir.as_ref());
path.push("pylsp-venv");
if !path.exists() {
- util::command::new_smol_command(python_path)
+ util::command::new_command(python_path)
.arg("-m")
.arg("venv")
.arg("pylsp-venv")
@@ -1641,7 +1641,7 @@ impl PyLspAdapter {
// Try to detect situations where `python3` exists but is not a real Python interpreter.
// Notably, on fresh Windows installs, `python3` is a shim that opens the Microsoft Store app
// when run with no arguments, and just fails otherwise.
- let Some(output) = new_smol_command(&path)
+ let Some(output) = new_command(&path)
.args(["-c", "print(1 + 2)"])
.output()
.await
@@ -1853,7 +1853,7 @@ impl LspInstaller for PyLspAdapter {
let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
let pip_path = venv.join(BINARY_DIR).join("pip3");
ensure!(
- util::command::new_smol_command(pip_path.as_path())
+ util::command::new_command(pip_path.as_path())
.arg("install")
.arg("python-lsp-server[all]")
.arg("--upgrade")
@@ -1864,7 +1864,7 @@ impl LspInstaller for PyLspAdapter {
"python-lsp-server[all] installation failed"
);
ensure!(
- util::command::new_smol_command(pip_path)
+ util::command::new_command(pip_path)
.arg("install")
.arg("pylsp-mypy")
.arg("--upgrade")
@@ -2421,7 +2421,7 @@ impl LspAdapter for RuffLspAdapter {
.0
.ok()?;
- let mut command = util::command::new_smol_command(&binary.path);
+ let mut command = util::command::new_command(&binary.path);
command
.args(&["config", "--output-format", "json"])
.stdout(Stdio::piped())
@@ -19,13 +19,13 @@ use smol::fs::{self};
use std::cmp::Reverse;
use std::fmt::Display;
use std::ops::Range;
-use std::process::Stdio;
use std::{
borrow::Cow,
path::{Path, PathBuf},
sync::{Arc, LazyLock},
};
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
+use util::command::Stdio;
use util::fs::{make_file_executable, remove_matching};
use util::merge_json_value_into;
use util::rel_path::RelPath;
@@ -144,13 +144,9 @@ impl RustLspAdapter {
use futures::pin_mut;
async fn from_ldd_version() -> Option<LibcType> {
- use util::command::new_smol_command;
+ use util::command::new_command;
- let ldd_output = new_smol_command("ldd")
- .arg("--version")
- .output()
- .await
- .ok()?;
+ let ldd_output = new_command("ldd").arg("--version").output().await.ok()?;
let ldd_version = String::from_utf8_lossy(&ldd_output.stdout);
if ldd_version.contains("GNU libc") || ldd_version.contains("GLIBC") {
@@ -543,7 +539,7 @@ impl LspAdapter for RustLspAdapter {
.0
.ok()?;
- let mut command = util::command::new_smol_command(&binary.path);
+ let mut command = util::command::new_command(&binary.path);
command
.arg("--print-config-schema")
.stdout(Stdio::piped())
@@ -1140,7 +1136,7 @@ async fn target_info_from_abs_path(
abs_path: &Path,
project_env: Option<&HashMap<String, String>>,
) -> Option<(Option<TargetInfo>, Arc<Path>)> {
- let mut command = util::command::new_smol_command("cargo");
+ let mut command = util::command::new_command("cargo");
if let Some(envs) = project_env {
command.envs(envs);
}
@@ -1215,7 +1211,7 @@ async fn human_readable_package_name(
package_directory: &Path,
project_env: Option<&HashMap<String, String>>,
) -> Option<String> {
- let mut command = util::command::new_smol_command("cargo");
+ let mut command = util::command::new_command("cargo");
if let Some(envs) = project_env {
command.envs(envs);
}
@@ -22,9 +22,10 @@ use serde_json::{Value, json, value::RawValue};
use smol::{
channel,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
- process::Child,
};
+use util::command::{Child, Stdio};
+use std::path::Path;
use std::{
any::TypeId,
collections::BTreeSet,
@@ -41,7 +42,6 @@ use std::{
task::Poll,
time::{Duration, Instant},
};
-use std::{path::Path, process::Stdio};
use util::{ConnectionResult, ResultExt, TryFutureExt, redact};
const JSON_RPC_VERSION: &str = "2.0";
@@ -418,7 +418,7 @@ impl LanguageServer {
working_dir,
&binary.arguments
);
- let mut command = util::command::new_smol_command(&binary.path);
+ let mut command = util::command::new_command(&binary.path);
command
.current_dir(working_dir)
.args(&binary.arguments)
@@ -427,7 +427,7 @@ impl ManagedNodeRuntime {
let node_ca_certs = env::var(NODE_CA_CERTS_ENV_VAR).unwrap_or_else(|_| String::new());
let valid = if fs::metadata(&node_binary).await.is_ok() {
- let result = util::command::new_smol_command(&node_binary)
+ let result = util::command::new_command(&node_binary)
.env(NODE_CA_CERTS_ENV_VAR, node_ca_certs)
.arg(npm_file)
.arg("--version")
@@ -555,7 +555,7 @@ impl NodeRuntimeTrait for ManagedNodeRuntime {
) -> Result<Output> {
let attempt = || async {
let npm_command = self.npm_command(proxy, subcommand, args).await?;
- let mut command = util::command::new_smol_command(npm_command.path);
+ let mut command = util::command::new_command(npm_command.path);
command.args(npm_command.args);
command.envs(npm_command.env);
configure_npm_command(&mut command, directory);
@@ -640,7 +640,7 @@ pub struct SystemNodeRuntime {
impl SystemNodeRuntime {
const MIN_VERSION: semver::Version = Version::new(22, 0, 0);
async fn new(node: PathBuf, npm: PathBuf) -> Result<Self> {
- let output = util::command::new_smol_command(&node)
+ let output = util::command::new_command(&node)
.arg("--version")
.output()
.await
@@ -723,7 +723,7 @@ impl NodeRuntimeTrait for SystemNodeRuntime {
args: &[&str],
) -> anyhow::Result<Output> {
let npm_command = self.npm_command(proxy, subcommand, args).await?;
- let mut command = util::command::new_smol_command(npm_command.path);
+ let mut command = util::command::new_command(npm_command.path);
command.args(npm_command.args);
command.envs(npm_command.env);
configure_npm_command(&mut command, directory);
@@ -841,7 +841,7 @@ impl NodeRuntimeTrait for UnavailableNodeRuntime {
}
}
-fn configure_npm_command(command: &mut smol::process::Command, directory: Option<&Path>) {
+fn configure_npm_command(command: &mut util::command::Command, directory: Option<&Path>) {
if let Some(directory) = directory {
command.current_dir(directory);
command.args(["--prefix".into(), directory.to_path_buf()]);
@@ -3,10 +3,10 @@ use async_trait::async_trait;
use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName};
use gpui::{BackgroundExecutor, SharedString};
use serde_json::{Value, json};
-use smol::{io::AsyncReadExt, process::Stdio};
+use smol::{io::AsyncReadExt, process::Stdio as SmolStdio};
use std::time::Duration;
use task::{BuildTaskDefinition, DebugScenario, ShellBuilder, SpawnInTerminal, TaskTemplate};
-use util::command::new_smol_command;
+use util::command::{Stdio, new_command};
pub(crate) struct CargoLocator;
@@ -19,7 +19,7 @@ async fn find_best_executable(
return executables.first().cloned();
}
for executable in executables {
- let Some(mut child) = new_smol_command(&executable)
+ let Some(mut child) = new_command(&executable)
.arg("--list")
.stdout(Stdio::piped())
.spawn()
@@ -136,7 +136,7 @@ impl DapLocator for CargoLocator {
)
.envs(build_config.env.iter().map(|(k, v)| (k.clone(), v.clone())))
.current_dir(cwd)
- .stdout(Stdio::piped())
+ .stdout(SmolStdio::piped())
.spawn()?;
let mut output = String::new();
@@ -51,7 +51,6 @@ use std::collections::{BTreeMap, VecDeque};
use std::net::Ipv4Addr;
use std::ops::RangeInclusive;
use std::path::PathBuf;
-use std::process::Stdio;
use std::time::Duration;
use std::u64;
use std::{
@@ -64,7 +63,8 @@ use std::{
use task::SharedTaskContext;
use text::{PointUtf16, ToPointUtf16};
use url::Url;
-use util::command::new_smol_command;
+use util::command::Stdio;
+use util::command::new_command;
use util::{ResultExt, debug_panic, maybe};
use worktree::Worktree;
@@ -2883,7 +2883,7 @@ impl Session {
let child = remote_client.update(cx, |client, _| {
let command = client.build_forward_ports_command(port_forwards)?;
- let child = new_smol_command(command.program)
+ let child = new_command(command.program)
.args(command.args)
.envs(command.env)
.spawn()
@@ -3067,7 +3067,7 @@ struct KillCompanionBrowserParams {
async fn spawn_companion(
node_runtime: NodeRuntime,
cx: &mut AsyncApp,
-) -> Result<(u16, smol::process::Child)> {
+) -> Result<(u16, util::command::Child)> {
let binary_path = node_runtime
.binary_path()
.await
@@ -3089,7 +3089,7 @@ async fn spawn_companion(
.to_string_lossy()
.to_string();
- let child = new_smol_command(binary_path)
+ let child = new_command(binary_path)
.arg(path)
.args([
format!("--listen=127.0.0.1:{port}"),
@@ -6,7 +6,7 @@ use rpc::proto::{self, REMOTE_SERVER_PROJECT_ID};
use std::{collections::VecDeque, path::Path, sync::Arc};
use task::{Shell, shell_to_proto};
use terminal::terminal_settings::TerminalSettings;
-use util::{ResultExt, command::new_smol_command, rel_path::RelPath};
+use util::{ResultExt, command::new_command, rel_path::RelPath};
use worktree::Worktree;
use collections::HashMap;
@@ -402,7 +402,7 @@ async fn load_direnv_environment(
};
let args = &["export", "json"];
- let direnv_output = new_smol_command(&direnv_path)
+ let direnv_output = new_command(&direnv_path)
.args(args)
.envs(env)
.env("TERM", "dumb")
@@ -2373,7 +2373,8 @@ impl LocalLspStore {
Some(worktree_path)
});
- let mut child = util::command::new_smol_command(command);
+ use util::command::Stdio;
+ let mut child = util::command::new_command(command);
if let Some(buffer_env) = buffer.env.as_ref() {
child.envs(buffer_env);
@@ -2394,9 +2395,9 @@ impl LocalLspStore {
}
let mut child = child
- .stdin(smol::process::Stdio::piped())
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped())
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
.spawn()?;
let stdin = child.stdin.as_mut().context("failed to acquire stdin")?;
@@ -14100,7 +14101,7 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
};
let env = self.shell_env().await;
- let output = util::command::new_smol_command(&npm)
+ let output = util::command::new_command(&npm)
.args(["root", "-g"])
.envs(env)
.current_dir(local_package_directory)
@@ -14135,7 +14136,7 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
if self.fs.is_file(&working_dir).await {
working_dir.pop();
}
- let output = util::command::new_smol_command(&command.path)
+ let output = util::command::new_command(&command.path)
.args(command.arguments)
.envs(command.env.clone().unwrap_or_default())
.current_dir(working_dir)
@@ -422,7 +422,7 @@ async fn path_exists(connection: &Arc<dyn RemoteConnection>, path: &Path) -> boo
) else {
return false;
};
- let Ok(mut child) = util::command::new_smol_command(command.program)
+ let Ok(mut child) = util::command::new_command(command.program)
.args(command.args)
.envs(command.env)
.spawn()
@@ -10,7 +10,7 @@ use futures::{
};
use gpui::{AppContext as _, AsyncApp, Task};
use rpc::proto::Envelope;
-use smol::process::Child;
+use util::command::Child;
pub mod docker;
#[cfg(any(test, feature = "test-support"))]
@@ -181,10 +181,9 @@ async fn build_remote_server_from_source(
binary_exists_on_server: bool,
cx: &mut AsyncApp,
) -> Result<Option<std::path::PathBuf>> {
- use smol::process::{Command, Stdio};
use std::env::VarError;
use std::path::Path;
- use util::command::new_smol_command;
+ use util::command::{Command, Stdio, new_command};
if let Ok(path) = std::env::var("ZED_COPY_REMOTE_SERVER") {
let path = std::path::PathBuf::from(path);
@@ -267,7 +266,7 @@ async fn build_remote_server_from_source(
delegate.set_status(Some("Building remote server binary from source"), cx);
log::info!("building remote server binary from source");
run_cmd(
- new_smol_command("cargo")
+ new_command("cargo")
.current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/../.."))
.args([
"build",
@@ -297,18 +296,12 @@ async fn build_remote_server_from_source(
.context("rustup not found on $PATH, install rustup (see https://rustup.rs/)")?;
delegate.set_status(Some("Adding rustup target for cross-compilation"), cx);
log::info!("adding rustup target");
- run_cmd(
- new_smol_command(rustup)
- .args(["target", "add"])
- .arg(&triple),
- )
- .await?;
+ run_cmd(new_command(rustup).args(["target", "add"]).arg(&triple)).await?;
if which("cargo-zigbuild", cx).await?.is_none() {
delegate.set_status(Some("Installing cargo-zigbuild for cross-compilation"), cx);
log::info!("installing cargo-zigbuild");
- run_cmd(new_smol_command("cargo").args(["install", "--locked", "cargo-zigbuild"]))
- .await?;
+ run_cmd(new_command("cargo").args(["install", "--locked", "cargo-zigbuild"])).await?;
}
delegate.set_status(
@@ -319,7 +312,7 @@ async fn build_remote_server_from_source(
);
log::info!("building remote binary from source for {triple} with Zig");
run_cmd(
- new_smol_command("cargo")
+ new_command("cargo")
.args([
"zigbuild",
"--package",
@@ -347,7 +340,7 @@ async fn build_remote_server_from_source(
#[cfg(not(target_os = "windows"))]
let archive_path = {
- run_cmd(new_smol_command("gzip").arg("-f").arg(&bin_path)).await?;
+ run_cmd(new_command("gzip").arg("-f").arg(&bin_path)).await?;
bin_path.with_extension("gz")
};
@@ -362,7 +355,7 @@ async fn build_remote_server_from_source(
bin_path.display(),
zip_path.display(),
);
- run_cmd(new_smol_command("powershell.exe").args([
+ run_cmd(new_command("powershell.exe").args([
"-NoProfile",
"-Command",
&compress_command,
@@ -9,10 +9,10 @@ use semver::Version as SemanticVersion;
use std::time::Instant;
use std::{
path::{Path, PathBuf},
- process::Stdio,
sync::Arc,
};
use util::ResultExt;
+use util::command::Stdio;
use util::shell::ShellKind;
use util::{
paths::{PathStyle, RemotePathBuf},
@@ -400,7 +400,7 @@ impl DockerExecConnection {
src_path: String,
dst_path: String,
) -> Result<()> {
- let mut command = util::command::new_smol_command(&docker_cli);
+ let mut command = util::command::new_command(&docker_cli);
command.arg("cp");
command.arg("-a");
command.arg(&src_path);
@@ -419,7 +419,7 @@ impl DockerExecConnection {
);
}
- let mut chown_command = util::command::new_smol_command(&docker_cli);
+ let mut chown_command = util::command::new_command(&docker_cli);
chown_command.arg("exec");
chown_command.arg(connection_options.container_id);
chown_command.arg("chown");
@@ -469,7 +469,7 @@ impl DockerExecConnection {
subcommand: &str,
args: &[impl AsRef<str>],
) -> Result<String> {
- let mut command = util::command::new_smol_command(self.docker_cli());
+ let mut command = util::command::new_command(self.docker_cli());
command.arg(subcommand);
for arg in args {
command.arg(arg.as_ref());
@@ -589,7 +589,7 @@ impl DockerExecConnection {
fn kill_inner(&self) -> Result<()> {
if let Some(pid) = self.proxy_process.lock().take() {
- if let Ok(_) = util::command::new_smol_command("kill")
+ if let Ok(_) = util::command::new_command("kill")
.arg(pid.to_string())
.spawn()
{
@@ -658,7 +658,7 @@ impl RemoteConnection for DockerExecConnection {
if reconnect {
docker_args.push("--reconnect".to_string());
}
- let mut command = util::command::new_smol_command(self.docker_cli());
+ let mut command = util::command::new_command(self.docker_cli());
command
.kill_on_drop(true)
.stdin(Stdio::piped())
@@ -18,10 +18,7 @@ use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::Envelope;
use semver::Version;
pub use settings::SshPortForwardOption;
-use smol::{
- fs,
- process::{self, Child, Stdio},
-};
+use smol::fs;
use std::{
net::IpAddr,
path::{Path, PathBuf},
@@ -29,6 +26,7 @@ use std::{
time::Instant,
};
use tempfile::TempDir;
+use util::command::{Child, Stdio};
use util::{
paths::{PathStyle, RemotePathBuf},
rel_path::RelPath,
@@ -157,7 +155,7 @@ impl MasterProcess {
"-o",
];
- let mut master_process = util::command::new_smol_command("ssh");
+ let mut master_process = util::command::new_command("ssh");
master_process
.kill_on_drop(true)
.stdin(Stdio::null())
@@ -206,7 +204,7 @@ impl MasterProcess {
&format!("echo '{}'; exec $0", Self::CONNECTION_ESTABLISHED_MAGIC),
];
- let mut master_process = util::command::new_smol_command("ssh");
+ let mut master_process = util::command::new_command("ssh");
master_process
.kill_on_drop(true)
.stdin(Stdio::null())
@@ -992,8 +990,8 @@ impl SshRemoteConnection {
src_path: &Path,
dest_path_str: &str,
args: Option<&[&str]>,
- ) -> process::Command {
- let mut command = util::command::new_smol_command("scp");
+ ) -> util::command::Command {
+ let mut command = util::command::new_command("scp");
self.socket.ssh_options(&mut command, false).args(
self.socket
.connection_options
@@ -1012,8 +1010,8 @@ impl SshRemoteConnection {
command
}
- fn build_sftp_command(&self) -> process::Command {
- let mut command = util::command::new_smol_command("sftp");
+ fn build_sftp_command(&self) -> util::command::Command {
+ let mut command = util::command::new_command("sftp");
self.socket.ssh_options(&mut command, false).args(
self.socket
.connection_options
@@ -1133,8 +1131,8 @@ impl SshSocket {
program: &str,
args: &[impl AsRef<str>],
allow_pseudo_tty: bool,
- ) -> process::Command {
- let mut command = util::command::new_smol_command("ssh");
+ ) -> util::command::Command {
+ let mut command = util::command::new_command("ssh");
let program = shell_kind.prepend_command_prefix(program);
let mut to_run = shell_kind
.try_quote_prefix_aware(&program)
@@ -1185,9 +1183,9 @@ impl SshSocket {
fn ssh_options<'a>(
&self,
- command: &'a mut process::Command,
+ command: &'a mut util::command::Command,
include_port_forwards: bool,
- ) -> &'a mut process::Command {
+ ) -> &'a mut util::command::Command {
let args = if include_port_forwards {
self.connection_options.additional_args()
} else {
@@ -11,16 +11,17 @@ use gpui::{App, AppContext as _, AsyncApp, Task};
use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::Envelope;
use semver::Version;
-use smol::{fs, process};
+use smol::fs;
use std::{
ffi::OsStr,
fmt::Write as _,
path::{Path, PathBuf},
- process::Stdio,
sync::Arc,
time::Instant,
};
+
use util::{
+ command::Stdio,
paths::{PathStyle, RemotePathBuf},
rel_path::RelPath,
shell::{Shell, ShellKind},
@@ -595,7 +596,9 @@ pub fn wsl_path_to_windows_path(
}
}
-fn run_wsl_command_impl(mut command: process::Command) -> impl Future<Output = Result<String>> {
+fn run_wsl_command_impl(
+ mut command: util::command::Command,
+) -> impl Future<Output = Result<String>> {
async move {
let output = command
.output()
@@ -622,8 +625,8 @@ fn wsl_command_impl(
program: &str,
args: &[impl AsRef<OsStr>],
exec: bool,
-) -> process::Command {
- let mut command = util::command::new_smol_command("wsl.exe");
+) -> util::command::Command {
+ let mut command = util::command::new_command("wsl.exe");
if let Some(user) = &options.user {
command.arg("--user").arg(user);
@@ -56,7 +56,7 @@ use std::{
sync::{Arc, LazyLock},
};
use thiserror::Error;
-use util::{ResultExt, command::new_smol_command};
+use util::{ResultExt, command::new_command};
#[derive(Subcommand)]
pub enum Commands {
@@ -945,11 +945,11 @@ fn spawn_server_windows(binary_name: &Path, paths: &ServerPaths) -> Result<(), S
#[cfg(not(windows))]
fn spawn_server_normal(binary_name: &Path, paths: &ServerPaths) -> Result<(), SpawnServerError> {
- let mut server_process = new_smol_command(binary_name);
+ let mut server_process = new_command(binary_name);
server_process
- .stdin(std::process::Stdio::null())
- .stdout(std::process::Stdio::null())
- .stderr(std::process::Stdio::null())
+ .stdin(util::command::Stdio::null())
+ .stdout(util::command::Stdio::null())
+ .stderr(util::command::Stdio::null())
.arg("run")
.arg("--log-file")
.arg(&paths.log_file)
@@ -977,7 +977,7 @@ pub struct CheckPidError {
pid: u32,
}
async fn check_server_running(pid: u32) -> std::io::Result<bool> {
- new_smol_command("kill")
+ new_command("kill")
.arg("-0")
.arg(pid.to_string())
.output()
@@ -190,7 +190,7 @@ pub fn python_env_kernel_specifications(
let python_path = toolchain.path.to_string();
let environment_kind = extract_environment_kind(&toolchain.as_json);
- let has_ipykernel = util::command::new_smol_command(&python_path)
+ let has_ipykernel = util::command::new_command(&python_path)
.args(&["-c", "import ipykernel"])
.output()
.await
@@ -12,7 +12,7 @@ use jupyter_protocol::{
};
use project::Fs;
use runtimelib::{RuntimeError, dirs};
-use smol::{net::TcpListener, process::Command};
+use smol::net::TcpListener;
use std::{
env,
fmt::Debug,
@@ -20,6 +20,7 @@ use std::{
path::PathBuf,
sync::Arc,
};
+use util::command::Command;
use uuid::Uuid;
use super::{KernelSession, RunningKernel};
@@ -52,7 +53,7 @@ impl LocalKernelSpecification {
self.name
);
- let mut cmd = util::command::new_smol_command(&argv[0]);
+ let mut cmd = util::command::new_command(&argv[0]);
for arg in &argv[1..] {
if arg == "{connection_file}" {
@@ -85,7 +86,7 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> {
}
pub struct NativeRunningKernel {
- pub process: smol::process::Child,
+ pub process: util::command::Child,
connection_path: PathBuf,
_process_status_task: Option<Task<()>>,
pub working_directory: PathBuf,
@@ -143,9 +144,9 @@ impl NativeRunningKernel {
let mut process = cmd
.current_dir(&working_directory)
- .stdout(std::process::Stdio::piped())
- .stderr(std::process::Stdio::piped())
- .stdin(std::process::Stdio::piped())
+ .stdout(util::command::Stdio::piped())
+ .stderr(util::command::Stdio::piped())
+ .stdin(util::command::Stdio::piped())
.kill_on_drop(true)
.spawn()
.context("failed to start the kernel process")?;
@@ -490,7 +491,7 @@ pub async fn local_kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<LocalKer
}
// Search for kernels inside the base python environment
- let command = util::command::new_smol_command("python")
+ let command = util::command::new_command("python")
.arg("-c")
.arg("import sys; print(sys.prefix)")
.output()
@@ -108,7 +108,7 @@ pub fn install_ipykernel_and_assign(
let window_handle = window.window_handle();
let install_task = cx.background_spawn(async move {
- let output = util::command::new_smol_command(python_path.to_string_lossy().as_ref())
+ let output = util::command::new_command(python_path.to_string_lossy().as_ref())
.args(&["-m", "pip", "install", "ipykernel"])
.output()
.await
@@ -17,13 +17,12 @@ use messages::*;
use postage::watch;
use serde::{Deserialize, Serialize};
use settings::SettingsStore;
-use smol::{
- io::AsyncWriteExt,
- process::{Child, ChildStdin, ChildStdout},
-};
-use std::{path::PathBuf, process::Stdio, sync::Arc};
+use smol::io::AsyncWriteExt;
+use std::{path::PathBuf, sync::Arc};
use ui::prelude::*;
use util::ResultExt;
+use util::command::Child;
+use util::command::Stdio;
actions!(
supermaven,
@@ -271,7 +270,7 @@ impl SupermavenAgent {
client: Arc<Client>,
cx: &mut Context<Supermaven>,
) -> Result<Self> {
- let mut process = util::command::new_smol_command(&binary_path)
+ let mut process = util::command::new_command(&binary_path)
.arg("stdio")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
@@ -308,9 +307,9 @@ impl SupermavenAgent {
})
}
- async fn handle_outgoing_messages(
+ async fn handle_outgoing_messages<W: smol::io::AsyncWrite + Unpin>(
mut outgoing: mpsc::UnboundedReceiver<OutboundMessage>,
- mut stdin: ChildStdin,
+ mut stdin: W,
) -> Result<()> {
while let Some(message) = outgoing.next().await {
let bytes = serde_json::to_vec(&message)?;
@@ -320,9 +319,9 @@ impl SupermavenAgent {
Ok(())
}
- async fn handle_incoming_messages(
+ async fn handle_incoming_messages<R: smol::io::AsyncRead + Unpin>(
this: WeakEntity<Supermaven>,
- stdout: ChildStdout,
+ stdout: R,
cx: &mut AsyncApp,
) -> Result<()> {
const MESSAGE_PREFIX: &str = "SM-MESSAGE ";
@@ -1,8 +1,20 @@
use std::ffi::OsStr;
+#[cfg(not(target_os = "macos"))]
+use std::path::Path;
+
+#[cfg(target_os = "macos")]
+mod darwin;
+
+#[cfg(target_os = "macos")]
+pub use darwin::{Child, Command, Stdio};
#[cfg(target_os = "windows")]
const CREATE_NO_WINDOW: u32 = 0x0800_0000_u32;
+pub fn new_command(program: impl AsRef<OsStr>) -> Command {
+ Command::new(program)
+}
+
#[cfg(target_os = "windows")]
pub fn new_std_command(program: impl AsRef<OsStr>) -> std::process::Command {
use std::os::windows::process::CommandExt;
@@ -17,86 +29,104 @@ pub fn new_std_command(program: impl AsRef<OsStr>) -> std::process::Command {
std::process::Command::new(program)
}
-#[cfg(target_os = "windows")]
-pub fn new_smol_command(program: impl AsRef<OsStr>) -> smol::process::Command {
- use smol::process::windows::CommandExt;
+#[cfg(not(target_os = "macos"))]
+pub type Child = smol::process::Child;
- let mut command = smol::process::Command::new(program);
- command.creation_flags(CREATE_NO_WINDOW);
- command
-}
+#[cfg(not(target_os = "macos"))]
+pub use std::process::Stdio;
-#[cfg(target_os = "macos")]
-pub fn new_smol_command(program: impl AsRef<OsStr>) -> smol::process::Command {
- use std::os::unix::process::CommandExt;
-
- // Create a std::process::Command first so we can use pre_exec
- let mut std_cmd = std::process::Command::new(program);
-
- // WORKAROUND: Reset exception ports before exec to prevent inheritance of
- // crash handler exception ports. Due to a timing issue, child processes can
- // inherit the parent's exception ports before they're fully stabilized,
- // which can block child process spawning.
- // See: https://github.com/zed-industries/zed/issues/36754
- unsafe {
- std_cmd.pre_exec(|| {
- // Reset all exception ports to system defaults for this task.
- // This prevents the child from inheriting the parent's crash handler
- // exception ports.
- reset_exception_ports();
- Ok(())
- });
+#[cfg(not(target_os = "macos"))]
+#[derive(Debug)]
+pub struct Command(smol::process::Command);
+
+#[cfg(not(target_os = "macos"))]
+impl Command {
+ #[inline]
+ pub fn new(program: impl AsRef<OsStr>) -> Self {
+ #[cfg(target_os = "windows")]
+ {
+ use smol::process::windows::CommandExt;
+ let mut cmd = smol::process::Command::new(program);
+ cmd.creation_flags(CREATE_NO_WINDOW);
+ Self(cmd)
+ }
+ #[cfg(not(target_os = "windows"))]
+ Self(smol::process::Command::new(program))
}
- // Convert to async_process::Command via From trait
- smol::process::Command::from(std_cmd)
-}
+ pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
+ self.0.arg(arg);
+ self
+ }
-#[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
-pub fn new_smol_command(program: impl AsRef<OsStr>) -> smol::process::Command {
- smol::process::Command::new(program)
-}
+ pub fn args<I, S>(&mut self, args: I) -> &mut Self
+ where
+ I: IntoIterator<Item = S>,
+ S: AsRef<OsStr>,
+ {
+ self.0.args(args);
+ self
+ }
-#[cfg(target_os = "macos")]
-pub fn reset_exception_ports() {
- use mach2::exception_types::{
- EXC_MASK_ALL, EXCEPTION_DEFAULT, exception_behavior_t, exception_mask_t,
- };
- use mach2::kern_return::{KERN_SUCCESS, kern_return_t};
- use mach2::mach_types::task_t;
- use mach2::port::{MACH_PORT_NULL, mach_port_t};
- use mach2::thread_status::{THREAD_STATE_NONE, thread_state_flavor_t};
- use mach2::traps::mach_task_self;
-
- // FFI binding for task_set_exception_ports (not exposed by mach2 crate)
- unsafe extern "C" {
- fn task_set_exception_ports(
- task: task_t,
- exception_mask: exception_mask_t,
- new_port: mach_port_t,
- behavior: exception_behavior_t,
- new_flavor: thread_state_flavor_t,
- ) -> kern_return_t;
+ pub fn env(&mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) -> &mut Self {
+ self.0.env(key, val);
+ self
}
- unsafe {
- let task = mach_task_self();
- // Reset all exception ports to MACH_PORT_NULL (system default)
- // This prevents the child process from inheriting the parent's crash handler
- let kr = task_set_exception_ports(
- task,
- EXC_MASK_ALL,
- MACH_PORT_NULL,
- EXCEPTION_DEFAULT as exception_behavior_t,
- THREAD_STATE_NONE,
- );
-
- if kr != KERN_SUCCESS {
- // Log but don't fail - the process can still work without this workaround
- eprintln!(
- "Warning: failed to reset exception ports in child process (kern_return: {})",
- kr
- );
- }
+ pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
+ where
+ I: IntoIterator<Item = (K, V)>,
+ K: AsRef<OsStr>,
+ V: AsRef<OsStr>,
+ {
+ self.0.envs(vars);
+ self
+ }
+
+ pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
+ self.0.env_remove(key);
+ self
+ }
+
+ pub fn env_clear(&mut self) -> &mut Self {
+ self.0.env_clear();
+ self
+ }
+
+ pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
+ self.0.current_dir(dir);
+ self
+ }
+
+ pub fn stdin(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
+ self.0.stdin(cfg.into());
+ self
+ }
+
+ pub fn stdout(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
+ self.0.stdout(cfg.into());
+ self
+ }
+
+ pub fn stderr(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
+ self.0.stderr(cfg.into());
+ self
+ }
+
+ pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self {
+ self.0.kill_on_drop(kill_on_drop);
+ self
+ }
+
+ pub fn spawn(&mut self) -> std::io::Result<Child> {
+ self.0.spawn()
+ }
+
+ pub async fn output(&mut self) -> std::io::Result<std::process::Output> {
+ self.0.output().await
+ }
+
+ pub async fn status(&mut self) -> std::io::Result<std::process::ExitStatus> {
+ self.0.status().await
}
}
@@ -0,0 +1,825 @@
+use mach2::exception_types::{
+ EXC_MASK_ALL, EXCEPTION_DEFAULT, exception_behavior_t, exception_mask_t,
+};
+use mach2::port::{MACH_PORT_NULL, mach_port_t};
+use mach2::thread_status::{THREAD_STATE_NONE, thread_state_flavor_t};
+use smol::Unblock;
+use std::collections::BTreeMap;
+use std::ffi::{CString, OsStr, OsString};
+use std::io;
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::io::FromRawFd;
+use std::os::unix::process::ExitStatusExt;
+use std::path::{Path, PathBuf};
+use std::process::{ExitStatus, Output};
+use std::ptr;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum Stdio {
+ /// A new pipe should be arranged to connect the parent and child processes.
+ #[default]
+ Piped,
+ /// The child inherits from the corresponding parent descriptor.
+ Inherit,
+ /// This stream will be ignored (redirected to `/dev/null`).
+ Null,
+}
+
+impl Stdio {
+ pub fn piped() -> Self {
+ Self::Piped
+ }
+
+ pub fn inherit() -> Self {
+ Self::Inherit
+ }
+
+ pub fn null() -> Self {
+ Self::Null
+ }
+}
+
+unsafe extern "C" {
+ fn posix_spawnattr_setexceptionports_np(
+ attr: *mut libc::posix_spawnattr_t,
+ mask: exception_mask_t,
+ new_port: mach_port_t,
+ behavior: exception_behavior_t,
+ new_flavor: thread_state_flavor_t,
+ ) -> libc::c_int;
+
+ fn posix_spawn_file_actions_addchdir_np(
+ file_actions: *mut libc::posix_spawn_file_actions_t,
+ path: *const libc::c_char,
+ ) -> libc::c_int;
+
+ fn posix_spawn_file_actions_addinherit_np(
+ file_actions: *mut libc::posix_spawn_file_actions_t,
+ filedes: libc::c_int,
+ ) -> libc::c_int;
+
+ static environ: *const *mut libc::c_char;
+}
+
+#[derive(Debug)]
+pub struct Command {
+ program: OsString,
+ args: Vec<OsString>,
+ envs: BTreeMap<OsString, Option<OsString>>,
+ env_clear: bool,
+ current_dir: Option<PathBuf>,
+ stdin_cfg: Option<Stdio>,
+ stdout_cfg: Option<Stdio>,
+ stderr_cfg: Option<Stdio>,
+ kill_on_drop: bool,
+}
+
+impl Command {
+ pub fn new(program: impl AsRef<OsStr>) -> Self {
+ Self {
+ program: program.as_ref().to_owned(),
+ args: Vec::new(),
+ envs: BTreeMap::new(),
+ env_clear: false,
+ current_dir: None,
+ stdin_cfg: None,
+ stdout_cfg: None,
+ stderr_cfg: None,
+ kill_on_drop: false,
+ }
+ }
+
+ pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
+ self.args.push(arg.as_ref().to_owned());
+ self
+ }
+
+ pub fn args<I, S>(&mut self, args: I) -> &mut Self
+ where
+ I: IntoIterator<Item = S>,
+ S: AsRef<OsStr>,
+ {
+ self.args
+ .extend(args.into_iter().map(|a| a.as_ref().to_owned()));
+ self
+ }
+
+ pub fn env(&mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) -> &mut Self {
+ self.envs
+ .insert(key.as_ref().to_owned(), Some(val.as_ref().to_owned()));
+ self
+ }
+
+ pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
+ where
+ I: IntoIterator<Item = (K, V)>,
+ K: AsRef<OsStr>,
+ V: AsRef<OsStr>,
+ {
+ for (key, val) in vars {
+ self.envs
+ .insert(key.as_ref().to_owned(), Some(val.as_ref().to_owned()));
+ }
+ self
+ }
+
+ pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
+ let key = key.as_ref().to_owned();
+ if self.env_clear {
+ self.envs.remove(&key);
+ } else {
+ self.envs.insert(key, None);
+ }
+ self
+ }
+
+ pub fn env_clear(&mut self) -> &mut Self {
+ self.env_clear = true;
+ self.envs.clear();
+ self
+ }
+
+ pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
+ self.current_dir = Some(dir.as_ref().to_owned());
+ self
+ }
+
+ pub fn stdin(&mut self, cfg: Stdio) -> &mut Self {
+ self.stdin_cfg = Some(cfg);
+ self
+ }
+
+ pub fn stdout(&mut self, cfg: Stdio) -> &mut Self {
+ self.stdout_cfg = Some(cfg);
+ self
+ }
+
+ pub fn stderr(&mut self, cfg: Stdio) -> &mut Self {
+ self.stderr_cfg = Some(cfg);
+ self
+ }
+
+ pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self {
+ self.kill_on_drop = kill_on_drop;
+ self
+ }
+
+ pub fn spawn(&mut self) -> io::Result<Child> {
+ let current_dir = self
+ .current_dir
+ .as_deref()
+ .unwrap_or_else(|| Path::new("."));
+
+ // Optimization: if no environment modifications were requested, pass None
+ // to spawn_posix so it uses the `environ` global directly, avoiding a
+ // full copy of the environment. This matches std::process::Command behavior.
+ let envs = if self.env_clear || !self.envs.is_empty() {
+ let mut result = BTreeMap::<OsString, OsString>::new();
+ if !self.env_clear {
+ for (key, val) in std::env::vars_os() {
+ result.insert(key, val);
+ }
+ }
+ for (key, maybe_val) in &self.envs {
+ if let Some(val) = maybe_val {
+ result.insert(key.clone(), val.clone());
+ } else {
+ result.remove(key);
+ }
+ }
+ Some(result.into_iter().collect::<Vec<_>>())
+ } else {
+ None
+ };
+
+ spawn_posix_spawn(
+ &self.program,
+ &self.args,
+ current_dir,
+ envs.as_deref(),
+ self.stdin_cfg.unwrap_or_default(),
+ self.stdout_cfg.unwrap_or_default(),
+ self.stderr_cfg.unwrap_or_default(),
+ self.kill_on_drop,
+ )
+ }
+
+ pub async fn output(&mut self) -> io::Result<Output> {
+ self.stdin_cfg.get_or_insert(Stdio::null());
+ self.stdout_cfg.get_or_insert(Stdio::piped());
+ self.stderr_cfg.get_or_insert(Stdio::piped());
+
+ let child = self.spawn()?;
+ child.output().await
+ }
+
+ pub async fn status(&mut self) -> io::Result<ExitStatus> {
+ let mut child = self.spawn()?;
+ child.status().await
+ }
+}
+
+#[derive(Debug)]
+pub struct Child {
+ pid: libc::pid_t,
+ pub stdin: Option<Unblock<std::fs::File>>,
+ pub stdout: Option<Unblock<std::fs::File>>,
+ pub stderr: Option<Unblock<std::fs::File>>,
+ kill_on_drop: bool,
+ status: Option<ExitStatus>,
+}
+
+impl Drop for Child {
+ fn drop(&mut self) {
+ if self.kill_on_drop && self.status.is_none() {
+ let _ = self.kill();
+ }
+ }
+}
+
+impl Child {
+ pub fn id(&self) -> u32 {
+ self.pid as u32
+ }
+
+ pub fn kill(&mut self) -> io::Result<()> {
+ let result = unsafe { libc::kill(self.pid, libc::SIGKILL) };
+ if result == -1 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+
+ pub fn try_status(&mut self) -> io::Result<Option<ExitStatus>> {
+ if let Some(status) = self.status {
+ return Ok(Some(status));
+ }
+
+ let mut status: libc::c_int = 0;
+ let result = unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) };
+
+ if result == -1 {
+ Err(io::Error::last_os_error())
+ } else if result == 0 {
+ Ok(None)
+ } else {
+ let exit_status = ExitStatus::from_raw(status);
+ self.status = Some(exit_status);
+ Ok(Some(exit_status))
+ }
+ }
+
+ pub fn status(
+ &mut self,
+ ) -> impl std::future::Future<Output = io::Result<ExitStatus>> + Send + 'static {
+ self.stdin.take();
+
+ let pid = self.pid;
+ let cached_status = self.status;
+
+ async move {
+ if let Some(status) = cached_status {
+ return Ok(status);
+ }
+
+ smol::unblock(move || {
+ let mut status: libc::c_int = 0;
+ let result = unsafe { libc::waitpid(pid, &mut status, 0) };
+ if result == -1 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(ExitStatus::from_raw(status))
+ }
+ })
+ .await
+ }
+ }
+
+ pub async fn output(mut self) -> io::Result<Output> {
+ use futures_lite::AsyncReadExt;
+
+ let status = self.status();
+
+ let stdout = self.stdout.take();
+ let stdout_future = async move {
+ let mut data = Vec::new();
+ if let Some(mut stdout) = stdout {
+ stdout.read_to_end(&mut data).await?;
+ }
+ io::Result::Ok(data)
+ };
+
+ let stderr = self.stderr.take();
+ let stderr_future = async move {
+ let mut data = Vec::new();
+ if let Some(mut stderr) = stderr {
+ stderr.read_to_end(&mut data).await?;
+ }
+ io::Result::Ok(data)
+ };
+
+ let (stdout_data, stderr_data) =
+ futures_lite::future::try_zip(stdout_future, stderr_future).await?;
+ let status = status.await?;
+
+ Ok(Output {
+ status,
+ stdout: stdout_data,
+ stderr: stderr_data,
+ })
+ }
+}
+
+fn spawn_posix_spawn(
+ program: &OsStr,
+ args: &[OsString],
+ current_dir: &Path,
+ envs: Option<&[(OsString, OsString)]>,
+ stdin_cfg: Stdio,
+ stdout_cfg: Stdio,
+ stderr_cfg: Stdio,
+ kill_on_drop: bool,
+) -> io::Result<Child> {
+ let program_cstr = CString::new(program.as_bytes()).map_err(|_| invalid_input_error())?;
+
+ let current_dir_cstr =
+ CString::new(current_dir.as_os_str().as_bytes()).map_err(|_| invalid_input_error())?;
+
+ let mut argv_cstrs = vec![program_cstr.clone()];
+ for arg in args {
+ let cstr = CString::new(arg.as_bytes()).map_err(|_| invalid_input_error())?;
+ argv_cstrs.push(cstr);
+ }
+ let mut argv_ptrs: Vec<*mut libc::c_char> = argv_cstrs
+ .iter()
+ .map(|s| s.as_ptr() as *mut libc::c_char)
+ .collect();
+ argv_ptrs.push(ptr::null_mut());
+
+ let envp: Vec<CString> = if let Some(envs) = envs {
+ envs.iter()
+ .map(|(key, value)| {
+ let mut env_str = key.as_bytes().to_vec();
+ env_str.push(b'=');
+ env_str.extend_from_slice(value.as_bytes());
+ CString::new(env_str)
+ })
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|_| invalid_input_error())?
+ } else {
+ Vec::new()
+ };
+ let mut envp_ptrs: Vec<*mut libc::c_char> = envp
+ .iter()
+ .map(|s| s.as_ptr() as *mut libc::c_char)
+ .collect();
+ envp_ptrs.push(ptr::null_mut());
+
+ let (stdin_read, stdin_write) = match stdin_cfg {
+ Stdio::Piped => {
+ let (r, w) = create_pipe()?;
+ (Some(r), Some(w))
+ }
+ Stdio::Null => {
+ let fd = open_dev_null(libc::O_RDONLY)?;
+ (Some(fd), None)
+ }
+ Stdio::Inherit => (None, None),
+ };
+
+ let (stdout_read, stdout_write) = match stdout_cfg {
+ Stdio::Piped => {
+ let (r, w) = create_pipe()?;
+ (Some(r), Some(w))
+ }
+ Stdio::Null => {
+ let fd = open_dev_null(libc::O_WRONLY)?;
+ (None, Some(fd))
+ }
+ Stdio::Inherit => (None, None),
+ };
+
+ let (stderr_read, stderr_write) = match stderr_cfg {
+ Stdio::Piped => {
+ let (r, w) = create_pipe()?;
+ (Some(r), Some(w))
+ }
+ Stdio::Null => {
+ let fd = open_dev_null(libc::O_WRONLY)?;
+ (None, Some(fd))
+ }
+ Stdio::Inherit => (None, None),
+ };
+
+ let mut attr: libc::posix_spawnattr_t = ptr::null_mut();
+ let mut file_actions: libc::posix_spawn_file_actions_t = ptr::null_mut();
+
+ unsafe {
+ cvt_nz(libc::posix_spawnattr_init(&mut attr))?;
+ cvt_nz(libc::posix_spawn_file_actions_init(&mut file_actions))?;
+
+ cvt_nz(libc::posix_spawnattr_setflags(
+ &mut attr,
+ libc::POSIX_SPAWN_CLOEXEC_DEFAULT as libc::c_short,
+ ))?;
+
+ cvt_nz(posix_spawnattr_setexceptionports_np(
+ &mut attr,
+ EXC_MASK_ALL,
+ MACH_PORT_NULL,
+ EXCEPTION_DEFAULT as exception_behavior_t,
+ THREAD_STATE_NONE,
+ ))?;
+
+ cvt_nz(posix_spawn_file_actions_addchdir_np(
+ &mut file_actions,
+ current_dir_cstr.as_ptr(),
+ ))?;
+
+ if let Some(fd) = stdin_read {
+ cvt_nz(libc::posix_spawn_file_actions_adddup2(
+ &mut file_actions,
+ fd,
+ libc::STDIN_FILENO,
+ ))?;
+ cvt_nz(posix_spawn_file_actions_addinherit_np(
+ &mut file_actions,
+ libc::STDIN_FILENO,
+ ))?;
+ }
+
+ if let Some(fd) = stdout_write {
+ cvt_nz(libc::posix_spawn_file_actions_adddup2(
+ &mut file_actions,
+ fd,
+ libc::STDOUT_FILENO,
+ ))?;
+ cvt_nz(posix_spawn_file_actions_addinherit_np(
+ &mut file_actions,
+ libc::STDOUT_FILENO,
+ ))?;
+ }
+
+ if let Some(fd) = stderr_write {
+ cvt_nz(libc::posix_spawn_file_actions_adddup2(
+ &mut file_actions,
+ fd,
+ libc::STDERR_FILENO,
+ ))?;
+ cvt_nz(posix_spawn_file_actions_addinherit_np(
+ &mut file_actions,
+ libc::STDERR_FILENO,
+ ))?;
+ }
+
+ let mut pid: libc::pid_t = 0;
+
+ let spawn_result = libc::posix_spawnp(
+ &mut pid,
+ program_cstr.as_ptr(),
+ &file_actions,
+ &attr,
+ argv_ptrs.as_ptr(),
+ if envs.is_some() {
+ envp_ptrs.as_ptr()
+ } else {
+ environ
+ },
+ );
+
+ libc::posix_spawnattr_destroy(&mut attr);
+ libc::posix_spawn_file_actions_destroy(&mut file_actions);
+
+ if let Some(fd) = stdin_read {
+ libc::close(fd);
+ }
+ if let Some(fd) = stdout_write {
+ libc::close(fd);
+ }
+ if let Some(fd) = stderr_write {
+ libc::close(fd);
+ }
+
+ cvt_nz(spawn_result)?;
+
+ Ok(Child {
+ pid,
+ stdin: stdin_write.map(|fd| Unblock::new(std::fs::File::from_raw_fd(fd))),
+ stdout: stdout_read.map(|fd| Unblock::new(std::fs::File::from_raw_fd(fd))),
+ stderr: stderr_read.map(|fd| Unblock::new(std::fs::File::from_raw_fd(fd))),
+ kill_on_drop,
+ status: None,
+ })
+ }
+}
+
+fn create_pipe() -> io::Result<(libc::c_int, libc::c_int)> {
+ let mut fds: [libc::c_int; 2] = [0; 2];
+ let result = unsafe { libc::pipe(fds.as_mut_ptr()) };
+ if result == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok((fds[0], fds[1]))
+}
+
+fn open_dev_null(flags: libc::c_int) -> io::Result<libc::c_int> {
+ let fd = unsafe { libc::open(c"/dev/null".as_ptr() as *const libc::c_char, flags) };
+ if fd == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(fd)
+}
+
+/// Zero means `Ok()`, all other values are treated as raw OS errors. Does not look at `errno`.
+/// Mirrored after Rust's std `cvt_nz` function.
+fn cvt_nz(error: libc::c_int) -> io::Result<()> {
+ if error == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::from_raw_os_error(error))
+ }
+}
+
+fn invalid_input_error() -> io::Error {
+ io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "invalid argument: path or argument contains null byte",
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use futures_lite::AsyncWriteExt;
+
+ #[test]
+ fn test_spawn_echo() {
+ smol::block_on(async {
+ let output = Command::new("/bin/echo")
+ .args(["-n", "hello world"])
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(output.status.success());
+ assert_eq!(output.stdout, b"hello world");
+ });
+ }
+
+ #[test]
+ fn test_spawn_cat_stdin() {
+ smol::block_on(async {
+ let mut child = Command::new("/bin/cat")
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()
+ .expect("failed to spawn");
+
+ if let Some(ref mut stdin) = child.stdin {
+ stdin
+ .write_all(b"hello from stdin")
+ .await
+ .expect("failed to write");
+ stdin.close().await.expect("failed to close");
+ }
+ drop(child.stdin.take());
+
+ let output = child.output().await.expect("failed to get output");
+ assert!(output.status.success());
+ assert_eq!(output.stdout, b"hello from stdin");
+ });
+ }
+
+ #[test]
+ fn test_spawn_stderr() {
+ smol::block_on(async {
+ let output = Command::new("/bin/sh")
+ .args(["-c", "echo error >&2"])
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(output.status.success());
+ assert_eq!(output.stderr, b"error\n");
+ });
+ }
+
+ #[test]
+ fn test_spawn_exit_code() {
+ smol::block_on(async {
+ let output = Command::new("/bin/sh")
+ .args(["-c", "exit 42"])
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(!output.status.success());
+ assert_eq!(output.status.code(), Some(42));
+ });
+ }
+
+ #[test]
+ fn test_spawn_current_dir() {
+ smol::block_on(async {
+ let output = Command::new("/bin/pwd")
+ .current_dir("/tmp")
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(output.status.success());
+ let pwd = String::from_utf8_lossy(&output.stdout);
+ assert!(pwd.trim() == "/tmp" || pwd.trim() == "/private/tmp");
+ });
+ }
+
+ #[test]
+ fn test_spawn_env() {
+ smol::block_on(async {
+ let output = Command::new("/bin/sh")
+ .args(["-c", "echo $MY_TEST_VAR"])
+ .env("MY_TEST_VAR", "test_value")
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(output.status.success());
+ assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "test_value");
+ });
+ }
+
+ #[test]
+ fn test_spawn_status() {
+ smol::block_on(async {
+ let status = Command::new("/usr/bin/true")
+ .status()
+ .await
+ .expect("failed to run command");
+
+ assert!(status.success());
+
+ let status = Command::new("/usr/bin/false")
+ .status()
+ .await
+ .expect("failed to run command");
+
+ assert!(!status.success());
+ });
+ }
+
+ #[test]
+ fn test_env_remove_removes_set_env() {
+ smol::block_on(async {
+ let output = Command::new("/bin/sh")
+ .args(["-c", "echo ${MY_VAR:-unset}"])
+ .env("MY_VAR", "set_value")
+ .env_remove("MY_VAR")
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(output.status.success());
+ assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "unset");
+ });
+ }
+
+ #[test]
+ fn test_env_remove_removes_inherited_env() {
+ smol::block_on(async {
+ // SAFETY: This test is single-threaded and we clean up the var at the end
+ unsafe { std::env::set_var("TEST_INHERITED_VAR", "inherited_value") };
+
+ let output = Command::new("/bin/sh")
+ .args(["-c", "echo ${TEST_INHERITED_VAR:-unset}"])
+ .env_remove("TEST_INHERITED_VAR")
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(output.status.success());
+ assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "unset");
+
+ // SAFETY: Cleaning up test env var
+ unsafe { std::env::remove_var("TEST_INHERITED_VAR") };
+ });
+ }
+
+ #[test]
+ fn test_env_after_env_remove() {
+ smol::block_on(async {
+ let output = Command::new("/bin/sh")
+ .args(["-c", "echo ${MY_VAR:-unset}"])
+ .env_remove("MY_VAR")
+ .env("MY_VAR", "new_value")
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(output.status.success());
+ assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "new_value");
+ });
+ }
+
+ #[test]
+ fn test_env_remove_after_env_clear() {
+ smol::block_on(async {
+ let output = Command::new("/bin/sh")
+ .args(["-c", "echo ${MY_VAR:-unset}"])
+ .env_clear()
+ .env("MY_VAR", "set_value")
+ .env_remove("MY_VAR")
+ .output()
+ .await
+ .expect("failed to run command");
+
+ assert!(output.status.success());
+ assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "unset");
+ });
+ }
+
+ #[test]
+ fn test_stdio_null_stdin() {
+ smol::block_on(async {
+ let child = Command::new("/bin/cat")
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .spawn()
+ .expect("failed to spawn");
+
+ let output = child.output().await.expect("failed to get output");
+ assert!(output.status.success());
+ assert!(
+ output.stdout.is_empty(),
+ "stdin from /dev/null should produce no output from cat"
+ );
+ });
+ }
+
+ #[test]
+ fn test_stdio_null_stdout() {
+ smol::block_on(async {
+ let mut child = Command::new("/bin/echo")
+ .args(["hello"])
+ .stdout(Stdio::null())
+ .spawn()
+ .expect("failed to spawn");
+
+ assert!(
+ child.stdout.is_none(),
+ "stdout should be None when Stdio::null() is used"
+ );
+
+ let status = child.status().await.expect("failed to get status");
+ assert!(status.success());
+ });
+ }
+
+ #[test]
+ fn test_stdio_null_stderr() {
+ smol::block_on(async {
+ let mut child = Command::new("/bin/sh")
+ .args(["-c", "echo error >&2"])
+ .stderr(Stdio::null())
+ .spawn()
+ .expect("failed to spawn");
+
+ assert!(
+ child.stderr.is_none(),
+ "stderr should be None when Stdio::null() is used"
+ );
+
+ let status = child.status().await.expect("failed to get status");
+ assert!(status.success());
+ });
+ }
+
+ #[test]
+ fn test_stdio_piped_stdin() {
+ smol::block_on(async {
+ let mut child = Command::new("/bin/cat")
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()
+ .expect("failed to spawn");
+
+ assert!(
+ child.stdin.is_some(),
+ "stdin should be Some when Stdio::piped() is used"
+ );
+
+ if let Some(ref mut stdin) = child.stdin {
+ stdin
+ .write_all(b"piped input")
+ .await
+ .expect("failed to write");
+ stdin.close().await.expect("failed to close");
+ }
+ drop(child.stdin.take());
+
+ let output = child.output().await.expect("failed to get output");
+ assert!(output.status.success());
+ assert_eq!(output.stdout, b"piped input");
+ });
+ }
+}
@@ -141,7 +141,7 @@ async fn capture_windows(
std::env::current_exe().context("Failed to determine current zed executable path.")?;
let shell_kind = ShellKind::new(shell_path, true);
- let mut cmd = crate::command::new_smol_command(shell_path);
+ let mut cmd = crate::command::new_command(shell_path);
cmd.args(args);
let cmd = match shell_kind {
ShellKind::Csh
@@ -409,8 +409,6 @@ pub fn set_pre_exec_to_start_new_session(
use std::os::unix::process::CommandExt;
command.pre_exec(|| {
libc::setsid();
- #[cfg(target_os = "macos")]
- crate::command::reset_exception_ports();
Ok(())
});
};