mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-21 11:31:15 -03:00
Compare commits
24 Commits
39239724ec
...
440e7fcbc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
440e7fcbc1 | ||
|
|
52495c8124 | ||
|
|
467b03d715 | ||
|
|
b5c40478f6 | ||
|
|
1286745e78 | ||
|
|
b99ae291d6 | ||
|
|
8ae71c80f4 | ||
|
|
cf6170200c | ||
|
|
c13038b968 | ||
|
|
816077281d | ||
|
|
78ea24a262 | ||
|
|
6a5b9bcde1 | ||
|
|
b7b786aabf | ||
|
|
dc63b7bb20 | ||
|
|
9c819c020e | ||
|
|
dc9b1141c8 | ||
|
|
3d364478ee | ||
|
|
faf331fdad | ||
|
|
47b6c0aec2 | ||
|
|
eb478bfc3e | ||
|
|
63cf79f5f6 | ||
|
|
ff284d642e | ||
|
|
cc64da62a9 | ||
|
|
a974fe990f |
@@ -15,6 +15,7 @@ Improved terminal support
|
||||
|
||||
Other improvements
|
||||
------------------
|
||||
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
|
||||
- ``fish_update_completions`` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10).
|
||||
|
||||
For distributors and developers
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -330,6 +330,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fish-build-helper",
|
||||
"fish-feature-flags",
|
||||
"fish-widestring",
|
||||
"libc",
|
||||
"nix",
|
||||
@@ -445,6 +446,7 @@ version = "0.0.0"
|
||||
name = "fish-widestring"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
@@ -8,11 +8,29 @@ if [ "$FISH_CHECK_LINT" = false ]; then
|
||||
lint=false
|
||||
fi
|
||||
|
||||
case "$(uname)" in
|
||||
MSYS*)
|
||||
is_cygwin=true
|
||||
cygwin_var=MSYS
|
||||
;;
|
||||
CYGWIN*)
|
||||
is_cygwin=true
|
||||
cygwin_var=CYGWIN
|
||||
;;
|
||||
*)
|
||||
is_cygwin=false
|
||||
;;
|
||||
esac
|
||||
|
||||
check_dependency_versions=false
|
||||
if [ "${FISH_CHECK_DEPENDENCY_VERSIONS:-false}" != false ]; then
|
||||
check_dependency_versions=true
|
||||
fi
|
||||
|
||||
green='\e[0;32m'
|
||||
yellow='\e[1;33m'
|
||||
reset='\e[m'
|
||||
|
||||
if $check_dependency_versions; then
|
||||
command -v curl
|
||||
command -v jq
|
||||
@@ -83,12 +101,45 @@ if $lint; then
|
||||
cargo clippy --workspace --all-targets $features
|
||||
done
|
||||
fi
|
||||
cargo test --no-default-features --workspace --all-targets
|
||||
|
||||
# When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`)
|
||||
# are dynamically linked against Rust's `std-xxx.dll` instead of being
|
||||
# statically link as they usually are.
|
||||
# On Cygwin, `PATH`is not properly updated to point to the `std-xxx.dll`
|
||||
# location, so we have to do it manually.
|
||||
# See:
|
||||
# - https://github.com/rust-lang/rust/issues/149050
|
||||
# - https://github.com/msys2/MSYS2-packages/issues/5784
|
||||
(
|
||||
if $is_cygwin; then
|
||||
export PATH="$PATH:$(rustc --print target-libdir)"
|
||||
fi
|
||||
cargo test --no-default-features --workspace --all-targets
|
||||
)
|
||||
cargo test --doc --workspace
|
||||
|
||||
if $lint; then
|
||||
cargo doc --workspace --no-deps
|
||||
fi
|
||||
FISH_GETTEXT_EXTRACTION_DIR=$gettext_template_dir "$workspace_root/tests/test_driver.py" "$build_dir"
|
||||
|
||||
# Using "()" not "{}" because we do want a subshell (for the export)
|
||||
system_tests() (
|
||||
[ -n "$@" ] && export "$@"
|
||||
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
|
||||
"$workspace_root/tests/test_driver.py" "$build_dir"
|
||||
)
|
||||
|
||||
test_cmd='FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir" "$workspace_root/tests/test_driver.py" "$build_dir"'
|
||||
if $is_cygwin; then
|
||||
echo -e "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}"
|
||||
system_tests $cygwin_var=winsymlinks
|
||||
|
||||
echo -e "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}"
|
||||
system_tests $cygwin_var=winsymlinks
|
||||
else
|
||||
echo -e "=== Running ${green}integration tests${reset}"
|
||||
system_tests
|
||||
fi
|
||||
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
fish-feature-flags.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
libc.workspace = true
|
||||
nix.workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,7 @@
|
||||
//! Helper functions for working with wcstring.
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, CString, OsString},
|
||||
os::unix::ffi::OsStringExt as _,
|
||||
};
|
||||
|
||||
use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy};
|
||||
use fish_widestring::{ELLIPSIS_CHAR, decode_byte_from_char, prelude::*};
|
||||
use fish_widestring::{ELLIPSIS_CHAR, prelude::*};
|
||||
|
||||
/// Return the number of newlines in a string.
|
||||
pub fn count_newlines(s: &wstr) -> usize {
|
||||
@@ -340,145 +335,6 @@ pub fn string_fuzzy_match_string(
|
||||
StringFuzzyMatch::try_create(string, match_against, anchor_start)
|
||||
}
|
||||
|
||||
/// Implementation of wcs2bytes that accepts a callback.
|
||||
/// The first argument can be either a `&str` or `&wstr`.
|
||||
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
|
||||
/// input, doing one invocation per character.
|
||||
/// If `func` returns false, it stops; otherwise it continues.
|
||||
/// Return false if the callback returned false, otherwise true.
|
||||
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
|
||||
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
|
||||
let mut converted = [0_u8; 4];
|
||||
|
||||
for c in input.chars() {
|
||||
let bytes = if let Some(byte) = decode_byte_from_char(c) {
|
||||
converted[0] = byte;
|
||||
&converted[..=0]
|
||||
} else {
|
||||
c.encode_utf8(&mut converted).as_bytes()
|
||||
};
|
||||
if !func(bytes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns a newly allocated multibyte character string equivalent of the specified wide character
|
||||
/// string.
|
||||
///
|
||||
/// This function decodes illegal character sequences in a reversible way using the private use
|
||||
/// area.
|
||||
pub fn wcs2bytes(input: impl IntoCharIter) -> Vec<u8> {
|
||||
let mut result = vec![];
|
||||
wcs2bytes_appending(&mut result, input);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn wcs2osstring(input: &wstr) -> OsString {
|
||||
if input.is_empty() {
|
||||
return OsString::new();
|
||||
}
|
||||
|
||||
let mut result = vec![];
|
||||
wcs2bytes_appending(&mut result, input);
|
||||
OsString::from_vec(result)
|
||||
}
|
||||
|
||||
/// Same as [`wcs2bytes`]. Meant to be used when we need a zero-terminated string to feed legacy APIs.
|
||||
/// Note: if `input` contains any interior NUL bytes, the result will be truncated at the first!
|
||||
pub fn wcs2zstring(input: &wstr) -> CString {
|
||||
if input.is_empty() {
|
||||
return CString::default();
|
||||
}
|
||||
|
||||
let mut vec = Vec::with_capacity(input.len() + 1);
|
||||
str2bytes_callback(input, |buff| {
|
||||
vec.extend_from_slice(buff);
|
||||
true
|
||||
});
|
||||
vec.push(b'\0');
|
||||
|
||||
match CString::from_vec_with_nul(vec) {
|
||||
Ok(cstr) => cstr,
|
||||
Err(err) => {
|
||||
// `input` contained a NUL in the middle; we can retrieve `vec`, though
|
||||
let mut vec = err.into_bytes();
|
||||
let pos = vec.iter().position(|c| *c == b'\0').unwrap();
|
||||
vec.truncate(pos + 1);
|
||||
// Safety: We truncated after the first NUL
|
||||
unsafe { CString::from_vec_with_nul_unchecked(vec) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`wcs2bytes`], but appends to `output` instead of returning a new string.
|
||||
pub fn wcs2bytes_appending(output: &mut Vec<u8>, input: impl IntoCharIter) {
|
||||
str2bytes_callback(input, |buff| {
|
||||
output.extend_from_slice(buff);
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This is only implemented for owned types where an owned instance will skip allocations (e.g.
|
||||
/// `CString` can return `self`) but not implemented for owned instances where a new allocation is
|
||||
/// always required (e.g. implemented for `&wstr` but not `WideString`) because you might as well be
|
||||
/// left with the original item if we're going to allocate from scratch in all cases.
|
||||
pub trait ToCString {
|
||||
/// Correctly convert to a nul-terminated [`CString`] that can be passed to OS functions.
|
||||
fn to_cstring(self) -> CString;
|
||||
}
|
||||
|
||||
impl ToCString for CString {
|
||||
fn to_cstring(self) -> CString {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCString for &CStr {
|
||||
fn to_cstring(self) -> CString {
|
||||
self.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely converts from `&wstr` to a `CString` to a nul-terminated `CString` that can be passed to
|
||||
/// OS functions, taking into account non-Unicode values that have been shifted into the private-use
|
||||
/// range by using [`wcs2zstring()`].
|
||||
impl ToCString for &wstr {
|
||||
/// The wide string may contain non-Unicode bytes mapped to the private-use Unicode range, so we
|
||||
/// have to use [`wcs2zstring()`](self::wcs2zstring) to convert it correctly.
|
||||
fn to_cstring(self) -> CString {
|
||||
self::wcs2zstring(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely converts from `&WString` to a nul-terminated `CString` that can be passed to OS
|
||||
/// functions, taking into account non-Unicode values that have been shifted into the private-use
|
||||
/// range by using [`wcs2zstring()`].
|
||||
impl ToCString for &WString {
|
||||
fn to_cstring(self) -> CString {
|
||||
self.as_utfstr().to_cstring()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a (probably ascii) string to CString that can be passed to OS functions.
|
||||
impl ToCString for Vec<u8> {
|
||||
fn to_cstring(mut self) -> CString {
|
||||
self.push(b'\0');
|
||||
CString::from_vec_with_nul(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a (probably ascii) string to nul-terminated CString that can be passed to OS functions.
|
||||
impl ToCString for &[u8] {
|
||||
fn to_cstring(self) -> CString {
|
||||
CString::new(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Split a string by runs of any of the separator characters provided in `seps`.
|
||||
/// Note the delimiters are the characters in `seps`, not `seps` itself.
|
||||
/// `seps` may contain the NUL character.
|
||||
|
||||
@@ -7,6 +7,7 @@ repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc.workspace = true
|
||||
unicode-width.workspace = true
|
||||
widestring.workspace = true
|
||||
|
||||
|
||||
@@ -6,16 +6,34 @@
|
||||
|
||||
pub mod word_char;
|
||||
|
||||
use std::{iter, slice};
|
||||
use std::{
|
||||
ffi::{CStr, CString, OsStr, OsString},
|
||||
iter,
|
||||
os::unix::ffi::{OsStrExt as _, OsStringExt as _},
|
||||
slice,
|
||||
};
|
||||
pub use widestring::{Utf32Str as wstr, Utf32String as WString, utf32str as L, utfstr::CharsUtf32};
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{IntoCharIter, L, ToWString, WExt, WString, wstr};
|
||||
}
|
||||
|
||||
// Highest legal ASCII value.
|
||||
pub const ASCII_MAX: char = 127 as char;
|
||||
|
||||
// Highest legal 16-bit Unicode value.
|
||||
pub const UCS2_MAX: char = '\u{FFFF}';
|
||||
|
||||
// Highest legal byte value.
|
||||
pub const BYTE_MAX: char = 0xFF as char;
|
||||
|
||||
// Unicode BOM value.
|
||||
pub const UTF8_BOM_WCHAR: char = '\u{FEFF}';
|
||||
|
||||
/// The character to use where the text has been truncated.
|
||||
pub const ELLIPSIS_CHAR: char = '\u{2026}'; // ('…')
|
||||
|
||||
pub const SPECIAL_KEY_ENCODE_BASE: char = '\u{F500}';
|
||||
// These are in the Unicode private-use range. We really shouldn't use this
|
||||
// range but have little choice in the matter given how our lexer/parser works.
|
||||
// We can't use non-characters for these two ranges because there are only 66 of
|
||||
@@ -28,9 +46,79 @@ pub mod prelude {
|
||||
// Note: We don't use the highest 8 bit range (0xF800 - 0xF8FF) because we know
|
||||
// of at least one use of a codepoint in that range: the Apple symbol (0xF8FF)
|
||||
// on Mac OS X. See http://www.unicode.org/faq/private_use.html.
|
||||
pub const ENCODE_DIRECT_BASE: char = '\u{F600}';
|
||||
pub const ENCODE_DIRECT_BASE: char = char_offset(SPECIAL_KEY_ENCODE_BASE, 256);
|
||||
pub const ENCODE_DIRECT_END: char = char_offset(ENCODE_DIRECT_BASE, 256);
|
||||
|
||||
// Use Unicode "non-characters" for internal characters as much as we can. This
|
||||
// gives us 32 "characters" for internal use that we can guarantee should not
|
||||
// appear in our input stream. See http://www.unicode.org/faq/private_use.html.
|
||||
pub const RESERVED_CHAR_BASE: char = '\u{FDD0}';
|
||||
pub const RESERVED_CHAR_END: char = '\u{FDF0}';
|
||||
// Split the available non-character values into two ranges to ensure there are
|
||||
// no conflicts among the places we use these special characters.
|
||||
pub const EXPAND_RESERVED_BASE: char = RESERVED_CHAR_BASE;
|
||||
pub const EXPAND_RESERVED_END: char = char_offset(EXPAND_RESERVED_BASE, 16);
|
||||
pub const WILDCARD_RESERVED_BASE: char = EXPAND_RESERVED_END;
|
||||
pub const WILDCARD_RESERVED_END: char = char_offset(WILDCARD_RESERVED_BASE, 16);
|
||||
// Make sure the ranges defined above don't exceed the range for non-characters.
|
||||
// This is to make sure we didn't do something stupid in subdividing the
|
||||
// Unicode range for our needs.
|
||||
const _: () = assert!(WILDCARD_RESERVED_END <= RESERVED_CHAR_END);
|
||||
|
||||
/// Character representing any character except '/' (slash).
|
||||
pub const ANY_CHAR: char = char_offset(WILDCARD_RESERVED_BASE, 0);
|
||||
/// Character representing any character string not containing '/' (slash).
|
||||
pub const ANY_STRING: char = char_offset(WILDCARD_RESERVED_BASE, 1);
|
||||
/// Character representing any character string.
|
||||
pub const ANY_STRING_RECURSIVE: char = char_offset(WILDCARD_RESERVED_BASE, 2);
|
||||
/// This is a special pseudo-char that is not used other than to mark the
|
||||
/// end of the special characters so we can sanity check the enum range.
|
||||
#[allow(dead_code)]
|
||||
pub const ANY_SENTINEL: char = char_offset(WILDCARD_RESERVED_BASE, 3);
|
||||
|
||||
/// Character representing a home directory.
|
||||
pub const HOME_DIRECTORY: char = char_offset(EXPAND_RESERVED_BASE, 0);
|
||||
/// Character representing process expansion for %self.
|
||||
pub const PROCESS_EXPAND_SELF: char = char_offset(EXPAND_RESERVED_BASE, 1);
|
||||
/// Character representing variable expansion.
|
||||
pub const VARIABLE_EXPAND: char = char_offset(EXPAND_RESERVED_BASE, 2);
|
||||
/// Character representing variable expansion into a single element.
|
||||
pub const VARIABLE_EXPAND_SINGLE: char = char_offset(EXPAND_RESERVED_BASE, 3);
|
||||
/// Character representing the start of a bracket expansion.
|
||||
pub const BRACE_BEGIN: char = char_offset(EXPAND_RESERVED_BASE, 4);
|
||||
/// Character representing the end of a bracket expansion.
|
||||
pub const BRACE_END: char = char_offset(EXPAND_RESERVED_BASE, 5);
|
||||
/// Character representing separation between two bracket elements.
|
||||
pub const BRACE_SEP: char = char_offset(EXPAND_RESERVED_BASE, 6);
|
||||
/// Character that takes the place of any whitespace within non-quoted text in braces
|
||||
pub const BRACE_SPACE: char = char_offset(EXPAND_RESERVED_BASE, 7);
|
||||
/// Separate subtokens in a token with this character.
|
||||
pub const INTERNAL_SEPARATOR: char = char_offset(EXPAND_RESERVED_BASE, 8);
|
||||
/// Character representing an empty variable expansion. Only used transitively while expanding
|
||||
/// variables.
|
||||
pub const VARIABLE_EXPAND_EMPTY: char = char_offset(EXPAND_RESERVED_BASE, 9);
|
||||
|
||||
const _: () = assert!(
|
||||
EXPAND_RESERVED_END as u32 > VARIABLE_EXPAND_EMPTY as u32,
|
||||
"Characters used in expansions must stay within private use area"
|
||||
);
|
||||
|
||||
/// The string represented by PROCESS_EXPAND_SELF
|
||||
pub const PROCESS_EXPAND_SELF_STR: &wstr = L!("%self");
|
||||
|
||||
/// Return true if the character is in a range reserved for fish's private use.
|
||||
///
|
||||
/// NOTE: This is used when tokenizing the input. It is also used when reading input, before
|
||||
/// tokenization, to replace such chars with REPLACEMENT_WCHAR if they're not part of a quoted
|
||||
/// string. We don't want external input to be able to feed reserved characters into our
|
||||
/// lexer/parser or code evaluator.
|
||||
//
|
||||
// TODO: Actually implement the replacement as documented above.
|
||||
pub fn fish_reserved_codepoint(c: char) -> bool {
|
||||
(c >= RESERVED_CHAR_BASE && c < RESERVED_CHAR_END)
|
||||
|| (c >= SPECIAL_KEY_ENCODE_BASE && c < ENCODE_DIRECT_END)
|
||||
}
|
||||
|
||||
/// Encode a literal byte in a UTF-32 character. This is required for e.g. the echo builtin, whose
|
||||
/// escape sequences can be used to construct raw byte sequences which are then interpreted as e.g.
|
||||
/// UTF-8 by the terminal. If we were to interpret each of those bytes as a codepoint and encode it
|
||||
@@ -43,6 +131,86 @@ pub fn encode_byte_to_char(byte: u8) -> char {
|
||||
.expect("private-use codepoint should be valid char")
|
||||
}
|
||||
|
||||
/// Returns a newly allocated multibyte character string equivalent of the specified wide character
|
||||
/// string.
|
||||
///
|
||||
/// This function decodes illegal character sequences in a reversible way using the private use
|
||||
/// area.
|
||||
pub fn wcs2bytes(input: impl IntoCharIter) -> Vec<u8> {
|
||||
let mut result = vec![];
|
||||
wcs2bytes_appending(&mut result, input);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn wcs2osstring(input: &wstr) -> OsString {
|
||||
if input.is_empty() {
|
||||
return OsString::new();
|
||||
}
|
||||
|
||||
let mut result = vec![];
|
||||
wcs2bytes_appending(&mut result, input);
|
||||
OsString::from_vec(result)
|
||||
}
|
||||
|
||||
/// Same as [`wcs2bytes`]. Meant to be used when we need a zero-terminated string to feed legacy APIs.
|
||||
/// Note: if `input` contains any interior NUL bytes, the result will be truncated at the first!
|
||||
pub fn wcs2zstring(input: &wstr) -> CString {
|
||||
if input.is_empty() {
|
||||
return CString::default();
|
||||
}
|
||||
|
||||
let mut vec = Vec::with_capacity(input.len() + 1);
|
||||
str2bytes_callback(input, |buff| {
|
||||
vec.extend_from_slice(buff);
|
||||
true
|
||||
});
|
||||
vec.push(b'\0');
|
||||
|
||||
match CString::from_vec_with_nul(vec) {
|
||||
Ok(cstr) => cstr,
|
||||
Err(err) => {
|
||||
// `input` contained a NUL in the middle; we can retrieve `vec`, though
|
||||
let mut vec = err.into_bytes();
|
||||
let pos = vec.iter().position(|c| *c == b'\0').unwrap();
|
||||
vec.truncate(pos + 1);
|
||||
// Safety: We truncated after the first NUL
|
||||
unsafe { CString::from_vec_with_nul_unchecked(vec) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`wcs2bytes`], but appends to `output` instead of returning a new string.
|
||||
pub fn wcs2bytes_appending(output: &mut Vec<u8>, input: impl IntoCharIter) {
|
||||
str2bytes_callback(input, |buff| {
|
||||
output.extend_from_slice(buff);
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
/// Implementation of wcs2bytes that accepts a callback.
|
||||
/// The first argument can be either a `&str` or `&wstr`.
|
||||
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
|
||||
/// input, doing one invocation per character.
|
||||
/// If `func` returns false, it stops; otherwise it continues.
|
||||
/// Return false if the callback returned false, otherwise true.
|
||||
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
|
||||
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
|
||||
let mut converted = [0_u8; 4];
|
||||
|
||||
for c in input.chars() {
|
||||
let bytes = if let Some(byte) = decode_byte_from_char(c) {
|
||||
converted[0] = byte;
|
||||
&converted[..=0]
|
||||
} else {
|
||||
c.encode_utf8(&mut converted).as_bytes()
|
||||
};
|
||||
if !func(bytes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Decode a literal byte from a UTF-32 character.
|
||||
pub fn decode_byte_from_char(c: char) -> Option<u8> {
|
||||
if c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END {
|
||||
@@ -56,6 +224,65 @@ pub fn decode_byte_from_char(c: char) -> Option<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This is only implemented for owned types where an owned instance will skip allocations (e.g.
|
||||
/// `CString` can return `self`) but not implemented for owned instances where a new allocation is
|
||||
/// always required (e.g. implemented for `&wstr` but not `WideString`) because you might as well be
|
||||
/// left with the original item if we're going to allocate from scratch in all cases.
|
||||
pub trait ToCString {
|
||||
/// Correctly convert to a nul-terminated [`CString`] that can be passed to OS functions.
|
||||
fn to_cstring(self) -> CString;
|
||||
}
|
||||
|
||||
impl ToCString for CString {
|
||||
fn to_cstring(self) -> CString {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCString for &CStr {
|
||||
fn to_cstring(self) -> CString {
|
||||
self.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely converts from `&wstr` to a `CString` to a nul-terminated `CString` that can be passed to
|
||||
/// OS functions, taking into account non-Unicode values that have been shifted into the private-use
|
||||
/// range by using [`wcs2zstring()`].
|
||||
impl ToCString for &wstr {
|
||||
/// The wide string may contain non-Unicode bytes mapped to the private-use Unicode range, so we
|
||||
/// have to use [`wcs2zstring()`](self::wcs2zstring) to convert it correctly.
|
||||
fn to_cstring(self) -> CString {
|
||||
self::wcs2zstring(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely converts from `&WString` to a nul-terminated `CString` that can be passed to OS
|
||||
/// functions, taking into account non-Unicode values that have been shifted into the private-use
|
||||
/// range by using [`wcs2zstring()`].
|
||||
impl ToCString for &WString {
|
||||
fn to_cstring(self) -> CString {
|
||||
self.as_utfstr().to_cstring()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a (probably ascii) string to CString that can be passed to OS functions.
|
||||
impl ToCString for Vec<u8> {
|
||||
fn to_cstring(mut self) -> CString {
|
||||
self.push(b'\0');
|
||||
CString::from_vec_with_nul(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a (probably ascii) string to nul-terminated CString that can be passed to OS functions.
|
||||
impl ToCString for &[u8] {
|
||||
fn to_cstring(self) -> CString {
|
||||
CString::new(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
mod decoder {
|
||||
use crate::{ENCODE_DIRECT_BASE, ENCODE_DIRECT_END, char_offset, wstr};
|
||||
use buffer::Buffer;
|
||||
@@ -276,6 +503,82 @@ pub const fn char_offset(base: char, offset: u32) -> char {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the bytes in `input` into a [`WString`], encoding non-UTF-8 bytes into private-use-area
|
||||
/// code-points. Bytes which would be parsed into our reserved PUA range are encoded individually,
|
||||
/// to allow for correct round-tripping.
|
||||
pub fn bytes2wcstring(mut input: &[u8]) -> WString {
|
||||
if input.is_empty() {
|
||||
return WString::new();
|
||||
}
|
||||
|
||||
let mut result = WString::with_capacity(input.len());
|
||||
|
||||
fn append_escaped_str(output: &mut WString, input: &str) {
|
||||
for (i, c) in input.char_indices() {
|
||||
if fish_reserved_codepoint(c) {
|
||||
for byte in &input.as_bytes()[i..i + c.len_utf8()] {
|
||||
output.push(encode_byte_to_char(*byte));
|
||||
}
|
||||
} else {
|
||||
output.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while !input.is_empty() {
|
||||
match std::str::from_utf8(input) {
|
||||
Ok(parsed_str) => {
|
||||
append_escaped_str(&mut result, parsed_str);
|
||||
// The entire remaining input could be parsed, so we are done.
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
let (valid, after_valid) = input.split_at(e.valid_up_to());
|
||||
// SAFETY: The previous `str::from_utf8` call established that the prefix `valid`
|
||||
// is valid UTF-8. This prefix may be empty.
|
||||
let parsed_str = unsafe { std::str::from_utf8_unchecked(valid) };
|
||||
append_escaped_str(&mut result, parsed_str);
|
||||
// The length of the prefix of `after_valid` which is invalid UTF-8.
|
||||
// The remaining bytes of `input` (if any) will be parsed in subsequent iterations
|
||||
// of the loop, starting from the first byte that starts a valid UTF-8-encoded codepoint.
|
||||
// `error_len` can return `None`, if it sees a byte sequence that could be the
|
||||
// prefix of a valid code-point encoding at the end of the byte slice.
|
||||
// This is useful when the input is chunked, but we don't do that, so in this case
|
||||
// we use our custom encoding for all remaining bytes (at most 3).
|
||||
let error_len = e.error_len().unwrap_or(after_valid.len());
|
||||
for byte in &after_valid[..error_len] {
|
||||
result.push(encode_byte_to_char(*byte));
|
||||
}
|
||||
input = &after_valid[error_len..];
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Use this rather than [`WString::from_str`] when the input could contain PUA bytes we use to
|
||||
/// encode non-UTF-8 bytes. Otherwise, when decoding the resulting [`WString`], the PUA bytes in
|
||||
/// the input would be converted to non-UTF-8 bytes.
|
||||
pub fn str2wcstring<S: AsRef<str>>(input: S) -> WString {
|
||||
bytes2wcstring(input.as_ref().as_bytes())
|
||||
}
|
||||
|
||||
pub fn cstr2wcstring<C: AsRef<CStr>>(input: C) -> WString {
|
||||
bytes2wcstring(input.as_ref().to_bytes())
|
||||
}
|
||||
|
||||
pub fn osstr2wcstring<O: AsRef<OsStr>>(input: O) -> WString {
|
||||
bytes2wcstring(input.as_ref().as_bytes())
|
||||
}
|
||||
|
||||
/// # SAFETY
|
||||
///
|
||||
/// `input` must point to a valid NUL-terminated string.
|
||||
pub unsafe fn charptr2wcstring(input: *const libc::c_char) -> WString {
|
||||
let input: &[u8] = unsafe { CStr::from_ptr(input).to_bytes() };
|
||||
bytes2wcstring(input)
|
||||
}
|
||||
|
||||
/// Finds `needle` in a `haystack` and returns the index of the first matching element, if any.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@@ -251,7 +251,7 @@ Some *OPTION_SPEC* examples:
|
||||
|
||||
- ``n/name=?`` means that both ``-n`` and ``--name`` are valid. It accepts an optional value and can be used at most once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the value associated with the flag if one was provided else it will be set with no values.
|
||||
|
||||
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurence. Each value will be the value given to the option, or the empty string if no value was given.
|
||||
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurrence. Each value will be the value given to the option, or the empty string if no value was given.
|
||||
|
||||
- ``name=+`` means that only ``--name`` is valid. It requires a value and can be used more than once. If the flag is seen then ``_flag_name`` will be set with the values associated with each occurrence.
|
||||
|
||||
|
||||
@@ -2161,6 +2161,7 @@ complete -x -c git -n '__fish_git_using_command push' -l exec -d 'Same as --rece
|
||||
### rebase
|
||||
complete -f -c git -n __fish_git_needs_command -a rebase -d 'Reapply commit sequence on a new base'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command rebase'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n 'string match -rq -- "^-i|^--interactive" (commandline -xpc)' -ka '(__fish_git_recent_commits)'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l continue -d 'Restart the rebasing process'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l abort -d 'Abort the rebase operation'
|
||||
complete -f -c git -n '__fish_git_using_command rebase' -n __fish_git_is_rebasing -l edit-todo -d 'Edit the todo list'
|
||||
|
||||
10
share/functions/__fish_cygwin_noacl.fish
Normal file
10
share/functions/__fish_cygwin_noacl.fish
Normal file
@@ -0,0 +1,10 @@
|
||||
# localization: skip(private)
|
||||
function __fish_cygwin_noacl
|
||||
# MSYS (default) and Cygwin (non-default) mounts may not support POSIX permissions.
|
||||
__fish_is_cygwin
|
||||
and {
|
||||
mount |
|
||||
string match "*on $(stat -c %m -- $argv[1]) *" |
|
||||
string match -qr "[(,]noacl[),]"
|
||||
}
|
||||
end
|
||||
4
share/functions/__fish_is_cygwin.fish
Normal file
4
share/functions/__fish_is_cygwin.fish
Normal file
@@ -0,0 +1,4 @@
|
||||
# localization: skip(private)
|
||||
function __fish_is_cygwin
|
||||
__fish_uname | string match -qr "^(MSYS|CYGWIN)"
|
||||
end
|
||||
@@ -9,6 +9,21 @@ function __fish_make_cache_dir --description "Create and return XDG_CACHE_HOME"
|
||||
# So if you call `__fish_make_cache_dir completions`,
|
||||
# this creates e.g. ~/.cache/fish/completions
|
||||
if not path is -d $xdg_cache_home/fish/"$argv[1]"
|
||||
mkdir -m 700 -p $xdg_cache_home/fish/"$argv[1]"
|
||||
set -l mkdir_options -m 700
|
||||
|
||||
# Can't set the permission in Cygwin on a `noacl` mount
|
||||
if __fish_is_cygwin
|
||||
# Find the first existing parent so we can `stat` it and get its mountpoint
|
||||
set -l existing_parent $xdg_cache_home/fish/"$argv[1]"
|
||||
while not path is -d $existing_parent
|
||||
set existing_parent (path dirname $existing_parent)
|
||||
end
|
||||
|
||||
if __fish_cygwin_noacl "$existing_parent"
|
||||
set mkdir_options
|
||||
end
|
||||
end
|
||||
|
||||
mkdir $mkdir_options -p $xdg_cache_home/fish/"$argv[1]"
|
||||
end; and echo $xdg_cache_home/fish/"$argv[1]"
|
||||
end
|
||||
|
||||
@@ -207,7 +207,7 @@ tt {
|
||||
border-top-left-radius: 5;
|
||||
border-bottom-left-radius: 5;
|
||||
|
||||
/* Pad one less than .master_element, to accomodate our border. */
|
||||
/* Pad one less than .master_element, to accommodate our border. */
|
||||
padding-top: 5px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 4px;
|
||||
|
||||
26
src/ast.rs
26
src/ast.rs
@@ -9,19 +9,21 @@
|
||||
*
|
||||
* Most clients will be interested in visiting the nodes of an ast.
|
||||
*/
|
||||
use crate::common::{UnescapeStringStyle, unescape_string};
|
||||
use crate::flog::{flog, flogf};
|
||||
use crate::parse_constants::{
|
||||
ERROR_BAD_COMMAND_ASSIGN_ERR_MSG, INVALID_PIPELINE_CMD_ERR_MSG, ParseError, ParseErrorCode,
|
||||
ParseErrorList, ParseKeyword, ParseTokenType, ParseTreeFlags, SOURCE_OFFSET_INVALID,
|
||||
SourceRange, StatementDecoration, token_type_user_presentable_description,
|
||||
};
|
||||
use crate::parse_tree::ParseToken;
|
||||
use crate::prelude::*;
|
||||
use crate::tokenizer::{
|
||||
TOK_ACCEPT_UNFINISHED, TOK_ARGUMENT_LIST, TOK_CONTINUE_AFTER_ERROR, TOK_SHOW_COMMENTS,
|
||||
TokFlags, TokenType, Tokenizer, TokenizerError, variable_assignment_equals_pos,
|
||||
use crate::{
|
||||
flog::{flog, flogf},
|
||||
parse_constants::{
|
||||
ERROR_BAD_COMMAND_ASSIGN_ERR_MSG, INVALID_PIPELINE_CMD_ERR_MSG, ParseError, ParseErrorCode,
|
||||
ParseErrorList, ParseKeyword, ParseTokenType, ParseTreeFlags, SOURCE_OFFSET_INVALID,
|
||||
SourceRange, StatementDecoration, token_type_user_presentable_description,
|
||||
},
|
||||
parse_tree::ParseToken,
|
||||
prelude::*,
|
||||
tokenizer::{
|
||||
TOK_ACCEPT_UNFINISHED, TOK_ARGUMENT_LIST, TOK_CONTINUE_AFTER_ERROR, TOK_SHOW_COMMENTS,
|
||||
TokFlags, TokenType, Tokenizer, TokenizerError, variable_assignment_equals_pos,
|
||||
},
|
||||
};
|
||||
use fish_common::{UnescapeStringStyle, unescape_string};
|
||||
use macro_rules_attribute::derive;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::AsMut;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//! The classes responsible for autoloading functions and completions.
|
||||
|
||||
use crate::common::{ScopeGuard, escape};
|
||||
use crate::env::Environment;
|
||||
use crate::flogf;
|
||||
use crate::io::IoChain;
|
||||
use crate::parser::Parser;
|
||||
use crate::wutil::{FileId, INVALID_FILE_ID, file_id_for_path};
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_widestring::{L, WExt as _, WString, wstr};
|
||||
use crate::{
|
||||
env::Environment,
|
||||
flogf,
|
||||
io::IoChain,
|
||||
parser::Parser,
|
||||
wutil::{FileId, INVALID_FILE_ID, file_id_for_path},
|
||||
};
|
||||
use fish_common::{ScopeGuard, escape};
|
||||
use fish_widestring::{L, WExt as _, WString, wcs2bytes, wstr};
|
||||
use lru::LruCache;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -133,7 +134,7 @@ pub fn perform_autoload(path: &AutoloadPath, parser: &Parser) {
|
||||
parser.eval(&script_source, &IoChain::new());
|
||||
}
|
||||
AutoloadPath::Embedded(name) => {
|
||||
use crate::common::bytes2wcstring;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use std::sync::Arc;
|
||||
flogf!(autoload, "Loading embedded: %s", name);
|
||||
let emfile = Asset::get(name).expect("Embedded file not found");
|
||||
@@ -464,7 +465,7 @@ mod tests {
|
||||
fn test_autoload() {
|
||||
let _cleanup = test_init();
|
||||
use crate::fds::wopen_cloexec;
|
||||
use fish_wcstringutil::wcs2zstring;
|
||||
use fish_widestring::wcs2zstring;
|
||||
use nix::fcntl::OFlag;
|
||||
|
||||
macro_rules! run {
|
||||
|
||||
@@ -24,10 +24,7 @@
|
||||
fish_indent, fish_key_reader,
|
||||
shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, VERSION_STRING_TEMPLATE},
|
||||
},
|
||||
common::{
|
||||
PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME, bytes2wcstring, escape, osstr2wcstring,
|
||||
save_term_foreground_process_group,
|
||||
},
|
||||
common::{PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME},
|
||||
env::{
|
||||
EnvMode, Statuses,
|
||||
config_paths::ConfigPaths,
|
||||
@@ -53,25 +50,28 @@
|
||||
Pid, get_login, is_interactive_session, mark_login, mark_no_exec, proc_init,
|
||||
set_interactive_session,
|
||||
},
|
||||
reader::{reader_init, reader_read, term_copy_modes},
|
||||
reader::{reader_exit_signal, reader_init, reader_read, term_copy_modes},
|
||||
signal::{signal_clear_cancel, signal_unblock_all},
|
||||
threads::{self},
|
||||
topic_monitor,
|
||||
wutil::waccess,
|
||||
};
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_common::{escape, save_term_foreground_process_group};
|
||||
use fish_widestring::{bytes2wcstring, osstr2wcstring, wcs2bytes};
|
||||
use libc::{STDERR_FILENO, STDIN_FILENO};
|
||||
use nix::{
|
||||
sys::resource::{UsageWho, getrusage},
|
||||
unistd::{AccessFlags, getpid},
|
||||
};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs::File;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{env, ops::ControlFlow};
|
||||
use std::{
|
||||
env,
|
||||
ffi::{OsStr, OsString},
|
||||
fs::File,
|
||||
ops::ControlFlow,
|
||||
os::unix::prelude::*,
|
||||
path::Path,
|
||||
sync::{Arc, atomic::Ordering},
|
||||
};
|
||||
|
||||
/// container to hold the options specified within the command line
|
||||
#[derive(Default, Debug)]
|
||||
@@ -625,6 +625,16 @@ fn throwing_main() -> i32 {
|
||||
}
|
||||
|
||||
history::save_all();
|
||||
|
||||
// If we deferred a fatal signal, re-raise it now so the parent sees WIFSIGNALED.
|
||||
let exit_sig = reader_exit_signal();
|
||||
if exit_sig != 0 {
|
||||
unsafe {
|
||||
libc::signal(exit_sig, libc::SIG_DFL);
|
||||
libc::raise(exit_sig);
|
||||
}
|
||||
}
|
||||
|
||||
if opts.print_rusage_self {
|
||||
print_rusage_self();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use super::prelude::*;
|
||||
use crate::abbrs::{self, Abbreviation, Position};
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::{EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_func_name};
|
||||
use crate::env::{EnvMode, EnvStackSetResult};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::re::{regex_make_anchored, to_boxed_chars};
|
||||
use crate::{err_fmt, err_str};
|
||||
use fish_common::help_section;
|
||||
use crate::{
|
||||
abbrs::{self, Abbreviation, Position},
|
||||
builtins::error::Error,
|
||||
common::valid_func_name,
|
||||
env::{EnvMode, EnvStackSetResult},
|
||||
err_fmt, err_str,
|
||||
highlight::highlight_and_colorize,
|
||||
parser::ParserEnvSetMode,
|
||||
re::{regex_make_anchored, to_boxed_chars},
|
||||
};
|
||||
use fish_common::{EscapeStringStyle, escape, escape_string, help_section};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use pcre2::utf32::{Regex, RegexBuilder};
|
||||
|
||||
localizable_consts! {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
//! Implementation of the bind builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::{
|
||||
EscapeFlags, EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_var_name,
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
common::valid_var_name,
|
||||
err_fmt, err_raw, err_str,
|
||||
highlight::highlight_and_colorize,
|
||||
input::{
|
||||
InputMapping, InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings,
|
||||
},
|
||||
key::{
|
||||
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
|
||||
},
|
||||
};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::input::{
|
||||
InputMapping, InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings,
|
||||
};
|
||||
use crate::key::{
|
||||
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
|
||||
};
|
||||
use crate::{err_fmt, err_raw, err_str};
|
||||
use fish_common::help_section;
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape, escape_string, help_section};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
const DEFAULT_BIND_MODE: &wstr = L!("default");
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
use super::prelude::*;
|
||||
use super::read::TokenOutputMode;
|
||||
use crate::ast::{self, Kind, Leaf as _};
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use crate::complete::Completion;
|
||||
use crate::expand::{ExpandFlags, ExpandResultCode, expand_string};
|
||||
use crate::input::input_function_get_code;
|
||||
use crate::input_common::{CharEvent, ReadlineCmd};
|
||||
use crate::operation_context::{OperationContext, no_cancel};
|
||||
use crate::parse_constants::ParseTreeFlags;
|
||||
use crate::parse_util::{
|
||||
detect_parse_errors, get_job_extent, get_offset_from_line, get_process_extent,
|
||||
get_token_extent, lineno,
|
||||
use crate::{
|
||||
ast::{self, Kind, Leaf as _},
|
||||
builtins::error::Error,
|
||||
complete::Completion,
|
||||
err_fmt, err_str,
|
||||
expand::{ExpandFlags, ExpandResultCode, expand_string},
|
||||
input::input_function_get_code,
|
||||
input_common::{CharEvent, ReadlineCmd},
|
||||
operation_context::{OperationContext, no_cancel},
|
||||
parse_constants::ParseTreeFlags,
|
||||
parse_util::{
|
||||
detect_parse_errors, get_job_extent, get_offset_from_line, get_process_extent,
|
||||
get_token_extent, lineno,
|
||||
},
|
||||
prelude::*,
|
||||
proc::is_interactive_session,
|
||||
reader::{
|
||||
JumpDirection, JumpPrecision, commandline_get_state, commandline_set_buffer,
|
||||
commandline_set_search_field, reader_execute_readline_cmd, reader_jump,
|
||||
reader_showing_suggestion,
|
||||
},
|
||||
tokenizer::{TOK_ACCEPT_UNFINISHED, TokenType, Tokenizer},
|
||||
};
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::{
|
||||
JumpDirection, JumpPrecision, commandline_get_state, commandline_set_buffer,
|
||||
commandline_set_search_field, reader_execute_readline_cmd, reader_jump,
|
||||
reader_showing_suggestion,
|
||||
};
|
||||
use crate::tokenizer::{TOK_ACCEPT_UNFINISHED, TokenType, Tokenizer};
|
||||
use crate::{err_fmt, err_str, prelude::*};
|
||||
use fish_common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::join_strings;
|
||||
use std::ops::Range;
|
||||
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use crate::complete::{CompletionRequestOptions, complete_add_wrapper, complete_remove_wrapper};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::ParseErrorList;
|
||||
use crate::parse_util::detect_errors_in_argument_list;
|
||||
use crate::parse_util::{detect_parse_errors, get_token_extent};
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::{commandline_get_state, completion_apply_to_command_line};
|
||||
use crate::{
|
||||
common::bytes2wcstring,
|
||||
builtins::error::Error,
|
||||
complete::{
|
||||
CompleteFlags, CompleteOptionType, CompletionMode, complete_add, complete_print,
|
||||
complete_remove, complete_remove_all,
|
||||
CompleteFlags, CompleteOptionType, CompletionMode, CompletionRequestOptions, complete_add,
|
||||
complete_add_wrapper, complete_print, complete_remove, complete_remove_all,
|
||||
complete_remove_wrapper,
|
||||
},
|
||||
err_fmt, err_raw, err_str,
|
||||
highlight::highlight_and_colorize,
|
||||
operation_context::OperationContext,
|
||||
parse_constants::ParseErrorList,
|
||||
parse_util::{detect_errors_in_argument_list, detect_parse_errors, get_token_extent},
|
||||
proc::is_interactive_session,
|
||||
reader::{commandline_get_state, completion_apply_to_command_line},
|
||||
};
|
||||
use crate::{err_fmt, err_raw, err_str};
|
||||
use fish_common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::string_suffixes_string;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
|
||||
// builtin_complete_* are a set of rather silly looping functions that make sure that all the proper
|
||||
// combinations of complete_add or complete_remove get called. This is needed since complete allows
|
||||
|
||||
@@ -1,42 +1,38 @@
|
||||
//! The fish_indent program.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write as _};
|
||||
use std::os::unix::ffi::OsStrExt as _;
|
||||
|
||||
use crate::builtins::error::Error;
|
||||
use crate::locale::set_libc_locales;
|
||||
use crate::panic::panic_handler;
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::ast::{
|
||||
self, AsNode as _, Ast, Kind, Leaf as _, Node, NodeVisitor, SourceRangeList, Traversal,
|
||||
use crate::{
|
||||
ast::{self, AsNode as _, Ast, Kind, Leaf as _, Node, NodeVisitor, SourceRangeList, Traversal},
|
||||
builtins::error::Error,
|
||||
common::{PROGRAM_NAME, get_program_name},
|
||||
env::{EnvStack, env_init, environment::Environment as _},
|
||||
err_fmt, err_str,
|
||||
global_safety::RelaxedAtomicBool,
|
||||
highlight::{HighlightRole, HighlightSpec, colorize, highlight_shell},
|
||||
locale::set_libc_locales,
|
||||
operation_context::OperationContext,
|
||||
panic::panic_handler,
|
||||
parse_constants::{ParseTokenType, ParseTreeFlags, SourceRange},
|
||||
parse_util::{SPACES_PER_INDENT, apply_indents, compute_indents},
|
||||
prelude::*,
|
||||
print_help::print_help,
|
||||
threads,
|
||||
tokenizer::{TOK_SHOW_BLANK_LINES, TOK_SHOW_COMMENTS, TokenType, Tokenizer},
|
||||
topic_monitor::topic_monitor_init,
|
||||
wutil::fish_iswalnum,
|
||||
};
|
||||
use crate::common::{
|
||||
PROGRAM_NAME, ReadExt as _, UnescapeFlags, UnescapeStringStyle, bytes2wcstring,
|
||||
get_program_name, osstr2wcstring, unescape_string,
|
||||
};
|
||||
use crate::env::EnvStack;
|
||||
use crate::env::env_init;
|
||||
use crate::env::environment::Environment as _;
|
||||
use crate::err_fmt;
|
||||
use crate::expand::INTERNAL_SEPARATOR;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::highlight::{HighlightRole, HighlightSpec, colorize, highlight_shell};
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::{ParseTokenType, ParseTreeFlags, SourceRange};
|
||||
use crate::parse_util::{SPACES_PER_INDENT, apply_indents, compute_indents};
|
||||
use crate::print_help::print_help;
|
||||
use crate::threads;
|
||||
use crate::tokenizer::{TOK_SHOW_BLANK_LINES, TOK_SHOW_COMMENTS, TokenType, Tokenizer};
|
||||
use crate::topic_monitor::topic_monitor_init;
|
||||
use crate::wutil::fish_iswalnum;
|
||||
use crate::{err_str, prelude::*};
|
||||
use assert_matches::assert_matches;
|
||||
use fish_wcstringutil::{count_preceding_backslashes, wcs2bytes};
|
||||
use fish_common::{ReadExt as _, UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::count_preceding_backslashes;
|
||||
use fish_wgetopt::{ArgType, WGetopter, WOption, wopt};
|
||||
use std::fmt::Write as _;
|
||||
use fish_widestring::{INTERNAL_SEPARATOR, bytes2wcstring, osstr2wcstring, wcs2bytes};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fmt::Write as _,
|
||||
fs,
|
||||
io::{Read, Write as _},
|
||||
os::unix::ffi::OsStrExt as _,
|
||||
};
|
||||
|
||||
/// Note: this got somewhat more complicated after introducing the new AST, because that AST no
|
||||
/// longer encodes detailed lexical information (e.g. every newline). This feels more complex
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
common::{PROGRAM_NAME, get_program_name, osstr2wcstring, shell_modes},
|
||||
common::{PROGRAM_NAME, get_program_name, shell_modes},
|
||||
env::{EnvStack, Environment as _, env_init},
|
||||
err_fmt, err_str,
|
||||
input_common::{
|
||||
@@ -27,13 +27,15 @@
|
||||
print_help::print_help,
|
||||
proc::set_interactive_session,
|
||||
reader::{
|
||||
check_exit_loop_maybe_warning, reader_init, reader_sighup, set_shell_modes, terminal_init,
|
||||
check_exit_loop_maybe_warning, reader_init, safe_reader_set_exit_signal, set_shell_modes,
|
||||
terminal_init,
|
||||
},
|
||||
threads,
|
||||
topic_monitor::topic_monitor_init,
|
||||
tty_handoff::TtyHandoff,
|
||||
};
|
||||
use fish_wgetopt::{ArgType, WGetopter, WOption, wopt};
|
||||
use fish_widestring::osstr2wcstring;
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
@@ -96,7 +98,7 @@ fn process_input(
|
||||
use QueryResultEvent::*;
|
||||
let kevt = match input_queue.readch() {
|
||||
CharEvent::Implicit(ImplicitEvent::Eof) => {
|
||||
reader_sighup();
|
||||
safe_reader_set_exit_signal(libc::SIGHUP);
|
||||
continue;
|
||||
}
|
||||
CharEvent::Key(kevt) => kevt,
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::common::escape_string;
|
||||
use crate::common::reformat_for_screen;
|
||||
use crate::common::valid_func_name;
|
||||
use crate::common::{EscapeFlags, EscapeStringStyle};
|
||||
use crate::err_fmt;
|
||||
use crate::err_str;
|
||||
use crate::event::{self};
|
||||
use crate::function;
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::parse_util::apply_indents;
|
||||
use crate::parse_util::compute_indents;
|
||||
use crate::parser_keywords::parser_keywords_is_reserved;
|
||||
use crate::termsize::termsize_last;
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
common::{reformat_for_screen, valid_func_name},
|
||||
err_fmt, err_str,
|
||||
event::{self},
|
||||
function,
|
||||
highlight::highlight_and_colorize,
|
||||
parse_util::{apply_indents, compute_indents},
|
||||
parser_keywords::parser_keywords_is_reserved,
|
||||
termsize::termsize_last,
|
||||
};
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
|
||||
#[derive(Default)]
|
||||
struct FunctionsCmdOpts<'args> {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// Functions for executing the jobs builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::common::{EscapeFlags, EscapeStringStyle, escape_string, timef};
|
||||
use crate::err_fmt;
|
||||
use crate::io::IoStreams;
|
||||
use crate::job_group::{JobId, MaybeJobId};
|
||||
use crate::localization::{wgettext, wgettext_fmt};
|
||||
use crate::parser::Parser;
|
||||
use crate::proc::{HAVE_PROC_STAT, Job, clock_ticks_to_seconds, proc_get_jiffies};
|
||||
use crate::wutil::fish_wcstoi;
|
||||
use crate::{
|
||||
err_fmt,
|
||||
io::IoStreams,
|
||||
job_group::{JobId, MaybeJobId},
|
||||
localization::{wgettext, wgettext_fmt},
|
||||
parser::Parser,
|
||||
proc::{HAVE_PROC_STAT, Job, clock_ticks_to_seconds, proc_get_jiffies},
|
||||
wutil::fish_wcstoi,
|
||||
};
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape_string, timef};
|
||||
use fish_wgetopt::{ArgType, WGetopter, WOption, wopt};
|
||||
use fish_widestring::{L, WExt as _, WString, wstr};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
@@ -1,39 +1,28 @@
|
||||
//! Implementation of the read builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::UnescapeStringStyle;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::common::escape;
|
||||
use crate::common::read_blocked;
|
||||
use crate::common::unescape_string;
|
||||
use crate::common::valid_var_name;
|
||||
use crate::env::EnvMode;
|
||||
use crate::env::Environment as _;
|
||||
use crate::env::READ_BYTE_LIMIT;
|
||||
use crate::env::{EnvVar, EnvVarFlags};
|
||||
use crate::err_fmt;
|
||||
use crate::err_str;
|
||||
use crate::input_common::DecodeState;
|
||||
use crate::input_common::InvalidPolicy;
|
||||
use crate::input_common::decode_one_codepoint_utf8;
|
||||
use crate::nix::isatty;
|
||||
use crate::parse_execution::varname_error;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::reader::ReaderConfig;
|
||||
use crate::reader::commandline_set_buffer;
|
||||
use crate::reader::{reader_pop, reader_push, reader_readline, set_shell_modes_temporarily};
|
||||
use crate::tokenizer::TOK_ACCEPT_UNFINISHED;
|
||||
use crate::tokenizer::TOK_ARGUMENT_LIST;
|
||||
use crate::tokenizer::Tok;
|
||||
use crate::tokenizer::Tokenizer;
|
||||
use crate::wutil;
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
common::valid_var_name,
|
||||
env::{EnvMode, EnvVar, EnvVarFlags, Environment as _, READ_BYTE_LIMIT},
|
||||
err_fmt, err_str,
|
||||
input_common::{DecodeState, InvalidPolicy, decode_utf8},
|
||||
nix::isatty,
|
||||
parse_execution::varname_error,
|
||||
parser::ParserEnvSetMode,
|
||||
reader::{
|
||||
ReaderConfig, commandline_set_buffer, reader_pop, reader_push, reader_readline,
|
||||
set_shell_modes_temporarily,
|
||||
},
|
||||
tokenizer::{TOK_ACCEPT_UNFINISHED, TOK_ARGUMENT_LIST, Tok, Tokenizer},
|
||||
wutil,
|
||||
};
|
||||
use fish_common::{UnescapeStringStyle, escape, read_blocked, unescape_string};
|
||||
use fish_util::perror;
|
||||
use fish_wcstringutil::{split_about, split_string_tok};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use libc::SEEK_CUR;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{num::NonZeroUsize, os::fd::RawFd, sync::atomic::Ordering};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) enum TokenOutputMode {
|
||||
@@ -396,7 +385,7 @@ fn read_one_char_at_a_time(
|
||||
}
|
||||
unconsumed.push(b[0]);
|
||||
nbytes += 1;
|
||||
match decode_one_codepoint_utf8(buff, InvalidPolicy::Passthrough, &unconsumed) {
|
||||
match decode_utf8(buff, InvalidPolicy::Passthrough, &unconsumed) {
|
||||
DecodeState::Incomplete => continue,
|
||||
DecodeState::Complete => {
|
||||
unconsumed.clear();
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::EscapeFlags;
|
||||
use crate::common::EscapeStringStyle;
|
||||
use crate::common::escape;
|
||||
use crate::common::escape_string;
|
||||
use crate::common::valid_var_name;
|
||||
use crate::env::EnvStackSetResult;
|
||||
use crate::env::EnvVarFlags;
|
||||
use crate::env::INHERITED_VARS;
|
||||
use crate::err_fmt;
|
||||
use crate::err_str;
|
||||
use crate::event;
|
||||
use crate::event::Event;
|
||||
use crate::expand::expand_escape_string;
|
||||
use crate::expand::expand_escape_variable;
|
||||
use crate::history::History;
|
||||
use crate::history::history_session_id;
|
||||
use crate::parse_execution::varname_error;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::{
|
||||
env::{EnvMode, EnvVar, Environment},
|
||||
builtins::error::Error,
|
||||
common::valid_var_name,
|
||||
env::{EnvMode, EnvStackSetResult, EnvVar, EnvVarFlags, Environment, INHERITED_VARS},
|
||||
err_fmt, err_str,
|
||||
event::{self, Event},
|
||||
expand::{expand_escape_string, expand_escape_variable},
|
||||
history::{History, history_session_id},
|
||||
parse_execution::varname_error,
|
||||
parser::ParserEnvSetMode,
|
||||
wutil::wcstoi::wcstoi_partial,
|
||||
};
|
||||
use fish_common::help_section;
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape, escape_string, help_section};
|
||||
use fish_widestring::ELLIPSIS_CHAR;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// Implementation of the set_color builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::err_fmt;
|
||||
use crate::screen::{is_dumb, only_grayscale};
|
||||
use crate::terminal::Outputter;
|
||||
use crate::text_face::{self, PrintColorsArgs, TextFace, TextStyling, parse_text_face_and_options};
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
err_fmt,
|
||||
screen::{is_dumb, only_grayscale},
|
||||
terminal::Outputter,
|
||||
text_face::{self, PrintColorsArgs, TextFace, TextStyling, parse_text_face_and_options},
|
||||
};
|
||||
use fish_color::Color;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
|
||||
fn print_colors(
|
||||
streams: &mut IoStreams,
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
use crate::common::{Named, bytes2wcstring, escape, get_by_sorted_name, str2wcstring};
|
||||
use crate::fds::BorrowedFdFile;
|
||||
use crate::io::OutputStream;
|
||||
use crate::parse_constants::UNKNOWN_BUILTIN_ERR_MSG;
|
||||
use crate::parse_util::argument_is_help;
|
||||
use crate::parser::{BlockType, LoopStatus};
|
||||
use crate::proc::{Pid, ProcStatus, no_exec};
|
||||
use crate::{builtins::prelude::*, builtins::*, err_fmt, wutil};
|
||||
use crate::{
|
||||
builtins::{prelude::*, *},
|
||||
err_fmt,
|
||||
fds::BorrowedFdFile,
|
||||
io::OutputStream,
|
||||
parse_constants::UNKNOWN_BUILTIN_ERR_MSG,
|
||||
parse_util::argument_is_help,
|
||||
parser::{BlockType, LoopStatus},
|
||||
proc::{Pid, ProcStatus, no_exec},
|
||||
wutil,
|
||||
};
|
||||
use errno::errno;
|
||||
use fish_common::assert_sorted_by_name;
|
||||
use fish_widestring::L;
|
||||
use fish_common::{Named, assert_sorted_by_name, escape, get_by_sorted_name};
|
||||
use fish_widestring::{L, bytes2wcstring, str2wcstring};
|
||||
use std::io::{BufRead as _, BufReader, Read as _};
|
||||
|
||||
pub type BuiltinCmd = fn(&Parser, &mut IoStreams, &mut [&wstr]) -> BuiltinResult;
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
use std::os::fd::AsRawFd as _;
|
||||
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
common::{FilenameRef, escape},
|
||||
err_fmt, err_raw, err_str,
|
||||
fds::wopen_cloexec,
|
||||
nix::isatty,
|
||||
parser::Block,
|
||||
reader::reader_read,
|
||||
};
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::{
|
||||
builtins::error::Error, err_fmt, err_raw, err_str, fds::wopen_cloexec, nix::isatty,
|
||||
parser::Block, reader::reader_read,
|
||||
};
|
||||
use fish_common::{FilenameRef, escape};
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
use std::os::fd::AsRawFd as _;
|
||||
|
||||
/// The source builtin, sometimes called `.`. Evaluates the contents of a file in the current
|
||||
/// context.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error;
|
||||
use crate::common::{bytes2wcstring, get_program_name, osstr2wcstring, str2wcstring};
|
||||
use crate::common::get_program_name;
|
||||
use crate::env::config_paths::get_fish_path;
|
||||
use crate::err_fmt;
|
||||
#[cfg(not(feature = "localize-messages"))]
|
||||
@@ -14,7 +14,7 @@
|
||||
use cfg_if::cfg_if;
|
||||
use fish_feature_flags::{self as features, feature_test};
|
||||
use fish_util::wcsfilecmp_glob;
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_widestring::{bytes2wcstring, osstr2wcstring, str2wcstring, wcs2bytes};
|
||||
use nix::unistd::AccessFlags;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Escape {
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use super::*;
|
||||
use crate::common::str2wcstring;
|
||||
use crate::env::{EnvVar, EnvVarFlags};
|
||||
use crate::flog::flog;
|
||||
use crate::parse_util::unescape_wildcards;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::wildcard::{ANY_STRING, wildcard_match};
|
||||
use crate::{
|
||||
env::{EnvVar, EnvVarFlags},
|
||||
flog::flog,
|
||||
parse_util::unescape_wildcards,
|
||||
parser::ParserEnvSetMode,
|
||||
wildcard::wildcard_match,
|
||||
};
|
||||
use fish_widestring::{ANY_STRING, str2wcstring};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Match<'args> {
|
||||
|
||||
@@ -154,7 +154,7 @@ enum StringReplacer<'args, 'opts> {
|
||||
|
||||
impl<'args, 'opts> StringReplacer<'args, 'opts> {
|
||||
fn interpret_escape(arg: &'args wstr) -> Option<WString> {
|
||||
use crate::common::read_unquoted_escape;
|
||||
use fish_common::read_unquoted_escape;
|
||||
|
||||
let mut result: WString = WString::with_capacity(arg.len());
|
||||
let mut cursor = arg;
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use super::string;
|
||||
use crate::builtins::shared::BuiltinResultExt as _;
|
||||
use crate::io::IoChain;
|
||||
use crate::io::{IoStreams, OutputStream, StringOutputStream};
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::{
|
||||
builtins::shared::BuiltinResultExt as _,
|
||||
io::{IoChain, IoStreams, OutputStream, StringOutputStream},
|
||||
prelude::*,
|
||||
tests::prelude::*,
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! validate {
|
||||
( [$($argv:expr),*], $expected_rc:expr, $expected_out:expr ) => {
|
||||
{
|
||||
use $crate::common::escape;
|
||||
use fish_common::escape;
|
||||
use $crate::prelude::*;
|
||||
use $crate::builtins::string::test_helpers::string_test;
|
||||
let (actual_out, actual_rc) = string_test(vec![$(L!($argv)),*]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::common::{UnescapeStringStyle, unescape_string};
|
||||
use fish_common::{UnescapeStringStyle, unescape_string};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Unescape {
|
||||
|
||||
@@ -1084,10 +1084,12 @@ pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::test as builtin_test;
|
||||
use crate::builtins::prelude::*;
|
||||
use crate::common::str2wcstring;
|
||||
use crate::io::{IoChain, OutputStream};
|
||||
use crate::tests::prelude::*;
|
||||
use crate::{
|
||||
builtins::prelude::*,
|
||||
io::{IoChain, OutputStream},
|
||||
tests::prelude::*,
|
||||
};
|
||||
use fish_widestring::str2wcstring;
|
||||
|
||||
fn run_one_test_test_mbracket(expected: i32, lst: &[&str], bracket: bool) -> bool {
|
||||
let parser = TestParser::new();
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use super::prelude::*;
|
||||
use crate::builtins::error::Error;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::parse_util::{apply_indents, compute_indents};
|
||||
use crate::path::{path_get_path, path_get_paths};
|
||||
use crate::{err_fmt, err_str, function};
|
||||
use crate::{
|
||||
builtins::error::Error,
|
||||
err_fmt, err_str, function,
|
||||
highlight::highlight_and_colorize,
|
||||
parse_util::{apply_indents, compute_indents},
|
||||
path::{path_get_path, path_get_paths},
|
||||
};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
|
||||
#[derive(Default)]
|
||||
struct type_cmd_opts_t {
|
||||
|
||||
@@ -107,7 +107,7 @@ fn wait_for_completion(parser: &Parser, whs: &[WaitHandleRef], any_flag: bool) -
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
|
||||
let mut sigint = SigChecker::new_sighupint();
|
||||
let mut sigint = SigChecker::new_sighupintterm();
|
||||
loop {
|
||||
let finished = if any_flag {
|
||||
whs.iter().any(is_completed)
|
||||
|
||||
1060
src/common.rs
1060
src/common.rs
File diff suppressed because it is too large
Load Diff
111
src/complete.rs
111
src/complete.rs
@@ -1,3 +1,41 @@
|
||||
use crate::{
|
||||
abbrs::with_abbrs,
|
||||
ast::unescape_keyword,
|
||||
autoload::{Autoload, AutoloadResult},
|
||||
builtins::shared::{builtin_exists, builtin_get_desc, builtin_get_names},
|
||||
common::valid_var_name_char,
|
||||
env::{EnvMode, EnvStack, Environment},
|
||||
exec::exec_subshell,
|
||||
expand::{
|
||||
ExpandFlags, ExpandResultCode, expand_escape_string, expand_escape_variable, expand_one,
|
||||
expand_string, expand_to_receiver,
|
||||
},
|
||||
flog::{flog, flogf},
|
||||
function,
|
||||
history::{History, history_session_id},
|
||||
localization::{LocalizableString, localizable_string},
|
||||
operation_context::OperationContext,
|
||||
parse_constants::SourceRange,
|
||||
parse_util::{get_cmdsubst_extent, get_process_extent, unescape_wildcards},
|
||||
parser::{Block, Parser, ParserEnvSetMode},
|
||||
parser_keywords::parser_keywords_is_subcommand,
|
||||
path::{path_get_path, path_try_get_path},
|
||||
prelude::*,
|
||||
reader::{get_quote, is_backslashed},
|
||||
tokenizer::{Tok, TokFlags, TokenType, Tokenizer, variable_assignment_equals_pos},
|
||||
wildcard::{wildcard_complete, wildcard_has, wildcard_match},
|
||||
wutil::wrealpath,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use bitflags::bitflags;
|
||||
use fish_common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, escape, unescape_string};
|
||||
use fish_util::wcsfilecmp;
|
||||
use fish_wcstringutil::{
|
||||
StringFuzzyMatch, string_fuzzy_match_string, string_prefixes_string,
|
||||
string_prefixes_string_case_insensitive, string_suffixes_string_case_insensitive,
|
||||
strip_executable_suffix,
|
||||
};
|
||||
use fish_widestring::{WExt as _, charptr2wcstring};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
@@ -10,51 +48,6 @@
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
abbrs::with_abbrs,
|
||||
autoload::Autoload,
|
||||
builtins::shared::{builtin_exists, builtin_get_desc, builtin_get_names},
|
||||
common::{
|
||||
ScopeGuard, UnescapeFlags, UnescapeStringStyle, escape, unescape_string,
|
||||
valid_var_name_char,
|
||||
},
|
||||
env::{EnvMode, EnvStack, Environment},
|
||||
exec::exec_subshell,
|
||||
expand::{
|
||||
ExpandFlags, ExpandResultCode, expand_escape_string, expand_escape_variable, expand_one,
|
||||
expand_string, expand_to_receiver,
|
||||
},
|
||||
flog::{flog, flogf},
|
||||
function,
|
||||
history::{History, history_session_id},
|
||||
operation_context::OperationContext,
|
||||
parse_constants::SourceRange,
|
||||
parse_util::{get_cmdsubst_extent, get_process_extent, unescape_wildcards},
|
||||
parser::{Block, Parser, ParserEnvSetMode},
|
||||
parser_keywords::parser_keywords_is_subcommand,
|
||||
path::{path_get_path, path_try_get_path},
|
||||
prelude::*,
|
||||
tokenizer::{Tok, TokFlags, TokenType, Tokenizer, variable_assignment_equals_pos},
|
||||
wildcard::{wildcard_complete, wildcard_has, wildcard_match},
|
||||
wutil::wrealpath,
|
||||
};
|
||||
use crate::{
|
||||
ast::unescape_keyword,
|
||||
autoload::AutoloadResult,
|
||||
common::charptr2wcstring,
|
||||
localization::{LocalizableString, localizable_string},
|
||||
reader::{get_quote, is_backslashed},
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use bitflags::bitflags;
|
||||
use fish_util::wcsfilecmp;
|
||||
use fish_wcstringutil::{
|
||||
StringFuzzyMatch, string_fuzzy_match_string, string_prefixes_string,
|
||||
string_prefixes_string_case_insensitive, string_suffixes_string_case_insensitive,
|
||||
strip_executable_suffix,
|
||||
};
|
||||
use fish_widestring::WExt as _;
|
||||
|
||||
// Completion description strings, mostly for different types of files, such as sockets, block
|
||||
// devices, etc.
|
||||
//
|
||||
@@ -1814,8 +1807,12 @@ fn getpwent_name() -> Option<WString> {
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: We established that `getpwent` returned non-NULL, in which case `ptr`
|
||||
// will point to a valid `passwd` struct.
|
||||
let pw = unsafe { ptr.read() };
|
||||
Some(charptr2wcstring(pw.pw_name))
|
||||
// SAFETY: This assumes that the successful `getpwent` call put a pointer to a valid
|
||||
// string into `pw.pw_name`.
|
||||
Some(unsafe { charptr2wcstring(pw.pw_name) })
|
||||
}
|
||||
|
||||
let _guard = SETPWENT_LOCK.lock().unwrap();
|
||||
@@ -2635,18 +2632,20 @@ mod tests {
|
||||
complete_add, complete_add_wrapper, complete_get_wrap_targets, complete_remove_wrapper,
|
||||
sort_and_prioritize,
|
||||
};
|
||||
use crate::abbrs::{self, Abbreviation, with_abbrs_mut};
|
||||
use crate::common::str2wcstring;
|
||||
use crate::env::{EnvMode, EnvSetMode, Environment as _};
|
||||
use crate::io::IoChain;
|
||||
use crate::operation_context::{
|
||||
EXPANSION_LIMIT_BACKGROUND, EXPANSION_LIMIT_DEFAULT, OperationContext, no_cancel,
|
||||
use crate::{
|
||||
abbrs::{self, Abbreviation, with_abbrs_mut},
|
||||
env::{EnvMode, EnvSetMode, Environment as _},
|
||||
io::IoChain,
|
||||
operation_context::{
|
||||
EXPANSION_LIMIT_BACKGROUND, EXPANSION_LIMIT_DEFAULT, OperationContext, no_cancel,
|
||||
},
|
||||
parser::ParserEnvSetMode,
|
||||
prelude::*,
|
||||
reader::completion_apply_to_command_line,
|
||||
tests::prelude::*,
|
||||
};
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::prelude::*;
|
||||
use crate::reader::completion_apply_to_command_line;
|
||||
use crate::tests::prelude::*;
|
||||
use fish_wcstringutil::join_strings;
|
||||
use fish_widestring::str2wcstring;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
|
||||
|
||||
67
src/env/environment.rs
vendored
67
src/env/environment.rs
vendored
@@ -1,41 +1,48 @@
|
||||
use super::ElectricVar;
|
||||
use super::environment_impl::{
|
||||
EnvMutex, EnvMutexGuard, EnvScopedImpl, EnvStackImpl, ModResult, UVAR_SCOPE_IS_GLOBAL,
|
||||
colon_split, uvars,
|
||||
use super::{
|
||||
ElectricVar,
|
||||
environment_impl::{
|
||||
EnvMutex, EnvMutexGuard, EnvScopedImpl, EnvStackImpl, ModResult, UVAR_SCOPE_IS_GLOBAL,
|
||||
colon_split, uvars,
|
||||
},
|
||||
};
|
||||
use crate::abbrs::{Abbreviation, Position, abbrs_get_set};
|
||||
use crate::builtins::shared::{BuiltinResult, SUCCESS};
|
||||
use crate::common::{
|
||||
UnescapeStringStyle, cstr2wcstring, osstr2wcstring, str2wcstring, unescape_string,
|
||||
use crate::{
|
||||
abbrs::{Abbreviation, Position, abbrs_get_set},
|
||||
builtins::shared::{BuiltinResult, SUCCESS},
|
||||
env::{
|
||||
EnvMode, EnvSetMode, EnvVar, Statuses,
|
||||
config_paths::{ConfigPaths, PREFIX},
|
||||
},
|
||||
env_dispatch::{VarChangeMilieu, env_dispatch_init, env_dispatch_var_change},
|
||||
event::Event,
|
||||
flog::flog,
|
||||
global_safety::RelaxedAtomicBool,
|
||||
input::{FISH_BIND_MODE_VAR, init_input},
|
||||
localization::wgettext,
|
||||
null_terminated_array::OwningNullTerminatedArray,
|
||||
path::{
|
||||
path_emit_config_directory_messages, path_get_cache, path_get_config, path_get_data,
|
||||
path_make_canonical, paths_are_same_file,
|
||||
},
|
||||
prelude::*,
|
||||
proc::is_interactive_session,
|
||||
termsize,
|
||||
universal_notifier::default_notifier,
|
||||
wutil::{fish_wcstol, wgetcwd},
|
||||
};
|
||||
use crate::env::config_paths::{ConfigPaths, PREFIX};
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvVar, Statuses};
|
||||
use crate::env_dispatch::{VarChangeMilieu, env_dispatch_init, env_dispatch_var_change};
|
||||
use crate::event::Event;
|
||||
use crate::flog::flog;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::input::{FISH_BIND_MODE_VAR, init_input};
|
||||
use crate::localization::wgettext;
|
||||
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||
use crate::path::{
|
||||
path_emit_config_directory_messages, path_get_cache, path_get_config, path_get_data,
|
||||
path_make_canonical, paths_are_same_file,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::termsize;
|
||||
use crate::universal_notifier::default_notifier;
|
||||
use crate::wutil::{fish_wcstol, wgetcwd};
|
||||
use fish_common::{UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::join_strings;
|
||||
use fish_widestring::{cstr2wcstring, osstr2wcstring, str2wcstring};
|
||||
use libc::c_int;
|
||||
use nix::{
|
||||
NixPath as _,
|
||||
unistd::{Uid, User, gethostname, getpid},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, LazyLock, OnceLock};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::CStr,
|
||||
path::PathBuf,
|
||||
sync::{Arc, LazyLock, OnceLock},
|
||||
};
|
||||
|
||||
/// Set when a universal variable has been modified but not yet been written to disk via sync().
|
||||
static UVARS_LOCALLY_MODIFIED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
2
src/env/environment_impl.rs
vendored
2
src/env/environment_impl.rs
vendored
@@ -13,7 +13,7 @@
|
||||
use crate::reader::{commandline_get_state, reader_status_count};
|
||||
use crate::threads::{is_forked_child, is_main_thread};
|
||||
use crate::wutil::fish_wcstol_radix;
|
||||
use fish_wcstringutil::wcs2zstring;
|
||||
use fish_widestring::wcs2zstring;
|
||||
use nix::sys::stat::{Mode, umask};
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::collections::HashSet;
|
||||
|
||||
4
src/env/mod.rs
vendored
4
src/env/mod.rs
vendored
@@ -4,7 +4,7 @@
|
||||
pub mod var;
|
||||
|
||||
pub use environment::*;
|
||||
use fish_wcstringutil::ToCString;
|
||||
use fish_widestring::ToCString;
|
||||
use std::sync::{Mutex, atomic::AtomicUsize};
|
||||
pub use var::*;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
/// environment variables.
|
||||
///
|
||||
/// As values could contain non-unicode characters, they must first be converted from &wstr to a
|
||||
/// `CString` with [`fish_wcstringutil::wcs2zstring()`].
|
||||
/// `CString` with [`fish_widestring::wcs2zstring()`].
|
||||
pub fn setenv_lock<S1: ToCString, S2: ToCString>(name: S1, value: S2, overwrite: bool) {
|
||||
let name = name.to_cstring();
|
||||
let value = value.to_cstring();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::common::{UnescapeFlags, UnescapeStringStyle, unescape_string, valid_var_name};
|
||||
use crate::common::valid_var_name;
|
||||
use crate::env::{EnvVar, EnvVarFlags, VarTable};
|
||||
use crate::flog::{flog, flogf};
|
||||
use crate::fs::{PotentialUpdate, lock_and_load, rewrite_via_temporary_file};
|
||||
use crate::path::path_get_config;
|
||||
use crate::prelude::*;
|
||||
use crate::wutil::{FileId, INVALID_FILE_ID, file_id_for_file, file_id_for_path_narrow, wrealpath};
|
||||
use fish_wcstringutil::{LineIterator, join_strings, wcs2zstring};
|
||||
use fish_widestring::decode_byte_from_char;
|
||||
use fish_common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::{LineIterator, join_strings};
|
||||
use fish_widestring::{decode_byte_from_char, wcs2zstring};
|
||||
use itertools::Itertools as _;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::hash_map::Entry;
|
||||
@@ -806,15 +807,15 @@ fn skip_spaces(mut s: &wstr) -> &wstr {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::common::osstr2wcstring;
|
||||
use crate::env::{EnvVar, EnvVarFlags, VarTable};
|
||||
use crate::env_universal_common::{EnvUniversal, UvarFormat};
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::wutil::{INVALID_FILE_ID, file_id_for_path};
|
||||
use crate::{
|
||||
env::{EnvVar, EnvVarFlags, VarTable},
|
||||
env_universal_common::{EnvUniversal, UvarFormat},
|
||||
prelude::*,
|
||||
tests::prelude::*,
|
||||
wutil::{INVALID_FILE_ID, file_id_for_path},
|
||||
};
|
||||
use fish_tempfile::TempDir;
|
||||
use fish_wcstringutil::wcs2osstring;
|
||||
use fish_widestring::{ENCODE_DIRECT_BASE, char_offset};
|
||||
use fish_widestring::{ENCODE_DIRECT_BASE, char_offset, osstr2wcstring, wcs2osstring};
|
||||
|
||||
const UVARS_PER_THREAD: usize = 8;
|
||||
|
||||
|
||||
28
src/event.rs
28
src/event.rs
@@ -4,18 +4,22 @@
|
||||
//! defined when these functions produce output or perform memory allocations, since such functions
|
||||
//! may not be safely called by signal handlers.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::common::{ScopeGuard, escape, str2wcstring};
|
||||
use crate::flog::flog;
|
||||
use crate::io::{IoChain, IoStreams};
|
||||
use crate::job_group::MaybeJobId;
|
||||
use crate::parser::{Block, Parser};
|
||||
use crate::prelude::*;
|
||||
use crate::proc::Pid;
|
||||
use crate::reader::reader_update_termsize;
|
||||
use crate::signal::{Signal, signal_check_cancel, signal_handle};
|
||||
use crate::{
|
||||
flog::flog,
|
||||
io::{IoChain, IoStreams},
|
||||
job_group::MaybeJobId,
|
||||
parser::{Block, Parser},
|
||||
prelude::*,
|
||||
proc::Pid,
|
||||
reader::reader_update_termsize,
|
||||
signal::{Signal, signal_check_cancel, signal_handle},
|
||||
};
|
||||
use fish_common::{ScopeGuard, escape};
|
||||
use fish_widestring::str2wcstring;
|
||||
use std::sync::{
|
||||
Arc, Mutex,
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
pub enum EventType {
|
||||
Any,
|
||||
|
||||
11
src/exec.rs
11
src/exec.rs
@@ -7,9 +7,6 @@
|
||||
ErrorCode, STATUS_CMD_ERROR, STATUS_CMD_UNKNOWN, STATUS_NOT_EXECUTABLE, STATUS_READ_TOO_MUCH,
|
||||
builtin_run,
|
||||
};
|
||||
use crate::common::{
|
||||
ScopeGuard, bytes2wcstring, exit_without_destructors, truncate_at_nul, write_loop,
|
||||
};
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvStack, Environment as _, READ_BYTE_LIMIT, Statuses};
|
||||
#[cfg(have_posix_spawn)]
|
||||
use crate::env_dispatch::use_posix_spawn;
|
||||
@@ -39,15 +36,15 @@
|
||||
InternalProc, Job, JobGroupRef, Pid, ProcStatus, Process, ProcessType, hup_jobs,
|
||||
is_interactive_session, jobs_requiring_warning_on_exit, no_exec, print_exit_warning_for_jobs,
|
||||
};
|
||||
use crate::reader::{reader_run_count, safe_restore_term_mode};
|
||||
use crate::reader::{reader_run_count, restore_term_mode};
|
||||
use crate::redirection::{Dup2List, dup2_list_resolve_chain};
|
||||
use crate::threads::{ThreadPool, is_forked_child};
|
||||
use crate::trace::trace_if_enabled_with_args;
|
||||
use crate::tty_handoff::TtyHandoff;
|
||||
use crate::wutil::{fish_wcstol, perror_io};
|
||||
use errno::{errno, set_errno};
|
||||
use fish_wcstringutil::{wcs2bytes, wcs2zstring};
|
||||
use fish_widestring::ToWString as _;
|
||||
use fish_common::{ScopeGuard, exit_without_destructors, truncate_at_nul, write_loop};
|
||||
use fish_widestring::{ToWString as _, bytes2wcstring, wcs2bytes, wcs2zstring};
|
||||
use libc::{
|
||||
EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
|
||||
STDIN_FILENO, STDOUT_FILENO,
|
||||
@@ -452,7 +449,7 @@ fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
|
||||
let actual_cmd = wcs2zstring(&p.actual_cmd);
|
||||
|
||||
// Ensure the terminal modes are what they were before we changed them.
|
||||
safe_restore_term_mode();
|
||||
restore_term_mode();
|
||||
// Bounce to launch_process. This never returns.
|
||||
safe_launch_process(p, &actual_cmd, &argv, &envp);
|
||||
}
|
||||
|
||||
104
src/expand.rs
104
src/expand.rs
@@ -3,32 +3,38 @@
|
||||
//! from using a more clever memory allocation scheme, perhaps an evil combination of talloc,
|
||||
//! string buffers and reference counting.
|
||||
|
||||
use crate::builtins::shared::{
|
||||
STATUS_CMD_ERROR, STATUS_CMD_UNKNOWN, STATUS_EXPAND_ERROR, STATUS_ILLEGAL_CMD,
|
||||
STATUS_INVALID_ARGS, STATUS_NOT_EXECUTABLE, STATUS_READ_TOO_MUCH, STATUS_UNMATCHED_WILDCARD,
|
||||
use crate::{
|
||||
builtins::shared::{
|
||||
STATUS_CMD_ERROR, STATUS_CMD_UNKNOWN, STATUS_EXPAND_ERROR, STATUS_ILLEGAL_CMD,
|
||||
STATUS_INVALID_ARGS, STATUS_NOT_EXECUTABLE, STATUS_READ_TOO_MUCH,
|
||||
STATUS_UNMATCHED_WILDCARD,
|
||||
},
|
||||
common::valid_var_name_char,
|
||||
complete::{CompleteFlags, Completion, CompletionList, CompletionReceiver},
|
||||
env::{EnvVar, Environment},
|
||||
exec::exec_subshell_for_expand,
|
||||
history::{History, history_session_id},
|
||||
operation_context::OperationContext,
|
||||
parse_constants::{ParseError, ParseErrorCode, ParseErrorList, SOURCE_LOCATION_UNKNOWN},
|
||||
parse_util::{MaybeParentheses, expand_variable_error, locate_cmdsubst_range},
|
||||
path::path_apply_working_directory,
|
||||
prelude::*,
|
||||
wildcard::{WildcardResult, wildcard_expand_string, wildcard_has_internal},
|
||||
wutil::{Options, normalize_path, wcstoi_partial},
|
||||
};
|
||||
use crate::common::{
|
||||
EscapeFlags, EscapeStringStyle, UnescapeFlags, UnescapeStringStyle, escape, escape_string,
|
||||
escape_string_for_double_quotes, osstr2wcstring, unescape_string, valid_var_name_char,
|
||||
};
|
||||
use crate::complete::{CompleteFlags, Completion, CompletionList, CompletionReceiver};
|
||||
use crate::env::{EnvVar, Environment};
|
||||
use crate::exec::exec_subshell_for_expand;
|
||||
use crate::history::{History, history_session_id};
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::{ParseError, ParseErrorCode, ParseErrorList, SOURCE_LOCATION_UNKNOWN};
|
||||
use crate::parse_util::{MaybeParentheses, expand_variable_error, locate_cmdsubst_range};
|
||||
use crate::path::path_apply_working_directory;
|
||||
use crate::prelude::*;
|
||||
use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE, WildcardResult};
|
||||
use crate::wildcard::{wildcard_expand_string, wildcard_has_internal};
|
||||
use crate::wutil::{Options, normalize_path, wcstoi_partial};
|
||||
use bitflags::bitflags;
|
||||
use fish_common::{EXPAND_RESERVED_BASE, EXPAND_RESERVED_END};
|
||||
use fish_common::{
|
||||
EscapeFlags, EscapeStringStyle, UnescapeFlags, UnescapeStringStyle, escape, escape_string,
|
||||
escape_string_for_double_quotes, unescape_string,
|
||||
};
|
||||
use fish_feature_flags::{FeatureFlag, feature_test};
|
||||
use fish_util::wcsfilecmp_glob;
|
||||
use fish_wcstringutil::{join_strings, trim};
|
||||
use fish_widestring::char_offset;
|
||||
use fish_widestring::{
|
||||
ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE, BRACE_BEGIN, BRACE_END, BRACE_SEP, BRACE_SPACE,
|
||||
HOME_DIRECTORY, INTERNAL_SEPARATOR, PROCESS_EXPAND_SELF, VARIABLE_EXPAND,
|
||||
VARIABLE_EXPAND_EMPTY, VARIABLE_EXPAND_SINGLE, osstr2wcstring,
|
||||
};
|
||||
use nix::unistd::{User, getpid};
|
||||
|
||||
bitflags! {
|
||||
@@ -77,33 +83,6 @@ pub struct ExpandFlags : u16 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Character representing a home directory.
|
||||
pub const HOME_DIRECTORY: char = char_offset(EXPAND_RESERVED_BASE, 0);
|
||||
/// Character representing process expansion for %self.
|
||||
pub const PROCESS_EXPAND_SELF: char = char_offset(EXPAND_RESERVED_BASE, 1);
|
||||
/// Character representing variable expansion.
|
||||
pub const VARIABLE_EXPAND: char = char_offset(EXPAND_RESERVED_BASE, 2);
|
||||
/// Character representing variable expansion into a single element.
|
||||
pub const VARIABLE_EXPAND_SINGLE: char = char_offset(EXPAND_RESERVED_BASE, 3);
|
||||
/// Character representing the start of a bracket expansion.
|
||||
pub const BRACE_BEGIN: char = char_offset(EXPAND_RESERVED_BASE, 4);
|
||||
/// Character representing the end of a bracket expansion.
|
||||
pub const BRACE_END: char = char_offset(EXPAND_RESERVED_BASE, 5);
|
||||
/// Character representing separation between two bracket elements.
|
||||
pub const BRACE_SEP: char = char_offset(EXPAND_RESERVED_BASE, 6);
|
||||
/// Character that takes the place of any whitespace within non-quoted text in braces
|
||||
pub const BRACE_SPACE: char = char_offset(EXPAND_RESERVED_BASE, 7);
|
||||
/// Separate subtokens in a token with this character.
|
||||
pub const INTERNAL_SEPARATOR: char = char_offset(EXPAND_RESERVED_BASE, 8);
|
||||
/// Character representing an empty variable expansion. Only used transitively while expanding
|
||||
/// variables.
|
||||
pub const VARIABLE_EXPAND_EMPTY: char = char_offset(EXPAND_RESERVED_BASE, 9);
|
||||
|
||||
const _: () = assert!(
|
||||
EXPAND_RESERVED_END as u32 > VARIABLE_EXPAND_EMPTY as u32,
|
||||
"Characters used in expansions must stay within private use area"
|
||||
);
|
||||
|
||||
impl ExpandResult {
|
||||
pub fn new(result: ExpandResultCode) -> Self {
|
||||
Self { result, status: 0 }
|
||||
@@ -127,9 +106,6 @@ fn eq(&self, other: &ExpandResultCode) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// The string represented by PROCESS_EXPAND_SELF
|
||||
pub const PROCESS_EXPAND_SELF_STR: &wstr = L!("%self");
|
||||
|
||||
/// Perform various forms of expansion on in, such as tilde expansion (\~USER becomes the users home
|
||||
/// directory), variable expansion (\$VAR_NAME becomes the value of the environment variable
|
||||
/// VAR_NAME), cmdsubst expansion and wildcard expansion. The results are inserted into the list
|
||||
@@ -1573,25 +1549,19 @@ pub struct ExpandResult {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::abbrs::Abbreviation;
|
||||
use crate::abbrs::{self};
|
||||
use crate::abbrs::{with_abbrs, with_abbrs_mut};
|
||||
use crate::common::str2wcstring;
|
||||
use crate::complete::{CompletionList, CompletionReceiver};
|
||||
use crate::env::{EnvMode, EnvStackSetResult};
|
||||
use crate::expand::{ExpandResultCode, expand_to_receiver};
|
||||
use crate::operation_context::{EXPANSION_LIMIT_DEFAULT, no_cancel};
|
||||
use crate::parse_constants::ParseErrorList;
|
||||
use crate::parser::ParserEnvSetMode;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::wildcard::ANY_STRING;
|
||||
use crate::{
|
||||
expand::{ExpandFlags, expand_string},
|
||||
operation_context::OperationContext,
|
||||
abbrs::{self, Abbreviation, with_abbrs, with_abbrs_mut},
|
||||
complete::{CompletionList, CompletionReceiver},
|
||||
env::{EnvMode, EnvStackSetResult},
|
||||
expand::{ExpandFlags, ExpandResultCode, expand_string, expand_to_receiver},
|
||||
operation_context::{EXPANSION_LIMIT_DEFAULT, OperationContext, no_cancel},
|
||||
parse_constants::ParseErrorList,
|
||||
parser::ParserEnvSetMode,
|
||||
prelude::*,
|
||||
tests::prelude::*,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::hash_map::RandomState;
|
||||
use fish_widestring::{ANY_STRING, str2wcstring};
|
||||
use std::collections::{HashSet, hash_map::RandomState};
|
||||
|
||||
fn expand_test_impl(
|
||||
input: &wstr,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::common::exit_without_destructors;
|
||||
use crate::fd_readable_set::{FdReadableSet, Timeout};
|
||||
use crate::flog::flog;
|
||||
use crate::portable_atomic::AtomicU64;
|
||||
@@ -6,6 +5,7 @@
|
||||
use crate::wutil::perror_nix;
|
||||
use cfg_if::cfg_if;
|
||||
use errno::errno;
|
||||
use fish_common::exit_without_destructors;
|
||||
use fish_util::perror;
|
||||
use libc::{EAGAIN, EINTR, EWOULDBLOCK};
|
||||
use std::collections::HashMap;
|
||||
|
||||
28
src/fds.rs
28
src/fds.rs
@@ -1,20 +1,20 @@
|
||||
use crate::flog::flog;
|
||||
use crate::prelude::*;
|
||||
use crate::signal::signal_check_cancel;
|
||||
use crate::wutil::perror_nix;
|
||||
use crate::{flog::flog, prelude::*, signal::signal_check_cancel, wutil::perror_nix};
|
||||
use cfg_if::cfg_if;
|
||||
use fish_util::perror;
|
||||
use fish_wcstringutil::wcs2zstring;
|
||||
use fish_widestring::wcs2zstring;
|
||||
use libc::{EINTR, F_GETFD, F_GETFL, F_SETFD, F_SETFL, FD_CLOEXEC, O_NONBLOCK, c_int};
|
||||
use nix::fcntl::FcntlArg;
|
||||
use nix::fcntl::OFlag;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
|
||||
use std::os::unix::prelude::*;
|
||||
use nix::fcntl::{FcntlArg, OFlag};
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
fs::File,
|
||||
io,
|
||||
mem::ManuallyDrop,
|
||||
ops::{Deref, DerefMut},
|
||||
os::{
|
||||
fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd},
|
||||
unix::prelude::*,
|
||||
},
|
||||
};
|
||||
|
||||
localizable_consts!(
|
||||
pub PIPE_ERROR
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use crate::wildcard::wildcard_match;
|
||||
use crate::{parse_util::unescape_wildcards, wutil::unescape_bytes_and_write_to_fd};
|
||||
use fish_util::write_to_fd;
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_widestring::wcs2bytes;
|
||||
use libc::c_int;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
use crate::null_terminated_array::OwningNullTerminatedArray;
|
||||
use crate::redirection::Dup2List;
|
||||
use crate::signal::signal_reset_handlers;
|
||||
use crate::{common::exit_without_destructors, wutil::fstat};
|
||||
use crate::wutil::fstat;
|
||||
use fish_common::exit_without_destructors;
|
||||
use libc::{O_RDONLY, pid_t};
|
||||
use nix::unistd::getpid;
|
||||
use std::ffi::CStr;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::{
|
||||
common::osstr2wcstring,
|
||||
fds::wopen_cloexec,
|
||||
flog, flogf,
|
||||
path::{DirRemoteness, path_remoteness},
|
||||
@@ -7,7 +6,7 @@
|
||||
wutil::{FileId, INVALID_FILE_ID, file_id_for_file, file_id_for_path, wdirname, wunlink},
|
||||
};
|
||||
use fish_tempfile::random_filename;
|
||||
use fish_wcstringutil::{wcs2bytes, wcs2osstring};
|
||||
use fish_widestring::{osstr2wcstring, wcs2bytes, wcs2osstring};
|
||||
use libc::{LOCK_EX, LOCK_SH, c_int};
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
use std::{
|
||||
|
||||
@@ -2,23 +2,28 @@
|
||||
// autoloading functions in the $fish_function_path. Actual function evaluation is taken care of by
|
||||
// the parser and to some degree the builtin handling library.
|
||||
|
||||
use crate::ast::{self, Node as _};
|
||||
use crate::autoload::{Autoload, AutoloadResult};
|
||||
use crate::common::{FilenameRef, assert_sync, escape, valid_func_name};
|
||||
use crate::complete::complete_wrap_map;
|
||||
use crate::env::{EnvStack, Environment};
|
||||
use crate::event::{self, EventDescription};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::parse_tree::NodeRef;
|
||||
use crate::parser::Parser;
|
||||
use crate::parser_keywords::parser_keywords_is_reserved;
|
||||
use crate::prelude::*;
|
||||
use crate::proc::Pid;
|
||||
use crate::wutil::dir_iter::DirIter;
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, LazyLock, Mutex};
|
||||
use crate::{
|
||||
ast::{self, Node as _},
|
||||
autoload::{Autoload, AutoloadResult},
|
||||
common::valid_func_name,
|
||||
complete::complete_wrap_map,
|
||||
env::{EnvStack, Environment},
|
||||
event::{self, EventDescription},
|
||||
global_safety::RelaxedAtomicBool,
|
||||
parse_tree::NodeRef,
|
||||
parser::Parser,
|
||||
parser_keywords::parser_keywords_is_reserved,
|
||||
prelude::*,
|
||||
proc::Pid,
|
||||
wutil::dir_iter::DirIter,
|
||||
};
|
||||
use fish_common::{FilenameRef, assert_sync, escape};
|
||||
use fish_widestring::wcs2bytes;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
num::NonZeroU32,
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FunctionProperties {
|
||||
|
||||
@@ -2,28 +2,29 @@
|
||||
// to support highlighting.
|
||||
// Because this may perform blocking I/O, we compute results in a separate thread,
|
||||
// and provide them optimistically.
|
||||
use crate::common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use crate::expand::{
|
||||
BRACE_BEGIN, BRACE_END, BRACE_SEP, INTERNAL_SEPARATOR, PROCESS_EXPAND_SELF, VARIABLE_EXPAND,
|
||||
VARIABLE_EXPAND_SINGLE, expand_one,
|
||||
};
|
||||
use crate::expand::{ExpandFlags, HOME_DIRECTORY, expand_tilde};
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::path::path_apply_working_directory;
|
||||
use crate::redirection::RedirectionMode;
|
||||
use crate::threads::assert_is_background_thread;
|
||||
use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
|
||||
use crate::wutil::{
|
||||
dir_iter::DirIter, fish_wcstoi, normalize_path, waccess, wbasename, wdirname, wstat,
|
||||
use crate::{
|
||||
expand::{ExpandFlags, expand_one, expand_tilde},
|
||||
operation_context::OperationContext,
|
||||
path::path_apply_working_directory,
|
||||
redirection::RedirectionMode,
|
||||
threads::assert_is_background_thread,
|
||||
wutil::{dir_iter::DirIter, fish_wcstoi, normalize_path, waccess, wbasename, wdirname, wstat},
|
||||
};
|
||||
use fish_common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::{
|
||||
string_prefixes_string, string_prefixes_string_case_insensitive, string_suffixes_string,
|
||||
};
|
||||
use fish_widestring::{L, WExt as _, WString, wstr};
|
||||
use fish_widestring::{
|
||||
ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE, BRACE_BEGIN, BRACE_END, BRACE_SEP, HOME_DIRECTORY,
|
||||
INTERNAL_SEPARATOR, L, PROCESS_EXPAND_SELF, VARIABLE_EXPAND, VARIABLE_EXPAND_SINGLE, WExt as _,
|
||||
WString, wstr,
|
||||
};
|
||||
use libc::PATH_MAX;
|
||||
use nix::unistd::AccessFlags;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::os::fd::RawFd;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
os::fd::RawFd,
|
||||
};
|
||||
|
||||
// This is used only internally to this file, and is exposed only for testing.
|
||||
#[derive(Clone, Copy, Default)]
|
||||
@@ -427,16 +428,19 @@ pub fn fs_is_case_insensitive(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FileTester, IsErr, IsFile, PathFlags, is_potential_path};
|
||||
use crate::common::osstr2wcstring;
|
||||
use crate::env::EnvStack;
|
||||
use crate::operation_context::{EXPANSION_LIMIT_DEFAULT, OperationContext};
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::*;
|
||||
|
||||
use crate::redirection::RedirectionMode;
|
||||
use std::fs::{self, File, Permissions, create_dir_all};
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
use std::path::PathBuf;
|
||||
use crate::{
|
||||
env::EnvStack,
|
||||
operation_context::{EXPANSION_LIMIT_DEFAULT, OperationContext},
|
||||
prelude::*,
|
||||
redirection::RedirectionMode,
|
||||
tests::prelude::*,
|
||||
};
|
||||
use fish_widestring::osstr2wcstring;
|
||||
use std::{
|
||||
fs::{self, File, Permissions, create_dir_all},
|
||||
os::unix::fs::PermissionsExt as _,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
struct TempDirWithCtx {
|
||||
tempdir: fish_tempfile::TempDir,
|
||||
|
||||
@@ -1,40 +1,39 @@
|
||||
//! Functions for syntax highlighting.
|
||||
use crate::abbrs::{self, with_abbrs};
|
||||
use crate::ast::{
|
||||
self, Argument, BlockStatement, BlockStatementHeader, BraceStatement, DecoratedStatement,
|
||||
Keyword, Kind, Node, NodeVisitor, Redirection, Token, VariableAssignment,
|
||||
use crate::{
|
||||
abbrs::{self, with_abbrs},
|
||||
ast::{
|
||||
self, Argument, BlockStatement, BlockStatementHeader, BraceStatement, DecoratedStatement,
|
||||
Keyword, Kind, Node, NodeVisitor, Redirection, Token, VariableAssignment,
|
||||
},
|
||||
builtins::shared::builtin_exists,
|
||||
common::{valid_var_name, valid_var_name_char},
|
||||
complete::complete_wrap_map,
|
||||
env::{EnvVar, Environment},
|
||||
expand::{ExpandFlags, ExpandResultCode, expand_one, expand_to_command_and_args},
|
||||
function,
|
||||
highlight::file_tester::FileTester,
|
||||
history::all_paths_are_valid,
|
||||
operation_context::OperationContext,
|
||||
parse_constants::{
|
||||
ParseKeyword, ParseTokenType, ParseTreeFlags, SourceRange, StatementDecoration,
|
||||
},
|
||||
parse_util::{
|
||||
MaybeParentheses, get_process_first_token_offset, locate_cmdsubst_range, slice_length,
|
||||
},
|
||||
path::{path_as_implicit_cd, path_get_cdpath, path_get_path, paths_are_same_file},
|
||||
terminal::Outputter,
|
||||
text_face::{ResettableStyle, SpecifiedTextFace, TextFace, UnderlineStyle, parse_text_face},
|
||||
threads::assert_is_background_thread,
|
||||
tokenizer::{PipeOrRedir, variable_assignment_equals_pos},
|
||||
};
|
||||
use crate::builtins::shared::builtin_exists;
|
||||
use crate::common::{valid_var_name, valid_var_name_char};
|
||||
use crate::complete::complete_wrap_map;
|
||||
use crate::env::{EnvVar, Environment};
|
||||
use crate::expand::{
|
||||
ExpandFlags, ExpandResultCode, PROCESS_EXPAND_SELF_STR, expand_one, expand_to_command_and_args,
|
||||
};
|
||||
use crate::function;
|
||||
use crate::highlight::file_tester::FileTester;
|
||||
use crate::history::all_paths_are_valid;
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::{
|
||||
ParseKeyword, ParseTokenType, ParseTreeFlags, SourceRange, StatementDecoration,
|
||||
};
|
||||
use crate::parse_util::{
|
||||
MaybeParentheses, get_process_first_token_offset, locate_cmdsubst_range, slice_length,
|
||||
};
|
||||
use crate::path::{path_as_implicit_cd, path_get_cdpath, path_get_path, paths_are_same_file};
|
||||
use crate::terminal::Outputter;
|
||||
use crate::text_face::{
|
||||
ResettableStyle, SpecifiedTextFace, TextFace, UnderlineStyle, parse_text_face,
|
||||
};
|
||||
use crate::threads::assert_is_background_thread;
|
||||
use crate::tokenizer::{PipeOrRedir, variable_assignment_equals_pos};
|
||||
use fish_color::Color;
|
||||
use fish_common::{ASCII_MAX, EXPAND_RESERVED_BASE, EXPAND_RESERVED_END};
|
||||
use fish_feature_flags::{FeatureFlag, feature_test};
|
||||
use fish_wcstringutil::string_prefixes_string;
|
||||
use fish_widestring::{L, WExt as _, WString, wstr};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use fish_widestring::{
|
||||
ASCII_MAX, EXPAND_RESERVED_BASE, EXPAND_RESERVED_END, L, PROCESS_EXPAND_SELF_STR, WExt as _,
|
||||
WString, wstr,
|
||||
};
|
||||
use std::collections::{HashMap, hash_map::Entry};
|
||||
|
||||
use super::file_tester::IsFile;
|
||||
|
||||
@@ -189,13 +188,15 @@ fn resolve_spec_uncached(highlight: &HighlightSpec, vars: &dyn Environment) -> T
|
||||
// Handle modifiers.
|
||||
if highlight.valid_path {
|
||||
if let Some(valid_path_var) = vars.get(L!("fish_color_valid_path")) {
|
||||
// Historical behavior is to not apply background.
|
||||
let valid_path_face = parse_text_face_for_highlight(&valid_path_var)
|
||||
.unwrap_or(TextFace::terminal_default());
|
||||
// Apply the foreground, except if it's normal. The intention here is likely
|
||||
// to only override foreground if the valid path color has an explicit foreground.
|
||||
if !valid_path_face.fg.is_normal() {
|
||||
face.fg = valid_path_face.fg;
|
||||
let valid_path_face = parse_text_face(valid_path_var.as_list());
|
||||
if let Some(fg) = valid_path_face.fg {
|
||||
face.fg = fg;
|
||||
}
|
||||
if let Some(bg) = valid_path_face.bg {
|
||||
face.bg = bg;
|
||||
}
|
||||
if let Some(underline_color) = valid_path_face.underline_color {
|
||||
face.underline_color = underline_color;
|
||||
}
|
||||
face.style = face.style.union_prefer_right(valid_path_face.style);
|
||||
}
|
||||
@@ -1312,13 +1313,13 @@ pub struct HighlightSpec {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{HighlightColorResolver, HighlightRole, HighlightSpec, highlight_shell};
|
||||
use crate::common::ScopeGuard;
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvVar, EnvVarFlags, Environment as _};
|
||||
use crate::highlight::parse_text_face_for_highlight;
|
||||
use crate::operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext};
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::text_face::{ResettableStyle, UnderlineStyle};
|
||||
use fish_common::ScopeGuard;
|
||||
use fish_feature_flags::{FeatureFlag, with_overridden_feature};
|
||||
use libc::PATH_MAX;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
path::{DirRemoteness, path_get_data_remoteness},
|
||||
wutil::FileId,
|
||||
};
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_widestring::wcs2bytes;
|
||||
use libc::{ENODEV, MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
use std::{
|
||||
fs::File,
|
||||
|
||||
@@ -15,16 +15,37 @@
|
||||
//! fallback solution attempts to detect races and retries if a race is detected.
|
||||
|
||||
use crate::{
|
||||
common::cstr2wcstring,
|
||||
env::{EnvSetMode, EnvVar},
|
||||
ast::{self, Kind, Node as _},
|
||||
common::{CancelChecker, valid_var_name},
|
||||
env::{EnvMode, EnvSetMode, EnvStack, EnvVar, Environment},
|
||||
expand::{ExpandFlags, expand_one},
|
||||
fds::wopen_cloexec,
|
||||
flog::{flog, flogf},
|
||||
fs::{
|
||||
LOCKED_FILE_MODE, LockedFile, LockingMode, PotentialUpdate, WriteMethod, lock_and_load,
|
||||
rewrite_via_temporary_file,
|
||||
LOCKED_FILE_MODE, LockedFile, LockingMode, PotentialUpdate, WriteMethod, fsync,
|
||||
lock_and_load, rewrite_via_temporary_file,
|
||||
},
|
||||
threads::ThreadPool,
|
||||
highlight::highlight_and_colorize,
|
||||
history::file::{HistoryFile, RawHistoryFile},
|
||||
io::IoStreams,
|
||||
localization::wgettext_fmt,
|
||||
operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext},
|
||||
parse_constants::{ParseTreeFlags, StatementDecoration},
|
||||
parse_util::{detect_parse_errors, unescape_wildcards},
|
||||
parser::Parser,
|
||||
path::{path_get_config, path_get_data, path_is_valid},
|
||||
prelude::*,
|
||||
threads::{ThreadPool, assert_is_background_thread},
|
||||
wildcard::wildcard_match,
|
||||
wutil::{FileId, INVALID_FILE_ID, file_id_for_file, wrealpath, wstat, wunlink},
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use fish_common::{UnescapeStringStyle, unescape_string};
|
||||
use fish_wcstringutil::{subsequence_in_string, trim};
|
||||
use fish_widestring::subslice_position;
|
||||
use fish_widestring::{ANY_STRING, bytes2wcstring, cstr2wcstring, subslice_position};
|
||||
use lru::LruCache;
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
use rand::Rng as _;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
@@ -38,34 +59,6 @@
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use lru::LruCache;
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
use rand::Rng as _;
|
||||
|
||||
use crate::{
|
||||
ast::{self, Kind, Node as _},
|
||||
common::{CancelChecker, UnescapeStringStyle, bytes2wcstring, unescape_string, valid_var_name},
|
||||
env::{EnvMode, EnvStack, Environment},
|
||||
expand::{ExpandFlags, expand_one},
|
||||
fds::wopen_cloexec,
|
||||
flog::{flog, flogf},
|
||||
fs::fsync,
|
||||
highlight::highlight_and_colorize,
|
||||
history::file::{HistoryFile, RawHistoryFile},
|
||||
io::IoStreams,
|
||||
localization::wgettext_fmt,
|
||||
operation_context::{EXPANSION_LIMIT_BACKGROUND, OperationContext},
|
||||
parse_constants::{ParseTreeFlags, StatementDecoration},
|
||||
parse_util::{detect_parse_errors, unescape_wildcards},
|
||||
parser::Parser,
|
||||
path::{path_get_config, path_get_data, path_is_valid},
|
||||
prelude::*,
|
||||
threads::assert_is_background_thread,
|
||||
wildcard::{ANY_STRING, wildcard_match},
|
||||
wutil::{FileId, INVALID_FILE_ID, file_id_for_file, wrealpath, wstat, wunlink},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SearchType {
|
||||
/// Search for commands exactly matching the given string.
|
||||
@@ -1806,22 +1799,24 @@ mod tests {
|
||||
History, HistoryItem, HistorySearch, PathList, PersistenceMode, SearchDirection,
|
||||
SearchFlags, SearchType, VACUUM_FREQUENCY,
|
||||
};
|
||||
use crate::common::{ESCAPE_TEST_CHAR, osstr2wcstring};
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvStack};
|
||||
use crate::fs::{LockedFile, WriteMethod};
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::test_init;
|
||||
use fish_build_helper::workspace_root;
|
||||
use fish_wcstringutil::{
|
||||
string_prefixes_string, string_prefixes_string_case_insensitive, wcs2bytes,
|
||||
use crate::{
|
||||
common::ESCAPE_TEST_CHAR,
|
||||
env::{EnvMode, EnvSetMode, EnvStack},
|
||||
fs::{LockedFile, WriteMethod},
|
||||
prelude::*,
|
||||
tests::prelude::test_init,
|
||||
};
|
||||
use fish_build_helper::workspace_root;
|
||||
use fish_wcstringutil::{string_prefixes_string, string_prefixes_string_case_insensitive};
|
||||
use fish_widestring::{osstr2wcstring, wcs2bytes};
|
||||
use rand::{Rng as _, rngs::ThreadRng};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ffi::OsString,
|
||||
io::BufReader,
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use rand::Rng as _;
|
||||
use rand::rngs::ThreadRng;
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::OsString;
|
||||
use std::io::BufReader;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn history_contains(history: &History, txt: &wstr) -> bool {
|
||||
for i in 1.. {
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
//! Implementation of the YAML-like history file format.
|
||||
|
||||
use super::{HistoryItem, PersistenceMode};
|
||||
use crate::flog::flog;
|
||||
use fish_widestring::{bytes2wcstring, subslice_position};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use fish_widestring::subslice_position;
|
||||
|
||||
use super::{HistoryItem, PersistenceMode};
|
||||
use crate::{common::bytes2wcstring, flog::flog};
|
||||
|
||||
// Our history format is nearly-valid YAML (but isn't quite). Here it is:
|
||||
//
|
||||
// - cmd: ssh blah blah blah
|
||||
|
||||
35
src/input.rs
35
src/input.rs
@@ -1,20 +1,23 @@
|
||||
use crate::common::{Named, escape, get_by_sorted_name};
|
||||
use crate::env::Environment;
|
||||
use crate::flog::flog;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::input_common::{
|
||||
CharEvent, CharInputStyle, ImplicitEvent, InputEventQueuer, KeyMatchQuality,
|
||||
R_END_INPUT_FUNCTIONS, ReadlineCmd, match_key_event_to_key,
|
||||
use crate::{
|
||||
env::Environment,
|
||||
flog::flog,
|
||||
global_safety::RelaxedAtomicBool,
|
||||
input_common::{
|
||||
CharEvent, CharInputStyle, ImplicitEvent, InputEventQueuer, KeyMatchQuality,
|
||||
R_END_INPUT_FUNCTIONS, ReadlineCmd, match_key_event_to_key,
|
||||
},
|
||||
key::{self, Key, Modifiers, canonicalize_raw_escapes, ctrl},
|
||||
prelude::*,
|
||||
reader::{Reader, reader_reset_interrupted},
|
||||
threads::assert_is_main_thread,
|
||||
};
|
||||
use crate::key::{self, Key, Modifiers, canonicalize_raw_escapes, ctrl};
|
||||
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::{
|
||||
Mutex, MutexGuard,
|
||||
atomic::{AtomicU32, Ordering},
|
||||
use fish_common::{Named, assert_sorted_by_name, escape, get_by_sorted_name};
|
||||
use std::{
|
||||
mem,
|
||||
sync::{
|
||||
Mutex, MutexGuard,
|
||||
atomic::{AtomicU32, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
pub const FISH_BIND_MODE_VAR: &wstr = L!("fish_bind_mode");
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
use crate::common::{
|
||||
WSL, bytes2wcstring, fish_reserved_codepoint, is_windows_subsystem_for_linux, read_blocked,
|
||||
shell_modes,
|
||||
use crate::{
|
||||
common::{WSL, is_windows_subsystem_for_linux, shell_modes},
|
||||
env::{EnvStack, Environment as _},
|
||||
fd_readable_set::{FdReadableSet, Timeout},
|
||||
flog::{FloggableDebug, FloggableDisplay, flog},
|
||||
key::{
|
||||
self, Key, Modifiers, ViewportPosition, alt, canonicalize_control_char,
|
||||
canonicalize_keyed_control_char, char_to_symbol, function_key, shift,
|
||||
},
|
||||
prelude::*,
|
||||
reader::reader_test_and_clear_interrupted,
|
||||
tty_handoff::{
|
||||
SCROLL_CONTENT_UP_TERMINFO_CODE, TERMINAL_OS_NAME, XTGETTCAP_QUERY_OS_NAME, XTVERSION,
|
||||
maybe_set_kitty_keyboard_capability, maybe_set_scroll_content_up_capability,
|
||||
},
|
||||
universal_notifier::default_notifier,
|
||||
wutil::{fish_is_pua, fish_wcstol},
|
||||
};
|
||||
use crate::env::{EnvStack, Environment as _};
|
||||
use crate::fd_readable_set::{FdReadableSet, Timeout};
|
||||
use crate::flog::{FloggableDebug, FloggableDisplay, flog};
|
||||
use crate::key::{
|
||||
self, Key, Modifiers, ViewportPosition, alt, canonicalize_control_char,
|
||||
canonicalize_keyed_control_char, char_to_symbol, function_key, shift,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::reader::reader_test_and_clear_interrupted;
|
||||
use crate::tty_handoff::{
|
||||
SCROLL_CONTENT_UP_TERMINFO_CODE, TERMINAL_OS_NAME, XTGETTCAP_QUERY_OS_NAME, XTVERSION,
|
||||
maybe_set_kitty_keyboard_capability, maybe_set_scroll_content_up_capability,
|
||||
};
|
||||
use crate::universal_notifier::default_notifier;
|
||||
use crate::wutil::{fish_is_pua, fish_wcstol};
|
||||
use fish_common::read_blocked;
|
||||
use fish_feature_flags::{FeatureFlag, feature_test};
|
||||
use fish_widestring::encode_byte_to_char;
|
||||
use fish_widestring::{bytes2wcstring, encode_byte_to_char, fish_reserved_codepoint};
|
||||
use nix::sys::{select::FdSet, signal::SigSet, time::TimeSpec};
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::collections::VecDeque;
|
||||
use std::os::fd::{BorrowedFd, RawFd};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
collections::VecDeque,
|
||||
os::fd::{BorrowedFd, RawFd},
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
// The range of key codes for inputrc-style keyboard functions.
|
||||
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump as usize) + 1;
|
||||
@@ -823,7 +825,7 @@ fn readch(&mut self) -> CharEvent {
|
||||
// or, if they are not valid UTF-8, ignored. On incomplete sequences, another
|
||||
// byte is read and decoding is tried again in the next iteration.
|
||||
let ok = loop {
|
||||
match decode_one_codepoint_utf8(&mut seq, InvalidPolicy::Error, &buffer) {
|
||||
match decode_utf8(&mut seq, InvalidPolicy::Error, &buffer) {
|
||||
DecodeState::Incomplete => {
|
||||
buffer.push(
|
||||
match next_input_event(
|
||||
@@ -1663,7 +1665,20 @@ pub(crate) enum InvalidPolicy {
|
||||
Passthrough,
|
||||
}
|
||||
|
||||
pub(crate) fn decode_one_codepoint_utf8(
|
||||
/// Decode the UTF-8-encoded `buffer`.
|
||||
/// On success, the result is appended to `out_seq` and [`DecodeState::Complete`] is returned.
|
||||
/// [`DecodeState::Incomplete`] is returned if the buffer contains valid UTF-8
|
||||
/// with the exception of the last bytes,
|
||||
/// where the last 1 to 3 bytes are a prefix of the encoding of a valid char,
|
||||
/// which can happen if input is read incrementally.
|
||||
/// In this case `out_seq` will not be modified.
|
||||
/// If other errors occur, the behavior depends on `invalid_policy`.
|
||||
/// For [`InvalidPolicy::Error`], [`DecodeState::Error`] will be returned, without modifying
|
||||
/// `out_seq`.
|
||||
/// For [`InvalidPolicy::Passthrough`], [`DecodeState::Complete`] will be returned
|
||||
/// and `out_seq` will have the individual bytes of `buffer` appended to it, each encoded using our
|
||||
/// PUA encoding scheme.
|
||||
pub(crate) fn decode_utf8(
|
||||
out_seq: &mut WString,
|
||||
invalid_policy: InvalidPolicy,
|
||||
buffer: &[u8],
|
||||
|
||||
38
src/io.rs
38
src/io.rs
@@ -1,26 +1,26 @@
|
||||
use crate::builtins::shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_READ_TOO_MUCH};
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::fd_monitor::{Callback, FdMonitor, FdMonitorItemId};
|
||||
use crate::fds::{
|
||||
BorrowedFdFile, PIPE_ERROR, make_autoclose_pipes, make_fd_nonblocking, wopen_cloexec,
|
||||
use crate::{
|
||||
builtins::shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_READ_TOO_MUCH},
|
||||
fd_monitor::{Callback, FdMonitor, FdMonitorItemId},
|
||||
fds::{BorrowedFdFile, PIPE_ERROR, make_autoclose_pipes, make_fd_nonblocking, wopen_cloexec},
|
||||
flog::{flog, flogf, should_flog},
|
||||
nix::isatty,
|
||||
path::path_apply_working_directory,
|
||||
prelude::*,
|
||||
proc::JobGroupRef,
|
||||
redirection::{RedirectionMode, RedirectionSpecList},
|
||||
wutil::{perror_io, unescape_bytes_and_write_to_fd, wdirname, wstat},
|
||||
};
|
||||
use crate::flog::{flog, flogf, should_flog};
|
||||
use crate::nix::isatty;
|
||||
use crate::path::path_apply_working_directory;
|
||||
use crate::prelude::*;
|
||||
use crate::proc::JobGroupRef;
|
||||
use crate::redirection::{RedirectionMode, RedirectionSpecList};
|
||||
use crate::wutil::{perror_io, unescape_bytes_and_write_to_fd, wdirname, wstat};
|
||||
use errno::Errno;
|
||||
use fish_util::perror;
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_widestring::{bytes2wcstring, wcs2bytes};
|
||||
use libc::{EAGAIN, EINTR, ENOENT, ENOTDIR, EWOULDBLOCK, STDOUT_FILENO};
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::sys::stat::Mode;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, OwnedFd, RawFd};
|
||||
use std::sync::{Arc, LazyLock, Mutex, MutexGuard};
|
||||
use nix::{fcntl::OFlag, sys::stat::Mode};
|
||||
use std::{
|
||||
fs::File,
|
||||
io,
|
||||
os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, OwnedFd, RawFd},
|
||||
sync::{Arc, LazyLock, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
/// separated_buffer_t represents a buffer of output from commands, prepared to be turned into a
|
||||
/// variable. For example, command substitutions output into one of these. Most commands just
|
||||
|
||||
75
src/key.rs
75
src/key.rs
@@ -1,39 +1,56 @@
|
||||
use libc::VERASE;
|
||||
|
||||
use crate::{
|
||||
common::{EscapeFlags, EscapeStringStyle, escape_string},
|
||||
flog::FloggableDebug,
|
||||
prelude::*,
|
||||
reader::safe_get_terminal_mode_on_startup,
|
||||
flog::FloggableDebug, localizable_string, reader::get_terminal_mode_on_startup, wgettext_fmt,
|
||||
wutil::fish_wcstoul,
|
||||
};
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
use fish_fallback::fish_wcwidth;
|
||||
use fish_feature_flags::{FeatureFlag, feature_test};
|
||||
use fish_widestring::decode_byte_from_char;
|
||||
use fish_widestring::{
|
||||
L, SPECIAL_KEY_ENCODE_BASE, WExt as _, WString, char_offset, decode_byte_from_char, wstr,
|
||||
};
|
||||
|
||||
pub(crate) const Backspace: char = '\u{F500}'; // below ENCODE_DIRECT_BASE
|
||||
pub(crate) const Delete: char = '\u{F501}';
|
||||
pub(crate) const Escape: char = '\u{F502}';
|
||||
pub(crate) const Enter: char = '\u{F503}';
|
||||
pub(crate) const Up: char = '\u{F504}';
|
||||
pub(crate) const Down: char = '\u{F505}';
|
||||
pub(crate) const Left: char = '\u{F506}';
|
||||
pub(crate) const Right: char = '\u{F507}';
|
||||
pub(crate) const PageUp: char = '\u{F508}';
|
||||
pub(crate) const PageDown: char = '\u{F509}';
|
||||
pub(crate) const Home: char = '\u{F50A}';
|
||||
pub(crate) const End: char = '\u{F50B}';
|
||||
pub(crate) const Insert: char = '\u{F50C}';
|
||||
pub(crate) const Tab: char = '\u{F50D}';
|
||||
pub(crate) const Space: char = '\u{F50E}';
|
||||
pub(crate) const Menu: char = '\u{F50F}';
|
||||
pub(crate) const PrintScreen: char = '\u{F510}';
|
||||
pub(crate) const MAX_FUNCTION_KEY: u32 = 12;
|
||||
pub(crate) fn function_key(n: u32) -> char {
|
||||
assert!((1..=MAX_FUNCTION_KEY).contains(&n));
|
||||
char::from_u32(u32::from('\u{F5FF}') - MAX_FUNCTION_KEY + (n - 1)).unwrap()
|
||||
const fn special_key_char(offset: u8) -> char {
|
||||
// TODO: use `u32::from(offset)` once that's available in const fn
|
||||
char_offset(SPECIAL_KEY_ENCODE_BASE, offset as u32)
|
||||
}
|
||||
|
||||
macro_rules! define_special_keys {
|
||||
($($key_name:ident: $offset:expr)*) => {
|
||||
$(
|
||||
pub(crate) const $key_name: char = special_key_char($offset);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
define_special_keys! {
|
||||
Backspace: 0
|
||||
Delete: 1
|
||||
Escape: 2
|
||||
Enter: 3
|
||||
Up: 4
|
||||
Down: 5
|
||||
Left: 6
|
||||
Right: 7
|
||||
PageUp: 8
|
||||
PageDown: 9
|
||||
Home: 10
|
||||
End: 11
|
||||
Insert: 12
|
||||
Tab: 13
|
||||
Space: 14
|
||||
Menu: 15
|
||||
PrintScreen: 16
|
||||
|
||||
Invalid: 255
|
||||
}
|
||||
|
||||
pub(crate) const MAX_FUNCTION_KEY: u8 = 12;
|
||||
pub(crate) fn function_key(n: u8) -> char {
|
||||
assert!((1..=MAX_FUNCTION_KEY).contains(&n));
|
||||
special_key_char(254 - MAX_FUNCTION_KEY + n)
|
||||
}
|
||||
pub(crate) const Invalid: char = '\u{F5FF}';
|
||||
|
||||
pub(crate) const KEY_NAMES: &[(char, &wstr)] = &[
|
||||
('-', L!("minus")),
|
||||
@@ -178,7 +195,7 @@ pub(crate) fn canonicalize_keyed_control_char(c: char) -> char {
|
||||
if c == ' ' {
|
||||
return Space;
|
||||
}
|
||||
if let Some(tm) = safe_get_terminal_mode_on_startup() {
|
||||
if let Some(tm) = get_terminal_mode_on_startup() {
|
||||
if c == char::from(tm.c_cc[VERASE]) {
|
||||
return Backspace;
|
||||
}
|
||||
@@ -299,7 +316,7 @@ pub(crate) fn parse_keys(value: &wstr) -> Result<Vec<Key>, WString> {
|
||||
let num = key_name.strip_prefix('f').unwrap();
|
||||
let codepoint = match fish_wcstoul(num) {
|
||||
Ok(n) if (1..=u64::from(MAX_FUNCTION_KEY)).contains(&n) => {
|
||||
function_key(u32::try_from(n).unwrap())
|
||||
function_key(u8::try_from(n).unwrap())
|
||||
}
|
||||
_ => {
|
||||
return Err(wgettext_fmt!(
|
||||
|
||||
@@ -41,7 +41,7 @@ fn gettext(message: MaybeStatic) -> &'static wstr {
|
||||
LazyLock::new(|| Mutex::new(HashMap::default()));
|
||||
let mut localizations_to_wide = LOCALIZATION_TO_WIDE.lock().unwrap();
|
||||
if !localizations_to_wide.contains_key(localized_str) {
|
||||
use crate::common::str2wcstring;
|
||||
use fish_widestring::str2wcstring;
|
||||
|
||||
let localization_wstr = Box::leak(str2wcstring(localized_str).into_boxed_utfstr());
|
||||
localizations_to_wide.insert(localized_str, localization_wstr);
|
||||
|
||||
@@ -87,7 +87,7 @@ fn append_space_separated_list<S: AsRef<str>>(
|
||||
) {
|
||||
for lang in list {
|
||||
string.push(' ');
|
||||
string.push_utfstr(&crate::common::escape(
|
||||
string.push_utfstr(&fish_common::escape(
|
||||
// lang is already PUA-encoded at this point. The reason we convert the PUA-encoded
|
||||
// WString into a String is to enable comparison with the language names we have
|
||||
// available. We could use WString for lang, but that would require converting our
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::common::{assert_send, assert_sync};
|
||||
use fish_common::{assert_send, assert_sync};
|
||||
use std::ffi::{CStr, CString, c_char};
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
|
||||
26
src/pager.rs
26
src/pager.rs
@@ -1,19 +1,21 @@
|
||||
//! Pager support.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use crate::common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
use crate::complete::{CompleteFlags, Completion};
|
||||
use crate::editable_line::EditableLine;
|
||||
use crate::highlight::{HighlightRole, HighlightSpec, highlight_shell};
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::prelude::*;
|
||||
use crate::screen::{CharOffset, Line, ScreenData, wcswidth_rendered, wcwidth_rendered};
|
||||
use crate::termsize::Termsize;
|
||||
use crate::{
|
||||
complete::{CompleteFlags, Completion},
|
||||
editable_line::EditableLine,
|
||||
highlight::{HighlightRole, HighlightSpec, highlight_shell},
|
||||
operation_context::OperationContext,
|
||||
prelude::*,
|
||||
screen::{CharOffset, Line, ScreenData, wcswidth_rendered, wcwidth_rendered},
|
||||
termsize::Termsize,
|
||||
};
|
||||
use fish_common::{EscapeFlags, EscapeStringStyle, escape_string};
|
||||
use fish_wcstringutil::string_fuzzy_match_string;
|
||||
use fish_widestring::{ELLIPSIS_CHAR, decoded_width};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, hash_map::Entry},
|
||||
};
|
||||
|
||||
/// Represents rendering from the pager.
|
||||
#[derive(Default)]
|
||||
|
||||
17
src/panic.rs
17
src/panic.rs
@@ -1,3 +1,6 @@
|
||||
use crate::{common::get_program_name, nix::isatty, threads::is_main_thread};
|
||||
use fish_common::read_blocked;
|
||||
use libc::STDIN_FILENO;
|
||||
use std::{
|
||||
panic::{UnwindSafe, set_hook, take_hook},
|
||||
sync::{
|
||||
@@ -7,14 +10,6 @@
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use libc::STDIN_FILENO;
|
||||
|
||||
use crate::{
|
||||
common::{get_program_name, read_blocked},
|
||||
nix::isatty,
|
||||
threads::is_main_thread,
|
||||
};
|
||||
|
||||
pub static AT_EXIT: OnceLock<Box<dyn Fn() + Send + Sync>> = OnceLock::new();
|
||||
|
||||
pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! {
|
||||
@@ -33,8 +28,10 @@ pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! {
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(at_exit) = AT_EXIT.get() {
|
||||
(at_exit)();
|
||||
if is_main_thread() {
|
||||
if let Some(at_exit) = AT_EXIT.get() {
|
||||
(at_exit)();
|
||||
}
|
||||
}
|
||||
eprintf!("%s crashed, please report a bug.", get_program_name());
|
||||
if !is_main_thread() {
|
||||
|
||||
@@ -1,63 +1,67 @@
|
||||
//! Provides the "linkage" between an ast and actual execution structures (job_t, etc.).
|
||||
|
||||
use crate::ast::{
|
||||
self, BlockStatementHeader, Keyword as _, Leaf as _, Node, Statement, Token as _,
|
||||
unescape_keyword,
|
||||
use crate::{
|
||||
ast::{
|
||||
self, BlockStatementHeader, Keyword as _, Leaf as _, Node, Statement, Token as _,
|
||||
unescape_keyword,
|
||||
},
|
||||
builtins::{
|
||||
self,
|
||||
error::Error,
|
||||
shared::{
|
||||
STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, STATUS_EXPAND_ERROR,
|
||||
STATUS_ILLEGAL_CMD, STATUS_INVALID_ARGS, STATUS_NOT_EXECUTABLE,
|
||||
STATUS_UNMATCHED_WILDCARD, builtin_exists,
|
||||
},
|
||||
},
|
||||
common::valid_var_name,
|
||||
complete::CompletionList,
|
||||
env::{EnvMode, EnvStackSetResult, EnvVar, EnvVarFlags, Environment as _, Statuses},
|
||||
err_fmt,
|
||||
event::{self, Event},
|
||||
exec::exec_job,
|
||||
expand::{
|
||||
ExpandFlags, ExpandResultCode, expand_one, expand_string, expand_to_command_and_args,
|
||||
},
|
||||
flog::flog,
|
||||
function,
|
||||
io::{IoChain, IoStreams, OutputStream, StringOutputStream},
|
||||
job_group::JobGroup,
|
||||
operation_context::OperationContext,
|
||||
parse_constants::{
|
||||
CALL_STACK_LIMIT_EXCEEDED_ERR_MSG, ERROR_TIME_BACKGROUND,
|
||||
FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, ILLEGAL_FD_ERR_MSG,
|
||||
INFINITE_FUNC_RECURSION_ERR_MSG, ParseError, ParseErrorCode, ParseErrorList, ParseKeyword,
|
||||
ParseTokenType, StatementDecoration, parse_error_offset_source_start,
|
||||
},
|
||||
parse_tree::{NodeRef, ParsedSourceRef},
|
||||
parse_util::{
|
||||
MaybeParentheses::CommandSubstitution, locate_cmdsubst_range, unescape_wildcards,
|
||||
},
|
||||
parser::{
|
||||
Block, BlockData, BlockId, BlockType, LoopStatus, Parser, ParserEnvSetMode, ProfileItem,
|
||||
},
|
||||
parser_keywords::parser_keywords_is_subcommand,
|
||||
path::{path_as_implicit_cd, path_try_get_path},
|
||||
prelude::*,
|
||||
proc::{
|
||||
ConcreteAssignment, Job, JobControl, JobProperties, JobRef, Process, ProcessType,
|
||||
get_job_control_mode, job_reap, no_exec,
|
||||
},
|
||||
reader::fish_is_unwinding_for_exit,
|
||||
redirection::{RedirectionMode, RedirectionSpec, RedirectionSpecList},
|
||||
signal::Signal,
|
||||
timer::push_timer,
|
||||
tokenizer::{PipeOrRedir, TokenType, variable_assignment_equals_pos},
|
||||
trace::{trace_if_enabled, trace_if_enabled_with_args},
|
||||
wildcard::wildcard_match,
|
||||
};
|
||||
use crate::builtins::error::Error;
|
||||
use crate::builtins::shared::{
|
||||
STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, STATUS_EXPAND_ERROR, STATUS_ILLEGAL_CMD,
|
||||
STATUS_INVALID_ARGS, STATUS_NOT_EXECUTABLE, STATUS_UNMATCHED_WILDCARD, builtin_exists,
|
||||
use fish_common::{
|
||||
ScopeGuard, ScopeGuarding, ScopedRefCell, escape, help_section, truncate_at_nul,
|
||||
};
|
||||
use crate::common::{
|
||||
ScopeGuard, ScopeGuarding, ScopedRefCell, escape, truncate_at_nul, valid_var_name,
|
||||
};
|
||||
use crate::complete::CompletionList;
|
||||
use crate::env::{EnvMode, EnvStackSetResult, EnvVar, EnvVarFlags, Environment as _, Statuses};
|
||||
use crate::event::{self, Event};
|
||||
use crate::exec::exec_job;
|
||||
use crate::expand::{
|
||||
ExpandFlags, ExpandResultCode, expand_one, expand_string, expand_to_command_and_args,
|
||||
};
|
||||
use crate::flog::flog;
|
||||
use crate::function;
|
||||
use crate::io::{IoChain, IoStreams, OutputStream, StringOutputStream};
|
||||
use crate::job_group::JobGroup;
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::{
|
||||
CALL_STACK_LIMIT_EXCEEDED_ERR_MSG, ERROR_TIME_BACKGROUND,
|
||||
FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, ILLEGAL_FD_ERR_MSG, INFINITE_FUNC_RECURSION_ERR_MSG,
|
||||
ParseError, ParseErrorCode, ParseErrorList, ParseKeyword, ParseTokenType, StatementDecoration,
|
||||
parse_error_offset_source_start,
|
||||
};
|
||||
use crate::parse_tree::{NodeRef, ParsedSourceRef};
|
||||
use crate::parse_util::{
|
||||
MaybeParentheses::CommandSubstitution, locate_cmdsubst_range, unescape_wildcards,
|
||||
};
|
||||
use crate::parser::{
|
||||
Block, BlockData, BlockId, BlockType, LoopStatus, Parser, ParserEnvSetMode, ProfileItem,
|
||||
};
|
||||
use crate::parser_keywords::parser_keywords_is_subcommand;
|
||||
use crate::path::{path_as_implicit_cd, path_try_get_path};
|
||||
use crate::prelude::*;
|
||||
use crate::proc::{
|
||||
ConcreteAssignment, Job, JobControl, JobProperties, JobRef, Process, ProcessType,
|
||||
get_job_control_mode, job_reap, no_exec,
|
||||
};
|
||||
use crate::reader::fish_is_unwinding_for_exit;
|
||||
use crate::redirection::{RedirectionMode, RedirectionSpec, RedirectionSpecList};
|
||||
use crate::signal::Signal;
|
||||
use crate::timer::push_timer;
|
||||
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 crate::{builtins, err_fmt};
|
||||
use fish_common::help_section;
|
||||
use fish_widestring::WExt as _;
|
||||
use libc::{ENOTDIR, EXIT_SUCCESS, STDERR_FILENO, STDOUT_FILENO, c_int};
|
||||
use std::io::ErrorKind;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{io::ErrorKind, rc::Rc, sync::Arc};
|
||||
|
||||
/// An eval_result represents evaluation errors including wildcards which failed to match, syntax
|
||||
/// errors, or other expansion errors. It also tracks when evaluation was skipped due to signal
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ast::{self, Ast, JobList, Node};
|
||||
use crate::common::{assert_send, assert_sync};
|
||||
use crate::parse_constants::{
|
||||
ParseErrorCode, ParseErrorList, ParseKeyword, ParseTokenType, ParseTreeFlags,
|
||||
SOURCE_OFFSET_INVALID, SourceOffset, SourceRange, token_type_user_presentable_description,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::tokenizer::TokenizerError;
|
||||
use fish_common::{assert_send, assert_sync};
|
||||
use fish_wcstringutil::count_newlines;
|
||||
|
||||
/// A struct representing the token type that we use internally.
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
//! Various mostly unrelated utility functions related to parsing, loading and evaluating fish code.
|
||||
use crate::ast::{
|
||||
self, Ast, Keyword as _, Kind, Leaf as _, Node, NodeVisitor, Token as _, Traversal,
|
||||
is_same_node,
|
||||
use crate::{
|
||||
ast::{
|
||||
self, Ast, Keyword as _, Kind, Leaf as _, Node, NodeVisitor, Token as _, Traversal,
|
||||
is_same_node,
|
||||
},
|
||||
builtins::shared::builtin_exists,
|
||||
common::{valid_var_name, valid_var_name_char},
|
||||
expand::{ExpandFlags, ExpandResultCode, expand_one, expand_to_command_and_args},
|
||||
operation_context::OperationContext,
|
||||
parse_constants::{
|
||||
ERROR_BAD_VAR_CHAR1, ERROR_BRACKETED_VARIABLE_QUOTED1, ERROR_BRACKETED_VARIABLE1,
|
||||
ERROR_NO_VAR_NAME, ERROR_NOT_ARGV_AT, ERROR_NOT_ARGV_COUNT, ERROR_NOT_ARGV_STAR,
|
||||
ERROR_NOT_PID, ERROR_NOT_STATUS, INVALID_BREAK_ERR_MSG, INVALID_CONTINUE_ERR_MSG,
|
||||
INVALID_PIPELINE_CMD_ERR_MSG, ParseError, ParseErrorCode, ParseErrorList, ParseIssue,
|
||||
ParseKeyword, ParseTokenType, ParseTreeFlags, PipelinePosition, SourceRange,
|
||||
StatementDecoration, UNKNOWN_BUILTIN_ERR_MSG, parse_error_offset_source_start,
|
||||
},
|
||||
prelude::*,
|
||||
tokenizer::{
|
||||
TOK_ACCEPT_UNFINISHED, TOK_SHOW_COMMENTS, Tok, TokenType, Tokenizer, comment_end,
|
||||
is_token_delimiter, quote_end,
|
||||
},
|
||||
};
|
||||
use crate::builtins::shared::builtin_exists;
|
||||
use crate::common::{
|
||||
EscapeFlags, EscapeStringStyle, UnescapeFlags, UnescapeStringStyle, escape_string,
|
||||
unescape_string, valid_var_name, valid_var_name_char,
|
||||
};
|
||||
use crate::expand::{
|
||||
BRACE_BEGIN, BRACE_END, BRACE_SEP, ExpandFlags, ExpandResultCode, INTERNAL_SEPARATOR,
|
||||
VARIABLE_EXPAND, VARIABLE_EXPAND_EMPTY, VARIABLE_EXPAND_SINGLE, expand_one,
|
||||
expand_to_command_and_args,
|
||||
};
|
||||
use crate::operation_context::OperationContext;
|
||||
use crate::parse_constants::{
|
||||
ERROR_BAD_VAR_CHAR1, ERROR_BRACKETED_VARIABLE_QUOTED1, ERROR_BRACKETED_VARIABLE1,
|
||||
ERROR_NO_VAR_NAME, ERROR_NOT_ARGV_AT, ERROR_NOT_ARGV_COUNT, ERROR_NOT_ARGV_STAR, ERROR_NOT_PID,
|
||||
ERROR_NOT_STATUS, INVALID_BREAK_ERR_MSG, INVALID_CONTINUE_ERR_MSG,
|
||||
INVALID_PIPELINE_CMD_ERR_MSG, ParseError, ParseErrorCode, ParseErrorList, ParseIssue,
|
||||
ParseKeyword, ParseTokenType, ParseTreeFlags, PipelinePosition, SourceRange,
|
||||
StatementDecoration, UNKNOWN_BUILTIN_ERR_MSG, parse_error_offset_source_start,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::tokenizer::{
|
||||
TOK_ACCEPT_UNFINISHED, TOK_SHOW_COMMENTS, Tok, TokenType, Tokenizer, comment_end,
|
||||
is_token_delimiter, quote_end,
|
||||
};
|
||||
use crate::wildcard::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
|
||||
use fish_common::help_section;
|
||||
use fish_common::{UnescapeFlags, UnescapeStringStyle, help_section, unescape_string};
|
||||
use fish_feature_flags::{FeatureFlag, feature_test};
|
||||
use fish_wcstringutil::{count_newlines, truncate};
|
||||
use std::ops::Range;
|
||||
use std::{iter, ops};
|
||||
use fish_widestring::{
|
||||
ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE, BRACE_BEGIN, BRACE_END, BRACE_SEP,
|
||||
INTERNAL_SEPARATOR, VARIABLE_EXPAND, VARIABLE_EXPAND_EMPTY, VARIABLE_EXPAND_SINGLE,
|
||||
};
|
||||
use std::{
|
||||
iter,
|
||||
ops::{self, Range},
|
||||
};
|
||||
|
||||
/// Handles slices: the square brackets in an expression like $foo[5..4]
|
||||
/// Return the length of the slice starting at `in`, or 0 if there is no slice, or None on error.
|
||||
@@ -694,64 +694,6 @@ fn error_for_character(c: char) -> WString {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to escape the string 'cmd' using the given quote type, as determined by the quote
|
||||
/// character. The quote can be a single quote or double quote, or L'\0' to indicate no quoting (and
|
||||
/// thus escaping should be with backslashes). Optionally do not escape tildes.
|
||||
pub fn escape_string_with_quote(
|
||||
cmd: &wstr,
|
||||
quote: Option<char>,
|
||||
escape_flags: EscapeFlags,
|
||||
) -> WString {
|
||||
let Some(quote) = quote else {
|
||||
return escape_string(cmd, EscapeStringStyle::Script(escape_flags));
|
||||
};
|
||||
// Here we are going to escape a string with quotes.
|
||||
// A few characters cannot be represented inside quotes, e.g. newlines. In that case,
|
||||
// terminate the quote and then re-enter it.
|
||||
let mut result = WString::new();
|
||||
result.reserve(cmd.len());
|
||||
for c in cmd.chars() {
|
||||
match c {
|
||||
'\n' => {
|
||||
for c in [quote, '\\', 'n', quote] {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
'\t' => {
|
||||
for c in [quote, '\\', 't', quote] {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
'\x08' => {
|
||||
for c in [quote, '\\', 'b', quote] {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
'\r' => {
|
||||
for c in [quote, '\\', 'r', quote] {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
'\\' => {
|
||||
result.push_str("\\\\");
|
||||
}
|
||||
'$' => {
|
||||
if quote == '"' {
|
||||
result.push('\\');
|
||||
}
|
||||
result.push('$');
|
||||
}
|
||||
_ => {
|
||||
if c == quote {
|
||||
result.push('\\');
|
||||
}
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Given a string, parse it as fish code and then return the indents. The return value has the same
|
||||
/// size as the string.
|
||||
pub fn compute_indents(src: &wstr) -> Vec<i32> {
|
||||
@@ -1965,10 +1907,9 @@ pub fn expand_variable_error(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
BOOL_AFTER_BACKGROUND_ERROR_MSG, compute_indents, detect_parse_errors,
|
||||
escape_string_with_quote, get_cmdsubst_extent, get_process_extent, slice_length,
|
||||
BOOL_AFTER_BACKGROUND_ERROR_MSG, compute_indents, detect_parse_errors, get_cmdsubst_extent,
|
||||
get_process_extent, slice_length,
|
||||
};
|
||||
use crate::common::EscapeFlags;
|
||||
use crate::parse_constants::{
|
||||
ERROR_BAD_VAR_CHAR1, ERROR_BRACKETED_VARIABLE_QUOTED1, ERROR_BRACKETED_VARIABLE1,
|
||||
ERROR_NO_VAR_NAME, ERROR_NOT_ARGV_AT, ERROR_NOT_ARGV_COUNT, ERROR_NOT_ARGV_STAR,
|
||||
@@ -2096,79 +2037,6 @@ fn test_slice_length() {
|
||||
assert_eq!(slice_length(L!("[\"foo\"")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_escape_quotes() {
|
||||
let _cleanup = test_init();
|
||||
macro_rules! validate {
|
||||
($cmd:expr, $quote:expr, $no_tilde:expr, $expected:expr) => {
|
||||
assert_eq!(
|
||||
escape_string_with_quote(
|
||||
L!($cmd),
|
||||
$quote,
|
||||
if $no_tilde {
|
||||
EscapeFlags::NO_TILDE
|
||||
} else {
|
||||
EscapeFlags::empty()
|
||||
}
|
||||
),
|
||||
L!($expected)
|
||||
);
|
||||
};
|
||||
}
|
||||
macro_rules! validate_no_quoted {
|
||||
($cmd:expr, $quote:expr, $no_tilde:expr, $expected:expr) => {
|
||||
assert_eq!(
|
||||
escape_string_with_quote(
|
||||
L!($cmd),
|
||||
$quote,
|
||||
EscapeFlags::NO_QUOTED
|
||||
| if $no_tilde {
|
||||
EscapeFlags::NO_TILDE
|
||||
} else {
|
||||
EscapeFlags::empty()
|
||||
}
|
||||
),
|
||||
L!($expected)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
validate!("abc~def", None, false, "'abc~def'");
|
||||
validate!("abc~def", None, true, "abc~def");
|
||||
validate!("~abc", None, false, "'~abc'");
|
||||
validate!("~abc", None, true, "~abc");
|
||||
|
||||
// These are "raw string literals"
|
||||
validate_no_quoted!("abc", None, false, "abc");
|
||||
validate_no_quoted!("abc~def", None, false, "abc\\~def");
|
||||
validate_no_quoted!("abc~def", None, true, "abc~def");
|
||||
validate_no_quoted!("abc\\~def", None, false, "abc\\\\\\~def");
|
||||
validate_no_quoted!("abc\\~def", None, true, "abc\\\\~def");
|
||||
validate_no_quoted!("~abc", None, false, "\\~abc");
|
||||
validate_no_quoted!("~abc", None, true, "~abc");
|
||||
validate_no_quoted!("~abc|def", None, false, "\\~abc\\|def");
|
||||
validate_no_quoted!("|abc~def", None, false, "\\|abc\\~def");
|
||||
validate_no_quoted!("|abc~def", None, true, "\\|abc~def");
|
||||
validate_no_quoted!("foo\nbar", None, false, "foo\\nbar");
|
||||
|
||||
// Note tildes are not expanded inside quotes, so no_tilde is ignored with a quote.
|
||||
validate_no_quoted!("abc", Some('\''), false, "abc");
|
||||
validate_no_quoted!("abc\\def", Some('\''), false, "abc\\\\def");
|
||||
validate_no_quoted!("abc'def", Some('\''), false, "abc\\'def");
|
||||
validate_no_quoted!("~abc'def", Some('\''), false, "~abc\\'def");
|
||||
validate_no_quoted!("~abc'def", Some('\''), true, "~abc\\'def");
|
||||
validate_no_quoted!("foo\nba'r", Some('\''), false, "foo'\\n'ba\\'r");
|
||||
validate_no_quoted!("foo\\\\bar", Some('\''), false, "foo\\\\\\\\bar");
|
||||
|
||||
validate_no_quoted!("abc", Some('"'), false, "abc");
|
||||
validate_no_quoted!("abc\\def", Some('"'), false, "abc\\\\def");
|
||||
validate_no_quoted!("~abc'def", Some('"'), false, "~abc'def");
|
||||
validate_no_quoted!("~abc'def", Some('"'), true, "~abc'def");
|
||||
validate_no_quoted!("foo\nba'r", Some('"'), false, "foo\"\\n\"ba'r");
|
||||
validate_no_quoted!("foo\\\\bar", Some('"'), false, "foo\\\\\\\\bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_indents() {
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
// The fish parser. Contains functions for parsing and evaluating code.
|
||||
|
||||
use crate::ast::{self, Node};
|
||||
use crate::builtins::shared::STATUS_ILLEGAL_CMD;
|
||||
use crate::common::{
|
||||
CancelChecker, EscapeFlags, EscapeStringStyle, FilenameRef, PROFILING_ACTIVE, ScopeGuarding,
|
||||
ScopedCell, ScopedRefCell, escape_string,
|
||||
use crate::{
|
||||
ast::{self, Node},
|
||||
builtins::shared::STATUS_ILLEGAL_CMD,
|
||||
common::{CancelChecker, PROFILING_ACTIVE},
|
||||
complete::CompletionList,
|
||||
env::{
|
||||
EnvMode, EnvSetMode, EnvStack, EnvStackSetResult, Environment,
|
||||
FISH_TERMINAL_COLOR_THEME_VAR, Statuses,
|
||||
},
|
||||
event::{self, Event},
|
||||
expand::{ExpandFlags, ExpandResultCode, expand_string, replace_home_directory_with_tilde},
|
||||
fds::{BEST_O_SEARCH, open_dir},
|
||||
flog, flogf, function,
|
||||
global_safety::RelaxedAtomicBool,
|
||||
input_common::TerminalQuery,
|
||||
io::IoChain,
|
||||
job_group::MaybeJobId,
|
||||
operation_context::{EXPANSION_LIMIT_DEFAULT, OperationContext},
|
||||
parse_constants::{
|
||||
FISH_MAX_EVAL_DEPTH, FISH_MAX_STACK_DEPTH, ParseError, ParseErrorList, ParseTreeFlags,
|
||||
SOURCE_LOCATION_UNKNOWN,
|
||||
},
|
||||
parse_execution::{EndExecutionReason, ExecutionContext},
|
||||
parse_tree::{NodeRef, ParsedSourceRef, SourceLineCache, parse_source},
|
||||
portable_atomic::AtomicU64,
|
||||
prelude::*,
|
||||
proc::{JobGroupRef, JobList, JobRef, Pid, ProcStatus, job_reap},
|
||||
signal::{Signal, signal_check_cancel, signal_clear_cancel},
|
||||
wait_handle::WaitHandleStore,
|
||||
wutil::perror_nix,
|
||||
};
|
||||
use crate::complete::CompletionList;
|
||||
use crate::env::{
|
||||
EnvMode, EnvSetMode, EnvStack, EnvStackSetResult, Environment, FISH_TERMINAL_COLOR_THEME_VAR,
|
||||
Statuses,
|
||||
};
|
||||
use crate::event::{self, Event};
|
||||
use crate::expand::{
|
||||
ExpandFlags, ExpandResultCode, expand_string, replace_home_directory_with_tilde,
|
||||
};
|
||||
use crate::fds::{BEST_O_SEARCH, open_dir};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::input_common::TerminalQuery;
|
||||
use crate::io::IoChain;
|
||||
use crate::job_group::MaybeJobId;
|
||||
use crate::operation_context::{EXPANSION_LIMIT_DEFAULT, OperationContext};
|
||||
use crate::parse_constants::{
|
||||
FISH_MAX_EVAL_DEPTH, FISH_MAX_STACK_DEPTH, ParseError, ParseErrorList, ParseTreeFlags,
|
||||
SOURCE_LOCATION_UNKNOWN,
|
||||
};
|
||||
use crate::parse_execution::{EndExecutionReason, ExecutionContext};
|
||||
use crate::parse_tree::{NodeRef, ParsedSourceRef, SourceLineCache, parse_source};
|
||||
use crate::portable_atomic::AtomicU64;
|
||||
use crate::prelude::*;
|
||||
use crate::proc::{JobGroupRef, JobList, JobRef, Pid, ProcStatus, job_reap};
|
||||
use crate::signal::{Signal, signal_check_cancel, signal_clear_cancel};
|
||||
use crate::wait_handle::WaitHandleStore;
|
||||
use crate::wutil::perror_nix;
|
||||
use crate::{flog, flogf, function};
|
||||
use assert_matches::assert_matches;
|
||||
use fish_common::{
|
||||
EscapeFlags, EscapeStringStyle, FilenameRef, ScopeGuarding, ScopedCell, ScopedRefCell,
|
||||
escape_string,
|
||||
};
|
||||
use fish_util::get_time;
|
||||
use fish_wcstringutil::wcs2bytes;
|
||||
use fish_widestring::WExt as _;
|
||||
use fish_widestring::{WExt as _, wcs2bytes};
|
||||
use libc::c_int;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::ffi::OsStr;
|
||||
@@ -1501,20 +1501,22 @@ pub enum LoopStatus {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CancelBehavior, Parser};
|
||||
use crate::ast::{self, Ast, JobList, Kind, Node, Traversal, is_same_node};
|
||||
use crate::common::str2wcstring;
|
||||
use crate::env::EnvStack;
|
||||
use crate::expand::ExpandFlags;
|
||||
use crate::io::{IoBufferfill, IoChain};
|
||||
use crate::parse_constants::{
|
||||
ParseErrorCode, ParseIssue, ParseTokenType, ParseTreeFlags, StatementDecoration,
|
||||
use crate::{
|
||||
ast::{self, Ast, JobList, Kind, Node, Traversal, is_same_node},
|
||||
env::EnvStack,
|
||||
expand::ExpandFlags,
|
||||
io::{IoBufferfill, IoChain},
|
||||
parse_constants::{
|
||||
ParseErrorCode, ParseIssue, ParseTokenType, ParseTreeFlags, StatementDecoration,
|
||||
},
|
||||
parse_util::{detect_errors_in_argument, detect_parse_errors},
|
||||
prelude::*,
|
||||
reader::{fake_scoped_reader, reader_reset_interrupted},
|
||||
signal::{signal_clear_cancel, signal_reset_handlers, signal_set_handlers},
|
||||
tests::prelude::*,
|
||||
};
|
||||
use crate::parse_util::{detect_errors_in_argument, detect_parse_errors};
|
||||
use crate::prelude::*;
|
||||
use crate::reader::{fake_scoped_reader, reader_reset_interrupted};
|
||||
use crate::signal::{signal_clear_cancel, signal_reset_handlers, signal_set_handlers};
|
||||
use crate::tests::prelude::*;
|
||||
use fish_wcstringutil::join_strings;
|
||||
use fish_widestring::str2wcstring;
|
||||
use libc::SIGINT;
|
||||
use std::time::Duration;
|
||||
#[test]
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
//! path-related issues.
|
||||
|
||||
use crate::env::{EnvMode, EnvSetMode, EnvStack, Environment, FALLBACK_PATH};
|
||||
use crate::expand::{HOME_DIRECTORY, expand_tilde};
|
||||
use crate::expand::expand_tilde;
|
||||
use crate::flog::{flog, flogf};
|
||||
use crate::prelude::*;
|
||||
use crate::wutil::{normalize_path, path_normalize_for_cd, waccess, wdirname, wstat};
|
||||
use cfg_if::cfg_if;
|
||||
use errno::{Errno, errno, set_errno};
|
||||
use fish_wcstringutil::{wcs2osstring, wcs2zstring};
|
||||
use fish_widestring::{HOME_DIRECTORY, wcs2osstring, wcs2zstring};
|
||||
use libc::{EACCES, ENOENT, ENOTDIR, X_OK};
|
||||
use nix::unistd::AccessFlags;
|
||||
use std::ffi::OsStr;
|
||||
@@ -560,7 +560,8 @@ fn make_base_directory(xdg_var: &wstr, non_xdg_homepath: &wstr) -> BaseDirectory
|
||||
// the actual $HOME or $XDG_XXX directories. This prevents the tests from failing and/or stops
|
||||
// the tests polluting the user's actual $HOME if a sandbox environment has not been set up.
|
||||
{
|
||||
use crate::common::{BUILD_DIR, osstr2wcstring};
|
||||
use crate::common::BUILD_DIR;
|
||||
use fish_widestring::osstr2wcstring;
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut build_dir = PathBuf::from(BUILD_DIR);
|
||||
|
||||
67
src/proc.rs
67
src/proc.rs
@@ -2,25 +2,28 @@
|
||||
//! functions for tracking children. These functions do not themselves launch new processes,
|
||||
//! the exec library will call proc to create representations of the running jobs as needed.
|
||||
|
||||
use crate::ast;
|
||||
use crate::common::{Timepoint, WSL, escape, is_windows_subsystem_for_linux, timef};
|
||||
use crate::env::Statuses;
|
||||
use crate::event::{self, Event};
|
||||
use crate::flog::{flog, flogf};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::io::IoChain;
|
||||
use crate::job_group::{JobGroup, MaybeJobId};
|
||||
use crate::parse_tree::NodeRef;
|
||||
use crate::parser::{Block, Parser};
|
||||
use crate::portable_atomic::AtomicU64;
|
||||
use crate::prelude::*;
|
||||
use crate::reader::{fish_is_unwinding_for_exit, reader_schedule_prompt_repaint};
|
||||
use crate::redirection::RedirectionSpecList;
|
||||
use crate::signal::{Signal, signal_set_handlers_once};
|
||||
use crate::topic_monitor::{GenerationsList, Topic, topic_monitor_principal};
|
||||
use crate::wait_handle::{InternalJobId, WaitHandle, WaitHandleRef, WaitHandleStore};
|
||||
use crate::wutil::{perror_nix, wbasename};
|
||||
use crate::{
|
||||
ast,
|
||||
common::{WSL, is_windows_subsystem_for_linux},
|
||||
env::Statuses,
|
||||
event::{self, Event},
|
||||
flog::{flog, flogf},
|
||||
global_safety::RelaxedAtomicBool,
|
||||
io::IoChain,
|
||||
job_group::{JobGroup, MaybeJobId},
|
||||
parse_tree::NodeRef,
|
||||
parser::{Block, Parser},
|
||||
portable_atomic::AtomicU64,
|
||||
prelude::*,
|
||||
reader::{fish_is_unwinding_for_exit, reader_schedule_prompt_repaint},
|
||||
redirection::RedirectionSpecList,
|
||||
signal::{Signal, signal_set_handlers_once},
|
||||
topic_monitor::{GenerationsList, Topic, topic_monitor_principal},
|
||||
wait_handle::{InternalJobId, WaitHandle, WaitHandleRef, WaitHandleStore},
|
||||
wutil::{perror_nix, wbasename},
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use fish_common::{Timepoint, escape, timef};
|
||||
use fish_widestring::ToWString;
|
||||
use libc::{
|
||||
_SC_CLK_TCK, EXIT_SUCCESS, SIG_IGN, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGPIPE, SIGQUIT,
|
||||
@@ -34,14 +37,18 @@
|
||||
},
|
||||
unistd::getpgrp,
|
||||
};
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::fs;
|
||||
use std::io::{Read as _, Write as _};
|
||||
use std::num::NonZeroU32;
|
||||
use std::os::fd::RawFd;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::{Arc, LazyLock, Mutex, OnceLock};
|
||||
use std::{
|
||||
cell::{Cell, Ref, RefCell, RefMut},
|
||||
fs,
|
||||
io::{Read as _, Write as _},
|
||||
num::NonZeroU32,
|
||||
os::fd::RawFd,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
Arc, LazyLock, Mutex, OnceLock,
|
||||
atomic::{AtomicU8, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
/// Types of processes.
|
||||
#[derive(Default)]
|
||||
@@ -1207,12 +1214,12 @@ fn process_mark_finished_children(parser: &Parser, block_ok: bool, block_io: Opt
|
||||
if proc.has_pid() {
|
||||
// Reaps with a pid.
|
||||
reapgens.set_min_from(Topic::SigChld, &proc.gens);
|
||||
reapgens.set_min_from(Topic::SigHupInt, &proc.gens);
|
||||
reapgens.set_min_from(Topic::SigHupIntTerm, &proc.gens);
|
||||
}
|
||||
if proc.internal_proc.borrow().is_some() {
|
||||
// Reaps with an internal process.
|
||||
reapgens.set_min_from(Topic::InternalExit, &proc.gens);
|
||||
reapgens.set_min_from(Topic::SigHupInt, &proc.gens);
|
||||
reapgens.set_min_from(Topic::SigHupIntTerm, &proc.gens);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1235,7 +1242,7 @@ fn process_mark_finished_children(parser: &Parser, block_ok: bool, block_io: Opt
|
||||
}
|
||||
|
||||
// Always update the signal hup/int gen.
|
||||
proc.gens.sighupint.set(reapgens.sighupint.get());
|
||||
proc.gens.sighupintterm.set(reapgens.sighupintterm.get());
|
||||
|
||||
// Nothing to do if we did not get a new sigchld.
|
||||
if proc.gens.sigchld == reapgens.sigchld {
|
||||
@@ -1304,7 +1311,7 @@ fn process_mark_finished_children(parser: &Parser, block_ok: bool, block_io: Opt
|
||||
}
|
||||
|
||||
// Always update the signal hup/int gen.
|
||||
proc.gens.sighupint.set(reapgens.sighupint.get());
|
||||
proc.gens.sighupintterm.set(reapgens.sighupintterm.get());
|
||||
|
||||
// Nothing to do if we did not get a new internal exit.
|
||||
if proc.gens.internal_exit == reapgens.internal_exit {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//! Reader implementation of InputEventQueuer.
|
||||
use std::os::fd::RawFd;
|
||||
|
||||
use crate::common::{bytes2wcstring, escape};
|
||||
use crate::event;
|
||||
use crate::input_common::{CharEvent, InputData, InputEventQueuer, ReadlineCmd};
|
||||
use crate::proc::job_reap;
|
||||
use crate::signal::signal_clear_cancel;
|
||||
|
||||
use super::{Reader, reader_reading_interrupted, reader_schedule_prompt_repaint};
|
||||
use crate::{
|
||||
event,
|
||||
input_common::{CharEvent, InputData, InputEventQueuer, ReadlineCmd},
|
||||
proc::job_reap,
|
||||
signal::signal_clear_cancel,
|
||||
};
|
||||
use fish_common::escape;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use std::os::fd::RawFd;
|
||||
|
||||
impl<'a> InputEventQueuer for Reader<'a> {
|
||||
fn get_input_data(&self) -> &InputData {
|
||||
|
||||
@@ -17,109 +17,105 @@
|
||||
//! control-C from generating SIGINT, so failing to disable these would prevent cancellation of wildcard
|
||||
//! expansion, etc.
|
||||
|
||||
use super::history_search::{ReaderHistorySearch, SearchMode, smartcase_flags};
|
||||
use super::iothreads::{self, Debouncers};
|
||||
use super::word_motion::{MoveWordDir, MoveWordStateMachine, MoveWordStyle};
|
||||
use crate::abbrs::abbrs_match;
|
||||
use crate::ast::{self, Kind, is_same_node};
|
||||
use crate::builtins::shared::ErrorCode;
|
||||
use crate::builtins::shared::STATUS_CMD_ERROR;
|
||||
use crate::builtins::shared::STATUS_CMD_OK;
|
||||
use crate::common::ScopeGuarding;
|
||||
use crate::common::{
|
||||
EscapeFlags, EscapeStringStyle, ScopeGuard, bytes2wcstring, escape, escape_string,
|
||||
exit_without_destructors, get_obfuscation_read_char, get_program_name,
|
||||
restore_term_foreground_process_group_for_exit, shell_modes, write_loop,
|
||||
use super::{
|
||||
history_search::{ReaderHistorySearch, SearchMode, smartcase_flags},
|
||||
iothreads::{self, Debouncers},
|
||||
word_motion::{MoveWordDir, MoveWordStateMachine, MoveWordStyle},
|
||||
};
|
||||
use crate::complete::{
|
||||
CompleteFlags, Completion, CompletionList, CompletionRequestOptions, complete, complete_load,
|
||||
sort_and_prioritize,
|
||||
use crate::{
|
||||
abbrs::{self, abbrs_match},
|
||||
ast::{self, Kind, is_same_node},
|
||||
builtins::shared::{ErrorCode, STATUS_CMD_ERROR, STATUS_CMD_OK},
|
||||
common::{get_program_name, shell_modes},
|
||||
complete::{
|
||||
CompleteFlags, Completion, CompletionList, CompletionRequestOptions, complete,
|
||||
complete_load, sort_and_prioritize,
|
||||
},
|
||||
editable_line::{Edit, EditableLine, line_at_cursor, range_of_line_at_cursor},
|
||||
env::{EnvMode, EnvStack, Environment, Statuses},
|
||||
env_dispatch::{MIDNIGHT_COMMANDER_SID, handle_emoji_width},
|
||||
event,
|
||||
exec::exec_subshell,
|
||||
expand::{ExpandFlags, ExpandResultCode, expand_one, expand_string, expand_tilde},
|
||||
fd_readable_set::poll_fd_readable,
|
||||
fds::{make_fd_blocking, wopen_cloexec},
|
||||
flog::{flog, flogf},
|
||||
function,
|
||||
global_safety::RelaxedAtomicBool,
|
||||
highlight::{
|
||||
HighlightRole, HighlightSpec, autosuggest_validate_from_history, highlight_shell,
|
||||
parse_text_face_for_highlight,
|
||||
},
|
||||
history::{
|
||||
History, HistorySearch, PersistenceMode, SearchDirection, SearchFlags, SearchType,
|
||||
history_session_id, in_private_mode,
|
||||
},
|
||||
input_common::{
|
||||
BackgroundColorQuery, CharEvent, CharInputStyle, CursorPositionQuery,
|
||||
CursorPositionQueryReason, ImplicitEvent, InputData, InputEventQueue,
|
||||
InputEventQueuer as _, LONG_READ_TIMEOUT, QueryResponse, QueryResultEvent, ReadlineCmd,
|
||||
RecurrentQuery, TerminalQuery, stop_query,
|
||||
},
|
||||
io::IoChain,
|
||||
key::ViewportPosition,
|
||||
kill::{kill_add, kill_replace, kill_yank, kill_yank_rotate},
|
||||
nix::isatty,
|
||||
operation_context::{OperationContext, get_bg_context},
|
||||
pager::{PageRendering, Pager, SelectionMotion},
|
||||
panic::AT_EXIT,
|
||||
parse_constants::{ParseIssue, ParseTreeFlags, SourceRange},
|
||||
parse_util::{
|
||||
MaybeParentheses, SPACES_PER_INDENT, compute_indents, contains_wildcards,
|
||||
detect_parse_errors, escape_wildcards, get_cmdsubst_extent, get_line_from_offset,
|
||||
get_offset, get_offset_from_line, get_process_extent, get_process_first_token_offset,
|
||||
get_token_extent, lineno, locate_cmdsubst_range,
|
||||
},
|
||||
parser::{BlockType, EvalRes, Parser, ParserEnvSetMode},
|
||||
portable_atomic::AtomicU64,
|
||||
prelude::*,
|
||||
proc::{
|
||||
HAVE_PROC_STAT, hup_jobs, is_interactive_session, job_reap, jobs_requiring_warning_on_exit,
|
||||
print_exit_warning_for_jobs, proc_update_jiffies,
|
||||
},
|
||||
reader::word_motion::bigword_class,
|
||||
screen::{CharOffset, Screen, is_dumb, screen_force_clear_to_end},
|
||||
should_flog,
|
||||
signal::{
|
||||
signal_check_cancel, signal_clear_cancel, signal_reset_handlers, signal_set_handlers,
|
||||
signal_set_handlers_once,
|
||||
},
|
||||
terminal::{
|
||||
BufferedOutputter, Outputter,
|
||||
TerminalCommand::{
|
||||
self, ClearScreen, DecrstAlternateScreenBuffer, DecsetAlternateScreenBuffer,
|
||||
DecsetShowCursor, Osc0WindowTitle, Osc1TabTitle, Osc133CommandFinished,
|
||||
Osc133CommandStart, QueryBackgroundColor, QueryCursorPosition,
|
||||
QueryKittyKeyboardProgressiveEnhancements, QueryPrimaryDeviceAttribute, QueryXtgettcap,
|
||||
QueryXtversion,
|
||||
},
|
||||
},
|
||||
termsize::{safe_termsize_invalidate_tty, termsize_last, termsize_update},
|
||||
text_face::{TextFace, parse_text_face},
|
||||
threads::{assert_is_background_thread, assert_is_main_thread},
|
||||
tokenizer::{
|
||||
TOK_ACCEPT_UNFINISHED, TOK_SHOW_COMMENTS, TokenType, Tokenizer, quote_end, tok_command,
|
||||
variable_assignment_equals_pos,
|
||||
},
|
||||
tty_handoff::{
|
||||
SCROLL_CONTENT_UP_TERMINFO_CODE, TtyHandoff, XTGETTCAP_QUERY_OS_NAME,
|
||||
deactivate_tty_protocols, get_tty_protocols_active, initialize_tty_protocols,
|
||||
},
|
||||
wildcard::wildcard_has,
|
||||
wutil::{fstat, perror_nix, wstat},
|
||||
};
|
||||
use crate::editable_line::{Edit, EditableLine, line_at_cursor, range_of_line_at_cursor};
|
||||
use crate::env::EnvStack;
|
||||
use crate::env::{EnvMode, Environment, Statuses};
|
||||
use crate::env_dispatch::MIDNIGHT_COMMANDER_SID;
|
||||
use crate::env_dispatch::handle_emoji_width;
|
||||
use crate::exec::exec_subshell;
|
||||
use crate::expand::expand_one;
|
||||
use crate::expand::{ExpandFlags, ExpandResultCode, expand_string, expand_tilde};
|
||||
use crate::fd_readable_set::poll_fd_readable;
|
||||
use crate::fds::{make_fd_blocking, wopen_cloexec};
|
||||
use crate::flog::{flog, flogf};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::highlight::{
|
||||
HighlightRole, HighlightSpec, autosuggest_validate_from_history, highlight_shell,
|
||||
parse_text_face_for_highlight,
|
||||
};
|
||||
use crate::history::{
|
||||
History, HistorySearch, PersistenceMode, SearchDirection, SearchFlags, SearchType,
|
||||
history_session_id, in_private_mode,
|
||||
};
|
||||
use crate::input_common::BackgroundColorQuery;
|
||||
use crate::input_common::CursorPositionQueryReason;
|
||||
use crate::input_common::InputEventQueue;
|
||||
use crate::input_common::InputEventQueuer as _;
|
||||
use crate::input_common::QueryResponse;
|
||||
use crate::input_common::{
|
||||
CharEvent, CharInputStyle, CursorPositionQuery, ImplicitEvent, InputData, LONG_READ_TIMEOUT,
|
||||
QueryResultEvent, ReadlineCmd, RecurrentQuery, TerminalQuery, stop_query,
|
||||
};
|
||||
use crate::io::IoChain;
|
||||
use crate::key::ViewportPosition;
|
||||
use crate::kill::{kill_add, kill_replace, kill_yank, kill_yank_rotate};
|
||||
use crate::nix::isatty;
|
||||
use crate::operation_context::{OperationContext, get_bg_context};
|
||||
use crate::pager::{PageRendering, Pager, SelectionMotion};
|
||||
use crate::panic::AT_EXIT;
|
||||
use crate::parse_constants::SourceRange;
|
||||
use crate::parse_constants::{ParseIssue, ParseTreeFlags};
|
||||
use crate::parse_util::{
|
||||
MaybeParentheses, SPACES_PER_INDENT, compute_indents, contains_wildcards, detect_parse_errors,
|
||||
escape_string_with_quote, escape_wildcards, get_cmdsubst_extent, get_line_from_offset,
|
||||
get_offset, get_offset_from_line, get_process_extent, get_process_first_token_offset,
|
||||
get_token_extent, lineno, locate_cmdsubst_range,
|
||||
};
|
||||
use crate::parser::{BlockType, EvalRes, Parser, ParserEnvSetMode};
|
||||
use crate::portable_atomic::AtomicU64;
|
||||
use crate::prelude::*;
|
||||
use crate::proc::{
|
||||
HAVE_PROC_STAT, hup_jobs, is_interactive_session, job_reap, jobs_requiring_warning_on_exit,
|
||||
print_exit_warning_for_jobs, proc_update_jiffies,
|
||||
};
|
||||
use crate::reader::word_motion::bigword_class;
|
||||
use crate::screen::{CharOffset, Screen, is_dumb, screen_force_clear_to_end};
|
||||
use crate::should_flog;
|
||||
use crate::signal::{
|
||||
signal_check_cancel, signal_clear_cancel, signal_reset_handlers, signal_set_handlers,
|
||||
signal_set_handlers_once,
|
||||
};
|
||||
use crate::terminal::TerminalCommand::{
|
||||
self, ClearScreen, DecrstAlternateScreenBuffer, DecsetAlternateScreenBuffer, DecsetShowCursor,
|
||||
Osc0WindowTitle, Osc1TabTitle, Osc133CommandFinished, Osc133CommandStart, QueryBackgroundColor,
|
||||
QueryCursorPosition, QueryKittyKeyboardProgressiveEnhancements, QueryPrimaryDeviceAttribute,
|
||||
QueryXtgettcap, QueryXtversion,
|
||||
};
|
||||
use crate::terminal::{BufferedOutputter, Outputter};
|
||||
use crate::termsize::{safe_termsize_invalidate_tty, termsize_last, termsize_update};
|
||||
use crate::text_face::{TextFace, parse_text_face};
|
||||
use crate::threads::{assert_is_background_thread, assert_is_main_thread};
|
||||
use crate::tokenizer::{
|
||||
TOK_ACCEPT_UNFINISHED, TOK_SHOW_COMMENTS, TokenType, Tokenizer, quote_end, tok_command,
|
||||
variable_assignment_equals_pos,
|
||||
};
|
||||
use crate::tty_handoff::SCROLL_CONTENT_UP_TERMINFO_CODE;
|
||||
use crate::tty_handoff::XTGETTCAP_QUERY_OS_NAME;
|
||||
use crate::tty_handoff::{
|
||||
TtyHandoff, get_tty_protocols_active, initialize_tty_protocols, safe_deactivate_tty_protocols,
|
||||
};
|
||||
use crate::wildcard::wildcard_has;
|
||||
use crate::wutil::{fstat, perror_nix, wstat};
|
||||
use crate::{abbrs, event, function};
|
||||
use assert_matches::assert_matches;
|
||||
use errno::{Errno, errno};
|
||||
use fish_common::{UTF8_BOM_WCHAR, help_section};
|
||||
use fish_fallback::fish_wcwidth;
|
||||
use fish_fallback::lowercase;
|
||||
use fish_common::{
|
||||
EscapeFlags, EscapeStringStyle, ScopeGuard, ScopeGuarding, escape, escape_string,
|
||||
escape_string_with_quote, exit_without_destructors, get_obfuscation_read_char, help_section,
|
||||
restore_term_foreground_process_group_for_exit, write_loop,
|
||||
};
|
||||
use fish_fallback::{fish_wcwidth, lowercase};
|
||||
use fish_feature_flags::FeatureFlag;
|
||||
use fish_util::{perror, write_to_fd};
|
||||
use fish_wcstringutil::{
|
||||
@@ -127,12 +123,11 @@
|
||||
join_strings, string_prefixes_string, string_prefixes_string_case_insensitive,
|
||||
string_prefixes_string_maybe_case_insensitive,
|
||||
};
|
||||
use fish_widestring::ELLIPSIS_CHAR;
|
||||
use fish_widestring::{ELLIPSIS_CHAR, UTF8_BOM_WCHAR, bytes2wcstring};
|
||||
use libc::{
|
||||
_POSIX_VDISABLE, EIO, EISDIR, ENOTTY, ESRCH, O_NONBLOCK, O_RDONLY, SIGINT, STDERR_FILENO,
|
||||
STDIN_FILENO, STDOUT_FILENO, VMIN, VQUIT, VSUSP, VTIME, c_char,
|
||||
};
|
||||
use nix::unistd::setpgid;
|
||||
use nix::{
|
||||
fcntl::OFlag,
|
||||
sys::{
|
||||
@@ -140,7 +135,7 @@
|
||||
stat::Mode,
|
||||
termios::{self, SetArg, Termios, tcgetattr, tcsetattr},
|
||||
},
|
||||
unistd::{getpgrp, getpid},
|
||||
unistd::{getpgrp, getpid, setpgid},
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@@ -179,7 +174,6 @@ fn zeroed_termios() -> Termios {
|
||||
pub static SHELL_MODES: LazyLock<Mutex<Termios>> = LazyLock::new(|| Mutex::new(zeroed_termios()));
|
||||
|
||||
/// The valid terminal modes on startup.
|
||||
/// Warning: this is read from the SIGTERM handler! Hence the raw global.
|
||||
static TERMINAL_MODE_ON_STARTUP: OnceLock<libc::termios> = OnceLock::new();
|
||||
|
||||
/// Mode we use to execute programs.
|
||||
@@ -193,12 +187,12 @@ fn zeroed_termios() -> Termios {
|
||||
/// This variable is set to a signal by the signal handler when ^C is pressed.
|
||||
static INTERRUPTED: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
/// If set, SIGHUP has been received. This latches to true.
|
||||
/// This is set from a signal handler.
|
||||
static SIGHUP_RECEIVED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
/// Stores the signal (SIGHUP or SIGTERM) that should cause fish to exit, or 0 if none.
|
||||
/// Set from a signal handler.
|
||||
static EXIT_SIGNAL: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
// Get the terminal mode on startup. This is "safe" because it's async-signal safe.
|
||||
pub fn safe_get_terminal_mode_on_startup() -> Option<&'static libc::termios> {
|
||||
// Get the terminal mode on startup.
|
||||
pub fn get_terminal_mode_on_startup() -> Option<&'static libc::termios> {
|
||||
TERMINAL_MODE_ON_STARTUP.get()
|
||||
}
|
||||
|
||||
@@ -217,8 +211,7 @@ fn redirect_tty_after_sighup() {
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
// If we have received SIGHUP, redirect the tty to avoid a user script triggering SIGTTIN or
|
||||
// SIGTTOU.
|
||||
assert!(reader_received_sighup(), "SIGHUP not received");
|
||||
// SIGTTOU. The caller checks reader_exit_signal() == SIGHUP before calling this.
|
||||
static TTY_REDIRECTED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
if TTY_REDIRECTED.swap(true) {
|
||||
return;
|
||||
@@ -291,7 +284,7 @@ pub fn terminal_init(vars: &dyn Environment, inputfd: RawFd) -> TerminalInitResu
|
||||
use ImplicitEvent::{CheckExit, Eof};
|
||||
use QueryResultEvent::*;
|
||||
match input_queue.readch() {
|
||||
Implicit(Eof) => reader_sighup(),
|
||||
Implicit(Eof) => safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
Implicit(CheckExit) => {}
|
||||
CharEvent::QueryResult(Response(QueryResponse::PrimaryDeviceAttribute)) => {
|
||||
break;
|
||||
@@ -896,9 +889,8 @@ fn read_i(parser: &Parser) {
|
||||
reader_pop();
|
||||
|
||||
// If we got SIGHUP, ensure the tty is redirected and release tty handoff without
|
||||
// trying to muck with protocols.
|
||||
if reader_received_sighup() {
|
||||
// If we are the top-level reader, then we translate SIGHUP into exit_forced.
|
||||
// trying to muck with protocols. SIGTERM does not redirect; the terminal is still valid.
|
||||
if reader_exit_signal() == libc::SIGHUP {
|
||||
redirect_tty_after_sighup();
|
||||
}
|
||||
|
||||
@@ -1001,7 +993,6 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
{
|
||||
Ok(modes) => {
|
||||
// Save the initial terminal mode.
|
||||
// Note this field is read by a signal handler, so do it atomically, with a leaked mode.
|
||||
// TODO: rationalize behavior if initial tcgetattr() fails.
|
||||
TERMINAL_MODE_ON_STARTUP.get_or_init(|| libc::termios::from(modes.clone()));
|
||||
modes
|
||||
@@ -1038,8 +1029,8 @@ pub fn reader_init(will_restore_foreground_pgroup: bool) {
|
||||
}
|
||||
|
||||
pub fn reader_deinit(restore_foreground_pgroup: bool) {
|
||||
safe_restore_term_mode();
|
||||
safe_deactivate_tty_protocols();
|
||||
restore_term_mode();
|
||||
deactivate_tty_protocols();
|
||||
if restore_foreground_pgroup {
|
||||
restore_term_foreground_process_group_for_exit();
|
||||
}
|
||||
@@ -1048,12 +1039,11 @@ pub fn reader_deinit(restore_foreground_pgroup: bool) {
|
||||
/// Restore the term mode if we own the terminal and are interactive (#8705).
|
||||
/// It's important we do this before restore_foreground_process_group,
|
||||
/// otherwise we won't think we own the terminal.
|
||||
/// THIS FUNCTION IS CALLED FROM A SIGNAL HANDLER. IT MUST BE ASYNC-SIGNAL-SAFE.
|
||||
pub fn safe_restore_term_mode() {
|
||||
pub fn restore_term_mode() {
|
||||
if !is_interactive_session() || getpgrp().as_raw() != unsafe { libc::tcgetpgrp(STDIN_FILENO) } {
|
||||
return;
|
||||
}
|
||||
if let Some(modes) = safe_get_terminal_mode_on_startup() {
|
||||
if let Some(modes) = get_terminal_mode_on_startup() {
|
||||
unsafe { libc::tcsetattr(STDIN_FILENO, libc::TCSANOW, modes) };
|
||||
}
|
||||
}
|
||||
@@ -1329,7 +1319,7 @@ pub fn read_generation_count() -> u32 {
|
||||
|
||||
/// The readers interrupt signal handler. Cancels all currently running blocks.
|
||||
/// This is called from a signal handler!
|
||||
pub fn reader_handle_sigint() {
|
||||
pub fn safe_reader_handle_sigint() {
|
||||
INTERRUPTED.store(SIGINT, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -1350,14 +1340,20 @@ pub fn reader_test_and_clear_interrupted() -> i32 {
|
||||
res
|
||||
}
|
||||
|
||||
/// Mark that we encountered SIGHUP and must (soon) exit. This is invoked from a signal handler.
|
||||
pub fn reader_sighup() {
|
||||
/// Mark that we received an exit signal (SIGHUP or SIGTERM). Invoked from a signal handler.
|
||||
pub fn safe_reader_set_exit_signal(sig: i32) {
|
||||
// Beware, we may be in a signal handler.
|
||||
SIGHUP_RECEIVED.store(true);
|
||||
EXIT_SIGNAL.store(sig, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn reader_received_sighup() -> bool {
|
||||
SIGHUP_RECEIVED.load()
|
||||
/// Return the exit signal we received, or 0 if none.
|
||||
pub fn reader_exit_signal() -> i32 {
|
||||
EXIT_SIGNAL.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Whether we received SIGHUP or SIGTERM and should exit.
|
||||
fn reader_received_exit_signal() -> bool {
|
||||
reader_exit_signal() != 0
|
||||
}
|
||||
|
||||
impl ReaderData {
|
||||
@@ -2877,7 +2873,7 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
CharEvent::Implicit(implicit_event) => {
|
||||
use ImplicitEvent::*;
|
||||
match implicit_event {
|
||||
Eof => reader_sighup(),
|
||||
Eof => safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
CheckExit => (),
|
||||
FocusIn => {
|
||||
event::fire_generic(self.parser, L!("fish_focus_in").to_owned(), vec![]);
|
||||
@@ -5018,10 +5014,7 @@ pub fn fish_is_unwinding_for_exit() -> bool {
|
||||
let exit_state = EXIT_STATE.load(Ordering::Relaxed);
|
||||
let exit_state: ExitState = unsafe { std::mem::transmute(exit_state) };
|
||||
match exit_state {
|
||||
ExitState::None => {
|
||||
// Cancel if we got SIGHUP.
|
||||
reader_received_sighup()
|
||||
}
|
||||
ExitState::None => reader_received_exit_signal(),
|
||||
ExitState::RunningHandlers => {
|
||||
// We intend to exit but we want to allow these handlers to run.
|
||||
false
|
||||
@@ -6522,8 +6515,7 @@ fn try_warn_on_background_jobs(&mut self) -> bool {
|
||||
/// Check if we should exit the reader loop.
|
||||
/// Return true if we should exit.
|
||||
pub fn check_exit_loop_maybe_warning(data: Option<&mut Reader>) -> bool {
|
||||
// sighup always forces exit.
|
||||
if reader_received_sighup() {
|
||||
if reader_received_exit_signal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! The current implementation is less smart than ncurses allows and can not for example move blocks
|
||||
//! of text around to handle text insertion.
|
||||
|
||||
use crate::common::{get_omitted_newline_str, has_working_tty_timestamps, shell_modes, write_loop};
|
||||
use crate::common::{get_omitted_newline_str, has_working_tty_timestamps, shell_modes};
|
||||
use crate::editable_line::line_at_cursor;
|
||||
use crate::env::Environment;
|
||||
use crate::flog::{flog, flogf};
|
||||
@@ -24,9 +24,10 @@
|
||||
use crate::terminal::{BufferedOutputter, CardinalDirection, Outputter};
|
||||
use crate::termsize::Termsize;
|
||||
use crate::wutil::fstat;
|
||||
use fish_common::write_loop;
|
||||
use fish_fallback::{fish_wcswidth_canonicalizing, fish_wcwidth};
|
||||
use fish_wcstringutil::{fish_wcwidth_visible, string_prefixes_string, wcs2bytes};
|
||||
use fish_widestring::ELLIPSIS_CHAR;
|
||||
use fish_wcstringutil::{fish_wcwidth_visible, string_prefixes_string};
|
||||
use fish_widestring::{ELLIPSIS_CHAR, wcs2bytes};
|
||||
use libc::{STDERR_FILENO, STDOUT_FILENO};
|
||||
use nix::sys::termios;
|
||||
use std::cell::RefCell;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::common::exit_without_destructors;
|
||||
use crate::event::{enqueue_signal, is_signal_observed};
|
||||
use crate::prelude::*;
|
||||
use crate::reader::{reader_handle_sigint, reader_sighup, safe_restore_term_mode};
|
||||
use crate::reader::{safe_reader_handle_sigint, safe_reader_set_exit_signal};
|
||||
use crate::termsize::safe_termsize_invalidate_tty;
|
||||
use crate::topic_monitor::{Generation, GenerationsList, Topic, topic_monitor_principal};
|
||||
use crate::tty_handoff::{safe_deactivate_tty_protocols, safe_mark_tty_invalid};
|
||||
use crate::tty_handoff::safe_mark_tty_invalid;
|
||||
use crate::wutil::fish_wcstoi;
|
||||
use errno::{errno, set_errno};
|
||||
use fish_common::exit_without_destructors;
|
||||
use fish_util::perror;
|
||||
use nix::sys::signal::kill;
|
||||
use nix::{
|
||||
@@ -88,33 +88,23 @@ extern "C" fn fish_signal_handler(
|
||||
// Respond to a winch signal by telling the termsize container.
|
||||
safe_termsize_invalidate_tty();
|
||||
}
|
||||
libc::SIGHUP => {
|
||||
libc::SIGHUP | libc::SIGTERM => {
|
||||
// Exit unless the signal was trapped.
|
||||
if !observed {
|
||||
reader_sighup();
|
||||
safe_mark_tty_invalid();
|
||||
}
|
||||
topic_monitor_principal().post(Topic::SigHupInt);
|
||||
}
|
||||
libc::SIGTERM => {
|
||||
// Handle sigterm. The only thing we do is restore the front process ID and disable protocols, then die.
|
||||
if !observed {
|
||||
safe_restore_term_mode();
|
||||
safe_deactivate_tty_protocols();
|
||||
// Safety: signal() and raise() are async-signal-safe.
|
||||
unsafe {
|
||||
libc::signal(libc::SIGTERM, libc::SIG_DFL);
|
||||
libc::raise(libc::SIGTERM);
|
||||
safe_reader_set_exit_signal(sig);
|
||||
if sig == libc::SIGHUP {
|
||||
safe_mark_tty_invalid();
|
||||
}
|
||||
}
|
||||
topic_monitor_principal().post(Topic::SigHupIntTerm);
|
||||
}
|
||||
libc::SIGINT => {
|
||||
// Cancel unless the signal was trapped.
|
||||
if !observed {
|
||||
CANCELLATION_SIGNAL.store(libc::SIGINT, Ordering::Relaxed);
|
||||
}
|
||||
reader_handle_sigint();
|
||||
topic_monitor_principal().post(Topic::SigHupInt);
|
||||
safe_reader_handle_sigint();
|
||||
topic_monitor_principal().post(Topic::SigHupIntTerm);
|
||||
}
|
||||
libc::SIGCHLD => {
|
||||
// A child process stopped or exited.
|
||||
@@ -177,7 +167,7 @@ fn set_interactive_handlers() {
|
||||
act.sa_flags = libc::SA_SIGINFO;
|
||||
sigaction(libc::SIGTTIN, &act, nullptr);
|
||||
|
||||
// SIGTERM restores the terminal controlling process before dying.
|
||||
// SIGTERM defers to allow graceful history save before exit.
|
||||
act.sa_sigaction = signal_handler;
|
||||
act.sa_flags = libc::SA_SIGINFO;
|
||||
sigaction(libc::SIGTERM, &act, nullptr);
|
||||
@@ -324,8 +314,8 @@ pub fn new(topic: Topic) -> Self {
|
||||
}
|
||||
|
||||
/// Create a new checker for SIGHUP and SIGINT.
|
||||
pub fn new_sighupint() -> Self {
|
||||
Self::new(Topic::SigHupInt)
|
||||
pub fn new_sighupintterm() -> Self {
|
||||
Self::new(Topic::SigHupIntTerm)
|
||||
}
|
||||
|
||||
/// Check if a sigint has been delivered since the last call to check(), or since the detector
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
// Generic output functions.
|
||||
use crate::common::{self, EscapeStringStyle, escape_string};
|
||||
use crate::prelude::*;
|
||||
use crate::screen::{is_dumb, only_grayscale};
|
||||
use crate::text_face::{ResettableStyle, TextFace, TextStyling, UnderlineStyle};
|
||||
use crate::threads::MainThread;
|
||||
use crate::{
|
||||
screen::{is_dumb, only_grayscale},
|
||||
text_face::{ResettableStyle, TextFace, TextStyling, UnderlineStyle},
|
||||
threads::MainThread,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use fish_color::{Color, Color24};
|
||||
use fish_common::{EscapeStringStyle, escape_string, write_loop};
|
||||
use fish_feature_flags::FeatureFlag;
|
||||
use fish_wcstringutil::{wcs2bytes, wcs2bytes_appending};
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::fd::RawFd;
|
||||
use std::os::unix::ffi::OsStrExt as _;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use fish_widestring::{wcs2bytes, wcs2bytes_appending};
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
ops::{Deref, DerefMut},
|
||||
os::{fd::RawFd, unix::ffi::OsStrExt as _},
|
||||
sync::{
|
||||
OnceLock,
|
||||
atomic::{AtomicU8, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy, Clone, Default)]
|
||||
@@ -545,7 +550,7 @@ pub fn take_contents(self) -> Vec<u8> {
|
||||
/// Output any buffered data to the given `fd`.
|
||||
fn flush_to(&mut self, fd: RawFd) {
|
||||
if fd >= 0 && !self.contents.is_empty() {
|
||||
let _ = common::write_loop(&fd, &self.contents);
|
||||
let _ = write_loop(&fd, &self.contents);
|
||||
self.contents.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Support for exposing the terminal size.
|
||||
use crate::common::assert_sync;
|
||||
use crate::env::{EnvMode, EnvVar, Environment};
|
||||
use crate::flog::flog;
|
||||
use crate::parser::{Parser, ParserEnvSetMode};
|
||||
use crate::prelude::*;
|
||||
use crate::wutil::fish_wcstoi;
|
||||
use fish_common::assert_sync;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::num::NonZeroU16;
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::common::{BUILD_DIR, ScopeGuard, ScopeGuarding};
|
||||
use crate::common::BUILD_DIR;
|
||||
use crate::env::env_init;
|
||||
use crate::env::{EnvMode, EnvVar, EnvVarFlags, Environment};
|
||||
use crate::locale::set_libc_locales;
|
||||
@@ -9,6 +9,7 @@
|
||||
use crate::topic_monitor::topic_monitor_init;
|
||||
use crate::wutil::wgetcwd;
|
||||
use crate::{env::EnvStack, proc::proc_init};
|
||||
use fish_common::{ScopeGuard, ScopeGuarding};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::env::set_current_dir;
|
||||
|
||||
@@ -38,15 +38,15 @@
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Topic {
|
||||
SigHupInt = 0, // Corresponds to both SIGHUP and SIGINT signals.
|
||||
SigChld = 1, // Corresponds to SIGCHLD signal.
|
||||
InternalExit = 2, // Corresponds to an internal process exit.
|
||||
SigHupIntTerm = 0, // Corresponds to both SIGHUP and SIGINT signals.
|
||||
SigChld = 1, // Corresponds to SIGCHLD signal.
|
||||
InternalExit = 2, // Corresponds to an internal process exit.
|
||||
}
|
||||
|
||||
// XXX: Is it correct to use the default or should the default be invalid_generation?
|
||||
#[derive(Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct GenerationsList {
|
||||
pub sighupint: Cell<u64>,
|
||||
pub sighupintterm: Cell<u64>,
|
||||
pub sigchld: Cell<u64>,
|
||||
pub internal_exit: Cell<u64>,
|
||||
}
|
||||
@@ -56,7 +56,7 @@ pub struct GenerationsList {
|
||||
impl GenerationsList {
|
||||
/// Update `self` gen counts to match those of `other`.
|
||||
pub fn update(&self, other: &Self) {
|
||||
self.sighupint.set(other.sighupint.get());
|
||||
self.sighupintterm.set(other.sighupintterm.get());
|
||||
self.sigchld.set(other.sigchld.get());
|
||||
self.internal_exit.set(other.internal_exit.get());
|
||||
}
|
||||
@@ -70,7 +70,7 @@ impl FloggableDebug for Topic {}
|
||||
pub const INVALID_GENERATION: Generation = u64::MAX;
|
||||
|
||||
pub fn all_topics() -> [Topic; 3] {
|
||||
[Topic::SigHupInt, Topic::SigChld, Topic::InternalExit]
|
||||
[Topic::SigHupIntTerm, Topic::SigChld, Topic::InternalExit]
|
||||
}
|
||||
|
||||
impl GenerationsList {
|
||||
@@ -81,7 +81,7 @@ pub fn new() -> Self {
|
||||
/// Generation list containing invalid generations only.
|
||||
pub fn invalid() -> GenerationsList {
|
||||
GenerationsList {
|
||||
sighupint: INVALID_GENERATION.into(),
|
||||
sighupintterm: INVALID_GENERATION.into(),
|
||||
sigchld: INVALID_GENERATION.into(),
|
||||
internal_exit: INVALID_GENERATION.into(),
|
||||
}
|
||||
@@ -106,7 +106,7 @@ fn describe(&self) -> WString {
|
||||
/// Sets the generation for `topic` to `value`.
|
||||
pub fn set(&self, topic: Topic, value: Generation) {
|
||||
match topic {
|
||||
Topic::SigHupInt => self.sighupint.set(value),
|
||||
Topic::SigHupIntTerm => self.sighupintterm.set(value),
|
||||
Topic::SigChld => self.sigchld.set(value),
|
||||
Topic::InternalExit => self.internal_exit.set(value),
|
||||
}
|
||||
@@ -115,7 +115,7 @@ pub fn set(&self, topic: Topic, value: Generation) {
|
||||
/// Return the value for a topic.
|
||||
pub fn get(&self, topic: Topic) -> Generation {
|
||||
match topic {
|
||||
Topic::SigHupInt => self.sighupint.get(),
|
||||
Topic::SigHupIntTerm => self.sighupintterm.get(),
|
||||
Topic::SigChld => self.sigchld.get(),
|
||||
Topic::InternalExit => self.internal_exit.get(),
|
||||
}
|
||||
@@ -124,7 +124,7 @@ pub fn get(&self, topic: Topic) -> Generation {
|
||||
/// Return ourselves as an array.
|
||||
pub fn as_array(&self) -> [Generation; 3] {
|
||||
[
|
||||
self.sighupint.get(),
|
||||
self.sighupintterm.get(),
|
||||
self.sigchld.get(),
|
||||
self.internal_exit.get(),
|
||||
]
|
||||
@@ -648,7 +648,7 @@ fn test_topic_monitor_torture() {
|
||||
let monitor = Arc::new(TopicMonitor::default());
|
||||
const THREAD_COUNT: usize = 64;
|
||||
let t1 = Topic::SigChld;
|
||||
let t2 = Topic::SigHupInt;
|
||||
let t2 = Topic::SigHupIntTerm;
|
||||
let mut gens_list = vec![GenerationsList::invalid(); THREAD_COUNT];
|
||||
let post_count = Arc::new(AtomicU64::new(0));
|
||||
for r#gen in &mut gens_list {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::flog::log_extra_to_flog_file;
|
||||
use crate::parser::Parser;
|
||||
use crate::{common::escape, global_safety::RelaxedAtomicBool, prelude::*};
|
||||
use crate::{
|
||||
flog::log_extra_to_flog_file, global_safety::RelaxedAtomicBool, parser::Parser, prelude::*,
|
||||
};
|
||||
use fish_common::escape;
|
||||
|
||||
static DO_TRACE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
static DO_TRACE_ALL: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Utility for transferring the tty to a child process in a scoped way,
|
||||
//! and reclaiming it after.
|
||||
|
||||
use crate::common::{self, safe_write_loop};
|
||||
use crate::env::Environment;
|
||||
use crate::env_dispatch::MIDNIGHT_COMMANDER_SID;
|
||||
use crate::flog::{flog, flogf};
|
||||
@@ -18,6 +17,7 @@
|
||||
};
|
||||
use crate::threads::assert_is_main_thread;
|
||||
use crate::wutil::{perror_nix, wcstoi};
|
||||
use fish_common::write_loop;
|
||||
use fish_util::perror;
|
||||
use libc::{EINVAL, ENOTTY, EPERM, STDIN_FILENO, WNOHANG};
|
||||
use nix::sys::termios::tcgetattr;
|
||||
@@ -25,7 +25,7 @@
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::sync::{
|
||||
OnceLock,
|
||||
atomic::{AtomicBool, AtomicPtr, Ordering},
|
||||
atomic::{AtomicPtr, Ordering},
|
||||
};
|
||||
|
||||
/// Whether kitty keyboard protocol support is present in the TTY.
|
||||
@@ -104,9 +104,7 @@ enum ProtocolKind {
|
||||
}
|
||||
|
||||
// Commands to emit to enable or disable TTY protocols. Each of these contains
|
||||
// the full serialized command sequence as bytes. It's structured in this awkward
|
||||
// way so that we can use it from a signal handler - no need to allocate or deallocate
|
||||
// as Kitty support is discovered through tty queries.
|
||||
// the full serialized command sequence as bytes.
|
||||
struct ProtocolBytes {
|
||||
kitty_keyboard: Box<[u8]>,
|
||||
other: Box<[u8]>,
|
||||
@@ -115,8 +113,6 @@ struct ProtocolBytes {
|
||||
}
|
||||
|
||||
// The combined set of TTY protocols.
|
||||
// This is created once at startup and then leaked, so it may be used
|
||||
// from the SIGTERM handler.
|
||||
struct TtyProtocolsSet {
|
||||
// TTY quirks.
|
||||
quirks: TtyQuirks,
|
||||
@@ -127,10 +123,8 @@ struct TtyProtocolsSet {
|
||||
|
||||
impl TtyProtocolsSet {
|
||||
// Get commands to enable or disable TTY protocols
|
||||
// and the KITTY_KEYBOARD_SUPPORTED global variable.
|
||||
// THIS IS USED FROM A SIGNAL HANDLER.
|
||||
fn safe_get_commands(&self, enable: bool) -> &[u8] {
|
||||
let protocol = self.quirks.safe_get_supported_protocol();
|
||||
fn get_commands(&self, enable: bool) -> &[u8] {
|
||||
let protocol = self.quirks.get_supported_protocol();
|
||||
let cmds = if enable {
|
||||
&self.enablers
|
||||
} else {
|
||||
@@ -156,8 +150,7 @@ fn serialize_commands<'a>(cmds: impl Iterator<Item = TerminalCommand<'a>>) -> Bo
|
||||
|
||||
impl TtyQuirks {
|
||||
// Determine which keyboard protocol.
|
||||
// This is used from a signal handler.
|
||||
fn safe_get_supported_protocol(&self) -> ProtocolKind {
|
||||
fn get_supported_protocol(&self) -> ProtocolKind {
|
||||
use TtyQuirks::{PreCsiMidnightCommander, PreKittyIterm2, Wezterm};
|
||||
if *self == PreCsiMidnightCommander {
|
||||
return ProtocolKind::None;
|
||||
@@ -232,7 +225,6 @@ fn get_protocols(self) -> TtyProtocolsSet {
|
||||
}
|
||||
|
||||
// The global tty protocols. This is set once at startup and not changed thereafter.
|
||||
// This is an AtomicPtr and not a OnceLock, etc. so that it can be used from a signal handler.
|
||||
static TTY_PROTOCOLS: AtomicPtr<TtyProtocolsSet> = AtomicPtr::new(std::ptr::null_mut());
|
||||
|
||||
// Get the TTY protocols, without initializing it.
|
||||
@@ -241,7 +233,7 @@ fn tty_protocols() -> Option<&'static TtyProtocolsSet> {
|
||||
unsafe { TTY_PROTOCOLS.load(Ordering::Acquire).as_ref() }
|
||||
}
|
||||
|
||||
// Initialize serialized commands for enabling/disabling TTY protocols in signal handlers.
|
||||
// Initialize serialized commands for enabling/disabling TTY protocols.
|
||||
pub fn initialize_tty_protocols(vars: &dyn Environment) {
|
||||
// Default missing query responses.
|
||||
KITTY_KEYBOARD_SUPPORTED.get_or_init(|| false);
|
||||
@@ -264,14 +256,13 @@ pub fn initialize_tty_protocols(vars: &dyn Environment) {
|
||||
}
|
||||
|
||||
// A marker of the current state of the tty protocols.
|
||||
static TTY_PROTOCOLS_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||
static TTY_PROTOCOLS_ACTIVE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
// A marker that the tty has been closed (SIGHUP, etc) and so we should not try to write to it.
|
||||
static TTY_INVALID: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
|
||||
|
||||
// Enable or disable TTY protocols by writing the appropriate commands to the tty.
|
||||
// Return true if we emitted any bytes to the tty.
|
||||
// Note this does NOT intialize the TTY protocls if not already initialized.
|
||||
// Note this does NOT intialize the TTY protocols if not already initialized.
|
||||
fn set_tty_protocols_active(on_write: fn(), enable: bool) {
|
||||
assert_is_main_thread();
|
||||
// Have protocols at all? We require someone else to have initialized them.
|
||||
@@ -281,11 +272,11 @@ fn set_tty_protocols_active(on_write: fn(), enable: bool) {
|
||||
// Already set?
|
||||
// Note we don't need atomic swaps as this is only called on the main thread.
|
||||
// Also note we (logically) set and clear this even if we got SIGHUP.
|
||||
if TTY_PROTOCOLS_ACTIVE.load(Ordering::Relaxed) == enable {
|
||||
if TTY_PROTOCOLS_ACTIVE.load() == enable {
|
||||
return;
|
||||
}
|
||||
if enable {
|
||||
TTY_PROTOCOLS_ACTIVE.store(true, Ordering::Release);
|
||||
TTY_PROTOCOLS_ACTIVE.store(true);
|
||||
}
|
||||
|
||||
// Did we get SIGHUP?
|
||||
@@ -294,15 +285,15 @@ fn set_tty_protocols_active(on_write: fn(), enable: bool) {
|
||||
}
|
||||
|
||||
// Write the commands to the tty, ignoring errors.
|
||||
let commands = protocols.safe_get_commands(enable);
|
||||
let _ = common::write_loop(&libc::STDOUT_FILENO, commands);
|
||||
let commands = protocols.get_commands(enable);
|
||||
let _ = write_loop(&libc::STDOUT_FILENO, commands);
|
||||
if !enable {
|
||||
TTY_PROTOCOLS_ACTIVE.store(false, Ordering::Relaxed);
|
||||
TTY_PROTOCOLS_ACTIVE.store(false);
|
||||
}
|
||||
|
||||
// Flog any terminal protocol changes of interest.
|
||||
let mode = if enable { "Enabling" } else { "Disabling" };
|
||||
match protocols.quirks.safe_get_supported_protocol() {
|
||||
match protocols.quirks.get_supported_protocol() {
|
||||
ProtocolKind::KittyKeyboard => flog!(reader, mode, "kitty keyboard protocol"),
|
||||
ProtocolKind::Other => flog!(reader, mode, "other extended keys"),
|
||||
ProtocolKind::WorkAroundWezTerm => flog!(reader, mode, "wezterm; no modifyOtherKeys"),
|
||||
@@ -313,19 +304,21 @@ fn set_tty_protocols_active(on_write: fn(), enable: bool) {
|
||||
|
||||
// Helper to check if TTY protocols are active.
|
||||
pub fn get_tty_protocols_active() -> bool {
|
||||
TTY_PROTOCOLS_ACTIVE.load(Ordering::Relaxed)
|
||||
TTY_PROTOCOLS_ACTIVE.load()
|
||||
}
|
||||
|
||||
// Called from a signal handler to deactivate TTY protocols before exiting.
|
||||
// Only async-signal-safe code can be run here.
|
||||
pub fn safe_deactivate_tty_protocols() {
|
||||
// Deactivate TTY protocols before exiting.
|
||||
pub fn deactivate_tty_protocols() {
|
||||
if !cfg!(test) {
|
||||
assert_is_main_thread();
|
||||
}
|
||||
// Safety: TTY_PROTOCOLS is never modified after initialization.
|
||||
let protocols = unsafe { TTY_PROTOCOLS.load(Ordering::Acquire).as_ref() };
|
||||
let Some(protocols) = protocols else {
|
||||
// No protocols set, nothing to do.
|
||||
return;
|
||||
};
|
||||
if !TTY_PROTOCOLS_ACTIVE.load(Ordering::Acquire) {
|
||||
if !TTY_PROTOCOLS_ACTIVE.load() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -334,10 +327,10 @@ pub fn safe_deactivate_tty_protocols() {
|
||||
return;
|
||||
}
|
||||
|
||||
let commands = protocols.safe_get_commands(false);
|
||||
let commands = protocols.get_commands(false);
|
||||
// Safety: just writing data to stdout.
|
||||
let _ = safe_write_loop(&libc::STDOUT_FILENO, commands);
|
||||
TTY_PROTOCOLS_ACTIVE.store(false, Ordering::Release);
|
||||
let _ = write_loop(&libc::STDOUT_FILENO, commands);
|
||||
TTY_PROTOCOLS_ACTIVE.store(false);
|
||||
}
|
||||
|
||||
// Called from a signal handler to mark the tty as invalid (e.g. SIGHUP).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use crate::prelude::*;
|
||||
use crate::universal_notifier::UniversalNotifier;
|
||||
use crate::wutil::{wbasename, wdirname};
|
||||
use fish_wcstringutil::wcs2osstring;
|
||||
use fish_widestring::wcs2osstring;
|
||||
use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify};
|
||||
use std::ffi::OsString;
|
||||
use std::os::fd::{AsFd as _, AsRawFd as _, RawFd};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use crate::prelude::*;
|
||||
use crate::universal_notifier::UniversalNotifier;
|
||||
use crate::wutil::wdirname;
|
||||
use fish_wcstringutil::wcs2osstring;
|
||||
use fish_widestring::wcs2osstring;
|
||||
use nix::sys::event::{EvFlags, EventFilter, FilterFlag, KEvent, Kqueue};
|
||||
use std::fs::File;
|
||||
use std::os::fd::AsFd;
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn test_notifiers(notifiers: &[&dyn UniversalNotifier], fish_variables_path:
|
||||
|
||||
// Helper to simulate modifying a file, using the atomic rename() approach.
|
||||
let modify_path = |path: &wstr| -> Result<(), std::io::Error> {
|
||||
use fish_wcstringutil::wcs2osstring;
|
||||
use fish_widestring::wcs2osstring;
|
||||
use std::fs;
|
||||
use std::io::Write as _;
|
||||
let path = wcs2osstring(path);
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
// Enumeration of all wildcard types.
|
||||
|
||||
use fish_common::WILDCARD_RESERVED_BASE;
|
||||
use fish_widestring::char_offset;
|
||||
use nix::unistd::AccessFlags;
|
||||
use std::cell::LazyCell;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
use std::os::unix::fs::MetadataExt as _;
|
||||
|
||||
use crate::common::{
|
||||
UnescapeFlags, UnescapeStringStyle, WSL, is_windows_subsystem_for_linux, unescape_string,
|
||||
use crate::{
|
||||
common::{WSL, is_windows_subsystem_for_linux},
|
||||
complete::{CompleteFlags, Completion, CompletionReceiver, PROG_COMPLETE_SEP},
|
||||
expand::ExpandFlags,
|
||||
prelude::*,
|
||||
wutil::{
|
||||
dir_iter::{DirEntry, DirEntryType},
|
||||
lwstat, waccess,
|
||||
},
|
||||
};
|
||||
use crate::complete::{CompleteFlags, Completion, CompletionReceiver, PROG_COMPLETE_SEP};
|
||||
use crate::expand::ExpandFlags;
|
||||
use crate::prelude::*;
|
||||
use crate::wutil::dir_iter::DirEntryType;
|
||||
use crate::wutil::{dir_iter::DirEntry, lwstat, waccess};
|
||||
use fish_common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
|
||||
use fish_fallback::wcscasecmp;
|
||||
use fish_feature_flags::{FeatureFlag, feature_test};
|
||||
use fish_wcstringutil::{
|
||||
CaseSensitivity, string_fuzzy_match_string, string_suffixes_string_case_insensitive,
|
||||
strip_executable_suffix,
|
||||
};
|
||||
use fish_widestring::{ANY_CHAR, ANY_STRING, ANY_STRING_RECURSIVE};
|
||||
use nix::unistd::AccessFlags;
|
||||
use std::{cell::LazyCell, cmp::Ordering, collections::HashSet, os::unix::fs::MetadataExt as _};
|
||||
|
||||
localizable_consts!(
|
||||
COMPLETE_EXEC_DESC "command"
|
||||
@@ -32,17 +30,6 @@
|
||||
COMPLETE_DIRECTORY_DESC "directory"
|
||||
);
|
||||
|
||||
/// Character representing any character except '/' (slash).
|
||||
pub const ANY_CHAR: char = char_offset(WILDCARD_RESERVED_BASE, 0);
|
||||
/// Character representing any character string not containing '/' (slash).
|
||||
pub const ANY_STRING: char = char_offset(WILDCARD_RESERVED_BASE, 1);
|
||||
/// Character representing any character string.
|
||||
pub const ANY_STRING_RECURSIVE: char = char_offset(WILDCARD_RESERVED_BASE, 2);
|
||||
/// This is a special pseudo-char that is not used other than to mark the
|
||||
/// end of the special characters so we can sanity check the enum range.
|
||||
#[allow(dead_code)]
|
||||
pub const ANY_SENTINEL: char = char_offset(WILDCARD_RESERVED_BASE, 3);
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum WildcardResult {
|
||||
/// The wildcard did not match.
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
use super::wopendir;
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::wutil::DevInode;
|
||||
use cfg_if::cfg_if;
|
||||
use fish_wcstringutil::wcs2zstring;
|
||||
use fish_widestring::{WString, wstr};
|
||||
use fish_widestring::{WString, bytes2wcstring, wcs2zstring, wstr};
|
||||
use libc::{
|
||||
EACCES, EIO, ELOOP, ENAMETOOLONG, ENODEV, ENOENT, ENOTDIR, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO,
|
||||
S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use std::io;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::fd::RawFd;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::Cell, io, mem::MaybeUninit, os::fd::RawFd, ptr::NonNull, rc::Rc};
|
||||
|
||||
/// Types of files that may be in a directory.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::wutil::wstr;
|
||||
use fish_wcstringutil::wcs2zstring;
|
||||
use std::ffi::{CStr, OsStr};
|
||||
use std::fs::{self, File, Metadata};
|
||||
use std::os::unix::prelude::*;
|
||||
use fish_widestring::wcs2zstring;
|
||||
use std::{
|
||||
ffi::{CStr, OsStr},
|
||||
fs::{self, File, Metadata},
|
||||
os::unix::prelude::*,
|
||||
};
|
||||
|
||||
/// Struct for representing a file's inode. We use this to detect and avoid symlink loops, among
|
||||
/// other things.
|
||||
|
||||
@@ -7,20 +7,21 @@
|
||||
pub mod wcstod;
|
||||
pub mod wcstoi;
|
||||
|
||||
use crate::common::{bytes2wcstring, fish_reserved_codepoint, osstr2wcstring};
|
||||
use crate::fds::BorrowedFdFile;
|
||||
use crate::flog;
|
||||
use crate::signal::SigChecker;
|
||||
use crate::topic_monitor::Topic;
|
||||
use crate::{fds::BorrowedFdFile, flog, signal::SigChecker};
|
||||
use errno::{Errno, set_errno};
|
||||
use fish_util::{perror, write_to_fd};
|
||||
use fish_wcstringutil::{join_strings, str2bytes_callback, wcs2osstring, wcs2zstring};
|
||||
use fish_widestring::{IntoCharIter, L, WExt as _, WString, wstr};
|
||||
use fish_wcstringutil::join_strings;
|
||||
use fish_widestring::{
|
||||
IntoCharIter, L, WExt as _, WString, bytes2wcstring, fish_reserved_codepoint, osstr2wcstring,
|
||||
str2bytes_callback, wcs2osstring, wcs2zstring, wstr,
|
||||
};
|
||||
use nix::unistd::AccessFlags;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, canonicalize};
|
||||
use std::io;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs::{self, canonicalize},
|
||||
io,
|
||||
os::unix::prelude::*,
|
||||
};
|
||||
|
||||
pub use crate::wutil::printf::{eprintf, fprintf, printf, sprintf};
|
||||
|
||||
@@ -391,7 +392,7 @@ fn do_write(
|
||||
true
|
||||
};
|
||||
|
||||
let mut sigcheck = SigChecker::new(Topic::SigHupInt);
|
||||
let mut sigcheck = SigChecker::new_sighupintterm();
|
||||
let mut success = str2bytes_callback(input, |buff: &[u8]| {
|
||||
if buff.len() + accumlen > accum_capacity {
|
||||
// We have to flush.
|
||||
@@ -458,9 +459,8 @@ mod tests {
|
||||
use super::{
|
||||
normalize_path, unescape_bytes_and_write_to_fd, wbasename, wdirname, wstr_offset_in,
|
||||
};
|
||||
use crate::common::bytes2wcstring;
|
||||
use crate::prelude::*;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::{prelude::*, tests::prelude::*};
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use rand::Rng as _;
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
set -g fish (realpath $fish)
|
||||
|
||||
cygwin_nosymlinks && set nosymlinks
|
||||
|
||||
# Store pwd to later go back before cleaning up
|
||||
set -l oldpwd (pwd)
|
||||
|
||||
@@ -25,30 +27,37 @@ rm -rf $tmp
|
||||
# Create a test directory to store our stuff.
|
||||
# macOS likes to return symlinks from (mktemp -d), make sure it does not.
|
||||
set -l base (realpath (mktemp -d))
|
||||
set real (realpath (mktemp -d))
|
||||
set link $base/link
|
||||
ln -s $real $link
|
||||
cd $link
|
||||
prevd
|
||||
nextd
|
||||
test "$PWD" = "$link" || echo "\$PWD != \$link:"\n "\$PWD: $PWD"\n "\$link: $link"\n
|
||||
test (pwd) = "$link" || echo "(pwd) != \$link:"\n "\$PWD: "(pwd)\n "\$link: $link"\n
|
||||
test (pwd -P) = "$real" || echo "(pwd -P) != \$real:"\n "\$PWD: $PWD"\n "\$real: $real"\n
|
||||
test (pwd -P -L) = "$link" || echo "(pwd -P -L) != \$link:"\n "\$PWD: $PWD"\n "\$link: $link"\n
|
||||
|
||||
if not set -q nosymlinks
|
||||
set real (realpath (mktemp -d))
|
||||
set link $base/link
|
||||
ln -s $real $link
|
||||
cd $link
|
||||
prevd
|
||||
nextd
|
||||
test "$PWD" = "$link" || echo "\$PWD != \$link:"\n "\$PWD: $PWD"\n "\$link: $link"\n
|
||||
test (pwd) = "$link" || echo "(pwd) != \$link:"\n "\$PWD: "(pwd)\n "\$link: $link"\n
|
||||
test (pwd -P) = "$real" || echo "(pwd -P) != \$real:"\n "\$PWD: $PWD"\n "\$real: $real"\n
|
||||
test (pwd -P -L) = "$link" || echo "(pwd -P -L) != \$link:"\n "\$PWD: $PWD"\n "\$link: $link"\n
|
||||
end
|
||||
# Expect no output on success.
|
||||
pwd abc
|
||||
# CHECKERR: pwd: expected 0 arguments; got 1
|
||||
|
||||
mkdir -p $base/pwd_real/subdir
|
||||
ln -s $base/pwd_real $base/pwd_link
|
||||
cd $base/pwd_link/subdir
|
||||
rmdir $base/pwd_real/subdir $base/pwd_real
|
||||
pwd -P
|
||||
# CHECKERR: pwd: realpath failed: No such file or directory
|
||||
if set -q nosymlinks
|
||||
echo "pwd: realpath failed: No such file or directory" >&2
|
||||
else
|
||||
mkdir -p $base/pwd_real/subdir
|
||||
ln -s $base/pwd_real $base/pwd_link
|
||||
cd $base/pwd_link/subdir
|
||||
rmdir $base/pwd_real/subdir $base/pwd_real
|
||||
pwd -P
|
||||
end
|
||||
# CHECKERR: pwd: realpath failed: {{.+}}
|
||||
|
||||
# Create a symlink and verify logical completion.
|
||||
# create directory $base/through/the/looking/glass
|
||||
# symlink $base/somewhere/teleport -> $base/through/the/looking/glass
|
||||
# symlink $base/somewhere/rabbithole -> $base/through/the/looking/glass
|
||||
# verify that .. completions work
|
||||
cd $base
|
||||
mkdir -p $base/through/the/looking/glass
|
||||
@@ -63,7 +72,13 @@ mkdir $base/through/the/looking/d2
|
||||
mkdir $base/through/the/looking/d3
|
||||
ln -s $base/through/the/looking/glass $base/somewhere/rabbithole
|
||||
|
||||
cd $base/somewhere/rabbithole
|
||||
if set -q nosymlinks
|
||||
# This is where we would be going if symlinks were working. This invalidates
|
||||
# that particular test case, but now we can proceed with the tests
|
||||
cd $base/through/the/looking/glass
|
||||
else
|
||||
cd $base/somewhere/rabbithole
|
||||
end
|
||||
echo "ls:"
|
||||
complete -C'ls ../'
|
||||
#CHECK: ls:
|
||||
@@ -75,6 +90,9 @@ complete -C'ls ../'
|
||||
#CHECK: ../d3/
|
||||
#CHECK: ../glass/
|
||||
|
||||
if set -q nosymlinks
|
||||
cd $base/somewhere/rabbithole
|
||||
end
|
||||
echo "cd:"
|
||||
complete -C'cd ../'
|
||||
#CHECK: cd:
|
||||
@@ -154,9 +172,18 @@ cd file
|
||||
#CHECKERR: called on line {{\d+}} of file {{.*}}/cd.fish
|
||||
|
||||
# a directory that isn't executable
|
||||
mkdir bad-perms
|
||||
chmod -x bad-perms
|
||||
cd bad-perms
|
||||
if cygwin_noacl ./
|
||||
echo "cd: Permission denied: 'bad-perms'" >&2
|
||||
echo "fake/cd.fish (line 123):" >&2
|
||||
echo "builtin cd \$argv" >&2
|
||||
echo "^" >&2
|
||||
echo "in function 'cd' with arguments 'bad-perms'" >&2
|
||||
echo "called on line 123 of file fake/cd.fish" >&2
|
||||
else
|
||||
mkdir bad-perms
|
||||
chmod -x bad-perms
|
||||
cd bad-perms
|
||||
end
|
||||
#CHECKERR: cd: Permission denied: 'bad-perms'
|
||||
#CHECKERR: {{.*}}/cd.fish (line {{\d+}}):
|
||||
#CHECKERR: builtin cd $argv
|
||||
@@ -189,7 +216,16 @@ cd $old_path
|
||||
set CDPATH $old_cdpath $PWD/cdpath-dir
|
||||
cd nonexistent
|
||||
cd $old_path
|
||||
cd bad-perms
|
||||
if cygwin_noacl ./
|
||||
echo "cd: Permission denied: 'bad-perms'" >&2
|
||||
echo "fake/cd.fish (line 123):" >&2
|
||||
echo "builtin cd \$argv" >&2
|
||||
echo "^" >&2
|
||||
echo "in function 'cd' with arguments 'bad-perms'" >&2
|
||||
echo "called on line 123 of file fake/cd.fish" >&2
|
||||
else
|
||||
cd bad-perms
|
||||
end
|
||||
# Permission errors are still a problem!
|
||||
#CHECKERR: cd: Permission denied: 'bad-perms'
|
||||
#CHECKERR: {{.*}}/cd.fish (line {{\d+}}):
|
||||
@@ -248,10 +284,19 @@ echo $status
|
||||
# CHECK: 1
|
||||
|
||||
cd (mktemp -d)
|
||||
ln -s no/such/directory broken-symbolic-link
|
||||
begin
|
||||
set -lx CDPATH
|
||||
cd broken-symbolic-link
|
||||
if set -q nosymlinks
|
||||
echo "cd: 'fake/broken-symbolic-link' is a broken symbolic link to 'no/such/directory'" >&2
|
||||
echo "fake/cd.fish (line 123):" >&2
|
||||
echo "builtin cd \$argv" >&2
|
||||
echo "^" >&2
|
||||
echo "in function 'cd' with arguments 'broken-symbolic-link'" >&2
|
||||
echo "called on line 123 of file fake/cd.fish" >&2
|
||||
else
|
||||
ln -s no/such/directory broken-symbolic-link
|
||||
begin
|
||||
set -lx CDPATH
|
||||
cd broken-symbolic-link
|
||||
end
|
||||
end
|
||||
# CHECKERR: cd: '{{.*}}/broken-symbolic-link' is a broken symbolic link to 'no/such/directory'
|
||||
# CHECKERR: {{.*}}/cd.fish (line {{\d+}}):
|
||||
@@ -261,9 +306,18 @@ end
|
||||
# CHECKERR: called on line {{\d+}} of file {{.*}}/cd.fish
|
||||
|
||||
# Make sure that "broken symlink" is reported over "no such file or directory".
|
||||
begin
|
||||
set -lx CDPATH other
|
||||
cd broken-symbolic-link
|
||||
if set -q nosymlinks
|
||||
echo "cd: 'fake/broken-symbolic-link' is a broken symbolic link to 'no/such/directory'" >&2
|
||||
echo "fake/cd.fish (line 123):" >&2
|
||||
echo "builtin cd \$argv" >&2
|
||||
echo "^" >&2
|
||||
echo "in function 'cd' with arguments 'broken-symbolic-link'" >&2
|
||||
echo "called on line 123 of file fake/cd.fish" >&2
|
||||
else
|
||||
begin
|
||||
set -lx CDPATH other
|
||||
cd broken-symbolic-link
|
||||
end
|
||||
end
|
||||
# CHECKERR: cd: '{{.*}}/broken-symbolic-link' is a broken symbolic link to 'no/such/directory'
|
||||
# CHECKERR: {{.*}}/cd.fish (line {{\d+}}):
|
||||
@@ -322,9 +376,18 @@ end
|
||||
HOME="" cd
|
||||
# CHECKERR: cd: Could not find home directory
|
||||
|
||||
ln -s loop1 loop2
|
||||
ln -s loop2 loop1
|
||||
cd loop1
|
||||
if set -q nosymlinks
|
||||
echo "cd: Too many levels of symbolic links: 'loop1'" >&2
|
||||
echo "fake/cd.fish (line 123):" >&2
|
||||
echo "builtin cd \$argv" >&2
|
||||
echo "^" >&2
|
||||
echo "in function 'cd' with arguments 'loop1'" >&2
|
||||
echo "called on line 123 of file fake/cd.fish" >&2
|
||||
else
|
||||
ln -s loop1 loop2
|
||||
ln -s loop2 loop1
|
||||
cd loop1
|
||||
end
|
||||
# CHECKERR: cd: Too many levels of symbolic links: 'loop1'
|
||||
# CHECKERR: {{.*}}/cd.fish (line {{\d+}}):
|
||||
# CHECKERR: builtin cd $argv
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# RUN: env PATH="a::b" CDPATH="d::e" MANPATH="x::y" %fish %s
|
||||
# RUN: env PATH="/usr/bin:a::b" CDPATH="d::e" MANPATH="x::y" %fish %s
|
||||
# PATH must contain `/usr/bin` for the test to work on Cygwin (Cygwin must be
|
||||
# able to find its DLLs when loading fish.exe)
|
||||
|
||||
# In PATH and CDPATH, empty elements are treated the same as "."
|
||||
# In fish we replace them explicitly. Ensure that works.
|
||||
# Do not replace empties in MATHPATH - see #4158.
|
||||
# Do not replace empties in MANPATH - see #4158.
|
||||
|
||||
echo "$PATH"
|
||||
# CHECK: a:.:b
|
||||
# CHECK: /usr/bin:a:.:b
|
||||
|
||||
echo "$CDPATH"
|
||||
# CHECK: d:.:e
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
#RUN: fish=%fish %fish %s
|
||||
__fish_migrate # make sure the interactive fish doesn't need mkdir in PATH
|
||||
set -g PATH
|
||||
if __fish_is_cygwin
|
||||
# The Cygwin/MSYS DLLs must be in the path, otherwise fish cannot be
|
||||
# executed
|
||||
set -g PATH /usr/bin
|
||||
else
|
||||
set -g PATH
|
||||
end
|
||||
|
||||
$fish -c "nonexistent-command-1234 banana rama"
|
||||
#CHECKERR: fish: Unknown command: nonexistent-command-1234
|
||||
#CHECKERR: fish:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#RUN: fish=%fish %fish %s
|
||||
|
||||
# REQUIRES: %fish -c "is_cygwin"
|
||||
# REQUIRES: %fish -c "__fish_is_cygwin"
|
||||
|
||||
mkdir dir
|
||||
echo "#!/bin/sh" >dir/foo.exe
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#RUN: fish=%fish %fish %s
|
||||
|
||||
function complete_test_alpha1
|
||||
echo $argv
|
||||
end
|
||||
@@ -420,8 +421,12 @@ rm -r $dir
|
||||
set -l dir (mktemp -d)
|
||||
cd $dir
|
||||
|
||||
: >command-not-in-path
|
||||
chmod +x command-not-in-path
|
||||
if cygwin_noacl ./
|
||||
echo "#!/bin/sh" >command-not-in-path
|
||||
else
|
||||
: >command-not-in-path
|
||||
chmod +x command-not-in-path
|
||||
end
|
||||
complete -p $PWD/command-not-in-path -xa relative-path
|
||||
complete -C './command-not-in-path '
|
||||
# CHECK: relative-path
|
||||
@@ -434,8 +439,12 @@ HOME=$PWD complete -C '~/command-not-in-path '
|
||||
|
||||
# Non-canonical command path
|
||||
mkdir -p subdir
|
||||
: >subdir/command-in-subdir
|
||||
chmod +x subdir/command-in-subdir
|
||||
if cygwin_noacl ./
|
||||
echo "#!/bin/sh" >subdir/command-in-subdir
|
||||
else
|
||||
: >subdir/command-in-subdir
|
||||
chmod +x subdir/command-in-subdir
|
||||
end
|
||||
complete -p "$PWD/subdir/command-in-subdir" -xa custom-completions
|
||||
complete -C './subdir/../subdir/command-in-subdir '
|
||||
# CHECK: custom-completions
|
||||
@@ -695,8 +704,12 @@ complete -C"command-line-aware-completions "
|
||||
# CHECK: command-line-aware-completions
|
||||
|
||||
begin
|
||||
: >"$TMPDIR/-command-starting-with-dash"
|
||||
chmod +x "$TMPDIR/-command-starting-with-dash"
|
||||
if cygwin_noacl ./
|
||||
echo "#!/bin/sh" >"$TMPDIR/-command-starting-with-dash"
|
||||
else
|
||||
: >"$TMPDIR/-command-starting-with-dash"
|
||||
chmod +x "$TMPDIR/-command-starting-with-dash"
|
||||
end
|
||||
|
||||
set -l PATH "$TMPDIR" $PATH
|
||||
complete -C-command-starting-with
|
||||
|
||||
@@ -11,7 +11,15 @@ $fish -c ''
|
||||
# Check that existing directories kept their permissions, and new directories
|
||||
# have the right permissions according to the XDG Base Directory Specification.
|
||||
# Use command ls and awk to strip off xattr or SELinux indicators.
|
||||
command ls -ld $dir/old $dir/old/new $dir/old/new/fish | awk '{print substr($1, 1, 10)}'
|
||||
command ls -ld $dir/old | awk '{print substr($1, 1, 10)}'
|
||||
# CHECK: drwxr-xr-x
|
||||
|
||||
set -l ls_result "$(command ls -ld $dir/old/new $dir/old/new/fish | awk '{print substr($1, 1, 10)}')"
|
||||
if cygwin_noacl $dir
|
||||
# No permission support => fake the result
|
||||
string replace -a drwxr-xr-x drwx------ $ls_result
|
||||
else
|
||||
printf "%s" "$ls_result"
|
||||
end
|
||||
# CHECK: drwx------
|
||||
# CHECK: drwx------
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#RUN: fish=%fish %fish %s
|
||||
|
||||
# Cygwin/MSYS PATH automatically inherits the Windows PATH
|
||||
# REQUIRES: %fish -c "not __fish_is_cygwin"
|
||||
|
||||
if command -q getconf
|
||||
# (no env -u, some systems don't support that)
|
||||
set -l getconf (command -s getconf)
|
||||
|
||||
@@ -286,7 +286,11 @@ set expandedtilde (env HOME=$tmpdir/linkhome $fish -c 'echo ~')
|
||||
if test $expandedtilde != $tmpdir/linkhome
|
||||
echo '~ expands to' $expandedtilde ' - expected ' $tmpdir/linkhome
|
||||
end
|
||||
rm $tmpdir/linkhome
|
||||
if cygwin_nosymlinks
|
||||
rmdir $tmpdir/linkhome
|
||||
else
|
||||
rm $tmpdir/linkhome
|
||||
end
|
||||
rmdir $tmpdir/realhome
|
||||
rmdir $tmpdir
|
||||
|
||||
@@ -337,7 +341,7 @@ printf '<%s>\n' ($fish -c 'echo "$abc["' 2>&1)
|
||||
|
||||
set -l pager command less
|
||||
echo foo | $pager
|
||||
#CHECKERR: {{.*}}checks/expansion.fish (line 339): The expanded command is a keyword.
|
||||
#CHECKERR: {{.*}}checks/expansion.fish (line {{\d+}}): The expanded command is a keyword.
|
||||
#CHECKERR: echo foo | $pager
|
||||
#CHECKERR: ^~~~~^
|
||||
|
||||
|
||||
@@ -37,7 +37,11 @@ git config --local alias.re 'restore --staged'
|
||||
|
||||
set -p PATH $PWD
|
||||
echo "echo foo" >git-frobnicate
|
||||
chmod +x git-frobnicate
|
||||
if cygwin_noacl ./
|
||||
echo "#!/bin/sh" >git-frobnicate
|
||||
else
|
||||
chmod +x git-frobnicate
|
||||
end
|
||||
|
||||
complete -c git-frobnicate -xa 'foo bar baz'
|
||||
complete -c git-frobnicate -l onto -xa 'onto1 onto2'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user