Compare commits

...

24 Commits

Author SHA1 Message Date
Nahor
440e7fcbc1 Fix failing system tests on Cygwin
The main changes are:
- disabling some checks related to POSIX file permissions when a filesystem is
mounted with "noacl" (default on MSYS2)
- disabling some checks related to symlinks when using fake ones (file copy)

Windows with acl hasn't been tested because 1) Cygwin itself does not have any
Rust package yet to compile fish, and 2) MSYS2 defaults to `noacl`

Part of #12171
2026-04-11 18:55:00 +08:00
Nahor
52495c8124 tests: make realpath tests easier to debug
- Use the different strings for different checks to more easily narrow down
where a failure happens
- Move CHECK comments outside a `if...else...end` to avoid giving the impression
that the check only runs in the `if` case.

Part of #12171
2026-04-11 18:44:22 +08:00
Vishrut Sachan
467b03d715 git completions: prioritize recent commits for rebase --interactive
Fixes #12537

Closes #12619
2026-04-11 18:33:05 +08:00
Johannes Altmanninger
b5c40478f6 Fix typo, closes #12586, closes #12577 2026-04-11 18:33:05 +08:00
Johannes Altmanninger
1286745e78 Remove bits for async-signal-safety of old SIGTERM handler
This is implied by the parent commit.

To enable this, stop trying to run cleanup in panic handlers if we
panic on a background thread.
2026-04-11 18:25:26 +08:00
Yakov Till
b99ae291d6 Save history on SIGTERM and SIGHUP before exit
Previously, SIGTERM immediately re-raised with SIG_DFL, killing
fish without saving history. SIGHUP deferred via a flag but never
re-raised, so the parent saw a normal exit instead of signal death.

Unify both signals: the handler stores the signal number in a single
AtomicI32, the reader loop exits normally, throwing_main() saves
history and re-raises with SIG_DFL so the parent sees WIFSIGNALED.

Fixes #10300

Closes #12615
2026-04-11 18:06:02 +08:00
Daniel Rainer
8ae71c80f4 refactor: extract string escape and unescape funcs
Move the functions for escaping and unescaping strings from
`src/common.rs` into `fish_common`. It might make sense to move them
into a dedicated crate at some point, but for now just move them to the
preexisting crate to unblock other extraction.

Closes #12625
2026-04-11 17:49:50 +08:00
Daniel Rainer
cf6170200c refactor: move const to fish_widestring
Another step to eliminate dependency cycles between `src/expand.rs` and
`src/common.rs`.

Part of #12625
2026-04-11 17:49:49 +08:00
Daniel Rainer
c13038b968 refactor: move move char consts to widestring
This time, move char constants from `src/expand.rs` to
`fish_widestring`, which resolves a dependency cycle between
`src/expand.rs` and `src/common.rs`.

Part of #12625
2026-04-11 17:49:48 +08:00
Daniel Rainer
816077281d refactor: move encoding functions to widestring
The decoding functions for our widestrings are already in the
`fish_widestring` crate, so by symmetry, it makes sense to put the
encoding functions there as well. This also makes it easier to depend on
these functions, giving more options when it comes to further code
extraction.

Part of #12625
2026-04-11 17:49:47 +08:00
Daniel Rainer
78ea24a262 refactor: move char definitions into widestring
Use `fish_widestring` as the place where char definitions live. This has
the advantage that all our code can depend on `fish_widestring` without
introducing dependency cycles. Having a common place for character
definitions also makes it easier to see which chars have a special
meaning assigned to them.

This change also unblocks some follow-up refactoring by removing a
dependency cycle between `src/common.rs` and `src/wildcard.rs`.

Part of #12625
2026-04-11 17:49:46 +08:00
Daniel Rainer
6a5b9bcde1 refactor: move PUA-decoding function to widestring
These functions don't depend on `wcstringutil` functionality, so there
is no need for them to be there. The advantage of putting them into our
`widestring` crate is that quite a lot of code depends on it, and
extracting some of that code would result in crate dependency cycles if
the functions stayed in the `wcstringutil` crate. Our `widestring` crate
does not depend on any of our other crates, so there won't be any cyclic
dependency issues with code in it.

Part of #12625
2026-04-11 17:49:45 +08:00
Daniel Rainer
b7b786aabf cleanup: move escape_string_with_quote
It makes a lot more sense to have this function in the same module as
the other escaping functions. There was no usage of this function in
`parse_util` except for the test, so it makes little sense to keep the
function there. Moving it also eliminates a pointless cyclic dependency
between `common` and `parse_util`.

Part of #12625
2026-04-11 17:49:43 +08:00
Daniel Rainer
dc63b7bb20 cleanup: don't export write_loop twice
Exporting it as both `safe_write_loop` and `write_loop` is redundant and
causes inconsistencies. Remove the `pub use` and use `write_loop` for
the function name. It is shorter, and in Rust the default assumption is
that code is safe unless otherwise indicated, so there is no need to be
explicit about it.

Part of #12625
2026-04-11 17:49:42 +08:00
Daniel Rainer
9c819c020e refactor: don't reexport fish_common in common
Not reexporting means that imports have to change to directly import
from `fish_common`. This makes it easier to see which dependencies on
`src/common.rs` actually remain, which helps with identifying candidates
for extraction.

While at it, group some imports.

Part of #12625
2026-04-11 17:49:41 +08:00
Daniel Rainer
dc9b1141c8 refactor: extract fish_reserved_codepoint
Part of #12625
2026-04-11 17:49:40 +08:00
Daniel Rainer
3d364478ee refactor: remove dep on key mod from common
Removing this dependency allows extracting the `fish_reserved_codepoint`
function, and other code depending on it in subsequent commits.

Part of #12625
2026-04-11 17:49:39 +08:00
Daniel Rainer
faf331fdad refactor: use macro for special key char def
Reduce verbosity of const definitions. Define a dedicated const for the
base of the special key encoding range. This range is 256 bytes wide, so
by defining consts via an u8 offset from the base, we can guarantee that
the consts fall into the allocated range. Ideally, we would also check
for collisions, but Rust's const capabilities don't allow for that as
far as I'm aware.

Having `SPECIAL_KEY_ENCODE_BASE` in the `rust-widestring` crate allows
getting rid of the dependency on `key::Backspace` in the
`fish_reserved_codepoint` function, which unblocks code extraction.

Part of #12625
2026-04-11 17:49:37 +08:00
Daniel Rainer
47b6c0aec2 cleanup: rename and document UTF-8 decoding function
While the function is only used to decode single codepoints, nothing in
its implementation limits it to single codepoints, so the name
`decode_one_codepoint_utf8` is misleading. Change it to the simpler and
more accurate `decode_utf8`. Add a doc comment to describe the
function's behavior.

Part of #12625
2026-04-11 17:49:36 +08:00
Daniel Rainer
eb478bfc3e refactor: remove syntactic deps on main crate
Part of #12625
2026-04-11 17:49:35 +08:00
Johannes Altmanninger
63cf79f5f6 Reuse function for creating sighupint topic monitor 2026-04-11 17:27:25 +08:00
Johannes Altmanninger
ff284d642e tests/checks/tmux-abbr.fish: fix on BusyBox less (alpine CI) 2026-04-11 17:03:46 +08:00
Johannes Altmanninger
cc64da62a9 fish_color_valid_path: also apply bg and underline colors
Closes #12622
2026-04-11 17:03:46 +08:00
Johannes Altmanninger
a974fe990f fish_color_valid_path: respect explicit normal foreground 2026-04-11 17:03:46 +08:00
116 changed files with 3044 additions and 2584 deletions

View File

@@ -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
View File

@@ -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",
]

View File

@@ -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
}

View File

@@ -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

View File

@@ -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.

View File

@@ -7,6 +7,7 @@ repository.workspace = true
license.workspace = true
[dependencies]
libc.workspace = true
unicode-width.workspace = true
widestring.workspace = true

View File

@@ -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

View File

@@ -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.

View File

@@ -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'

View 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

View File

@@ -0,0 +1,4 @@
# localization: skip(private)
function __fish_is_cygwin
__fish_uname | string match -qr "^(MSYS|CYGWIN)"
end

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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();
}

View File

@@ -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! {

View File

@@ -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");

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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> {

View File

@@ -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;

View File

@@ -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();

View File

@@ -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)]

View File

@@ -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,

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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> {

View File

@@ -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;

View File

@@ -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)),*]);

View File

@@ -1,5 +1,5 @@
use super::*;
use crate::common::{UnescapeStringStyle, unescape_string};
use fish_common::{UnescapeStringStyle, unescape_string};
#[derive(Default)]
pub struct Unescape {

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
View File

@@ -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();

View File

@@ -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;

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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};

View File

@@ -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;

View File

@@ -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::{

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View 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.. {

View File

@@ -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

View File

@@ -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");

View File

@@ -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],

View File

@@ -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

View File

@@ -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!(

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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)]

View File

@@ -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() {

View File

@@ -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

View File

@@ -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.

View File

@@ -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() {

View File

@@ -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]

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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).

View File

@@ -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};

View File

@@ -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;

View File

@@ -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);

View File

@@ -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.

View File

@@ -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)]

View File

@@ -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.

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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------

View File

@@ -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)

View File

@@ -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: ^~~~~^

View File

@@ -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