993 lines · 30.7 KB · Rust
RawDownload
pub mod arc_cow;pub mod command;pub mod fs;pub mod markdown;pub mod paths;pub mod serde;#[cfg(any(test, feature = "test-support"))]pub mod test; use anyhow::Result;use futures::Future;use itertools::Either;use regex::Regex;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; #[cfg(unix)]use anyhow::{anyhow, Context as _}; pub use take_until::*; #[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);        }    };} 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")    }} 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);} #[cfg(unix)]pub 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,        )    };    let entry = unsafe { pwd.assume_init() };     anyhow::ensure!(        status == 0,        "call to getpwuid_r failed. uid: {}, status: {}",        uid,        status    );    anyhow::ensure!(!result.is_null(), "passwd entry for uid {} not found", uid);    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() };    if env::var("SHELL").map_or(true, |shell_env| shell_env != shell) {        log::info!(            "updating SHELL environment variable to value from passwd entry: {:?}",            shell,        );        env::set_var("SHELL", shell);    }     Ok(())} #[cfg(unix)]pub fn load_login_shell_environment() -> Result<()> {    let marker = "ZED_LOGIN_SHELL_START";    let shell = env::var("SHELL").context(        "SHELL environment variable is not assigned so we can't source login environment variables",    )?;     // 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.    let shell_cmd_prefix = std::env::var_os("HOME")        .and_then(|home| home.into_string().ok())        .map(|home| format!("cd '{home}';"));     // The `exit 0` is the result of hours of debugging, trying to find out    // why running this command here, without `exit 0`, would mess    // up signal process for our process so that `ctrl-c` doesn't work    // anymore.    // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'`  would    // do that, but it does, and `exit 0` helps.    let shell_cmd = format!(        "{}printf '%s' {marker}; /usr/bin/env; exit 0;",        shell_cmd_prefix.as_deref().unwrap_or("")    );     let output = std::process::Command::new(&shell)        .args(["-l", "-i", "-c", &shell_cmd])        .output()        .context("failed to spawn login shell to source login environment variables")?;    if !output.status.success() {        Err(anyhow!("login shell exited with error"))?;    }     let stdout = String::from_utf8_lossy(&output.stdout);     if let Some(env_output_start) = stdout.find(marker) {        let env_output = &stdout[env_output_start + marker.len()..];         parse_env_output(env_output, |key, value| env::set_var(key, value));         log::info!(            "set environment variables from shell:{}, path:{}",            shell,            env::var("PATH").unwrap_or_default(),        );    }     Ok(())} /// Parse the result of calling `usr/bin/env` with no argumentspub fn parse_env_output(env: &str, mut f: impl FnMut(String, String)) {    let mut current_key: Option<String> = None;    let mut current_value: Option<String> = None;     for line in env.split_terminator('\n') {        if let Some(separator_index) = line.find('=') {            if !line[..separator_index].is_empty() {                if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {                    f(key, value)                }                current_key = Some(line[..separator_index].to_string());                current_value = Some(line[separator_index + 1..].to_string());                continue;            };        }        if let Some(value) = current_value.as_mut() {            value.push('\n');            value.push_str(line);        }    }    if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {        f(key, value)    }} 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.clone(), 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.clone(), 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 iterate_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)    } else if start_wraps {        let wrapped_start = (range.start + wrap_length).saturating_sub(additional_before);        if wrapped_start <= range.end {            Either::Left(0..wrap_length)        } else {            Either::Right((0..range.end + additional_after).chain(wrapped_start..wrap_length))        }    } else if end_wraps {        let wrapped_end = range.end + additional_after - wrap_length;        if range.start <= wrapped_end {            Either::Left(0..wrap_length)        } else {            Either::Right((0..wrapped_end).chain(range.start - additional_before..wrap_length))        }    } else {        Either::Left((range.start - additional_before)..(range.end + additional_after))    }} pub trait ResultExt<E> {    type Ok;     fn log_err(self) -> Option<Self::Ok>;    /// Assert that this result should never be an error in development or tests.    fn debug_assert_ok(self, reason: &str) -> Self;    fn warn_on_err(self) -> Option<Self::Ok>;    fn anyhow(self) -> anyhow::Result<Self::Ok>    where        E: Into<anyhow::Error>;} impl<T, E> ResultExt<E> for Result<T, E>where    E: std::fmt::Debug,{    type Ok = T;     #[track_caller]    fn log_err(self) -> Option<T> {        match self {            Ok(value) => Some(value),            Err(error) => {                log_error_with_caller(*Location::caller(), error, log::Level::Error);                None            }        }    }     #[track_caller]    fn debug_assert_ok(self, reason: &str) -> Self {        if let Err(error) = &self {            debug_panic!("{reason} - {error:?}");        }        self    }     #[track_caller]    fn warn_on_err(self) -> Option<T> {        match self {            Ok(value) => Some(value),            Err(error) => {                log_error_with_caller(*Location::caller(), error, log::Level::Warn);                None            }        }    }     fn anyhow(self) -> anyhow::Result<T>    where        E: Into<anyhow::Error>,    {        self.map_err(Into::into)    }} fn log_error_with_caller<E>(caller: core::panic::Location<'_>, error: E, level: log::Level)where    E: std::fmt::Debug,{    #[cfg(not(target_os = "windows"))]    let file = caller.file();    #[cfg(target_os = "windows")]    let file = caller.file().replace('\\', "/");    // In this codebase, the first segment of the file path is    // the 'crates' folder, followed by the crate name.    let target = file.split('/').nth(1);     log::logger().log(        &log::Record::builder()            .target(target.unwrap_or(""))            .module_path(target)            .args(format_args!("{:?}", error))            .file(Some(caller.file()))            .line(Some(caller.line()))            .level(level)            .build(),    );} pub fn log_err<E: std::fmt::Debug>(error: &E) {    log_error_with_caller(*Location::caller(), error, log::Level::Warn);} pub trait TryFutureExt {    fn log_err(self) -> LogErrorFuture<Self>    where        Self: Sized;     fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>    where        Self: Sized;     fn warn_on_err(self) -> LogErrorFuture<Self>    where        Self: Sized;    fn unwrap(self) -> UnwrapFuture<Self>    where        Self: Sized;} impl<F, T, E> TryFutureExt for Fwhere    F: Future<Output = Result<T, E>>,    E: std::fmt::Debug,{    #[track_caller]    fn log_err(self) -> LogErrorFuture<Self>    where        Self: Sized,    {        let location = Location::caller();        LogErrorFuture(self, log::Level::Error, *location)    }     fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>    where        Self: Sized,    {        LogErrorFuture(self, log::Level::Error, location)    }     #[track_caller]    fn warn_on_err(self) -> LogErrorFuture<Self>    where        Self: Sized,    {        let location = Location::caller();

Lines 1–500 of 993.