pub mod arc_cow;pub mod archive;pub mod command;pub mod fs;pub mod markdown;pub mod paths;pub mod process;pub mod redact;pub mod rel_path;pub mod schemars;pub mod serde;pub mod shell;pub mod shell_builder;pub mod shell_env;pub mod size;#[cfg(any(test, feature = "test-support"))]pub mod test;pub mod time; use anyhow::{Context as _, Result};use futures::Future;use itertools::Either;use paths::PathExt;use regex::Regex;use std::path::PathBuf;use std::sync::{LazyLock, OnceLock};use std::{ borrow::Cow, cmp::{self, Ordering}, env, ops::{AddAssign, Range, RangeInclusive}, panic::Location, pin::Pin, task::{Context, Poll}, time::Instant,};use unicase::UniCase; pub use take_until::*;#[cfg(any(test, feature = "test-support"))]pub use util_macros::{line_endings, path, uri}; #[macro_export]macro_rules! debug_panic { ( $($fmt_arg:tt)* ) => { if cfg!(debug_assertions) { panic!( $($fmt_arg)* ); } else { let backtrace = std::backtrace::Backtrace::capture(); log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace); } };} #[inline]pub const fn is_utf8_char_boundary(u8: u8) -> bool { // This is bit magic equivalent to: b < 128 || b >= 192 (u8 as i8) >= -0x40} pub fn truncate(s: &str, max_chars: usize) -> &str { match s.char_indices().nth(max_chars) { None => s, Some((idx, _)) => &s[..idx], }} /// Removes characters from the end of the string if its length is greater than `max_chars` and/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars.pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); // If the string's byte length is <= max_chars, walking the string can be skipped since the // number of chars is <= the number of bytes. if s.len() <= max_chars { return s.to_string(); } let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars); match truncation_ix { Some(index) => s[..index].to_string() + "…", _ => s.to_string(), }} /// Removes characters from the front of the string if its length is greater than `max_chars` and/// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars.pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); // If the string's byte length is <= max_chars, walking the string can be skipped since the // number of chars is <= the number of bytes. if s.len() <= max_chars { return s.to_string(); } let suffix_char_length = max_chars.saturating_sub(1); let truncation_ix = s .char_indices() .map(|(i, _)| i) .nth_back(suffix_char_length); match truncation_ix { Some(index) if index > 0 => "…".to_string() + &s[index..], _ => s.to_string(), }} /// Takes only `max_lines` from the string and, if there were more than `max_lines-1`, appends a/// a newline and "..." to the string, so that `max_lines` are returned./// Returns string unchanged if its length is smaller than max_lines.pub fn truncate_lines_and_trailoff(s: &str, max_lines: usize) -> String { let mut lines = s.lines().take(max_lines).collect::<Vec<_>>(); if lines.len() > max_lines - 1 { lines.pop(); lines.join("\n") + "\n…" } else { lines.join("\n") }} /// Truncates the string at a character boundary, such that the result is less than `max_bytes` in/// length.pub fn truncate_to_byte_limit(s: &str, max_bytes: usize) -> &str { if s.len() < max_bytes { return s; } for i in (0..max_bytes).rev() { if s.is_char_boundary(i) { return &s[..i]; } } ""} /// Takes a prefix of complete lines which fit within the byte limit. If the first line is longer/// than the limit, truncates at a character boundary.pub fn truncate_lines_to_byte_limit(s: &str, max_bytes: usize) -> &str { if s.len() < max_bytes { return s; } for i in (0..max_bytes).rev() { if s.is_char_boundary(i) && s.as_bytes()[i] == b'\n' { // Since the i-th character is \n, valid to slice at i + 1. return &s[..i + 1]; } } truncate_to_byte_limit(s, max_bytes)} #[test]fn test_truncate_lines_to_byte_limit() { let text = "Line 1\nLine 2\nLine 3\nLine 4"; // Limit that includes all lines assert_eq!(truncate_lines_to_byte_limit(text, 100), text); // Exactly the first line assert_eq!(truncate_lines_to_byte_limit(text, 7), "Line 1\n"); // Limit between lines assert_eq!(truncate_lines_to_byte_limit(text, 13), "Line 1\n"); assert_eq!(truncate_lines_to_byte_limit(text, 20), "Line 1\nLine 2\n"); // Limit before first newline assert_eq!(truncate_lines_to_byte_limit(text, 6), "Line "); // Test with non-ASCII characters let text_utf8 = "Line 1\nLíne 2\nLine 3"; assert_eq!( truncate_lines_to_byte_limit(text_utf8, 15), "Line 1\nLíne 2\n" );} pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T { let prev = *value; *value += T::from(1); prev} /// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and/// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this,/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator.pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F)where I: IntoIterator<Item = T>, F: FnMut(&T, &T) -> Ordering,{ let mut start_index = 0; for new_item in new_items { if let Err(i) = vec[start_index..].binary_search_by(|m| cmp(m, &new_item)) { let index = start_index + i; if vec.len() < limit { vec.insert(index, new_item); } else if index < vec.len() { vec.pop(); vec.insert(index, new_item); } start_index = index; } }} pub fn truncate_to_bottom_n_sorted_by<T, F>(items: &mut Vec<T>, limit: usize, compare: &F)where F: Fn(&T, &T) -> Ordering,{ if limit == 0 { items.truncate(0); } if items.len() <= limit { items.sort_by(compare); return; } // When limit is near to items.len() it may be more efficient to sort the whole list and // truncate, rather than always doing selection first as is done below. It's hard to analyze // where the threshold for this should be since the quickselect style algorithm used by // `select_nth_unstable_by` makes the prefix partially sorted, and so its work is not wasted - // the expected number of comparisons needed by `sort_by` is less than it is for some arbitrary // unsorted input. items.select_nth_unstable_by(limit, compare); items.truncate(limit); items.sort_by(compare);} /// Prevents execution of the application with root privileges on Unix systems.////// This function checks if the current process is running with root privileges/// and terminates the program with an error message unless explicitly allowed via the/// `ZED_ALLOW_ROOT` environment variable.#[cfg(unix)]pub fn prevent_root_execution() { let is_root = nix::unistd::geteuid().is_root(); let allow_root = std::env::var("ZED_ALLOW_ROOT").is_ok_and(|val| val == "true"); if is_root && !allow_root { eprintln!( "\Error: Running Zed as root or via sudo is unsupported. Doing so (even once) may subtly break things for all subsequent non-root usage of Zed. It is untested and not recommended, don't complain when things break. If you wish to proceed anyways, set `ZED_ALLOW_ROOT=true` in your environment." ); std::process::exit(1); }} #[cfg(unix)]fn load_shell_from_passwd() -> Result<()> { let buflen = match unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) } { n if n < 0 => 1024, n => n as usize, }; let mut buffer = Vec::with_capacity(buflen); let mut pwd: std::mem::MaybeUninit<libc::passwd> = std::mem::MaybeUninit::uninit(); let mut result: *mut libc::passwd = std::ptr::null_mut(); let uid = unsafe { libc::getuid() }; let status = unsafe { libc::getpwuid_r( uid, pwd.as_mut_ptr(), buffer.as_mut_ptr() as *mut libc::c_char, buflen, &mut result, ) }; anyhow::ensure!(!result.is_null(), "passwd entry for uid {} not found", uid); // SAFETY: If `getpwuid_r` doesn't error, we have the entry here. let entry = unsafe { pwd.assume_init() }; anyhow::ensure!( status == 0, "call to getpwuid_r failed. uid: {}, status: {}", uid, status ); anyhow::ensure!( entry.pw_uid == uid, "passwd entry has different uid ({}) than getuid ({}) returned", entry.pw_uid, uid, ); let shell = unsafe { std::ffi::CStr::from_ptr(entry.pw_shell).to_str().unwrap() }; let should_set_shell = env::var("SHELL").map_or(true, |shell_env| { shell_env != shell && !std::path::Path::new(&shell_env).exists() }); if should_set_shell { log::info!( "updating SHELL environment variable to value from passwd entry: {:?}", shell, ); unsafe { env::set_var("SHELL", shell) }; } Ok(())} /// Returns a shell escaped path for the current zed executablepub fn get_shell_safe_zed_path(shell_kind: shell::ShellKind) -> anyhow::Result<String> { let zed_path = std::env::current_exe().context("Failed to determine current zed executable path.")?; zed_path .try_shell_safe(shell_kind) .context("Failed to shell-escape Zed executable path.")} /// Returns a path for the zed cli executable, this function/// should be called from the zed executable, not zed-cli.pub fn get_zed_cli_path() -> Result<PathBuf> { let zed_path = std::env::current_exe().context("Failed to determine current zed executable path.")?; let parent = zed_path .parent() .context("Failed to determine parent directory of zed executable path.")?; let possible_locations: &[&str] = if cfg!(target_os = "macos") { // On macOS, the zed executable and zed-cli are inside the app bundle, // so here ./cli is for both installed and development builds. &["./cli"] } else if cfg!(target_os = "windows") { // bin/zed.exe is for installed builds, ./cli.exe is for development builds. &["bin/zed.exe", "./cli.exe"] } else if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") { // bin is the standard, ./cli is for the target directory in development builds. &["../bin/zed", "./cli"] } else { anyhow::bail!("unsupported platform for determining zed-cli path"); }; possible_locations .iter() .find_map(|p| { parent .join(p) .canonicalize() .ok() .filter(|p| p != &zed_path) }) .with_context(|| { format!( "could not find zed-cli from any of: {}", possible_locations.join(", ") ) })} #[cfg(unix)]pub async fn load_login_shell_environment() -> Result<()> { load_shell_from_passwd().log_err(); // If possible, we want to `cd` in the user's `$HOME` to trigger programs // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook // into shell's `cd` command (and hooks) to manipulate env. // We do this so that we get the env a user would have when spawning a shell // in home directory. for (name, value) in shell_env::capture(get_system_shell(), &[], paths::home_dir()) .await .with_context(|| format!("capturing environment with {:?}", get_system_shell()))? { unsafe { env::set_var(&name, &value) }; } log::info!( "set environment variables from shell:{}, path:{}", std::env::var("SHELL").unwrap_or_default(), std::env::var("PATH").unwrap_or_default(), ); Ok(())} /// Configures the process to start a new session, to prevent interactive shells from taking control/// of the terminal.////// For more details: <https://registerspill.thorstenball.com/p/how-to-lose-control-of-your-shell>pub fn set_pre_exec_to_start_new_session( command: &mut std::process::Command,) -> &mut std::process::Command { // safety: code in pre_exec should be signal safe. // https://man7.org/linux/man-pages/man7/signal-safety.7.html #[cfg(not(target_os = "windows"))] unsafe { use std::os::unix::process::CommandExt; command.pre_exec(|| { libc::setsid(); #[cfg(target_os = "macos")] crate::command::reset_exception_ports(); Ok(()) }); }; command} pub fn merge_json_lenient_value_into( source: serde_json_lenient::Value, target: &mut serde_json_lenient::Value,) { match (source, target) { (serde_json_lenient::Value::Object(source), serde_json_lenient::Value::Object(target)) => { for (key, value) in source { if let Some(target) = target.get_mut(&key) { merge_json_lenient_value_into(value, target); } else { target.insert(key, value); } } } (serde_json_lenient::Value::Array(source), serde_json_lenient::Value::Array(target)) => { for value in source { target.push(value); } } (source, target) => *target = source, }} pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) { use serde_json::Value; match (source, target) { (Value::Object(source), Value::Object(target)) => { for (key, value) in source { if let Some(target) = target.get_mut(&key) { merge_json_value_into(value, target); } else { target.insert(key, value); } } } (Value::Array(source), Value::Array(target)) => { for value in source { target.push(value); } } (source, target) => *target = source, }} pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) { use serde_json::Value; if let Value::Object(source_object) = source { let target_object = if let Value::Object(target) = target { target } else { *target = Value::Object(Default::default()); target.as_object_mut().unwrap() }; for (key, value) in source_object { if let Some(target) = target_object.get_mut(&key) { merge_non_null_json_value_into(value, target); } else if !value.is_null() { target_object.insert(key, value); } } } else if !source.is_null() { *target = source }} pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R { static ZED_MEASUREMENTS: OnceLock<bool> = OnceLock::new(); let zed_measurements = ZED_MEASUREMENTS.get_or_init(|| { env::var("ZED_MEASUREMENTS") .map(|measurements| measurements == "1" || measurements == "true") .unwrap_or(false) }); if *zed_measurements { let start = Instant::now(); let result = f(); let elapsed = start.elapsed(); eprintln!("{}: {:?}", label, elapsed); result } else { f() }} pub fn expanded_and_wrapped_usize_range( range: Range<usize>, additional_before: usize, additional_after: usize, wrap_length: usize,) -> impl Iterator<Item = usize> { let start_wraps = range.start < additional_before; let end_wraps = wrap_length < range.end + additional_after; if start_wraps && end_wraps { Either::Left(0..wrap_length)Lines 1–500 of 1327. Load more
pub mod arc_cow; pub mod archive; pub mod command; pub mod fs; pub mod markdown; pub mod paths; pub mod process; pub mod redact; pub mod rel_path; pub mod schemars; pub mod serde; pub mod shell; pub mod shell_builder; pub mod shell_env; pub mod size; #[cfg(any(test, feature = "test-support"))] pub mod test; pub mod time; use anyhow::{Context as _, Result}; use futures::Future; use itertools::Either; use paths::PathExt; use regex::Regex; use std::path::PathBuf; use std::sync::{LazyLock, OnceLock}; use std::{ borrow::Cow, cmp::{self, Ordering}, env, ops::{AddAssign, Range, RangeInclusive}, panic::Location, pin::Pin, task::{Context, Poll}, time::Instant, }; use unicase::UniCase; pub use take_until::*; #[cfg(any(test, feature = "test-support"))] pub use util_macros::{line_endings, path, uri}; #[macro_export] macro_rules! debug_panic { ( $($fmt_arg:tt)* ) => { if cfg!(debug_assertions) { panic!( $($fmt_arg)* ); } else { let backtrace = std::backtrace::Backtrace::capture(); log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace); } }; } #[inline] pub const fn is_utf8_char_boundary(u8: u8) -> bool { // This is bit magic equivalent to: b < 128 || b >= 192 (u8 as i8) >= -0x40 } pub fn truncate(s: &str, max_chars: usize) -> &str { match s.char_indices().nth(max_chars) { None => s, Some((idx, _)) => &s[..idx], } } /// Removes characters from the end of the string if its length is greater than `max_chars` and /// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars. pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); // If the string's byte length is <= max_chars, walking the string can be skipped since the // number of chars is <= the number of bytes. if s.len() <= max_chars { return s.to_string(); } let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars); match truncation_ix { Some(index) => s[..index].to_string() + "…", _ => s.to_string(), } } /// Removes characters from the front of the string if its length is greater than `max_chars` and /// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars. pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); // If the string's byte length is <= max_chars, walking the string can be skipped since the // number of chars is <= the number of bytes. if s.len() <= max_chars { return s.to_string(); } let suffix_char_length = max_chars.saturating_sub(1); let truncation_ix = s .char_indices() .map(|(i, _)| i) .nth_back(suffix_char_length); match truncation_ix { Some(index) if index > 0 => "…".to_string() + &s[index..], _ => s.to_string(), } } /// Takes only `max_lines` from the string and, if there were more than `max_lines-1`, appends a /// a newline and "..." to the string, so that `max_lines` are returned. /// Returns string unchanged if its length is smaller than max_lines. pub fn truncate_lines_and_trailoff(s: &str, max_lines: usize) -> String { let mut lines = s.lines().take(max_lines).collect::<Vec<_>>(); if lines.len() > max_lines - 1 { lines.pop(); lines.join("\n") + "\n…" } else { lines.join("\n") } } /// Truncates the string at a character boundary, such that the result is less than `max_bytes` in /// length. pub fn truncate_to_byte_limit(s: &str, max_bytes: usize) -> &str { if s.len() < max_bytes { return s; } for i in (0..max_bytes).rev() { if s.is_char_boundary(i) { return &s[..i]; } } "" } /// Takes a prefix of complete lines which fit within the byte limit. If the first line is longer /// than the limit, truncates at a character boundary. pub fn truncate_lines_to_byte_limit(s: &str, max_bytes: usize) -> &str { if s.len() < max_bytes { return s; } for i in (0..max_bytes).rev() { if s.is_char_boundary(i) && s.as_bytes()[i] == b'\n' { // Since the i-th character is \n, valid to slice at i + 1. return &s[..i + 1]; } } truncate_to_byte_limit(s, max_bytes) } #[test] fn test_truncate_lines_to_byte_limit() { let text = "Line 1\nLine 2\nLine 3\nLine 4"; // Limit that includes all lines assert_eq!(truncate_lines_to_byte_limit(text, 100), text); // Exactly the first line assert_eq!(truncate_lines_to_byte_limit(text, 7), "Line 1\n"); // Limit between lines assert_eq!(truncate_lines_to_byte_limit(text, 13), "Line 1\n"); assert_eq!(truncate_lines_to_byte_limit(text, 20), "Line 1\nLine 2\n"); // Limit before first newline assert_eq!(truncate_lines_to_byte_limit(text, 6), "Line "); // Test with non-ASCII characters let text_utf8 = "Line 1\nLíne 2\nLine 3"; assert_eq!( truncate_lines_to_byte_limit(text_utf8, 15), "Line 1\nLíne 2\n" ); } pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T { let prev = *value; *value += T::from(1); prev } /// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and /// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this, /// both `vec` and `new_items` should already be sorted according to the `cmp` comparator. pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F) where I: IntoIterator<Item = T>, F: FnMut(&T, &T) -> Ordering, { let mut start_index = 0; for new_item in new_items { if let Err(i) = vec[start_index..].binary_search_by(|m| cmp(m, &new_item)) { let index = start_index + i; if vec.len() < limit { vec.insert(index, new_item); } else if index < vec.len() { vec.pop(); vec.insert(index, new_item); } start_index = index; } } } pub fn truncate_to_bottom_n_sorted_by<T, F>(items: &mut Vec<T>, limit: usize, compare: &F) where F: Fn(&T, &T) -> Ordering, { if limit == 0 { items.truncate(0); } if items.len() <= limit { items.sort_by(compare); return; } // When limit is near to items.len() it may be more efficient to sort the whole list and // truncate, rather than always doing selection first as is done below. It's hard to analyze // where the threshold for this should be since the quickselect style algorithm used by // `select_nth_unstable_by` makes the prefix partially sorted, and so its work is not wasted - // the expected number of comparisons needed by `sort_by` is less than it is for some arbitrary // unsorted input. items.select_nth_unstable_by(limit, compare); items.truncate(limit); items.sort_by(compare); } /// Prevents execution of the application with root privileges on Unix systems. /// /// This function checks if the current process is running with root privileges /// and terminates the program with an error message unless explicitly allowed via the /// `ZED_ALLOW_ROOT` environment variable. #[cfg(unix)] pub fn prevent_root_execution() { let is_root = nix::unistd::geteuid().is_root(); let allow_root = std::env::var("ZED_ALLOW_ROOT").is_ok_and(|val| val == "true"); if is_root && !allow_root { eprintln!( "\ Error: Running Zed as root or via sudo is unsupported. Doing so (even once) may subtly break things for all subsequent non-root usage of Zed. It is untested and not recommended, don't complain when things break. If you wish to proceed anyways, set `ZED_ALLOW_ROOT=true` in your environment." ); std::process::exit(1); } } #[cfg(unix)] fn load_shell_from_passwd() -> Result<()> { let buflen = match unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) } { n if n < 0 => 1024, n => n as usize, }; let mut buffer = Vec::with_capacity(buflen); let mut pwd: std::mem::MaybeUninit<libc::passwd> = std::mem::MaybeUninit::uninit(); let mut result: *mut libc::passwd = std::ptr::null_mut(); let uid = unsafe { libc::getuid() }; let status = unsafe { libc::getpwuid_r( uid, pwd.as_mut_ptr(), buffer.as_mut_ptr() as *mut libc::c_char, buflen, &mut result, ) }; anyhow::ensure!(!result.is_null(), "passwd entry for uid {} not found", uid); // SAFETY: If `getpwuid_r` doesn't error, we have the entry here. let entry = unsafe { pwd.assume_init() }; anyhow::ensure!( status == 0, "call to getpwuid_r failed. uid: {}, status: {}", uid, status ); anyhow::ensure!( entry.pw_uid == uid, "passwd entry has different uid ({}) than getuid ({}) returned", entry.pw_uid, uid, ); let shell = unsafe { std::ffi::CStr::from_ptr(entry.pw_shell).to_str().unwrap() }; let should_set_shell = env::var("SHELL").map_or(true, |shell_env| { shell_env != shell && !std::path::Path::new(&shell_env).exists() }); if should_set_shell { log::info!( "updating SHELL environment variable to value from passwd entry: {:?}", shell, ); unsafe { env::set_var("SHELL", shell) }; } Ok(()) } /// Returns a shell escaped path for the current zed executable pub fn get_shell_safe_zed_path(shell_kind: shell::ShellKind) -> anyhow::Result<String> { let zed_path = std::env::current_exe().context("Failed to determine current zed executable path.")?; zed_path .try_shell_safe(shell_kind) .context("Failed to shell-escape Zed executable path.") } /// Returns a path for the zed cli executable, this function /// should be called from the zed executable, not zed-cli. pub fn get_zed_cli_path() -> Result<PathBuf> { let zed_path = std::env::current_exe().context("Failed to determine current zed executable path.")?; let parent = zed_path .parent() .context("Failed to determine parent directory of zed executable path.")?; let possible_locations: &[&str] = if cfg!(target_os = "macos") { // On macOS, the zed executable and zed-cli are inside the app bundle, // so here ./cli is for both installed and development builds. &["./cli"] } else if cfg!(target_os = "windows") { // bin/zed.exe is for installed builds, ./cli.exe is for development builds. &["bin/zed.exe", "./cli.exe"] } else if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") { // bin is the standard, ./cli is for the target directory in development builds. &["../bin/zed", "./cli"] } else { anyhow::bail!("unsupported platform for determining zed-cli path"); }; possible_locations .iter() .find_map(|p| { parent .join(p) .canonicalize() .ok() .filter(|p| p != &zed_path) }) .with_context(|| { format!( "could not find zed-cli from any of: {}", possible_locations.join(", ") ) }) } #[cfg(unix)] pub async fn load_login_shell_environment() -> Result<()> { load_shell_from_passwd().log_err(); // If possible, we want to `cd` in the user's `$HOME` to trigger programs // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook // into shell's `cd` command (and hooks) to manipulate env. // We do this so that we get the env a user would have when spawning a shell // in home directory. for (name, value) in shell_env::capture(get_system_shell(), &[], paths::home_dir()) .await .with_context(|| format!("capturing environment with {:?}", get_system_shell()))? { unsafe { env::set_var(&name, &value) }; } log::info!( "set environment variables from shell:{}, path:{}", std::env::var("SHELL").unwrap_or_default(), std::env::var("PATH").unwrap_or_default(), ); Ok(()) } /// Configures the process to start a new session, to prevent interactive shells from taking control /// of the terminal. /// /// For more details: <https://registerspill.thorstenball.com/p/how-to-lose-control-of-your-shell> pub fn set_pre_exec_to_start_new_session( command: &mut std::process::Command, ) -> &mut std::process::Command { // safety: code in pre_exec should be signal safe. // https://man7.org/linux/man-pages/man7/signal-safety.7.html #[cfg(not(target_os = "windows"))] unsafe { use std::os::unix::process::CommandExt; command.pre_exec(|| { libc::setsid(); #[cfg(target_os = "macos")] crate::command::reset_exception_ports(); Ok(()) }); }; command } pub fn merge_json_lenient_value_into( source: serde_json_lenient::Value, target: &mut serde_json_lenient::Value, ) { match (source, target) { (serde_json_lenient::Value::Object(source), serde_json_lenient::Value::Object(target)) => { for (key, value) in source { if let Some(target) = target.get_mut(&key) { merge_json_lenient_value_into(value, target); } else { target.insert(key, value); } } } (serde_json_lenient::Value::Array(source), serde_json_lenient::Value::Array(target)) => { for value in source { target.push(value); } } (source, target) => *target = source, } } pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) { use serde_json::Value; match (source, target) { (Value::Object(source), Value::Object(target)) => { for (key, value) in source { if let Some(target) = target.get_mut(&key) { merge_json_value_into(value, target); } else { target.insert(key, value); } } } (Value::Array(source), Value::Array(target)) => { for value in source { target.push(value); } } (source, target) => *target = source, } } pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) { use serde_json::Value; if let Value::Object(source_object) = source { let target_object = if let Value::Object(target) = target { target } else { *target = Value::Object(Default::default()); target.as_object_mut().unwrap() }; for (key, value) in source_object { if let Some(target) = target_object.get_mut(&key) { merge_non_null_json_value_into(value, target); } else if !value.is_null() { target_object.insert(key, value); } } } else if !source.is_null() { *target = source } } pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R { static ZED_MEASUREMENTS: OnceLock<bool> = OnceLock::new(); let zed_measurements = ZED_MEASUREMENTS.get_or_init(|| { env::var("ZED_MEASUREMENTS") .map(|measurements| measurements == "1" || measurements == "true") .unwrap_or(false) }); if *zed_measurements { let start = Instant::now(); let result = f(); let elapsed = start.elapsed(); eprintln!("{}: {:?}", label, elapsed); result } else { f() } } pub fn expanded_and_wrapped_usize_range( range: Range<usize>, additional_before: usize, additional_after: usize, wrap_length: usize, ) -> impl Iterator<Item = usize> { let start_wraps = range.start < additional_before; let end_wraps = wrap_length < range.end + additional_after; if start_wraps && end_wraps { Either::Left(0..wrap_length)