#![allow( clippy::disallowed_methods, reason = "We are not in an async environment, so std::process::Command is fine")]#![cfg_attr( any(target_os = "linux", target_os = "freebsd", target_os = "windows"), allow(dead_code))] use anyhow::{Context as _, Result};use clap::Parser;use cli::{CliRequest, CliResponse, IpcHandshake, ipc::IpcOneShotServer};use parking_lot::Mutex;use std::{ env, fs, io, path::{Path, PathBuf}, process::ExitStatus, sync::Arc, thread::{self, JoinHandle},};use tempfile::NamedTempFile;use util::paths::PathWithPosition; #[cfg(any(target_os = "linux", target_os = "freebsd"))]use std::io::IsTerminal; const URL_PREFIX: [&'static str; 5] = ["zed://", "http://", "https://", "file://", "ssh://"]; struct Detect; trait InstalledApp { fn zed_version_string(&self) -> String; fn launch(&self, ipc_url: String) -> anyhow::Result<()>; fn run_foreground( &self, ipc_url: String, user_data_dir: Option<&str>, ) -> io::Result<ExitStatus>; fn path(&self) -> PathBuf;} #[derive(Parser, Debug)]#[command( name = "zed", disable_version_flag = true, before_help = "The Zed CLI binary.This CLI is a separate binary that invokes Zed. Examples: `zed` Simply opens Zed `zed --foreground` Runs in foreground (shows all logs) `zed path-to-your-project` Open your project in Zed `zed -n path-to-file ` Open file/folder in a new window", after_help = "To read from stdin, append '-', e.g. 'ps axf | zed -'")]struct Args { /// Wait for all of the given paths to be opened/closed before exiting. #[arg(short, long)] wait: bool, /// Add files to the currently open workspace #[arg(short, long, overrides_with_all = ["new", "reuse"])] add: bool, /// Create a new workspace #[arg(short, long, overrides_with_all = ["add", "reuse"])] new: bool, /// Reuse an existing window, replacing its workspace #[arg(short, long, overrides_with_all = ["add", "new"])] reuse: bool, /// Sets a custom directory for all user data (e.g., database, extensions, logs). /// This overrides the default platform-specific data directory location: #[cfg_attr(target_os = "macos", doc = "`~/Library/Application Support/Zed`.")] #[cfg_attr(target_os = "windows", doc = "`%LOCALAPPDATA%\\Zed`.")] #[cfg_attr( not(any(target_os = "windows", target_os = "macos")), doc = "`$XDG_DATA_HOME/zed`." )] #[arg(long, value_name = "DIR")] user_data_dir: Option<String>, /// The paths to open in Zed (space-separated). /// /// Use `path:line:column` syntax to open a file at the given line and column. paths_with_position: Vec<String>, /// Print Zed's version and the app path. #[arg(short, long)] version: bool, /// Run zed in the foreground (useful for debugging) #[arg(long)] foreground: bool, /// Custom path to Zed.app or the zed binary #[arg(long)] zed: Option<PathBuf>, /// Run zed in dev-server mode #[arg(long)] dev_server_token: Option<String>, /// The username and WSL distribution to use when opening paths. If not specified, /// Zed will attempt to open the paths directly. /// /// The username is optional, and if not specified, the default user for the distribution /// will be used. /// /// Example: `me@Ubuntu` or `Ubuntu`. /// /// WARN: You should not fill in this field by hand. #[cfg(target_os = "windows")] #[arg(long, value_name = "USER@DISTRO")] wsl: Option<String>, /// Not supported in Zed CLI, only supported on Zed binary /// Will attempt to give the correct command to run #[arg(long)] system_specs: bool, /// Pairs of file paths to diff. Can be specified multiple times. #[arg(long, action = clap::ArgAction::Append, num_args = 2, value_names = ["OLD_PATH", "NEW_PATH"])] diff: Vec<String>, /// Uninstall Zed from user system #[cfg(all( any(target_os = "linux", target_os = "macos"), not(feature = "no-bundled-uninstall") ))] #[arg(long)] uninstall: bool, /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency, /// by having Zed act like netcat communicating over a Unix socket. #[arg(long, hide = true)] askpass: Option<String>,} fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> { let canonicalized = match Path::new(argument_str).canonicalize() { Ok(existing_path) => PathWithPosition::from_path(existing_path), Err(_) => { let path = PathWithPosition::parse_str(argument_str); let curdir = env::current_dir().context("retrieving current directory")?; path.map_path(|path| match fs::canonicalize(&path) { Ok(path) => Ok(path), Err(e) => { if let Some(mut parent) = path.parent() { if parent == Path::new("") { parent = &curdir } match fs::canonicalize(parent) { Ok(parent) => Ok(parent.join(path.file_name().unwrap())), Err(_) => Err(e), } } else { Err(e) } } }) } .with_context(|| format!("parsing as path with position {argument_str}"))?, }; Ok(canonicalized.to_string(|path| path.to_string_lossy().into_owned()))} fn parse_path_in_wsl(source: &str, wsl: &str) -> Result<String> { let mut source = PathWithPosition::parse_str(source); let mut command = util::command::new_std_command("wsl.exe"); let (user, distro_name) = if let Some((user, distro)) = wsl.split_once('@') { if user.is_empty() { anyhow::bail!("user is empty in wsl argument"); } (Some(user), distro) } else { (None, wsl) }; if let Some(user) = user { command.arg("--user").arg(user); } let output = command .arg("--distribution") .arg(distro_name) .arg("--exec") .arg("wslpath") .arg("-m") .arg(&source.path) .output()?; let result = String::from_utf8_lossy(&output.stdout); let prefix = format!("//wsl.localhost/{}", distro_name); source.path = Path::new(result.trim().strip_prefix(&prefix).unwrap_or(&result)).to_owned(); Ok(source.to_string(|path| path.to_string_lossy().into_owned()))} fn main() -> Result<()> { #[cfg(unix)] util::prevent_root_execution(); // Exit flatpak sandbox if needed #[cfg(target_os = "linux")] { flatpak::try_restart_to_host(); flatpak::ld_extra_libs(); } // Intercept version designators #[cfg(target_os = "macos")] if let Some(channel) = std::env::args().nth(1).filter(|arg| arg.starts_with("--")) { // When the first argument is a name of a release channel, we're going to spawn off the CLI of that version, with trailing args passed along. use std::str::FromStr as _; if let Ok(channel) = release_channel::ReleaseChannel::from_str(&channel[2..]) { return mac_os::spawn_channel_cli(channel, std::env::args().skip(2).collect()); } } let args = Args::parse(); // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass if let Some(socket) = &args.askpass { askpass::main(socket); return Ok(()); } // Set custom data directory before any path operations let user_data_dir = args.user_data_dir.clone(); if let Some(dir) = &user_data_dir { paths::set_custom_data_dir(dir); } #[cfg(target_os = "linux")] let args = flatpak::set_bin_if_no_escape(args); let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?; if args.version { println!("{}", app.zed_version_string()); return Ok(()); } if args.system_specs { let path = app.path(); let msg = [ "The `--system-specs` argument is not supported in the Zed CLI, only on Zed binary.", "To retrieve the system specs on the command line, run the following command:", &format!("{} --system-specs", path.display()), ]; anyhow::bail!(msg.join("\n")); } #[cfg(all( any(target_os = "linux", target_os = "macos"), not(feature = "no-bundled-uninstall") ))] if args.uninstall { static UNINSTALL_SCRIPT: &[u8] = include_bytes!("../../../script/uninstall.sh"); let tmp_dir = tempfile::tempdir()?; let script_path = tmp_dir.path().join("uninstall.sh"); fs::write(&script_path, UNINSTALL_SCRIPT)?; use std::os::unix::fs::PermissionsExt as _; fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?; let status = std::process::Command::new("sh") .arg(&script_path) .env("ZED_CHANNEL", &*release_channel::RELEASE_CHANNEL_NAME) .status() .context("Failed to execute uninstall script")?; std::process::exit(status.code().unwrap_or(1)); } let (server, server_name) = IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?; let url = format!("zed-cli://{server_name}"); let open_new_workspace = if args.new { Some(true) } else if args.add { Some(false) } else { None }; let env = { #[cfg(any(target_os = "linux", target_os = "freebsd"))] { use collections::HashMap; // On Linux, the desktop entry uses `cli` to spawn `zed`. // We need to handle env vars correctly since std::env::vars() may not contain // project-specific vars (e.g. those set by direnv). // By setting env to None here, the LSP will use worktree env vars instead, // which is what we want. if !std::io::stdout().is_terminal() { None } else { Some(std::env::vars().collect::<HashMap<_, _>>()) } } #[cfg(target_os = "windows")] { // On Windows, by default, a child process inherits a copy of the environment block of the parent process. // So we don't need to pass env vars explicitly. None } #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))] { use collections::HashMap; Some(std::env::vars().collect::<HashMap<_, _>>()) } }; let exit_status = Arc::new(Mutex::new(None)); let mut paths = vec![]; let mut urls = vec![]; let mut diff_paths = vec![]; let mut stdin_tmp_file: Option<fs::File> = None; let mut anonymous_fd_tmp_files = vec![]; for path in args.diff.chunks(2) { diff_paths.push([ parse_path_with_position(&path[0])?, parse_path_with_position(&path[1])?, ]); } #[cfg(target_os = "windows")] let wsl = args.wsl.as_ref(); #[cfg(not(target_os = "windows"))] let wsl = None; for path in args.paths_with_position.iter() { if URL_PREFIX.iter().any(|&prefix| path.starts_with(prefix)) { urls.push(path.to_string()); } else if path == "-" && args.paths_with_position.len() == 1 { let file = NamedTempFile::new()?; paths.push(file.path().to_string_lossy().into_owned()); let (file, _) = file.keep()?; stdin_tmp_file = Some(file); } else if let Some(file) = anonymous_fd(path) { let tmp_file = NamedTempFile::new()?; paths.push(tmp_file.path().to_string_lossy().into_owned()); let (tmp_file, _) = tmp_file.keep()?; anonymous_fd_tmp_files.push((file, tmp_file)); } else if let Some(wsl) = wsl { urls.push(format!("file://{}", parse_path_in_wsl(path, wsl)?)); } else { paths.push(parse_path_with_position(path)?); } } anyhow::ensure!( args.dev_server_token.is_none(), "Dev servers were removed in v0.157.x please upgrade to SSH remoting: https://zed.dev/docs/remote-development" ); rayon::ThreadPoolBuilder::new() .num_threads(4) .stack_size(10 * 1024 * 1024) .thread_name(|ix| format!("RayonWorker{}", ix)) .build_global() .unwrap(); let sender: JoinHandle<anyhow::Result<()>> = thread::Builder::new() .name("CliReceiver".to_string()) .spawn({ let exit_status = exit_status.clone(); let user_data_dir_for_thread = user_data_dir.clone(); move || { let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; let (tx, rx) = (handshake.requests, handshake.responses); #[cfg(target_os = "windows")] let wsl = args.wsl; #[cfg(not(target_os = "windows"))] let wsl = None; tx.send(CliRequest::Open { paths, urls, diff_paths, wsl, wait: args.wait, open_new_workspace, reuse: args.reuse, env, user_data_dir: user_data_dir_for_thread, })?; while let Ok(response) = rx.recv() { match response { CliResponse::Ping => {} CliResponse::Stdout { message } => println!("{message}"), CliResponse::Stderr { message } => eprintln!("{message}"), CliResponse::Exit { status } => { exit_status.lock().replace(status); return Ok(()); } } } Ok(()) } }) .unwrap(); let stdin_pipe_handle: Option<JoinHandle<anyhow::Result<()>>> = stdin_tmp_file.map(|mut tmp_file| { thread::Builder::new() .name("CliStdin".to_string()) .spawn(move || { let mut stdin = std::io::stdin().lock(); if !io::IsTerminal::is_terminal(&stdin) { io::copy(&mut stdin, &mut tmp_file)?; } Ok(()) }) .unwrap() }); let anonymous_fd_pipe_handles: Vec<_> = anonymous_fd_tmp_files .into_iter() .map(|(mut file, mut tmp_file)| { thread::Builder::new() .name("CliAnonymousFd".to_string()) .spawn(move || io::copy(&mut file, &mut tmp_file)) .unwrap() }) .collect(); if args.foreground { app.run_foreground(url, user_data_dir.as_deref())?; } else { app.launch(url)?; sender.join().unwrap()?; if let Some(handle) = stdin_pipe_handle { handle.join().unwrap()?; } for handle in anonymous_fd_pipe_handles { handle.join().unwrap()?; } } if let Some(exit_status) = exit_status.lock().take() { std::process::exit(exit_status); } Ok(())} fn anonymous_fd(path: &str) -> Option<fs::File> { #[cfg(target_os = "linux")] { use std::os::fd::{self, FromRawFd}; let fd_str = path.strip_prefix("/proc/self/fd/")?; let link = fs::read_link(path).ok()?; if !link.starts_with("memfd:") { return None; } let fd: fd::RawFd = fd_str.parse().ok()?; let file = unsafe { fs::File::from_raw_fd(fd) }; Some(file) } #[cfg(any(target_os = "macos", target_os = "freebsd"))] { use std::os::{ fd::{self, FromRawFd}, unix::fs::FileTypeExt, }; let fd_str = path.strip_prefix("/dev/fd/")?; let metadata = fs::metadata(path).ok()?; let file_type = metadata.file_type(); if !file_type.is_fifo() && !file_type.is_socket() { return None; } let fd: fd::RawFd = fd_str.parse().ok()?; let file = unsafe { fs::File::from_raw_fd(fd) }; Some(file) } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))] { _ = path; // not implemented for bsd, windows. Could be, but isn't yet None }} #[cfg(any(target_os = "linux", target_os = "freebsd"))]mod linux { use std::{ env, ffi::OsString, io, os::unix::net::{SocketAddr, UnixDatagram},Lines 1–500 of 1076. Load more
#![allow( clippy::disallowed_methods, reason = "We are not in an async environment, so std::process::Command is fine" )] #![cfg_attr( any(target_os = "linux", target_os = "freebsd", target_os = "windows"), allow(dead_code) )] use anyhow::{Context as _, Result}; use clap::Parser; use cli::{CliRequest, CliResponse, IpcHandshake, ipc::IpcOneShotServer}; use parking_lot::Mutex; use std::{ env, fs, io, path::{Path, PathBuf}, process::ExitStatus, sync::Arc, thread::{self, JoinHandle}, }; use tempfile::NamedTempFile; use util::paths::PathWithPosition; #[cfg(any(target_os = "linux", target_os = "freebsd"))] use std::io::IsTerminal; const URL_PREFIX: [&'static str; 5] = ["zed://", "http://", "https://", "file://", "ssh://"]; struct Detect; trait InstalledApp { fn zed_version_string(&self) -> String; fn launch(&self, ipc_url: String) -> anyhow::Result<()>; fn run_foreground( &self, ipc_url: String, user_data_dir: Option<&str>, ) -> io::Result<ExitStatus>; fn path(&self) -> PathBuf; } #[derive(Parser, Debug)] #[command( name = "zed", disable_version_flag = true, before_help = "The Zed CLI binary. This CLI is a separate binary that invokes Zed. Examples: `zed` Simply opens Zed `zed --foreground` Runs in foreground (shows all logs) `zed path-to-your-project` Open your project in Zed `zed -n path-to-file ` Open file/folder in a new window", after_help = "To read from stdin, append '-', e.g. 'ps axf | zed -'" )] struct Args { /// Wait for all of the given paths to be opened/closed before exiting. #[arg(short, long)] wait: bool, /// Add files to the currently open workspace #[arg(short, long, overrides_with_all = ["new", "reuse"])] add: bool, /// Create a new workspace #[arg(short, long, overrides_with_all = ["add", "reuse"])] new: bool, /// Reuse an existing window, replacing its workspace #[arg(short, long, overrides_with_all = ["add", "new"])] reuse: bool, /// Sets a custom directory for all user data (e.g., database, extensions, logs). /// This overrides the default platform-specific data directory location: #[cfg_attr(target_os = "macos", doc = "`~/Library/Application Support/Zed`.")] #[cfg_attr(target_os = "windows", doc = "`%LOCALAPPDATA%\\Zed`.")] #[cfg_attr( not(any(target_os = "windows", target_os = "macos")), doc = "`$XDG_DATA_HOME/zed`." )] #[arg(long, value_name = "DIR")] user_data_dir: Option<String>, /// The paths to open in Zed (space-separated). /// /// Use `path:line:column` syntax to open a file at the given line and column. paths_with_position: Vec<String>, /// Print Zed's version and the app path. #[arg(short, long)] version: bool, /// Run zed in the foreground (useful for debugging) #[arg(long)] foreground: bool, /// Custom path to Zed.app or the zed binary #[arg(long)] zed: Option<PathBuf>, /// Run zed in dev-server mode #[arg(long)] dev_server_token: Option<String>, /// The username and WSL distribution to use when opening paths. If not specified, /// Zed will attempt to open the paths directly. /// /// The username is optional, and if not specified, the default user for the distribution /// will be used. /// /// Example: `me@Ubuntu` or `Ubuntu`. /// /// WARN: You should not fill in this field by hand. #[cfg(target_os = "windows")] #[arg(long, value_name = "USER@DISTRO")] wsl: Option<String>, /// Not supported in Zed CLI, only supported on Zed binary /// Will attempt to give the correct command to run #[arg(long)] system_specs: bool, /// Pairs of file paths to diff. Can be specified multiple times. #[arg(long, action = clap::ArgAction::Append, num_args = 2, value_names = ["OLD_PATH", "NEW_PATH"])] diff: Vec<String>, /// Uninstall Zed from user system #[cfg(all( any(target_os = "linux", target_os = "macos"), not(feature = "no-bundled-uninstall") ))] #[arg(long)] uninstall: bool, /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency, /// by having Zed act like netcat communicating over a Unix socket. #[arg(long, hide = true)] askpass: Option<String>, } fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> { let canonicalized = match Path::new(argument_str).canonicalize() { Ok(existing_path) => PathWithPosition::from_path(existing_path), Err(_) => { let path = PathWithPosition::parse_str(argument_str); let curdir = env::current_dir().context("retrieving current directory")?; path.map_path(|path| match fs::canonicalize(&path) { Ok(path) => Ok(path), Err(e) => { if let Some(mut parent) = path.parent() { if parent == Path::new("") { parent = &curdir } match fs::canonicalize(parent) { Ok(parent) => Ok(parent.join(path.file_name().unwrap())), Err(_) => Err(e), } } else { Err(e) } } }) } .with_context(|| format!("parsing as path with position {argument_str}"))?, }; Ok(canonicalized.to_string(|path| path.to_string_lossy().into_owned())) } fn parse_path_in_wsl(source: &str, wsl: &str) -> Result<String> { let mut source = PathWithPosition::parse_str(source); let mut command = util::command::new_std_command("wsl.exe"); let (user, distro_name) = if let Some((user, distro)) = wsl.split_once('@') { if user.is_empty() { anyhow::bail!("user is empty in wsl argument"); } (Some(user), distro) } else { (None, wsl) }; if let Some(user) = user { command.arg("--user").arg(user); } let output = command .arg("--distribution") .arg(distro_name) .arg("--exec") .arg("wslpath") .arg("-m") .arg(&source.path) .output()?; let result = String::from_utf8_lossy(&output.stdout); let prefix = format!("//wsl.localhost/{}", distro_name); source.path = Path::new(result.trim().strip_prefix(&prefix).unwrap_or(&result)).to_owned(); Ok(source.to_string(|path| path.to_string_lossy().into_owned())) } fn main() -> Result<()> { #[cfg(unix)] util::prevent_root_execution(); // Exit flatpak sandbox if needed #[cfg(target_os = "linux")] { flatpak::try_restart_to_host(); flatpak::ld_extra_libs(); } // Intercept version designators #[cfg(target_os = "macos")] if let Some(channel) = std::env::args().nth(1).filter(|arg| arg.starts_with("--")) { // When the first argument is a name of a release channel, we're going to spawn off the CLI of that version, with trailing args passed along. use std::str::FromStr as _; if let Ok(channel) = release_channel::ReleaseChannel::from_str(&channel[2..]) { return mac_os::spawn_channel_cli(channel, std::env::args().skip(2).collect()); } } let args = Args::parse(); // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass if let Some(socket) = &args.askpass { askpass::main(socket); return Ok(()); } // Set custom data directory before any path operations let user_data_dir = args.user_data_dir.clone(); if let Some(dir) = &user_data_dir { paths::set_custom_data_dir(dir); } #[cfg(target_os = "linux")] let args = flatpak::set_bin_if_no_escape(args); let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?; if args.version { println!("{}", app.zed_version_string()); return Ok(()); } if args.system_specs { let path = app.path(); let msg = [ "The `--system-specs` argument is not supported in the Zed CLI, only on Zed binary.", "To retrieve the system specs on the command line, run the following command:", &format!("{} --system-specs", path.display()), ]; anyhow::bail!(msg.join("\n")); } #[cfg(all( any(target_os = "linux", target_os = "macos"), not(feature = "no-bundled-uninstall") ))] if args.uninstall { static UNINSTALL_SCRIPT: &[u8] = include_bytes!("../../../script/uninstall.sh"); let tmp_dir = tempfile::tempdir()?; let script_path = tmp_dir.path().join("uninstall.sh"); fs::write(&script_path, UNINSTALL_SCRIPT)?; use std::os::unix::fs::PermissionsExt as _; fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?; let status = std::process::Command::new("sh") .arg(&script_path) .env("ZED_CHANNEL", &*release_channel::RELEASE_CHANNEL_NAME) .status() .context("Failed to execute uninstall script")?; std::process::exit(status.code().unwrap_or(1)); } let (server, server_name) = IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?; let url = format!("zed-cli://{server_name}"); let open_new_workspace = if args.new { Some(true) } else if args.add { Some(false) } else { None }; let env = { #[cfg(any(target_os = "linux", target_os = "freebsd"))] { use collections::HashMap; // On Linux, the desktop entry uses `cli` to spawn `zed`. // We need to handle env vars correctly since std::env::vars() may not contain // project-specific vars (e.g. those set by direnv). // By setting env to None here, the LSP will use worktree env vars instead, // which is what we want. if !std::io::stdout().is_terminal() { None } else { Some(std::env::vars().collect::<HashMap<_, _>>()) } } #[cfg(target_os = "windows")] { // On Windows, by default, a child process inherits a copy of the environment block of the parent process. // So we don't need to pass env vars explicitly. None } #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))] { use collections::HashMap; Some(std::env::vars().collect::<HashMap<_, _>>()) } }; let exit_status = Arc::new(Mutex::new(None)); let mut paths = vec![]; let mut urls = vec![]; let mut diff_paths = vec![]; let mut stdin_tmp_file: Option<fs::File> = None; let mut anonymous_fd_tmp_files = vec![]; for path in args.diff.chunks(2) { diff_paths.push([ parse_path_with_position(&path[0])?, parse_path_with_position(&path[1])?, ]); } #[cfg(target_os = "windows")] let wsl = args.wsl.as_ref(); #[cfg(not(target_os = "windows"))] let wsl = None; for path in args.paths_with_position.iter() { if URL_PREFIX.iter().any(|&prefix| path.starts_with(prefix)) { urls.push(path.to_string()); } else if path == "-" && args.paths_with_position.len() == 1 { let file = NamedTempFile::new()?; paths.push(file.path().to_string_lossy().into_owned()); let (file, _) = file.keep()?; stdin_tmp_file = Some(file); } else if let Some(file) = anonymous_fd(path) { let tmp_file = NamedTempFile::new()?; paths.push(tmp_file.path().to_string_lossy().into_owned()); let (tmp_file, _) = tmp_file.keep()?; anonymous_fd_tmp_files.push((file, tmp_file)); } else if let Some(wsl) = wsl { urls.push(format!("file://{}", parse_path_in_wsl(path, wsl)?)); } else { paths.push(parse_path_with_position(path)?); } } anyhow::ensure!( args.dev_server_token.is_none(), "Dev servers were removed in v0.157.x please upgrade to SSH remoting: https://zed.dev/docs/remote-development" ); rayon::ThreadPoolBuilder::new() .num_threads(4) .stack_size(10 * 1024 * 1024) .thread_name(|ix| format!("RayonWorker{}", ix)) .build_global() .unwrap(); let sender: JoinHandle<anyhow::Result<()>> = thread::Builder::new() .name("CliReceiver".to_string()) .spawn({ let exit_status = exit_status.clone(); let user_data_dir_for_thread = user_data_dir.clone(); move || { let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; let (tx, rx) = (handshake.requests, handshake.responses); #[cfg(target_os = "windows")] let wsl = args.wsl; #[cfg(not(target_os = "windows"))] let wsl = None; tx.send(CliRequest::Open { paths, urls, diff_paths, wsl, wait: args.wait, open_new_workspace, reuse: args.reuse, env, user_data_dir: user_data_dir_for_thread, })?; while let Ok(response) = rx.recv() { match response { CliResponse::Ping => {} CliResponse::Stdout { message } => println!("{message}"), CliResponse::Stderr { message } => eprintln!("{message}"), CliResponse::Exit { status } => { exit_status.lock().replace(status); return Ok(()); } } } Ok(()) } }) .unwrap(); let stdin_pipe_handle: Option<JoinHandle<anyhow::Result<()>>> = stdin_tmp_file.map(|mut tmp_file| { thread::Builder::new() .name("CliStdin".to_string()) .spawn(move || { let mut stdin = std::io::stdin().lock(); if !io::IsTerminal::is_terminal(&stdin) { io::copy(&mut stdin, &mut tmp_file)?; } Ok(()) }) .unwrap() }); let anonymous_fd_pipe_handles: Vec<_> = anonymous_fd_tmp_files .into_iter() .map(|(mut file, mut tmp_file)| { thread::Builder::new() .name("CliAnonymousFd".to_string()) .spawn(move || io::copy(&mut file, &mut tmp_file)) .unwrap() }) .collect(); if args.foreground { app.run_foreground(url, user_data_dir.as_deref())?; } else { app.launch(url)?; sender.join().unwrap()?; if let Some(handle) = stdin_pipe_handle { handle.join().unwrap()?; } for handle in anonymous_fd_pipe_handles { handle.join().unwrap()?; } } if let Some(exit_status) = exit_status.lock().take() { std::process::exit(exit_status); } Ok(()) } fn anonymous_fd(path: &str) -> Option<fs::File> { #[cfg(target_os = "linux")] { use std::os::fd::{self, FromRawFd}; let fd_str = path.strip_prefix("/proc/self/fd/")?; let link = fs::read_link(path).ok()?; if !link.starts_with("memfd:") { return None; } let fd: fd::RawFd = fd_str.parse().ok()?; let file = unsafe { fs::File::from_raw_fd(fd) }; Some(file) } #[cfg(any(target_os = "macos", target_os = "freebsd"))] { use std::os::{ fd::{self, FromRawFd}, unix::fs::FileTypeExt, }; let fd_str = path.strip_prefix("/dev/fd/")?; let metadata = fs::metadata(path).ok()?; let file_type = metadata.file_type(); if !file_type.is_fifo() && !file_type.is_socket() { return None; } let fd: fd::RawFd = fd_str.parse().ok()?; let file = unsafe { fs::File::from_raw_fd(fd) }; Some(file) } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))] { _ = path; // not implemented for bsd, windows. Could be, but isn't yet None } } #[cfg(any(target_os = "linux", target_os = "freebsd"))] mod linux { use std::{ env, ffi::OsString, io, os::unix::net::{SocketAddr, UnixDatagram},