mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-25 14:51:15 -03:00
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
This commit is contained in:
committed by
Johannes Altmanninger
parent
4762d6a0a7
commit
116bcdac28
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -207,8 +207,10 @@ dependencies = [
|
||||
name = "fish-common"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"nix",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<usize> {
|
||||
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<u8>) -> std::io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Read + ?Sized> ReadExt for T {
|
||||
fn read_to_end_interruptible(&mut self, buf: &mut Vec<u8>) -> 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: AsRawFd>(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::<i32>() >= mem::size_of::<libc::pid_t>());
|
||||
|
||||
/// 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<T>(Cell<T>);
|
||||
|
||||
impl<T> Deref for ScopedCell<T> {
|
||||
type Target = Cell<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ScopedCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> ScopedCell<T> {
|
||||
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<T>(RefCell<T>);
|
||||
|
||||
impl<T> Deref for ScopedRefCell<T> {
|
||||
type Target = RefCell<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ScopedRefCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ScopedRefCell<T> {
|
||||
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<T, F: FnOnce(T)>(Option<(T, F)>);
|
||||
|
||||
impl<T, F: FnOnce(T)> ScopeGuard<T, F> {
|
||||
/// 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<T, F: FnOnce(T)> Deref for ScopeGuard<T, F> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0.as_ref().unwrap().0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F: FnOnce(T)> DerefMut for ScopeGuard<T, F> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0.as_mut().unwrap().0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F: FnOnce(T)> Drop for ScopeGuard<T, F> {
|
||||
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<T, F: FnOnce(T)> ScopeGuarding for ScopeGuard<T, F> {}
|
||||
|
||||
pub const fn assert_send<T: Send>() {}
|
||||
pub const fn assert_sync<T: Sync>() {}
|
||||
|
||||
/// Asserts that a slice is alphabetically sorted by a <code>&[wstr]</code> `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<T: Named>(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!(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
env::{EnvMode, EnvVar, Environment},
|
||||
wutil::wcstoi::wcstoi_partial,
|
||||
};
|
||||
use fish_common::help_section;
|
||||
|
||||
localizable_consts!(
|
||||
MISMATCHED_ARGS
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
653
src/common.rs
653
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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<str> = 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<usize> {
|
||||
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<u8>) -> std::io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Read + ?Sized> ReadExt for T {
|
||||
fn read_to_end_interruptible(&mut self, buf: &mut Vec<u8>) -> 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: AsRawFd>(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::<i32>() >= mem::size_of::<libc::pid_t>());
|
||||
|
||||
/// 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<T: Eq>(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<T>(Cell<T>);
|
||||
|
||||
impl<T> Deref for ScopedCell<T> {
|
||||
type Target = Cell<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ScopedCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> ScopedCell<T> {
|
||||
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<T>(RefCell<T>);
|
||||
|
||||
impl<T> Deref for ScopedRefCell<T> {
|
||||
type Target = RefCell<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ScopedRefCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ScopedRefCell<T> {
|
||||
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<T, F: FnOnce(T)>(Option<(T, F)>);
|
||||
|
||||
impl<T, F: FnOnce(T)> ScopeGuard<T, F> {
|
||||
/// 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<T, F: FnOnce(T)> Deref for ScopeGuard<T, F> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0.as_ref().unwrap().0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F: FnOnce(T)> DerefMut for ScopeGuard<T, F> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0.as_mut().unwrap().0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F: FnOnce(T)> Drop for ScopeGuard<T, F> {
|
||||
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<T, F: FnOnce(T)> ScopeGuarding for ScopeGuard<T, F> {}
|
||||
|
||||
pub const fn assert_send<T: Send>() {}
|
||||
pub const fn assert_sync<T: Sync>() {}
|
||||
|
||||
/// Asserts that a slice is alphabetically sorted by a <code>&[wstr]</code> `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<T: Named>(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")]
|
||||
|
||||
1
src/env/var.rs
vendored
1
src/env/var.rs
vendored
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
prelude::*,
|
||||
wutil::{Error as wcstodError, wcstod::wcstod_underscores},
|
||||
};
|
||||
use fish_common::assert_sorted_by_name;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Function {
|
||||
|
||||
Reference in New Issue
Block a user