pub mod arc_cow;pub mod archive;pub mod command;pub mod fs;pub mod markdown;pub mod paths;pub mod serde;pub mod size;#[cfg(any(test, feature = "test-support"))]pub mod test;pub mod time; use anyhow::Result;use futures::Future;use itertools::Either;use regex::Regex;use std::num::NonZeroU32;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::Context as _; pub use take_until::*;#[cfg(any(test, feature = "test-support"))]pub use util_macros::{line_endings, separator, 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); } };} /// A macro to add "C:" to the beginning of a path literal on Windows, and replace all/// the separator from `/` to `\`./// But on non-Windows platforms, it will return the path literal as is.////// # Examples/// ```rust/// use util::path;////// let path = path!("/Users/user/file.txt");/// #[cfg(target_os = "windows")]/// assert_eq!(path, "C:\\Users\\user\\file.txt");/// #[cfg(not(target_os = "windows"))]/// assert_eq!(path, "/Users/user/file.txt");/// ```#[cfg(all(any(test, feature = "test-support"), target_os = "windows"))]#[macro_export]macro_rules! path { ($path:literal) => { concat!("C:", util::separator!($path)) };} /// A macro to add "C:" to the beginning of a path literal on Windows, and replace all/// the separator from `/` to `\`./// But on non-Windows platforms, it will return the path literal as is.////// # Examples/// ```rust/// use util::path;////// let path = path!("/Users/user/file.txt");/// #[cfg(target_os = "windows")]/// assert_eq!(path, "C:\\Users\\user\\file.txt");/// #[cfg(not(target_os = "windows"))]/// assert_eq!(path, "/Users/user/file.txt");/// ```#[cfg(all(any(test, feature = "test-support"), not(target_os = "windows")))]#[macro_export]macro_rules! path { ($path:literal) => { $path };} 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) { if 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)} fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize { let tab_size = tab_size.get() as usize; let mut width = offset; for ch in text.chars() { width += if ch == '\t' { tab_size - (width % tab_size) } else { 1 }; } width - offset} /// Tokenizes a string into runs of text that should stick together, or that is whitespace.struct WordBreakingTokenizer<'a> { input: &'a str,} impl<'a> WordBreakingTokenizer<'a> { fn new(input: &'a str) -> Self { Self { input } }} fn is_char_ideographic(ch: char) -> bool { use unicode_script::Script::*; use unicode_script::UnicodeScript; matches!(ch.script(), Han | Tangut | Yi)} fn is_grapheme_ideographic(text: &str) -> bool { text.chars().any(is_char_ideographic)} fn is_grapheme_whitespace(text: &str) -> bool { text.chars().any(|x| x.is_whitespace())} fn should_stay_with_preceding_ideograph(text: &str) -> bool { text.chars().next().map_or(false, |ch| { matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…') })} #[derive(PartialEq, Eq, Debug, Clone, Copy)]enum WordBreakToken<'a> { Word { token: &'a str, grapheme_len: usize }, InlineWhitespace { token: &'a str, grapheme_len: usize }, Newline,} impl<'a> Iterator for WordBreakingTokenizer<'a> { /// Yields a span, the count of graphemes in the token, and whether it was /// whitespace. Note that it also breaks at word boundaries. type Item = WordBreakToken<'a>; fn next(&mut self) -> Option<Self::Item> { use unicode_segmentation::UnicodeSegmentation; if self.input.is_empty() { return None; } let mut iter = self.input.graphemes(true).peekable(); let mut offset = 0; let mut grapheme_len = 0; if let Some(first_grapheme) = iter.next() { let is_newline = first_grapheme == "\n"; let is_whitespace = is_grapheme_whitespace(first_grapheme); offset += first_grapheme.len(); grapheme_len += 1; if is_grapheme_ideographic(first_grapheme) && !is_whitespace { if let Some(grapheme) = iter.peek().copied() { if should_stay_with_preceding_ideograph(grapheme) { offset += grapheme.len(); grapheme_len += 1; } } } else { let mut words = self.input[offset..].split_word_bound_indices().peekable(); let mut next_word_bound = words.peek().copied(); if next_word_bound.map_or(false, |(i, _)| i == 0) { next_word_bound = words.next(); } while let Some(grapheme) = iter.peek().copied() { if next_word_bound.map_or(false, |(i, _)| i == offset) { break; }; if is_grapheme_whitespace(grapheme) != is_whitespace || (grapheme == "\n") != is_newline { break; }; offset += grapheme.len(); grapheme_len += 1; iter.next(); } } let token = &self.input[..offset]; self.input = &self.input[offset..]; if token == "\n" { Some(WordBreakToken::Newline) } else if is_whitespace { Some(WordBreakToken::InlineWhitespace { token, grapheme_len, }) } else { Some(WordBreakToken::Word { token, grapheme_len, }) } } else { None } }} pub fn wrap_with_prefix( line_prefix: String, unwrapped_text: String, wrap_column: usize, tab_size: NonZeroU32, preserve_existing_whitespace: bool,) -> String { let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size); let mut wrapped_text = String::new(); let mut current_line = line_prefix.clone(); let tokenizer = WordBreakingTokenizer::new(&unwrapped_text); let mut current_line_len = line_prefix_len; let mut in_whitespace = false; for token in tokenizer { let have_preceding_whitespace = in_whitespace; match token { WordBreakToken::Word { token, grapheme_len, } => { in_whitespace = false; if current_line_len + grapheme_len > wrap_column && current_line_len != line_prefix_len { wrapped_text.push_str(current_line.trim_end()); wrapped_text.push('\n'); current_line.truncate(line_prefix.len()); current_line_len = line_prefix_len; } current_line.push_str(token); current_line_len += grapheme_len; } WordBreakToken::InlineWhitespace { mut token, mut grapheme_len, } => { in_whitespace = true; if have_preceding_whitespace && !preserve_existing_whitespace { continue; } if !preserve_existing_whitespace { token = " "; grapheme_len = 1; } if current_line_len + grapheme_len > wrap_column { wrapped_text.push_str(current_line.trim_end()); wrapped_text.push('\n'); current_line.truncate(line_prefix.len()); current_line_len = line_prefix_len; } else if current_line_len != line_prefix_len || preserve_existing_whitespace { current_line.push_str(token); current_line_len += grapheme_len; } } WordBreakToken::Newline => { in_whitespace = true; if preserve_existing_whitespace { wrapped_text.push_str(current_line.trim_end()); wrapped_text.push('\n'); current_line.truncate(line_prefix.len()); current_line_len = line_prefix_len; } else if have_preceding_whitespace { continue; } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len { wrapped_text.push_str(current_line.trim_end()); wrapped_text.push('\n'); current_line.truncate(line_prefix.len()); current_line_len = line_prefix_len; } else if current_line_len != line_prefix_len { current_line.push(' '); current_line_len += 1; } } } } if !current_line.is_empty() { wrapped_text.push_str(¤t_line); } wrapped_text} 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)]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, ); unsafe { env::set_var("SHELL", shell) }; } Ok(())} #[cfg(unix)]pub fn load_login_shell_environment() -> Result<()> { load_shell_from_passwd().log_err(); 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 programsLines 1–500 of 1756. Load more
pub mod arc_cow; pub mod archive; pub mod command; pub mod fs; pub mod markdown; pub mod paths; pub mod serde; pub mod size; #[cfg(any(test, feature = "test-support"))] pub mod test; pub mod time; use anyhow::Result; use futures::Future; use itertools::Either; use regex::Regex; use std::num::NonZeroU32; 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::Context as _; pub use take_until::*; #[cfg(any(test, feature = "test-support"))] pub use util_macros::{line_endings, separator, 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); } }; } /// A macro to add "C:" to the beginning of a path literal on Windows, and replace all /// the separator from `/` to `\`. /// But on non-Windows platforms, it will return the path literal as is. /// /// # Examples /// ```rust /// use util::path; /// /// let path = path!("/Users/user/file.txt"); /// #[cfg(target_os = "windows")] /// assert_eq!(path, "C:\\Users\\user\\file.txt"); /// #[cfg(not(target_os = "windows"))] /// assert_eq!(path, "/Users/user/file.txt"); /// ``` #[cfg(all(any(test, feature = "test-support"), target_os = "windows"))] #[macro_export] macro_rules! path { ($path:literal) => { concat!("C:", util::separator!($path)) }; } /// A macro to add "C:" to the beginning of a path literal on Windows, and replace all /// the separator from `/` to `\`. /// But on non-Windows platforms, it will return the path literal as is. /// /// # Examples /// ```rust /// use util::path; /// /// let path = path!("/Users/user/file.txt"); /// #[cfg(target_os = "windows")] /// assert_eq!(path, "C:\\Users\\user\\file.txt"); /// #[cfg(not(target_os = "windows"))] /// assert_eq!(path, "/Users/user/file.txt"); /// ``` #[cfg(all(any(test, feature = "test-support"), not(target_os = "windows")))] #[macro_export] macro_rules! path { ($path:literal) => { $path }; } 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) { if 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) } fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize { let tab_size = tab_size.get() as usize; let mut width = offset; for ch in text.chars() { width += if ch == '\t' { tab_size - (width % tab_size) } else { 1 }; } width - offset } /// Tokenizes a string into runs of text that should stick together, or that is whitespace. struct WordBreakingTokenizer<'a> { input: &'a str, } impl<'a> WordBreakingTokenizer<'a> { fn new(input: &'a str) -> Self { Self { input } } } fn is_char_ideographic(ch: char) -> bool { use unicode_script::Script::*; use unicode_script::UnicodeScript; matches!(ch.script(), Han | Tangut | Yi) } fn is_grapheme_ideographic(text: &str) -> bool { text.chars().any(is_char_ideographic) } fn is_grapheme_whitespace(text: &str) -> bool { text.chars().any(|x| x.is_whitespace()) } fn should_stay_with_preceding_ideograph(text: &str) -> bool { text.chars().next().map_or(false, |ch| { matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…') }) } #[derive(PartialEq, Eq, Debug, Clone, Copy)] enum WordBreakToken<'a> { Word { token: &'a str, grapheme_len: usize }, InlineWhitespace { token: &'a str, grapheme_len: usize }, Newline, } impl<'a> Iterator for WordBreakingTokenizer<'a> { /// Yields a span, the count of graphemes in the token, and whether it was /// whitespace. Note that it also breaks at word boundaries. type Item = WordBreakToken<'a>; fn next(&mut self) -> Option<Self::Item> { use unicode_segmentation::UnicodeSegmentation; if self.input.is_empty() { return None; } let mut iter = self.input.graphemes(true).peekable(); let mut offset = 0; let mut grapheme_len = 0; if let Some(first_grapheme) = iter.next() { let is_newline = first_grapheme == "\n"; let is_whitespace = is_grapheme_whitespace(first_grapheme); offset += first_grapheme.len(); grapheme_len += 1; if is_grapheme_ideographic(first_grapheme) && !is_whitespace { if let Some(grapheme) = iter.peek().copied() { if should_stay_with_preceding_ideograph(grapheme) { offset += grapheme.len(); grapheme_len += 1; } } } else { let mut words = self.input[offset..].split_word_bound_indices().peekable(); let mut next_word_bound = words.peek().copied(); if next_word_bound.map_or(false, |(i, _)| i == 0) { next_word_bound = words.next(); } while let Some(grapheme) = iter.peek().copied() { if next_word_bound.map_or(false, |(i, _)| i == offset) { break; }; if is_grapheme_whitespace(grapheme) != is_whitespace || (grapheme == "\n") != is_newline { break; }; offset += grapheme.len(); grapheme_len += 1; iter.next(); } } let token = &self.input[..offset]; self.input = &self.input[offset..]; if token == "\n" { Some(WordBreakToken::Newline) } else if is_whitespace { Some(WordBreakToken::InlineWhitespace { token, grapheme_len, }) } else { Some(WordBreakToken::Word { token, grapheme_len, }) } } else { None } } } pub fn wrap_with_prefix( line_prefix: String, unwrapped_text: String, wrap_column: usize, tab_size: NonZeroU32, preserve_existing_whitespace: bool, ) -> String { let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size); let mut wrapped_text = String::new(); let mut current_line = line_prefix.clone(); let tokenizer = WordBreakingTokenizer::new(&unwrapped_text); let mut current_line_len = line_prefix_len; let mut in_whitespace = false; for token in tokenizer { let have_preceding_whitespace = in_whitespace; match token { WordBreakToken::Word { token, grapheme_len, } => { in_whitespace = false; if current_line_len + grapheme_len > wrap_column && current_line_len != line_prefix_len { wrapped_text.push_str(current_line.trim_end()); wrapped_text.push('\n'); current_line.truncate(line_prefix.len()); current_line_len = line_prefix_len; } current_line.push_str(token); current_line_len += grapheme_len; } WordBreakToken::InlineWhitespace { mut token, mut grapheme_len, } => { in_whitespace = true; if have_preceding_whitespace && !preserve_existing_whitespace { continue; } if !preserve_existing_whitespace { token = " "; grapheme_len = 1; } if current_line_len + grapheme_len > wrap_column { wrapped_text.push_str(current_line.trim_end()); wrapped_text.push('\n'); current_line.truncate(line_prefix.len()); current_line_len = line_prefix_len; } else if current_line_len != line_prefix_len || preserve_existing_whitespace { current_line.push_str(token); current_line_len += grapheme_len; } } WordBreakToken::Newline => { in_whitespace = true; if preserve_existing_whitespace { wrapped_text.push_str(current_line.trim_end()); wrapped_text.push('\n'); current_line.truncate(line_prefix.len()); current_line_len = line_prefix_len; } else if have_preceding_whitespace { continue; } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len { wrapped_text.push_str(current_line.trim_end()); wrapped_text.push('\n'); current_line.truncate(line_prefix.len()); current_line_len = line_prefix_len; } else if current_line_len != line_prefix_len { current_line.push(' '); current_line_len += 1; } } } } if !current_line.is_empty() { wrapped_text.push_str(¤t_line); } wrapped_text } 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)] 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, ); unsafe { env::set_var("SHELL", shell) }; } Ok(()) } #[cfg(unix)] pub fn load_login_shell_environment() -> Result<()> { load_shell_from_passwd().log_err(); 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