From 116bcdac28aebd60182dcd0671aabe72e2d18ae7 Mon Sep 17 00:00:00 2001 From: Daniel Rainer Date: Thu, 18 Dec 2025 02:35:03 +0100 Subject: [PATCH] common: extract more code into separate crate This extracts the remaining code from `src/common.rs` which does not depend on other parts of the main library crate. No functional changes. Closes #12310 --- Cargo.lock | 2 + crates/common/Cargo.toml | 2 + crates/common/src/lib.rs | 650 +++++++++++++++++++++++++++++++++++++- src/builtins/abbr.rs | 1 + src/builtins/bind.rs | 1 + src/builtins/set.rs | 1 + src/builtins/shared.rs | 1 + src/color.rs | 1 + src/common.rs | 653 +-------------------------------------- src/env/var.rs | 1 + src/input.rs | 1 + src/parse_execution.rs | 1 + src/parse_util.rs | 1 + src/reader/reader.rs | 2 +- src/tinyexpr.rs | 1 + 15 files changed, 669 insertions(+), 650 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c0c4df75..cf1b548e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,8 +207,10 @@ dependencies = [ name = "fish-common" version = "0.0.0" dependencies = [ + "bitflags", "libc", "nix", + "widestring", ] [[package]] diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 5a3159b44..351bb8734 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -7,8 +7,10 @@ repository.workspace = true license.workspace = true [dependencies] +bitflags.workspace = true libc.workspace = true nix.workspace = true +widestring.workspace = true [lints] workspace = true diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index f13455326..fa066e4a1 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,7 +1,14 @@ -use libc::STDIN_FILENO; -use std::env; +use bitflags::bitflags; +use libc::{SIG_IGN, SIGTTOU, STDIN_FILENO}; +use std::cell::{Cell, RefCell}; +use std::io::Read; +use std::ops::{Deref, DerefMut}; +use std::os::fd::{AsRawFd, BorrowedFd, RawFd}; use std::os::unix::ffi::OsStrExt; use std::sync::OnceLock; +use std::sync::atomic::{AtomicI32, AtomicU32, Ordering}; +use std::{env, mem, time}; +use widestring::Utf32Str as wstr; pub const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME"); @@ -48,6 +55,93 @@ pub const ENCODE_DIRECT_BASE: char = '\u{F600}'; pub const ENCODE_DIRECT_END: char = char_offset(ENCODE_DIRECT_BASE, 256); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EscapeStringStyle { + Script(EscapeFlags), + Url, + Var, + Regex, +} + +impl Default for EscapeStringStyle { + fn default() -> Self { + Self::Script(EscapeFlags::default()) + } +} + +impl TryFrom<&wstr> for EscapeStringStyle { + type Error = &'static wstr; + fn try_from(s: &wstr) -> Result { + use EscapeStringStyle::*; + match s { + s if s == "script" => Ok(Self::default()), + s if s == "var" => Ok(Var), + s if s == "url" => Ok(Url), + s if s == "regex" => Ok(Regex), + _ => Err(widestring::utf32str!("Invalid escape style")), + } + } +} + +bitflags! { + /// Flags for the [`escape_string()`] function. These are only applicable when the escape style is + /// [`EscapeStringStyle::Script`]. + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] + pub struct EscapeFlags: u32 { + /// Do not escape special fish syntax characters like the semicolon. Only escape non-printable + /// characters and backslashes. + const NO_PRINTABLES = 1 << 0; + /// Do not try to use 'simplified' quoted escapes, and do not use empty quotes as the empty + /// string. + const NO_QUOTED = 1 << 1; + /// Do not escape tildes. + const NO_TILDE = 1 << 2; + /// Replace non-printable control characters with Unicode symbols. + const SYMBOLIC = 1 << 3; + /// Escape , + const COMMA = 1 << 4; + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UnescapeStringStyle { + Script(UnescapeFlags), + Url, + Var, +} + +impl Default for UnescapeStringStyle { + fn default() -> Self { + Self::Script(UnescapeFlags::default()) + } +} + +impl TryFrom<&wstr> for UnescapeStringStyle { + type Error = &'static wstr; + fn try_from(s: &wstr) -> Result { + use UnescapeStringStyle::*; + match s { + s if s == "script" => Ok(Self::default()), + s if s == "var" => Ok(Var), + s if s == "url" => Ok(Url), + _ => Err(widestring::utf32str!("Invalid escape style")), + } + } +} + +bitflags! { + /// Flags for unescape_string functions. + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] + pub struct UnescapeFlags: u32 { + /// escape special fish syntax characters like the semicolon + const SPECIAL = 1 << 0; + /// allow incomplete escape sequences + const INCOMPLETE = 1 << 1; + /// don't handle backslash escapes + const NO_BACKSLASHES = 1 << 2; + } +} + pub const fn char_offset(base: char, offset: u32) -> char { match char::from_u32(base as u32 + offset) { Some(c) => c, @@ -88,3 +182,555 @@ pub fn is_console_session() -> bool { }) }) } + +/// Exits without invoking destructors (via _exit), useful for code after fork. +pub fn exit_without_destructors(code: libc::c_int) -> ! { + unsafe { libc::_exit(code) }; +} + +/// The character to use where the text has been truncated. +pub fn get_ellipsis_char() -> char { + '\u{2026}' // ('…') +} + +/// The character or string to use where text has been truncated (ellipsis if possible, otherwise +/// ...) +pub fn get_ellipsis_str() -> &'static wstr { + widestring::utf32str!("\u{2026}") +} + +// Only pub for `src/common.rs` +pub static OBFUSCATION_READ_CHAR: AtomicU32 = AtomicU32::new(0); + +pub fn get_obfuscation_read_char() -> char { + char::from_u32(OBFUSCATION_READ_CHAR.load(Ordering::Relaxed)).unwrap() +} + +/// Call read, blocking and repeating on EINTR. Exits on EAGAIN. +/// Return the number of bytes read, or 0 on EOF, or an error. +pub fn read_blocked(fd: RawFd, buf: &mut [u8]) -> nix::Result { + loop { + let res = nix::unistd::read(unsafe { BorrowedFd::borrow_raw(fd) }, buf); + if let Err(nix::Error::EINTR) = res { + continue; + } + return res; + } +} + +pub trait ReadExt { + /// Like [`std::io::Read::read_to_end`], but does not retry on EINTR. + fn read_to_end_interruptible(&mut self, buf: &mut Vec) -> std::io::Result<()>; +} + +impl ReadExt for T { + fn read_to_end_interruptible(&mut self, buf: &mut Vec) -> std::io::Result<()> { + let mut chunk = [0_u8; 4096]; + loop { + match self.read(&mut chunk)? { + 0 => return Ok(()), + n => buf.extend_from_slice(&chunk[..n]), + } + } + } +} + +/// A rusty port of the C++ `write_loop()` function from `common.cpp`. This should be deprecated in +/// favor of native rust read/write methods at some point. +pub fn safe_write_loop(fd: &Fd, buf: &[u8]) -> std::io::Result<()> { + let fd = fd.as_raw_fd(); + let mut total = 0; + while total < buf.len() { + match nix::unistd::write(unsafe { BorrowedFd::borrow_raw(fd) }, &buf[total..]) { + Ok(written) => { + total += written; + } + Err(err) => { + if matches!(err, nix::Error::EAGAIN | nix::Error::EINTR) { + continue; + } + return Err(std::io::Error::from(err)); + } + } + } + Ok(()) +} + +pub use safe_write_loop as write_loop; + +pub const fn help_section_exists(section: &str) -> bool { + let haystack = include_str!("../../../share/help_sections"); + let needle = section; + + let needle = needle.as_bytes(); + let haystack = haystack.as_bytes(); + let nlen = needle.len(); + let mut line_start = 0; + let mut i = 0; + while i <= haystack.len() { + if i == haystack.len() || haystack[i] == b'\n' { + let line_len = i - line_start; + + if line_len == nlen { + let mut j = 0; + while j < nlen && haystack[line_start + j] == needle[j] { + j += 1; + } + if j == nlen { + return true; + } + } + line_start = i + 1; + } + i += 1; + } + false +} + +#[macro_export] +macro_rules! help_section { + ($section:expr) => {{ + const _: () = assert!($crate::help_section_exists($section)); + $section + }}; +} + +pub type Timepoint = f64; + +/// Return the number of seconds from the UNIX epoch, with subsecond precision. This function uses +/// the gettimeofday function and will have the same precision as that function. +pub fn timef() -> Timepoint { + match time::SystemTime::now().duration_since(time::UNIX_EPOCH) { + Ok(difference) => difference.as_secs() as f64, + Err(until_epoch) => -(until_epoch.duration().as_secs() as f64), + } +} + +/// Be able to restore the term's foreground process group. +/// This is set during startup and not modified after. +static INITIAL_FG_PROCESS_GROUP: AtomicI32 = AtomicI32::new(-1); // HACK, should be pid_t +const _: () = assert!(mem::size_of::() >= mem::size_of::()); + +/// Save the value of tcgetpgrp so we can restore it on exit. +pub fn save_term_foreground_process_group() { + INITIAL_FG_PROCESS_GROUP.store(unsafe { libc::tcgetpgrp(STDIN_FILENO) }, Ordering::Relaxed); +} + +pub fn restore_term_foreground_process_group_for_exit() { + // We wish to restore the tty to the initial owner. There's two ways this can go wrong: + // 1. We may steal the tty from someone else (#7060). + // 2. The call to tcsetpgrp may deliver SIGSTOP to us, and we will not exit. + // Hanging on exit seems worse, so ensure that SIGTTOU is ignored so we do not get SIGSTOP. + // Note initial_fg_process_group == 0 is possible with Linux pid namespaces. + // This is called during shutdown and from a signal handler. We don't bother to complain on + // failure because doing so is unlikely to be noticed. + // Safety: All of getpgrp, signal, and tcsetpgrp are async-signal-safe. + let initial_fg_process_group = INITIAL_FG_PROCESS_GROUP.load(Ordering::Relaxed); + if initial_fg_process_group > 0 && initial_fg_process_group != unsafe { libc::getpgrp() } { + unsafe { + libc::signal(SIGTTOU, SIG_IGN); + libc::tcsetpgrp(STDIN_FILENO, initial_fg_process_group); + } + } +} + +/// A wrapper around Cell which supports modifying the contents, scoped to a region of code. +/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the value, +/// instead of requiring an accessor function which returns a mutable reference to a field. +pub struct ScopedCell(Cell); + +impl Deref for ScopedCell { + type Target = Cell; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ScopedCell { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl ScopedCell { + pub fn new(value: T) -> Self { + Self(Cell::new(value)) + } + + /// Temporarily modify a value in the ScopedCell, restoring it when the returned object is dropped. + /// + /// This is useful when you want to apply a change for the duration of a scope + /// without having to manually restore the previous value. + /// + /// # Example + /// + /// ``` + /// use fish_common::ScopedCell; + /// + /// let cell = ScopedCell::new(5); + /// assert_eq!(cell.get(), 5); + /// + /// { + /// let _guard = cell.scoped_mod(|v| *v += 10); + /// assert_eq!(cell.get(), 15); + /// } + /// + /// // Restored after scope + /// assert_eq!(cell.get(), 5); + /// ``` + pub fn scoped_mod<'a, Modifier: FnOnce(&mut T)>( + &'a self, + modifier: Modifier, + ) -> impl ScopeGuarding + 'a { + let mut val = self.get(); + modifier(&mut val); + let saved = self.replace(val); + ScopeGuard::new(self, move |cell| cell.set(saved)) + } +} + +/// A wrapper around RefCell which supports modifying the contents, scoped to a region of code. +pub struct ScopedRefCell(RefCell); + +impl Deref for ScopedRefCell { + type Target = RefCell; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ScopedRefCell { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl ScopedRefCell { + pub fn new(value: T) -> Self { + Self(RefCell::new(value)) + } + + /// Temporarily modify a field in the ScopedRefCell, restoring it when the returned guard is dropped. + /// + /// This is useful when you want to change part of a data structure for the duration of a scope, + /// and automatically restore the original value afterward. + /// + /// The `accessor` function selects the field to modify by returning a mutable reference to it. + /// + /// # Example + /// ``` + /// use fish_common::ScopedRefCell; + /// + /// struct State { flag: bool } + /// + /// let cell = ScopedRefCell::new(State { flag: false }); + /// assert_eq!(cell.borrow().flag, false); + /// + /// { + /// let _guard = cell.scoped_set(true, |s| &mut s.flag); + /// assert_eq!(cell.borrow().flag, true); + /// } + /// + /// // Restored after scope + /// assert_eq!(cell.borrow().flag, false); + /// ``` + pub fn scoped_set<'a, Accessor, Value: 'a>( + &'a self, + value: Value, + accessor: Accessor, + ) -> impl ScopeGuarding + 'a + where + Accessor: Fn(&mut T) -> &mut Value + 'a, + { + let mut data = self.borrow_mut(); + let mut saved = std::mem::replace(accessor(&mut data), value); + ScopeGuard::new(self, move |cell| { + let mut data = cell.borrow_mut(); + std::mem::swap((accessor)(&mut data), &mut saved); + }) + } + + /// Convenience method for replacing the entire contents of the ScopedRefCell, restoring it when dropped. + /// + /// Equivalent to `scoped_set(value, |s| s)`. + /// + /// # Example + /// ``` + /// use fish_common::ScopedRefCell; + /// + /// let cell = ScopedRefCell::new(10); + /// assert_eq!(*cell.borrow(), 10); + /// + /// { + /// let _guard = cell.scoped_replace(99); + /// assert_eq!(*cell.borrow(), 99); + /// } + /// + /// assert_eq!(*cell.borrow(), 10); + /// ``` + pub fn scoped_replace<'a>(&'a self, value: T) -> impl ScopeGuarding + 'a { + self.scoped_set(value, |s| s) + } +} + +/// A RAII cleanup object. Unlike in C++ where there is no borrow checker, we can't just provide a +/// callback that modifies live objects willy-nilly because then there would be two &mut references +/// to the same object - the original variables we keep around to use and their captured references +/// held by the closure until its scope expires. +/// +/// Instead we have a `ScopeGuard` type that takes exclusive ownership of (a mutable reference to) +/// the object to be managed. In lieu of keeping the original value around, we obtain a regular or +/// mutable reference to it via ScopeGuard's [`Deref`] and [`DerefMut`] impls. +/// +/// The `ScopeGuard` is considered to be the exclusively owner of the passed value for the +/// duration of its lifetime. If you need to use the value again, use `ScopeGuard` to shadow the +/// value and obtain a reference to it via the `ScopeGuard` itself: +/// +/// ```rust +/// use std::io::prelude::*; +/// use fish_common::ScopeGuard; +/// +/// let file = std::fs::File::create("/dev/null").unwrap(); +/// // Create a scope guard to write to the file when the scope expires. +/// // To be able to still use the file, shadow `file` with the ScopeGuard itself. +/// let mut file = ScopeGuard::new(file, |mut file| file.write_all(b"goodbye\n").unwrap()); +/// // Now write to the file normally "through" the capturing ScopeGuard instance. +/// file.write_all(b"hello\n").unwrap(); +/// +/// // hello will be written first, then goodbye. +/// ``` +pub struct ScopeGuard(Option<(T, F)>); + +impl ScopeGuard { + /// Creates a new `ScopeGuard` wrapping `value`. The `on_drop` callback is executed when the + /// ScopeGuard's lifetime expires or when it is manually dropped. + pub fn new(value: T, on_drop: F) -> Self { + Self(Some((value, on_drop))) + } + + /// Invokes the callback, consuming the ScopeGuard. + pub fn commit(guard: Self) { + std::mem::drop(guard) + } + + /// Cancels the invocation of the callback, returning the original wrapped value. + pub fn cancel(mut guard: Self) -> T { + let (value, _) = guard.0.take().expect("Should always have Some value"); + value + } +} + +impl Deref for ScopeGuard { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0.as_ref().unwrap().0 + } +} + +impl DerefMut for ScopeGuard { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0.as_mut().unwrap().0 + } +} + +impl Drop for ScopeGuard { + fn drop(&mut self) { + if let Some((value, on_drop)) = self.0.take() { + on_drop(value); + } + } +} + +/// A trait expressing what ScopeGuard can do. This is necessary because our scoped cells return an +/// `impl Trait` object and therefore methods on ScopeGuard which take a self parameter cannot be +/// used. +pub trait ScopeGuarding: DerefMut + Sized { + /// Invokes the callback, consuming the guard. + fn commit(guard: Self) { + std::mem::drop(guard); + } +} + +impl ScopeGuarding for ScopeGuard {} + +pub const fn assert_send() {} +pub const fn assert_sync() {} + +/// Asserts that a slice is alphabetically sorted by a &[wstr] `name` field. +/// +/// Mainly useful for static asserts/const eval. +/// +/// # Panics +/// +/// This function panics if the given slice is unsorted. +/// +/// # Examples +/// +/// ``` +/// use widestring::{utf32str as L,Utf32Str as wstr}; +/// use fish_common::assert_sorted_by_name; +/// +/// const COLORS: &[(&wstr, u32)] = &[ +/// // must be in alphabetical order +/// (L!("blue"), 0x0000ff), +/// (L!("green"), 0x00ff00), +/// (L!("red"), 0xff0000), +/// ]; +/// +/// assert_sorted_by_name!(COLORS, 0); +/// ``` +/// +/// While this example would fail to compile: +/// +/// ```compile_fail +/// use widestring::{utf32str as L,Utf32Str as wstr}; +/// use fish_common::assert_sorted_by_name; +/// +/// const COLORS: &[(&wstr, u32)] = &[ +/// // not in alphabetical order +/// (L!("green"), 0x00ff00), +/// (L!("blue"), 0x0000ff), +/// (L!("red"), 0xff0000), +/// ]; +/// +/// assert_sorted_by_name!(COLORS, 0); +/// ``` +#[macro_export] +macro_rules! assert_sorted_by_name { + ($slice:expr, $field:tt) => { + const _: () = { + use std::cmp::Ordering; + + // ugly const eval workarounds below. + const fn cmp_i32(lhs: i32, rhs: i32) -> Ordering { + match lhs - rhs { + ..=-1 => Ordering::Less, + 0 => Ordering::Equal, + 1.. => Ordering::Greater, + } + } + + const fn cmp_slice(s1: &[char], s2: &[char]) -> Ordering { + let mut i = 0; + while i < s1.len() && i < s2.len() { + match cmp_i32(s1[i] as i32, s2[i] as i32) { + Ordering::Equal => i += 1, + other => return other, + } + } + cmp_i32(s1.len() as i32, s2.len() as i32) + } + + let mut i = 1; + while i < $slice.len() { + let prev = $slice[i - 1].$field.as_char_slice(); + let cur = $slice[i].$field.as_char_slice(); + if matches!(cmp_slice(prev, cur), Ordering::Greater) { + panic!("array must be sorted"); + } + i += 1; + } + }; + }; + ($slice:expr) => { + assert_sorted_by_name!($slice, name); + }; +} + +pub trait Named { + fn name(&self) -> &'static wstr; +} + +/// Return a reference to the first entry with the given name, assuming the entries are sorted by +/// name. Return None if not found. +pub fn get_by_sorted_name(name: &wstr, vals: &'static [T]) -> Option<&'static T> { + match vals.binary_search_by_key(&name, |val| val.name()) { + Ok(index) => Some(&vals[index]), + Err(_) => None, + } +} + +/// Given an input string, return a prefix of the string up to the first NUL character, +/// or the entire string if there is no NUL character. +pub fn truncate_at_nul(input: &wstr) -> &wstr { + match input.chars().position(|c| c == '\0') { + Some(nul_pos) => &input[..nul_pos], + None => input, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scoped_cell() { + let cell = ScopedCell::new(42); + + { + let _guard = cell.scoped_mod(|x| *x += 1); + assert_eq!(cell.get(), 43); + } + + assert_eq!(cell.get(), 42); + } + + #[test] + fn test_scoped_refcell() { + #[derive(Debug, PartialEq, Clone)] + struct Data { + x: i32, + y: i32, + } + + let cell = ScopedRefCell::new(Data { x: 1, y: 2 }); + + { + let _guard = cell.scoped_set(10, |d| &mut d.x); + assert_eq!(cell.borrow().x, 10); + } + assert_eq!(cell.borrow().x, 1); + + { + let _guard = cell.scoped_replace(Data { x: 42, y: 99 }); + assert_eq!(*cell.borrow(), Data { x: 42, y: 99 }); + } + assert_eq!(*cell.borrow(), Data { x: 1, y: 2 }); + } + + #[test] + fn test_scope_guard() { + let relaxed = std::sync::atomic::Ordering::Relaxed; + let counter = std::sync::atomic::AtomicUsize::new(0); + { + let guard = ScopeGuard::new(123, |arg| { + assert_eq!(arg, 123); + counter.fetch_add(1, relaxed); + }); + assert_eq!(counter.load(relaxed), 0); + std::mem::drop(guard); + assert_eq!(counter.load(relaxed), 1); + } + // commit also invokes the callback. + { + let guard = ScopeGuard::new(123, |arg| { + assert_eq!(arg, 123); + counter.fetch_add(1, relaxed); + }); + assert_eq!(counter.load(relaxed), 1); + ScopeGuard::commit(guard); + assert_eq!(counter.load(relaxed), 2); + } + } + + #[test] + fn test_truncate_at_nul() { + use widestring::utf32str as L; + assert_eq!(truncate_at_nul(L!("abc\0def")), L!("abc")); + assert_eq!(truncate_at_nul(L!("abc")), L!("abc")); + assert_eq!(truncate_at_nul(L!("\0abc")), L!("")); + } +} diff --git a/src/builtins/abbr.rs b/src/builtins/abbr.rs index 019ed42ae..b13a775f5 100644 --- a/src/builtins/abbr.rs +++ b/src/builtins/abbr.rs @@ -5,6 +5,7 @@ use crate::highlight::highlight_and_colorize; use crate::parser::ParserEnvSetMode; use crate::re::{regex_make_anchored, to_boxed_chars}; +use fish_common::help_section; use pcre2::utf32::{Regex, RegexBuilder}; const CMD: &wstr = L!("abbr"); diff --git a/src/builtins/bind.rs b/src/builtins/bind.rs index 458ec9feb..845301b48 100644 --- a/src/builtins/bind.rs +++ b/src/builtins/bind.rs @@ -11,6 +11,7 @@ use crate::key::{ self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys, }; +use fish_common::help_section; use std::sync::MutexGuard; const DEFAULT_BIND_MODE: &wstr = L!("default"); diff --git a/src/builtins/set.rs b/src/builtins/set.rs index fa628d111..8cb3b375d 100644 --- a/src/builtins/set.rs +++ b/src/builtins/set.rs @@ -21,6 +21,7 @@ env::{EnvMode, EnvVar, Environment}, wutil::wcstoi::wcstoi_partial, }; +use fish_common::help_section; localizable_consts!( MISMATCHED_ARGS diff --git a/src/builtins/shared.rs b/src/builtins/shared.rs index acc64106d..fa8ef68bf 100644 --- a/src/builtins/shared.rs +++ b/src/builtins/shared.rs @@ -8,6 +8,7 @@ use crate::proc::{Pid, ProcStatus, no_exec}; use crate::{builtins::*, wutil}; use errno::errno; +use fish_common::assert_sorted_by_name; use fish_wchar::L; use std::io::{BufRead, BufReader, Read}; diff --git a/src/color.rs b/src/color.rs index 493b83e5c..60de11b21 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,6 +1,7 @@ use std::cmp::Ordering; use crate::prelude::*; +use fish_common::assert_sorted_by_name; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Color24 { diff --git a/src/common.rs b/src/common.rs index abd191561..dd100f400 100644 --- a/src/common.rs +++ b/src/common.rs @@ -15,116 +15,20 @@ use crate::wcstringutil::str2bytes_callback; use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE}; use crate::wutil::fish_iswalnum; -use bitflags::bitflags; -use fish_common::{ - ASCII_MAX, BYTE_MAX, ENCODE_DIRECT_END, RESERVED_CHAR_BASE, RESERVED_CHAR_END, UCS2_MAX, - is_console_session, subslice_position, -}; use fish_fallback::fish_wcwidth; use fish_wchar::{decode_byte_from_char, encode_byte_to_char}; -use libc::{SIG_IGN, SIGTTOU, STDIN_FILENO}; -use std::cell::{Cell, RefCell}; use std::env; use std::ffi::{CStr, CString, OsString}; -use std::io::Read; -use std::mem; -use std::ops::{Deref, DerefMut}; use std::os::unix::prelude::*; -use std::sync::atomic::{AtomicI32, AtomicU32, Ordering}; +use std::sync::atomic::Ordering; use std::sync::{Arc, MutexGuard, OnceLock}; -use std::time; + +pub use fish_common::*; pub const BUILD_DIR: &str = env!("FISH_RESOLVED_BUILD_DIR"); pub const PACKAGE_NAME: &str = env!("CARGO_PKG_NAME"); -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EscapeStringStyle { - Script(EscapeFlags), - Url, - Var, - Regex, -} - -impl Default for EscapeStringStyle { - fn default() -> Self { - Self::Script(EscapeFlags::default()) - } -} - -impl TryFrom<&wstr> for EscapeStringStyle { - type Error = &'static wstr; - fn try_from(s: &wstr) -> Result { - use EscapeStringStyle::*; - match s { - s if s == "script" => Ok(Self::default()), - s if s == "var" => Ok(Var), - s if s == "url" => Ok(Url), - s if s == "regex" => Ok(Regex), - _ => Err(L!("Invalid escape style")), - } - } -} - -bitflags! { - /// Flags for the [`escape_string()`] function. These are only applicable when the escape style is - /// [`EscapeStringStyle::Script`]. - #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] - pub struct EscapeFlags: u32 { - /// Do not escape special fish syntax characters like the semicolon. Only escape non-printable - /// characters and backslashes. - const NO_PRINTABLES = 1 << 0; - /// Do not try to use 'simplified' quoted escapes, and do not use empty quotes as the empty - /// string. - const NO_QUOTED = 1 << 1; - /// Do not escape tildes. - const NO_TILDE = 1 << 2; - /// Replace non-printable control characters with Unicode symbols. - const SYMBOLIC = 1 << 3; - /// Escape , - const COMMA = 1 << 4; - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum UnescapeStringStyle { - Script(UnescapeFlags), - Url, - Var, -} - -impl Default for UnescapeStringStyle { - fn default() -> Self { - Self::Script(UnescapeFlags::default()) - } -} - -impl TryFrom<&wstr> for UnescapeStringStyle { - type Error = &'static wstr; - fn try_from(s: &wstr) -> Result { - use UnescapeStringStyle::*; - match s { - s if s == "script" => Ok(Self::default()), - s if s == "var" => Ok(Var), - s if s == "url" => Ok(Url), - _ => Err(L!("Invalid escape style")), - } - } -} - -bitflags! { - /// Flags for unescape_string functions. - #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] - pub struct UnescapeFlags: u32 { - /// escape special fish syntax characters like the semicolon - const SPECIAL = 1 << 0; - /// allow incomplete escape sequences - const INCOMPLETE = 1 << 1; - /// don't handle backslash escapes - const NO_BACKSLASHES = 1 << 2; - } -} - /// Replace special characters with backslash escape sequences. Newline is replaced with `\n`, etc. pub fn escape(s: &wstr) -> WString { escape_string(s, EscapeStringStyle::Script(EscapeFlags::default())) @@ -978,26 +882,10 @@ pub fn read_unquoted_escape( Some(in_pos) } -/// Exits without invoking destructors (via _exit), useful for code after fork. -pub fn exit_without_destructors(code: libc::c_int) -> ! { - unsafe { libc::_exit(code) }; -} - pub fn shell_modes() -> MutexGuard<'static, libc::termios> { crate::reader::SHELL_MODES.lock().unwrap() } -/// The character to use where the text has been truncated. -pub fn get_ellipsis_char() -> char { - '\u{2026}' // ('…') -} - -/// The character or string to use where text has been truncated (ellipsis if possible, otherwise -/// ...) -pub fn get_ellipsis_str() -> &'static wstr { - L!("\u{2026}") -} - /// Character representing an omitted newline at the end of text. pub fn get_omitted_newline_str() -> &'static str { OMITTED_NEWLINE_STR.load() @@ -1005,12 +893,6 @@ pub fn get_omitted_newline_str() -> &'static str { static OMITTED_NEWLINE_STR: AtomicRef = AtomicRef::new(&""); -static OBFUSCATION_READ_CHAR: AtomicU32 = AtomicU32::new(0); - -pub fn get_obfuscation_read_char() -> char { - char::from_u32(OBFUSCATION_READ_CHAR.load(Ordering::Relaxed)).unwrap() -} - /// Profiling flag. True if commands should be profiled. pub static PROFILING_ACTIVE: RelaxedAtomicBool = RelaxedAtomicBool::new(false); @@ -1094,15 +976,6 @@ fn append_escaped_str(output: &mut WString, input: &str) { result } -/// Given an input string, return a prefix of the string up to the first NUL character, -/// or the entire string if there is no NUL character. -pub fn truncate_at_nul(input: &wstr) -> &wstr { - match input.chars().position(|c| c == '\0') { - Some(nul_pos) => &input[..nul_pos], - None => input, - } -} - pub fn cstr2wcstring(input: &[u8]) -> WString { let input = CStr::from_bytes_until_nul(input).unwrap().to_bytes(); bytes2wcstring(input) @@ -1192,35 +1065,6 @@ pub fn init_special_chars_once() { } } -/// Call read, blocking and repeating on EINTR. Exits on EAGAIN. -/// Return the number of bytes read, or 0 on EOF, or an error. -pub fn read_blocked(fd: RawFd, buf: &mut [u8]) -> nix::Result { - loop { - let res = nix::unistd::read(unsafe { BorrowedFd::borrow_raw(fd) }, buf); - if let Err(nix::Error::EINTR) = res { - continue; - } - return res; - } -} - -pub trait ReadExt { - /// Like [`std::io::Read::read_to_end`], but does not retry on EINTR. - fn read_to_end_interruptible(&mut self, buf: &mut Vec) -> std::io::Result<()>; -} - -impl ReadExt for T { - fn read_to_end_interruptible(&mut self, buf: &mut Vec) -> std::io::Result<()> { - let mut chunk = [0_u8; 4096]; - loop { - match self.read(&mut chunk)? { - 0 => return Ok(()), - n => buf.extend_from_slice(&chunk[..n]), - } - } - } -} - /// Test if the string is a valid function name. pub fn valid_func_name(name: &wstr) -> bool { !(name.is_empty() @@ -1230,29 +1074,6 @@ pub fn valid_func_name(name: &wstr) -> bool { || name.contains('\0')) } -/// A rusty port of the C++ `write_loop()` function from `common.cpp`. This should be deprecated in -/// favor of native rust read/write methods at some point. -pub fn safe_write_loop(fd: &Fd, buf: &[u8]) -> std::io::Result<()> { - let fd = fd.as_raw_fd(); - let mut total = 0; - while total < buf.len() { - match nix::unistd::write(unsafe { BorrowedFd::borrow_raw(fd) }, &buf[total..]) { - Ok(written) => { - total += written; - } - Err(err) => { - if matches!(err, nix::Error::EAGAIN | nix::Error::EINTR) { - continue; - } - return Err(std::io::Error::from(err)); - } - } - } - Ok(()) -} - -pub use safe_write_loop as write_loop; - // Output writes always succeed; this adapter allows us to use it in a write-like macro. struct OutputWriteAdapter<'a, T: Output>(&'a mut T); @@ -1285,42 +1106,6 @@ macro_rules! write_to_output { }}; } -pub const fn help_section_exists(section: &str) -> bool { - let haystack = include_str!("../share/help_sections"); - let needle = section; - - let needle = needle.as_bytes(); - let haystack = haystack.as_bytes(); - let nlen = needle.len(); - let mut line_start = 0; - let mut i = 0; - while i <= haystack.len() { - if i == haystack.len() || haystack[i] == b'\n' { - let line_len = i - line_start; - - if line_len == nlen { - let mut j = 0; - while j < nlen && haystack[line_start + j] == needle[j] { - j += 1; - } - if j == nlen { - return true; - } - } - line_start = i + 1; - } - i += 1; - } - false -} - -macro_rules! help_section { - ($section:expr) => {{ - const _: () = assert!(crate::common::help_section_exists($section)); - $section - }}; -} - /// Write the given paragraph of output, redoing linebreaks to fit `termsize`. pub fn reformat_for_screen(msg: &wstr, termsize: &Termsize) -> WString { let mut buff = WString::new(); @@ -1382,45 +1167,6 @@ pub fn reformat_for_screen(msg: &wstr, termsize: &Termsize) -> WString { buff } -pub type Timepoint = f64; - -/// Return the number of seconds from the UNIX epoch, with subsecond precision. This function uses -/// the gettimeofday function and will have the same precision as that function. -pub fn timef() -> Timepoint { - match time::SystemTime::now().duration_since(time::UNIX_EPOCH) { - Ok(difference) => difference.as_secs() as f64, - Err(until_epoch) => -(until_epoch.duration().as_secs() as f64), - } -} - -/// Be able to restore the term's foreground process group. -/// This is set during startup and not modified after. -static INITIAL_FG_PROCESS_GROUP: AtomicI32 = AtomicI32::new(-1); // HACK, should be pid_t -const _: () = assert!(mem::size_of::() >= mem::size_of::()); - -/// Save the value of tcgetpgrp so we can restore it on exit. -pub fn save_term_foreground_process_group() { - INITIAL_FG_PROCESS_GROUP.store(unsafe { libc::tcgetpgrp(STDIN_FILENO) }, Ordering::Relaxed); -} - -pub fn restore_term_foreground_process_group_for_exit() { - // We wish to restore the tty to the initial owner. There's two ways this can go wrong: - // 1. We may steal the tty from someone else (#7060). - // 2. The call to tcsetpgrp may deliver SIGSTOP to us, and we will not exit. - // Hanging on exit seems worse, so ensure that SIGTTOU is ignored so we do not get SIGSTOP. - // Note initial_fg_process_group == 0 is possible with Linux pid namespaces. - // This is called during shutdown and from a signal handler. We don't bother to complain on - // failure because doing so is unlikely to be noticed. - // Safety: All of getpgrp, signal, and tcsetpgrp are async-signal-safe. - let initial_fg_process_group = INITIAL_FG_PROCESS_GROUP.load(Ordering::Relaxed); - if initial_fg_process_group > 0 && initial_fg_process_group != crate::nix::getpgrp() { - unsafe { - libc::signal(SIGTTOU, SIG_IGN); - libc::tcsetpgrp(STDIN_FILENO, initial_fg_process_group); - } - } -} - #[allow(unused)] // This function is unused in some configurations/on some platforms fn slice_contains_slice(a: &[T], b: &[T]) -> bool { @@ -1468,7 +1214,7 @@ pub fn is_windows_subsystem_for_linux(v: WSL) -> bool { let wsl = RESULT.get_or_init(|| { let release = unsafe { - let mut info = mem::MaybeUninit::uninit(); + let mut info = std::mem::MaybeUninit::uninit(); libc::uname(info.as_mut_ptr()); let info = info.assume_init(); info.release @@ -1550,325 +1296,6 @@ pub fn valid_var_name(s: &wstr) -> bool { !s.is_empty() && s.chars().all(valid_var_name_char) } -/// A wrapper around Cell which supports modifying the contents, scoped to a region of code. -/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the value, -/// instead of requiring an accessor function which returns a mutable reference to a field. -pub struct ScopedCell(Cell); - -impl Deref for ScopedCell { - type Target = Cell; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for ScopedCell { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl ScopedCell { - pub fn new(value: T) -> Self { - Self(Cell::new(value)) - } - - /// Temporarily modify a value in the ScopedCell, restoring it when the returned object is dropped. - /// - /// This is useful when you want to apply a change for the duration of a scope - /// without having to manually restore the previous value. - /// - /// # Example - /// - /// ``` - /// use fish::common::ScopedCell; - /// - /// let cell = ScopedCell::new(5); - /// assert_eq!(cell.get(), 5); - /// - /// { - /// let _guard = cell.scoped_mod(|v| *v += 10); - /// assert_eq!(cell.get(), 15); - /// } - /// - /// // Restored after scope - /// assert_eq!(cell.get(), 5); - /// ``` - pub fn scoped_mod<'a, Modifier: FnOnce(&mut T)>( - &'a self, - modifier: Modifier, - ) -> impl ScopeGuarding + 'a { - let mut val = self.get(); - modifier(&mut val); - let saved = self.replace(val); - ScopeGuard::new(self, move |cell| cell.set(saved)) - } -} - -/// A wrapper around RefCell which supports modifying the contents, scoped to a region of code. -pub struct ScopedRefCell(RefCell); - -impl Deref for ScopedRefCell { - type Target = RefCell; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for ScopedRefCell { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl ScopedRefCell { - pub fn new(value: T) -> Self { - Self(RefCell::new(value)) - } - - /// Temporarily modify a field in the ScopedRefCell, restoring it when the returned guard is dropped. - /// - /// This is useful when you want to change part of a data structure for the duration of a scope, - /// and automatically restore the original value afterward. - /// - /// The `accessor` function selects the field to modify by returning a mutable reference to it. - /// - /// # Example - /// ``` - /// use fish::common::ScopedRefCell; - /// - /// struct State { flag: bool } - /// - /// let cell = ScopedRefCell::new(State { flag: false }); - /// assert_eq!(cell.borrow().flag, false); - /// - /// { - /// let _guard = cell.scoped_set(true, |s| &mut s.flag); - /// assert_eq!(cell.borrow().flag, true); - /// } - /// - /// // Restored after scope - /// assert_eq!(cell.borrow().flag, false); - /// ``` - pub fn scoped_set<'a, Accessor, Value: 'a>( - &'a self, - value: Value, - accessor: Accessor, - ) -> impl ScopeGuarding + 'a - where - Accessor: Fn(&mut T) -> &mut Value + 'a, - { - let mut data = self.borrow_mut(); - let mut saved = std::mem::replace(accessor(&mut data), value); - ScopeGuard::new(self, move |cell| { - let mut data = cell.borrow_mut(); - std::mem::swap((accessor)(&mut data), &mut saved); - }) - } - - /// Convenience method for replacing the entire contents of the ScopedRefCell, restoring it when dropped. - /// - /// Equivalent to `scoped_set(value, |s| s)`. - /// - /// # Example - /// ``` - /// use fish::common::ScopedRefCell; - /// - /// let cell = ScopedRefCell::new(10); - /// assert_eq!(*cell.borrow(), 10); - /// - /// { - /// let _guard = cell.scoped_replace(99); - /// assert_eq!(*cell.borrow(), 99); - /// } - /// - /// assert_eq!(*cell.borrow(), 10); - /// ``` - pub fn scoped_replace<'a>(&'a self, value: T) -> impl ScopeGuarding + 'a { - self.scoped_set(value, |s| s) - } -} - -/// A RAII cleanup object. Unlike in C++ where there is no borrow checker, we can't just provide a -/// callback that modifies live objects willy-nilly because then there would be two &mut references -/// to the same object - the original variables we keep around to use and their captured references -/// held by the closure until its scope expires. -/// -/// Instead we have a `ScopeGuard` type that takes exclusive ownership of (a mutable reference to) -/// the object to be managed. In lieu of keeping the original value around, we obtain a regular or -/// mutable reference to it via ScopeGuard's [`Deref`] and [`DerefMut`] impls. -/// -/// The `ScopeGuard` is considered to be the exclusively owner of the passed value for the -/// duration of its lifetime. If you need to use the value again, use `ScopeGuard` to shadow the -/// value and obtain a reference to it via the `ScopeGuard` itself: -/// -/// ```rust -/// use std::io::prelude::*; -/// use fish::common::ScopeGuard; -/// -/// let file = std::fs::File::create("/dev/null").unwrap(); -/// // Create a scope guard to write to the file when the scope expires. -/// // To be able to still use the file, shadow `file` with the ScopeGuard itself. -/// let mut file = ScopeGuard::new(file, |mut file| file.write_all(b"goodbye\n").unwrap()); -/// // Now write to the file normally "through" the capturing ScopeGuard instance. -/// file.write_all(b"hello\n").unwrap(); -/// -/// // hello will be written first, then goodbye. -/// ``` -pub struct ScopeGuard(Option<(T, F)>); - -impl ScopeGuard { - /// Creates a new `ScopeGuard` wrapping `value`. The `on_drop` callback is executed when the - /// ScopeGuard's lifetime expires or when it is manually dropped. - pub fn new(value: T, on_drop: F) -> Self { - Self(Some((value, on_drop))) - } - - /// Invokes the callback, consuming the ScopeGuard. - pub fn commit(guard: Self) { - std::mem::drop(guard) - } - - /// Cancels the invocation of the callback, returning the original wrapped value. - pub fn cancel(mut guard: Self) -> T { - let (value, _) = guard.0.take().expect("Should always have Some value"); - value - } -} - -impl Deref for ScopeGuard { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0.as_ref().unwrap().0 - } -} - -impl DerefMut for ScopeGuard { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0.as_mut().unwrap().0 - } -} - -impl Drop for ScopeGuard { - fn drop(&mut self) { - if let Some((value, on_drop)) = self.0.take() { - on_drop(value); - } - } -} - -/// A trait expressing what ScopeGuard can do. This is necessary because our scoped cells return an -/// `impl Trait` object and therefore methods on ScopeGuard which take a self parameter cannot be -/// used. -pub trait ScopeGuarding: DerefMut + Sized { - /// Invokes the callback, consuming the guard. - fn commit(guard: Self) { - std::mem::drop(guard); - } -} - -impl ScopeGuarding for ScopeGuard {} - -pub const fn assert_send() {} -pub const fn assert_sync() {} - -/// Asserts that a slice is alphabetically sorted by a &[wstr] `name` field. -/// -/// Mainly useful for static asserts/const eval. -/// -/// # Panics -/// -/// This function panics if the given slice is unsorted. -/// -/// # Examples -/// -/// ``` -/// use fish::prelude::*; -/// use fish::assert_sorted_by_name; -/// -/// const COLORS: &[(&wstr, u32)] = &[ -/// // must be in alphabetical order -/// (L!("blue"), 0x0000ff), -/// (L!("green"), 0x00ff00), -/// (L!("red"), 0xff0000), -/// ]; -/// -/// assert_sorted_by_name!(COLORS, 0); -/// ``` -/// -/// While this example would fail to compile: -/// -/// ```compile_fail -/// use fish::prelude::*; -/// use fish::assert_sorted_by_name; -/// -/// const COLORS: &[(&wstr, u32)] = &[ -/// // not in alphabetical order -/// (L!("green"), 0x00ff00), -/// (L!("blue"), 0x0000ff), -/// (L!("red"), 0xff0000), -/// ]; -/// -/// assert_sorted_by_name!(COLORS, 0); -/// ``` -#[macro_export] -macro_rules! assert_sorted_by_name { - ($slice:expr, $field:tt) => { - const _: () = { - use std::cmp::Ordering; - - // ugly const eval workarounds below. - const fn cmp_i32(lhs: i32, rhs: i32) -> Ordering { - match lhs - rhs { - ..=-1 => Ordering::Less, - 0 => Ordering::Equal, - 1.. => Ordering::Greater, - } - } - - const fn cmp_slice(s1: &[char], s2: &[char]) -> Ordering { - let mut i = 0; - while i < s1.len() && i < s2.len() { - match cmp_i32(s1[i] as i32, s2[i] as i32) { - Ordering::Equal => i += 1, - other => return other, - } - } - cmp_i32(s1.len() as i32, s2.len() as i32) - } - - let mut i = 1; - while i < $slice.len() { - let prev = $slice[i - 1].$field.as_char_slice(); - let cur = $slice[i].$field.as_char_slice(); - if matches!(cmp_slice(prev, cur), Ordering::Greater) { - panic!("array must be sorted"); - } - i += 1; - } - }; - }; - ($slice:expr) => { - assert_sorted_by_name!($slice, name); - }; -} - -pub trait Named { - fn name(&self) -> &'static wstr; -} - -/// Return a reference to the first entry with the given name, assuming the entries are sorted by -/// name. Return None if not found. -pub fn get_by_sorted_name(name: &wstr, vals: &'static [T]) -> Option<&'static T> { - match vals.binary_search_by_key(&name, |val| val.name()) { - Ok(index) => Some(&vals[index]), - Err(_) => None, - } -} - /// A trait to make it more convenient to pass ascii/Unicode strings to functions that can take /// non-Unicode values. The result is nul-terminated and can be passed to OS functions. /// @@ -1950,9 +1377,8 @@ macro_rules! env_stack_set_from_env { mod tests { use super::{ - ENCODE_DIRECT_END, ESCAPE_TEST_CHAR, EscapeFlags, EscapeStringStyle, ScopeGuard, - ScopedCell, ScopedRefCell, UnescapeStringStyle, bytes2wcstring, escape_string, - truncate_at_nul, unescape_string, wcs2bytes, + ENCODE_DIRECT_END, ESCAPE_TEST_CHAR, EscapeFlags, EscapeStringStyle, UnescapeStringStyle, + bytes2wcstring, escape_string, unescape_string, wcs2bytes, }; use crate::util::get_seeded_rng; use fish_common::ENCODE_DIRECT_BASE; @@ -2203,73 +1629,6 @@ fn test_convert_private_use() { assert_eq!(wcs2bytes(&ws), s); } } - - #[test] - fn test_scoped_cell() { - let cell = ScopedCell::new(42); - - { - let _guard = cell.scoped_mod(|x| *x += 1); - assert_eq!(cell.get(), 43); - } - - assert_eq!(cell.get(), 42); - } - - #[test] - fn test_scoped_refcell() { - #[derive(Debug, PartialEq, Clone)] - struct Data { - x: i32, - y: i32, - } - - let cell = ScopedRefCell::new(Data { x: 1, y: 2 }); - - { - let _guard = cell.scoped_set(10, |d| &mut d.x); - assert_eq!(cell.borrow().x, 10); - } - assert_eq!(cell.borrow().x, 1); - - { - let _guard = cell.scoped_replace(Data { x: 42, y: 99 }); - assert_eq!(*cell.borrow(), Data { x: 42, y: 99 }); - } - assert_eq!(*cell.borrow(), Data { x: 1, y: 2 }); - } - - #[test] - fn test_scope_guard() { - let relaxed = std::sync::atomic::Ordering::Relaxed; - let counter = std::sync::atomic::AtomicUsize::new(0); - { - let guard = ScopeGuard::new(123, |arg| { - assert_eq!(arg, 123); - counter.fetch_add(1, relaxed); - }); - assert_eq!(counter.load(relaxed), 0); - std::mem::drop(guard); - assert_eq!(counter.load(relaxed), 1); - } - // commit also invokes the callback. - { - let guard = ScopeGuard::new(123, |arg| { - assert_eq!(arg, 123); - counter.fetch_add(1, relaxed); - }); - assert_eq!(counter.load(relaxed), 1); - ScopeGuard::commit(guard); - assert_eq!(counter.load(relaxed), 2); - } - } - - #[test] - fn test_truncate_at_nul() { - assert_eq!(truncate_at_nul(L!("abc\0def")), L!("abc")); - assert_eq!(truncate_at_nul(L!("abc")), L!("abc")); - assert_eq!(truncate_at_nul(L!("\0abc")), L!("")); - } } #[cfg(feature = "benchmark")] diff --git a/src/env/var.rs b/src/env/var.rs index 23bb7885f..7108d8610 100644 --- a/src/env/var.rs +++ b/src/env/var.rs @@ -1,6 +1,7 @@ use crate::signal::Signal; use crate::wcstringutil::join_strings; use bitflags::bitflags; +use fish_common::assert_sorted_by_name; use fish_wchar::{L, WString, wstr}; use libc::c_int; use std::collections::HashMap; diff --git a/src/input.rs b/src/input.rs index c663c03be..ea3e574b1 100644 --- a/src/input.rs +++ b/src/input.rs @@ -10,6 +10,7 @@ use crate::prelude::*; use crate::reader::{Reader, reader_reset_interrupted}; use crate::threads::assert_is_main_thread; +use fish_common::assert_sorted_by_name; use std::mem; use std::sync::{ LazyLock, Mutex, MutexGuard, diff --git a/src/parse_execution.rs b/src/parse_execution.rs index dda145900..28f79f43a 100644 --- a/src/parse_execution.rs +++ b/src/parse_execution.rs @@ -52,6 +52,7 @@ use crate::tokenizer::{PipeOrRedir, TokenType, variable_assignment_equals_pos}; use crate::trace::{trace_if_enabled, trace_if_enabled_with_args}; use crate::wildcard::wildcard_match; +use fish_common::help_section; use fish_wchar::WExt; use libc::{ENOTDIR, EXIT_SUCCESS, STDERR_FILENO, STDOUT_FILENO, c_int}; use std::io::ErrorKind; diff --git a/src/parse_util.rs b/src/parse_util.rs index 756075d00..b1409c257 100644 --- a/src/parse_util.rs +++ b/src/parse_util.rs @@ -30,6 +30,7 @@ use crate::wcstringutil::count_newlines; use crate::wcstringutil::truncate; use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE}; +use fish_common::help_section; use std::ops::Range; use std::{iter, ops}; diff --git a/src/reader/reader.rs b/src/reader/reader.rs index e3623f1b1..79f39b5c7 100644 --- a/src/reader/reader.rs +++ b/src/reader/reader.rs @@ -18,7 +18,7 @@ //! expansion, etc. use crate::portable_atomic::AtomicU64; -use fish_common::UTF8_BOM_WCHAR; +use fish_common::{UTF8_BOM_WCHAR, help_section}; use libc::{ _POSIX_VDISABLE, ECHO, EINTR, EIO, EISDIR, ENOTTY, EPERM, ESRCH, ICANON, ICRNL, IEXTEN, INLCR, IXOFF, IXON, O_NONBLOCK, O_RDONLY, ONLCR, OPOST, SIGINT, SIGTTIN, STDERR_FILENO, STDIN_FILENO, diff --git a/src/tinyexpr.rs b/src/tinyexpr.rs index fe2870cb7..326f42d69 100644 --- a/src/tinyexpr.rs +++ b/src/tinyexpr.rs @@ -34,6 +34,7 @@ prelude::*, wutil::{Error as wcstodError, wcstod::wcstod_underscores}, }; +use fish_common::assert_sorted_by_name; #[derive(Clone, Copy)] enum Function {