mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-04-22 01:51:14 -03:00
Compare commits
35 Commits
47a3757f73
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca2b5dc40b | ||
|
|
0dfe06f4c9 | ||
|
|
4e47f47d85 | ||
|
|
f3e43e932f | ||
|
|
1dfc75bb9c | ||
|
|
fa33f6f0e0 | ||
|
|
31363120aa | ||
|
|
2304077e0d | ||
|
|
86c052b6ba | ||
|
|
68472da48a | ||
|
|
4b172fc735 | ||
|
|
944ab91fab | ||
|
|
34535fcb61 | ||
|
|
9e4eb37696 | ||
|
|
dda76d7f18 | ||
|
|
fdb1d95521 | ||
|
|
937f3bc6cb | ||
|
|
ebc32adc09 | ||
|
|
a4b6348315 | ||
|
|
b21a4a7197 | ||
|
|
0cd227533f | ||
|
|
5eb7687a64 | ||
|
|
8d6426295e | ||
|
|
85e76ba356 | ||
|
|
fee4288122 | ||
|
|
413246a93d | ||
|
|
3cb939c9a8 | ||
|
|
4790a444d8 | ||
|
|
da924927a0 | ||
|
|
29ff2fdd43 | ||
|
|
732c04420b | ||
|
|
947abd7464 | ||
|
|
12cfe59578 | ||
|
|
4b60d18b44 | ||
|
|
dd8e59db03 |
2
.github/actions/rust-toolchain/action.yml
vendored
2
.github/actions/rust-toolchain/action.yml
vendored
@@ -25,7 +25,7 @@ runs:
|
||||
set -x
|
||||
toolchain=$(
|
||||
case "$toolchain_channel" in
|
||||
(stable) echo 1.93 ;; # updatecli.d/rust.yml
|
||||
(stable) echo 1.95 ;; # updatecli.d/rust.yml
|
||||
(msrv) echo 1.85 ;; # updatecli.d/rust.yml
|
||||
(*)
|
||||
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"
|
||||
|
||||
2
.github/workflows/lint-dependencies.yml
vendored
2
.github/workflows/lint-dependencies.yml
vendored
@@ -21,4 +21,4 @@ jobs:
|
||||
with:
|
||||
command: check licenses
|
||||
arguments: --all-features --locked --exclude-dev
|
||||
rust-version: 1.93 # updatecli.d/rust.yml
|
||||
rust-version: 1.95 # updatecli.d/rust.yml
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -32,6 +32,8 @@ jobs:
|
||||
features: ""
|
||||
- rust_version: "stable"
|
||||
features: "--no-default-features"
|
||||
- rust_version: "stable"
|
||||
features: "--all-features"
|
||||
- rust_version: "msrv"
|
||||
features: ""
|
||||
steps:
|
||||
|
||||
@@ -19,6 +19,7 @@ Other improvements
|
||||
------------------
|
||||
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
|
||||
- :doc:`fish_update_completions <cmds/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).
|
||||
- Improve user experience when removing history entries via the :doc:`web-based config <cmds/fish_config>`.
|
||||
|
||||
For distributors and developers
|
||||
-------------------------------
|
||||
|
||||
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -296,7 +296,9 @@ dependencies = [
|
||||
"rand",
|
||||
"rsconf",
|
||||
"rust-embed",
|
||||
"rustc_version",
|
||||
"serial_test",
|
||||
"strum_macros",
|
||||
"unix_path",
|
||||
"xterm-color",
|
||||
]
|
||||
@@ -889,6 +891,15 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -919,6 +930,12 @@ version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -1015,6 +1032,18 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
|
||||
@@ -73,7 +73,9 @@ rust-embed = { version = "8.11.0", features = [
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
] }
|
||||
rustc_version = "0.4.1"
|
||||
serial_test = { version = "3", default-features = false }
|
||||
strum_macros = "0.28.0"
|
||||
widestring = "1.2.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
unicode-width = "0.2.0"
|
||||
@@ -128,6 +130,7 @@ num-traits.workspace = true
|
||||
once_cell.workspace = true
|
||||
pcre2.workspace = true
|
||||
rand.workspace = true
|
||||
strum_macros.workspace = true
|
||||
xterm-color.workspace = true
|
||||
|
||||
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
|
||||
@@ -156,6 +159,7 @@ fish-build-helper.workspace = true
|
||||
fish-gettext-mo-file-parser.workspace = true
|
||||
phf_codegen = { workspace = true, optional = true }
|
||||
rsconf.workspace = true
|
||||
rustc_version.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
unix_path.workspace = true
|
||||
@@ -194,7 +198,6 @@ tsan = []
|
||||
|
||||
[workspace.lints]
|
||||
rust.non_camel_case_types = "allow"
|
||||
rust.non_upper_case_globals = "allow"
|
||||
rust.unknown_lints = { level = "allow", priority = -1 }
|
||||
rust.unstable_name_collisions = "allow"
|
||||
rustdoc.private_intra_doc_links = "allow"
|
||||
|
||||
4
build.rs
4
build.rs
@@ -6,6 +6,10 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn main() {
|
||||
let is_nightly =
|
||||
rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly;
|
||||
rsconf::declare_cfg("nightly", is_nightly);
|
||||
|
||||
setup_paths();
|
||||
|
||||
// Add our default to enable tools that don't go through CMake, like "cargo test" and the
|
||||
|
||||
@@ -60,6 +60,7 @@ cargo() {
|
||||
fi
|
||||
}
|
||||
|
||||
# shellcheck disable=2329
|
||||
cleanup () {
|
||||
if [ -n "$gettext_template_dir" ] && [ -e "$gettext_template_dir" ]; then
|
||||
rm -r "$gettext_template_dir"
|
||||
@@ -89,6 +90,7 @@ fi
|
||||
|
||||
gettext_template_dir=$(mktemp -d)
|
||||
(
|
||||
# shellcheck disable=2030
|
||||
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
|
||||
cargo build --workspace --all-targets --features=gettext-extract
|
||||
)
|
||||
@@ -97,7 +99,7 @@ if $lint; then
|
||||
cargo deny --all-features --locked --exclude-dev check licenses
|
||||
fi
|
||||
PATH="$build_dir:$PATH" cargo xtask format --all --check
|
||||
for features in "" --no-default-features; do
|
||||
for features in "" --no-default-features --all-features; do
|
||||
cargo clippy --workspace --all-targets $features
|
||||
done
|
||||
fi
|
||||
@@ -112,7 +114,8 @@ fi
|
||||
# - https://github.com/msys2/MSYS2-packages/issues/5784
|
||||
(
|
||||
if $is_cygwin; then
|
||||
export PATH="$PATH:$(rustc --print target-libdir)"
|
||||
PATH="$PATH:$(rustc --print target-libdir)"
|
||||
export PATH
|
||||
fi
|
||||
cargo test --no-default-features --workspace --all-targets
|
||||
)
|
||||
@@ -124,20 +127,24 @@ fi
|
||||
|
||||
# Using "()" not "{}" because we do want a subshell (for the export)
|
||||
system_tests() (
|
||||
[ -n "$@" ] && export "$@"
|
||||
# shellcheck disable=2163
|
||||
[ -n "$*" ] && export "$@"
|
||||
# shellcheck disable=2031
|
||||
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
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}\n"
|
||||
system_tests "$cygwin_var"=winsymlinks
|
||||
|
||||
echo -e "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}"
|
||||
system_tests $cygwin_var=winsymlinks
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}\n"
|
||||
system_tests "$cygwin_var"=
|
||||
else
|
||||
echo -e "=== Running ${green}integration tests${reset}"
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests${reset}\n"
|
||||
system_tests
|
||||
fi
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@ codename=$(
|
||||
curl -fsS https://sources.debian.org/api/src/"${package}"/ |
|
||||
jq -r --arg codename "${codename}" '
|
||||
.versions[] | select(.suites[] == $codename) | .version' |
|
||||
sed 's/^\([0-9]\+\.[0-9]\+\).*/\1/' |
|
||||
sed -E 's/^([0-9]+\.[0-9]+).*/\1/' |
|
||||
sort --version-sort |
|
||||
tail -1
|
||||
|
||||
@@ -398,7 +398,7 @@ fn escape_string_url(input: &wstr) -> WString {
|
||||
for byte in narrow {
|
||||
if (byte & 0x80) == 0 {
|
||||
let c = char::from_u32(u32::from(byte)).unwrap();
|
||||
if c.is_alphanumeric() || [b'/', b'.', b'~', b'-', b'_'].contains(&byte) {
|
||||
if c.is_alphanumeric() || b"/.~-_".contains(&byte) {
|
||||
// The above characters don't need to be encoded.
|
||||
out.push(c);
|
||||
continue;
|
||||
@@ -534,8 +534,8 @@ enum Mode {
|
||||
let mut to_append_or_none = Some(c);
|
||||
if mode == Mode::Unquoted {
|
||||
match c {
|
||||
'\\' => {
|
||||
if !ignore_backslashes {
|
||||
'\\'
|
||||
if !ignore_backslashes => {
|
||||
// Backslashes (escapes) are complicated and may result in errors, or
|
||||
// appending INTERNAL_SEPARATORs, so we have to handle them specially.
|
||||
if let Some(escape_chars) = read_unquoted_escape(
|
||||
@@ -555,28 +555,25 @@ enum Mode {
|
||||
// We've already appended, don't append anything else.
|
||||
to_append_or_none = None;
|
||||
}
|
||||
}
|
||||
'~' => {
|
||||
'~'
|
||||
if unescape_special
|
||||
&& (input_position == 0 || Some(input_position) == potential_word_start)
|
||||
{
|
||||
=> {
|
||||
to_append_or_none = Some(HOME_DIRECTORY);
|
||||
}
|
||||
}
|
||||
'%' => {
|
||||
'%'
|
||||
// Note that this only recognizes %self if the string is literally %self.
|
||||
// %self/foo will NOT match this.
|
||||
if allow_percent_self
|
||||
&& unescape_special
|
||||
&& input_position == 0
|
||||
&& input == PROCESS_EXPAND_SELF_STR
|
||||
{
|
||||
=> {
|
||||
to_append_or_none = Some(PROCESS_EXPAND_SELF);
|
||||
input_position += PROCESS_EXPAND_SELF_STR.len() - 1; // skip over 'self's
|
||||
}
|
||||
}
|
||||
'*' => {
|
||||
if unescape_special {
|
||||
'*'
|
||||
if unescape_special => {
|
||||
// In general, this is ANY_STRING. But as a hack, if the last appended char
|
||||
// is ANY_STRING, delete the last char and store ANY_STRING_RECURSIVE to
|
||||
// reflect the fact that ** is the recursive wildcard.
|
||||
@@ -588,14 +585,12 @@ enum Mode {
|
||||
to_append_or_none = Some(ANY_STRING);
|
||||
}
|
||||
}
|
||||
}
|
||||
'?' => {
|
||||
if unescape_special && !feature_test(FeatureFlag::QuestionMarkNoGlob) {
|
||||
'?'
|
||||
if unescape_special && !feature_test(FeatureFlag::QuestionMarkNoGlob) => {
|
||||
to_append_or_none = Some(ANY_CHAR);
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
if unescape_special {
|
||||
'$'
|
||||
if unescape_special => {
|
||||
let is_cmdsub = input_position + 1 < input.len()
|
||||
&& input.char_at(input_position + 1) == '(';
|
||||
if !is_cmdsub {
|
||||
@@ -603,18 +598,16 @@ enum Mode {
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
}
|
||||
}
|
||||
'{' => {
|
||||
if unescape_special {
|
||||
'{'
|
||||
if unescape_special => {
|
||||
brace_count += 1;
|
||||
to_append_or_none = Some(BRACE_BEGIN);
|
||||
// We need to store where the brace *ends up* in the output.
|
||||
braces.push(result.len());
|
||||
potential_word_start = Some(input_position + 1);
|
||||
}
|
||||
}
|
||||
'}' => {
|
||||
if unescape_special {
|
||||
'}'
|
||||
if unescape_special => {
|
||||
// HACK: The completion machinery sometimes hands us partial tokens.
|
||||
// We can't parse them properly, but it shouldn't hurt,
|
||||
// so we don't assert here.
|
||||
@@ -646,19 +639,16 @@ enum Mode {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
',' => {
|
||||
if unescape_special && brace_count > 0 {
|
||||
','
|
||||
if unescape_special && brace_count > 0 => {
|
||||
to_append_or_none = Some(BRACE_SEP);
|
||||
vars_or_seps.push(input_position);
|
||||
potential_word_start = Some(input_position + 1);
|
||||
}
|
||||
}
|
||||
' ' => {
|
||||
if unescape_special && brace_count > 0 {
|
||||
' '
|
||||
if unescape_special && brace_count > 0 => {
|
||||
to_append_or_none = Some(BRACE_SPACE);
|
||||
}
|
||||
}
|
||||
'\'' => {
|
||||
mode = Mode::SingleQuotes;
|
||||
to_append_or_none = if unescape_special {
|
||||
@@ -743,11 +733,9 @@ enum Mode {
|
||||
}
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
if unescape_special {
|
||||
to_append_or_none = Some(VARIABLE_EXPAND_SINGLE);
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
'$' if unescape_special => {
|
||||
to_append_or_none = Some(VARIABLE_EXPAND_SINGLE);
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ Synopsis
|
||||
Description
|
||||
-----------
|
||||
|
||||
``set_color`` is used to control the color and styling of text in the terminal.
|
||||
*VALUE* describes that styling.
|
||||
``set_color`` controls the color and styling of text in the terminal.
|
||||
It writes non-printing color and text style escape sequences to standard output.
|
||||
|
||||
*VALUE* describes the styling.
|
||||
*VALUE* can be a reserved color name like **red** or an RGB color value given as 3 or 6 hexadecimal digits ("F27" or "FF2277").
|
||||
A special keyword **normal** resets text formatting to terminal defaults, however it is not recommended and the **--reset** option is preferred as it is less confusing and more future-proof.
|
||||
|
||||
@@ -93,7 +95,9 @@ Notes
|
||||
3. Because of the risk of confusion, ``set_color --reset`` is recommended over ``set_color normal``.
|
||||
4. Setting the background color only affects subsequently written characters. Fish provides no way to set the background color for the entire terminal window. Configuring the window background color (and other attributes such as its opacity) has to be done using whatever mechanisms the terminal provides. Look for a config option.
|
||||
5. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
|
||||
6. ``set_color`` works by printing sequences of characters to standard output. If used in command substitution or a pipe, these characters will also be captured. This may or may not be desirable. Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
|
||||
6. If you use ``set_color`` in a command substitution or a pipe, these characters will also be captured.
|
||||
This may or may not be desirable.
|
||||
Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
@@ -56,6 +56,7 @@ tt {
|
||||
.tab:hover,
|
||||
#tab_contents .master_element:hover,
|
||||
.color_scheme_choice_container:hover,
|
||||
.data_table > tr:hover,
|
||||
.prompt_choices_list > .ng-scope:hover {
|
||||
background-color: #DDE;
|
||||
}
|
||||
@@ -284,6 +285,7 @@ tt {
|
||||
width: 100%;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.data_table_row {}
|
||||
@@ -310,7 +312,8 @@ tt {
|
||||
}
|
||||
|
||||
.history_delete {
|
||||
width: 20px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.data_table_cell,
|
||||
|
||||
@@ -2889,7 +2889,7 @@ fn test_is_same_node() {
|
||||
}
|
||||
|
||||
// Run with cargo +nightly bench --features=benchmark
|
||||
#[cfg(feature = "benchmark")]
|
||||
#[cfg(all(nightly, feature = "benchmark"))]
|
||||
#[cfg(test)]
|
||||
mod bench {
|
||||
extern crate test;
|
||||
|
||||
@@ -1206,39 +1206,6 @@ fn read_file(mut f: impl Read) -> Result<WString, ()> {
|
||||
Ok(bytes2wcstring(&buf))
|
||||
}
|
||||
|
||||
fn highlight_role_to_string(role: HighlightRole) -> &'static wstr {
|
||||
match role {
|
||||
HighlightRole::normal => L!("normal"),
|
||||
HighlightRole::error => L!("error"),
|
||||
HighlightRole::command => L!("command"),
|
||||
HighlightRole::keyword => L!("keyword"),
|
||||
HighlightRole::statement_terminator => L!("statement_terminator"),
|
||||
HighlightRole::param => L!("param"),
|
||||
HighlightRole::option => L!("option"),
|
||||
HighlightRole::comment => L!("comment"),
|
||||
HighlightRole::search_match => L!("search_match"),
|
||||
HighlightRole::operat => L!("operat"),
|
||||
HighlightRole::escape => L!("escape"),
|
||||
HighlightRole::quote => L!("quote"),
|
||||
HighlightRole::redirection => L!("redirection"),
|
||||
HighlightRole::autosuggestion => L!("autosuggestion"),
|
||||
HighlightRole::selection => L!("selection"),
|
||||
HighlightRole::pager_progress => L!("pager_progress"),
|
||||
HighlightRole::pager_background => L!("pager_background"),
|
||||
HighlightRole::pager_prefix => L!("pager_prefix"),
|
||||
HighlightRole::pager_completion => L!("pager_completion"),
|
||||
HighlightRole::pager_description => L!("pager_description"),
|
||||
HighlightRole::pager_secondary_background => L!("pager_secondary_background"),
|
||||
HighlightRole::pager_secondary_prefix => L!("pager_secondary_prefix"),
|
||||
HighlightRole::pager_secondary_completion => L!("pager_secondary_completion"),
|
||||
HighlightRole::pager_secondary_description => L!("pager_secondary_description"),
|
||||
HighlightRole::pager_selected_background => L!("pager_selected_background"),
|
||||
HighlightRole::pager_selected_prefix => L!("pager_selected_prefix"),
|
||||
HighlightRole::pager_selected_completion => L!("pager_selected_completion"),
|
||||
HighlightRole::pager_selected_description => L!("pager_selected_description"),
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point for Pygments CSV output.
|
||||
// Our output is a newline-separated string.
|
||||
// Each line is of the form `start,end,role`
|
||||
@@ -1281,14 +1248,7 @@ struct TokenRange {
|
||||
// Now render these to a string.
|
||||
let mut result = String::with_capacity(token_ranges.len() * 32);
|
||||
for range in token_ranges {
|
||||
writeln!(
|
||||
result,
|
||||
"{},{},{}",
|
||||
range.start,
|
||||
range.end,
|
||||
highlight_role_to_string(range.role)
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(result, "{},{},{}", range.start, range.end, range.role).unwrap();
|
||||
}
|
||||
result.into_bytes()
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
print_help::print_help,
|
||||
proc::set_interactive_session,
|
||||
reader::{
|
||||
check_exit_loop_maybe_warning, reader_init, safe_reader_set_exit_signal, set_shell_modes,
|
||||
terminal_init,
|
||||
check_exit_loop_maybe_warning, reader_init, set_shell_modes,
|
||||
signal_safe_reader_set_exit_signal, terminal_init,
|
||||
},
|
||||
threads,
|
||||
topic_monitor::topic_monitor_init,
|
||||
@@ -98,7 +98,7 @@ fn process_input(
|
||||
use QueryResultEvent::*;
|
||||
let kevt = match input_queue.readch() {
|
||||
CharEvent::Implicit(ImplicitEvent::Eof) => {
|
||||
safe_reader_set_exit_signal(libc::SIGHUP);
|
||||
signal_safe_reader_set_exit_signal(libc::SIGHUP);
|
||||
continue;
|
||||
}
|
||||
CharEvent::Key(kevt) => kevt,
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
use crate::{builtins::error::Error, env::Environment as _, err_fmt, wutil::wrealpath};
|
||||
|
||||
// The pwd builtin. Respect -P to resolve symbolic links. Respect -L to not do that (the default).
|
||||
const short_options: &wstr = L!("LPh");
|
||||
const long_options: &[WOption] = &[
|
||||
const SHORT_OPTIONS: &wstr = L!("LPh");
|
||||
const LONG_OPTIONS: &[WOption] = &[
|
||||
wopt(L!("help"), NoArgument, 'h'),
|
||||
wopt(L!("logical"), NoArgument, 'L'),
|
||||
wopt(L!("physical"), NoArgument, 'P'),
|
||||
@@ -16,7 +16,7 @@ pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Buil
|
||||
let cmd = argv[0];
|
||||
let argc = argv.len();
|
||||
let mut resolve_symlinks = false;
|
||||
let mut w = WGetopter::new(short_options, long_options, argv);
|
||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, argv);
|
||||
while let Some(opt) = w.next_opt() {
|
||||
match opt {
|
||||
'L' => resolve_symlinks = false,
|
||||
|
||||
@@ -16,8 +16,8 @@ struct Options {
|
||||
no_symlinks: bool,
|
||||
}
|
||||
|
||||
const short_options: &wstr = L!("+hs");
|
||||
const long_options: &[WOption] = &[
|
||||
const SHORT_OPTIONS: &wstr = L!("+hs");
|
||||
const LONG_OPTIONS: &[WOption] = &[
|
||||
wopt(L!("no-symlinks"), NoArgument, 's'),
|
||||
wopt(L!("help"), NoArgument, 'h'),
|
||||
];
|
||||
@@ -31,7 +31,7 @@ fn parse_options(
|
||||
|
||||
let mut opts = Options::default();
|
||||
|
||||
let mut w = WGetopter::new(short_options, long_options, args);
|
||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, args);
|
||||
|
||||
while let Some(c) = w.next_opt() {
|
||||
match c {
|
||||
|
||||
@@ -160,12 +160,8 @@ fn interpret_escape(arg: &'args wstr) -> Option<WString> {
|
||||
let mut cursor = arg;
|
||||
while !cursor.is_empty() {
|
||||
if cursor.char_at(0) == '\\' {
|
||||
if let Some(escape_len) = read_unquoted_escape(cursor, &mut result, true, false) {
|
||||
cursor = cursor.slice_from(escape_len);
|
||||
} else {
|
||||
// invalid escape
|
||||
return None;
|
||||
}
|
||||
let escape_len = read_unquoted_escape(cursor, &mut result, true, false)?;
|
||||
cursor = cursor.slice_from(escape_len);
|
||||
} else {
|
||||
result.push(cursor.char_at(0));
|
||||
cursor = cursor.slice_from(1);
|
||||
|
||||
@@ -286,7 +286,7 @@ pub fn is_windows_subsystem_for_linux(v: WSL) -> bool {
|
||||
Some(WSL::V1)
|
||||
});
|
||||
|
||||
wsl.map(|wsl| v == WSL::Any || wsl == v).unwrap_or(false)
|
||||
wsl.is_some_and(|wsl| v == WSL::Any || wsl == v)
|
||||
}
|
||||
|
||||
/// Test if the given char is valid in a variable name.
|
||||
@@ -707,11 +707,11 @@ macro_rules! check_decode {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "benchmark")]
|
||||
#[cfg(all(nightly, feature = "benchmark"))]
|
||||
#[cfg(test)]
|
||||
mod bench {
|
||||
extern crate test;
|
||||
use crate::common::bytes2wcstring;
|
||||
use fish_widestring::bytes2wcstring;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
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,
|
||||
string_suffixes_string_case_insensitive, strip_executable_suffix,
|
||||
};
|
||||
use fish_widestring::{WExt as _, charptr2wcstring};
|
||||
use std::{
|
||||
@@ -159,7 +158,10 @@ pub fn new(
|
||||
r#match: StringFuzzyMatch, /* = exact_match */
|
||||
flags: CompleteFlags,
|
||||
) -> Self {
|
||||
let flags = resolve_auto_space(&completion, flags);
|
||||
let mut flags = resolve_auto_space(&completion, flags);
|
||||
if r#match.requires_full_replacement() {
|
||||
flags |= CompleteFlags::REPLACES_TOKEN;
|
||||
}
|
||||
Self {
|
||||
completion,
|
||||
description,
|
||||
@@ -1483,14 +1485,12 @@ fn complete_param_for_command(
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut offset = 0;
|
||||
let mut flags = CompleteFlags::empty();
|
||||
|
||||
if r#match.requires_full_replacement() {
|
||||
flags = CompleteFlags::REPLACES_TOKEN;
|
||||
let offset = if r#match.requires_full_replacement() {
|
||||
0
|
||||
} else {
|
||||
offset = s.len();
|
||||
}
|
||||
s.len()
|
||||
};
|
||||
let completion = whole_opt.slice_from(offset);
|
||||
|
||||
// does this switch have any known arguments
|
||||
let has_arg = !o.comp.is_empty();
|
||||
@@ -1502,14 +1502,14 @@ fn complete_param_for_command(
|
||||
// a completion. By default we avoid using '=' and instead rely on '--switch
|
||||
// switch-arg', since it is more commonly supported by homebrew getopt-like
|
||||
// functions.
|
||||
let completion = sprintf!("%s=", whole_opt.slice_from(offset));
|
||||
let completion = sprintf!("%s=", completion);
|
||||
|
||||
// Append a long-style option with a mandatory trailing equal sign
|
||||
if !self.completions.add(Completion::new(
|
||||
completion,
|
||||
o.desc.localize().to_owned(),
|
||||
StringFuzzyMatch::exact_match(),
|
||||
flags | CompleteFlags::NO_SPACE,
|
||||
r#match,
|
||||
CompleteFlags::NO_SPACE,
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1517,10 +1517,10 @@ fn complete_param_for_command(
|
||||
|
||||
// Append a long-style option
|
||||
if !self.completions.add(Completion::new(
|
||||
whole_opt.slice_from(offset).to_owned(),
|
||||
completion.to_owned(),
|
||||
o.desc.localize().to_owned(),
|
||||
StringFuzzyMatch::exact_match(),
|
||||
flags,
|
||||
r#match,
|
||||
CompleteFlags::empty(),
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1666,7 +1666,7 @@ fn complete_variable(&mut self, s: &wstr, start_offset: usize) -> bool {
|
||||
// Take only the suffix.
|
||||
env_name.slice_from(varlen).to_owned()
|
||||
} else {
|
||||
flags |= CompleteFlags::REPLACES_TOKEN | CompleteFlags::DONT_ESCAPE;
|
||||
flags |= CompleteFlags::DONT_ESCAPE;
|
||||
whole_var.slice_to(start_offset).to_owned() + env_name.as_utfstr()
|
||||
};
|
||||
|
||||
@@ -1735,10 +1735,8 @@ enum Mode {
|
||||
|
||||
match c {
|
||||
'\\' => skip_next = true,
|
||||
'$' => {
|
||||
if mode == Unquoted || mode == DoubleQuoted {
|
||||
variable_start = Some(in_pos);
|
||||
}
|
||||
'$' if (mode == Unquoted || mode == DoubleQuoted) => {
|
||||
variable_start = Some(in_pos);
|
||||
}
|
||||
'\'' => {
|
||||
if mode == SingleQuoted {
|
||||
@@ -1754,9 +1752,7 @@ enum Mode {
|
||||
mode = DoubleQuoted;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// all other chars ignored here
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1823,30 +1819,23 @@ fn getpwent_name() -> Option<WString> {
|
||||
break;
|
||||
}
|
||||
|
||||
if string_prefixes_string(user_name, &pw_name) {
|
||||
if let Some(r#match) = StringFuzzyMatch::try_create(user_name, &pw_name, true) {
|
||||
let desc = wgettext_fmt!(COMPLETE_USER_DESC, &pw_name);
|
||||
// Append a user name.
|
||||
// TODO: propagate overflow?
|
||||
let mut flags = CompleteFlags::NO_SPACE;
|
||||
if r#match.requires_full_replacement() {
|
||||
flags |= CompleteFlags::DONT_ESCAPE;
|
||||
}
|
||||
let _ = self.completions.add(Completion::new(
|
||||
pw_name.slice_from(name_len).to_owned(),
|
||||
if r#match.requires_full_replacement() {
|
||||
sprintf!("~%s", &pw_name)
|
||||
} else {
|
||||
pw_name.slice_from(name_len).to_owned()
|
||||
},
|
||||
desc,
|
||||
StringFuzzyMatch::exact_match(),
|
||||
CompleteFlags::NO_SPACE,
|
||||
));
|
||||
result = true;
|
||||
} else if string_prefixes_string_case_insensitive(user_name, &pw_name) {
|
||||
let name = sprintf!("~%s", &pw_name);
|
||||
let desc = wgettext_fmt!(COMPLETE_USER_DESC, &pw_name);
|
||||
|
||||
// Append a user name
|
||||
// TODO: propagate overflow?
|
||||
let _ = self.completions.add(Completion::new(
|
||||
name,
|
||||
desc,
|
||||
StringFuzzyMatch::exact_match(),
|
||||
CompleteFlags::REPLACES_TOKEN
|
||||
| CompleteFlags::DONT_ESCAPE
|
||||
| CompleteFlags::NO_SPACE,
|
||||
r#match,
|
||||
flags,
|
||||
));
|
||||
result = true;
|
||||
}
|
||||
|
||||
12
src/exec.rs
12
src/exec.rs
@@ -20,7 +20,7 @@
|
||||
PATH_BSHELL, blocked_signals_for_job,
|
||||
postfork::{
|
||||
child_setup_process, execute_fork, execute_setpgid, report_setpgid_error,
|
||||
safe_report_exec_error,
|
||||
signal_safe_report_exec_error,
|
||||
},
|
||||
};
|
||||
use crate::function::{self, FunctionProperties};
|
||||
@@ -393,7 +393,7 @@ pub fn is_thompson_shell_script(path: &CStr) -> bool {
|
||||
/// This function is executed by the child process created by a call to fork(). It should be called
|
||||
/// after \c child_setup_process. It calls execve to replace the fish process image with the command
|
||||
/// specified in \c p. It never returns. Called in a forked child! Do not allocate memory, etc.
|
||||
fn safe_launch_process(
|
||||
fn signal_safe_launch_process(
|
||||
_p: &Process,
|
||||
actual_cmd: &CStr,
|
||||
argv: &OwningNullTerminatedArray,
|
||||
@@ -431,7 +431,7 @@ fn safe_launch_process(
|
||||
}
|
||||
|
||||
set_errno(err);
|
||||
safe_report_exec_error(errno().0, actual_cmd, argv, envv);
|
||||
signal_safe_report_exec_error(errno().0, actual_cmd, argv, envv);
|
||||
exit_without_destructors(exit_code_from_exec_error(err.0));
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
|
||||
// Ensure the terminal modes are what they were before we changed them.
|
||||
restore_term_mode();
|
||||
// Bounce to launch_process. This never returns.
|
||||
safe_launch_process(p, &actual_cmd, &argv, &envp);
|
||||
signal_safe_launch_process(p, &actual_cmd, &argv, &envp);
|
||||
}
|
||||
|
||||
// Returns whether we can use posix spawn for a given process in a given job.
|
||||
@@ -908,7 +908,7 @@ fn exec_external_command(
|
||||
let pid = match pid {
|
||||
Ok(pid) => pid,
|
||||
Err(err) => {
|
||||
safe_report_exec_error(err.0, &actual_cmd, &argv, &envv);
|
||||
signal_safe_report_exec_error(err.0, &actual_cmd, &argv, &envv);
|
||||
p.status
|
||||
.set(ProcStatus::from_exit_code(exit_code_from_exec_error(err.0)));
|
||||
return Err(());
|
||||
@@ -940,7 +940,7 @@ fn exec_external_command(
|
||||
}
|
||||
|
||||
fork_child_for_process(j, p, &dup2s, pgroup_policy, |p| {
|
||||
safe_launch_process(p, &actual_cmd, &argv, &envv)
|
||||
signal_safe_launch_process(p, &actual_cmd, &argv, &envv)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -800,10 +800,8 @@ fn expand_braces(
|
||||
brace_end = Some(pos);
|
||||
}
|
||||
}
|
||||
BRACE_SEP => {
|
||||
if brace_count == 1 {
|
||||
last_sep = Some(pos);
|
||||
}
|
||||
BRACE_SEP if brace_count == 1 => {
|
||||
last_sep = Some(pos);
|
||||
}
|
||||
_ => {
|
||||
// we ignore all other characters here
|
||||
|
||||
@@ -225,7 +225,7 @@ pub fn execute_fork() -> pid_t {
|
||||
exit_without_destructors(1)
|
||||
}
|
||||
|
||||
pub(crate) fn safe_report_exec_error(
|
||||
pub(crate) fn signal_safe_report_exec_error(
|
||||
err: i32,
|
||||
actual_cmd: &CStr,
|
||||
argvv: &OwningNullTerminatedArray,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use crate::exec::{PgroupPolicy, is_thompson_shell_script};
|
||||
use crate::proc::Job;
|
||||
use crate::redirection::Dup2List;
|
||||
use crate::signal::signals_to_default;
|
||||
use crate::signal::SIGNALS_TO_DEFAULT;
|
||||
use errno::Errno;
|
||||
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
|
||||
use std::ffi::{CStr, CString};
|
||||
@@ -129,7 +129,7 @@ pub fn new(
|
||||
}
|
||||
|
||||
// Everybody gets default handlers.
|
||||
attr.set_sigdefault(&signals_to_default)?;
|
||||
attr.set_sigdefault(&SIGNALS_TO_DEFAULT)?;
|
||||
|
||||
// Reset the sigmask.
|
||||
let mut sigmask = MaybeUninit::uninit();
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
WString, wstr,
|
||||
};
|
||||
use std::collections::{HashMap, hash_map::Entry};
|
||||
use strum_macros::Display;
|
||||
|
||||
use super::file_tester::IsFile;
|
||||
|
||||
@@ -586,10 +587,8 @@ enum Mode {
|
||||
} else {
|
||||
// Not a backslash.
|
||||
match c {
|
||||
'~' => {
|
||||
if in_pos == 0 {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'~' if in_pos == 0 => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'$' => {
|
||||
assert!(in_pos < buff_len);
|
||||
@@ -597,10 +596,8 @@ enum Mode {
|
||||
// Subtract one to account for the upcoming loop increment.
|
||||
in_pos -= 1;
|
||||
}
|
||||
'?' => {
|
||||
if !feature_test(FeatureFlag::QuestionMarkNoGlob) {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'?' if !feature_test(FeatureFlag::QuestionMarkNoGlob) => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'*' | '(' | ')' => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
@@ -613,10 +610,8 @@ enum Mode {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
bracket_count -= 1;
|
||||
}
|
||||
',' => {
|
||||
if bracket_count > 0 {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
',' if bracket_count > 0 => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::operat);
|
||||
}
|
||||
'\'' => {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::quote);
|
||||
@@ -660,9 +655,9 @@ enum Mode {
|
||||
'"' => {
|
||||
mode = Mode::unquoted;
|
||||
}
|
||||
'\\' => {
|
||||
'\\'
|
||||
// Backslash
|
||||
if in_pos + 1 < buff_len {
|
||||
if in_pos + 1 < buff_len => {
|
||||
let escaped_char = buffstr.as_char_slice()[in_pos + 1];
|
||||
if matches!(escaped_char, '\\' | '"' | '\n' | '$') {
|
||||
colors[in_pos] = HighlightSpec::with_fg(HighlightRole::escape); // backslash
|
||||
@@ -670,7 +665,6 @@ enum Mode {
|
||||
in_pos += 1; // skip over backslash
|
||||
}
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
in_pos += color_variable(&buffstr[in_pos..], &mut colors[in_pos..]);
|
||||
// Subtract one to account for the upcoming increment in the loop.
|
||||
@@ -1264,7 +1258,7 @@ fn get_fallback(role: HighlightRole) -> HighlightRole {
|
||||
}
|
||||
|
||||
/// Describes the role of a span of text.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Display)]
|
||||
#[repr(u8)]
|
||||
pub enum HighlightRole {
|
||||
#[default]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(feature = "benchmark", feature(test))]
|
||||
#![cfg_attr(all(nightly, feature = "benchmark"), feature(test))]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
pub const BUILD_VERSION: &str = env!("FISH_BUILD_VERSION");
|
||||
|
||||
@@ -968,17 +968,13 @@ fn visit(&mut self, node: &'a dyn Node) {
|
||||
// ....cmd3
|
||||
// end
|
||||
// See #7252.
|
||||
Kind::JobContinuation(node) => {
|
||||
if self.has_newline(&node.newlines) {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
Kind::JobContinuation(node) if self.has_newline(&node.newlines) => {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
|
||||
// Likewise for && and ||.
|
||||
Kind::JobConjunctionContinuation(node) => {
|
||||
if self.has_newline(&node.newlines) {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
Kind::JobConjunctionContinuation(node) if self.has_newline(&node.newlines) => {
|
||||
inc_dec = (1, 1);
|
||||
}
|
||||
|
||||
Kind::CaseItemList(_) => {
|
||||
@@ -1150,23 +1146,21 @@ pub fn detect_parse_errors_in_ast(
|
||||
let mut traversal = ast::Traversal::new(ast.top());
|
||||
while let Some(node) = traversal.next() {
|
||||
match node.kind() {
|
||||
Kind::JobContinuation(jc) => {
|
||||
Kind::JobContinuation(jc)
|
||||
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
||||
// See if our pipe has source but our statement does not.
|
||||
if jc.pipe.has_source() && jc.statement.try_source_range().is_none() {
|
||||
if jc.pipe.has_source() && jc.statement.try_source_range().is_none() => {
|
||||
has_unclosed_pipe = true;
|
||||
}
|
||||
}
|
||||
Kind::JobConjunction(job_conjunction) => {
|
||||
issue.error |= detect_errors_in_job_conjunction(job_conjunction, &mut out_errors);
|
||||
}
|
||||
Kind::JobConjunctionContinuation(jcc) => {
|
||||
Kind::JobConjunctionContinuation(jcc)
|
||||
// Somewhat clumsy way of checking for a job without source in a conjunction.
|
||||
// See if our conjunction operator (&& or ||) has source but our job does not.
|
||||
if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() {
|
||||
if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() => {
|
||||
has_unclosed_conjunction = true;
|
||||
}
|
||||
}
|
||||
Kind::Argument(arg) => {
|
||||
let arg_src = arg.source(buff_src);
|
||||
if let Err(e) = detect_errors_in_argument(arg, arg_src, &mut out_errors) {
|
||||
@@ -1174,7 +1168,7 @@ pub fn detect_parse_errors_in_ast(
|
||||
issue.incomplete |= e.incomplete;
|
||||
}
|
||||
}
|
||||
Kind::JobPipeline(job) => {
|
||||
Kind::JobPipeline(job)
|
||||
// Disallow background in the following cases:
|
||||
//
|
||||
// foo & ; and bar
|
||||
@@ -1182,11 +1176,10 @@ pub fn detect_parse_errors_in_ast(
|
||||
// if foo & ; end
|
||||
// while foo & ; end
|
||||
// If it's not a background job, nothing to do.
|
||||
if job.bg.is_some() {
|
||||
if job.bg.is_some() => {
|
||||
issue.error |=
|
||||
detect_errors_in_backgrounded_job(&traversal, job, &mut out_errors);
|
||||
}
|
||||
}
|
||||
Kind::DecoratedStatement(stmt) => {
|
||||
issue.error |= detect_errors_in_decorated_statement(
|
||||
buff_src,
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
QueryXtversion,
|
||||
},
|
||||
},
|
||||
termsize::{safe_termsize_invalidate_tty, termsize_last, termsize_update},
|
||||
termsize::{signal_safe_termsize_invalidate_tty, termsize_last, termsize_update},
|
||||
text_face::{TextFace, parse_text_face},
|
||||
threads::{assert_is_background_thread, assert_is_main_thread},
|
||||
tokenizer::{
|
||||
@@ -284,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) => safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
Implicit(Eof) => signal_safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
Implicit(CheckExit) => {}
|
||||
CharEvent::QueryResult(Response(QueryResponse::PrimaryDeviceAttribute)) => {
|
||||
break;
|
||||
@@ -1319,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 safe_reader_handle_sigint() {
|
||||
pub fn signal_safe_reader_handle_sigint() {
|
||||
INTERRUPTED.store(SIGINT, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -1341,7 +1341,7 @@ pub fn reader_test_and_clear_interrupted() -> i32 {
|
||||
}
|
||||
|
||||
/// Mark that we received an exit signal (SIGHUP or SIGTERM). Invoked from a signal handler.
|
||||
pub fn safe_reader_set_exit_signal(sig: i32) {
|
||||
pub fn signal_safe_reader_set_exit_signal(sig: i32) {
|
||||
// Beware, we may be in a signal handler.
|
||||
EXIT_SIGNAL.store(sig, Ordering::Relaxed);
|
||||
}
|
||||
@@ -2690,7 +2690,7 @@ fn run_input_command_scripts(&mut self, cmd: &wstr) {
|
||||
// ECHO mode, causing a race between new input and restoring the mode (#7770). So we leave the
|
||||
// tty alone, run the commands in shell mode, and then restore shell modes.
|
||||
set_shell_modes(STDIN_FILENO, "bind scripts");
|
||||
safe_termsize_invalidate_tty();
|
||||
signal_safe_termsize_invalidate_tty();
|
||||
}
|
||||
|
||||
/// Read normal characters, inserting them into the command line.
|
||||
@@ -2873,7 +2873,7 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
|
||||
CharEvent::Implicit(implicit_event) => {
|
||||
use ImplicitEvent::*;
|
||||
match implicit_event {
|
||||
Eof => safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
Eof => signal_safe_reader_set_exit_signal(libc::SIGHUP),
|
||||
CheckExit => (),
|
||||
FocusIn => {
|
||||
event::fire_generic(self.parser, L!("fish_focus_in").to_owned(), vec![]);
|
||||
@@ -4869,7 +4869,7 @@ fn term_steal(copy_modes: bool) {
|
||||
term_copy_modes();
|
||||
}
|
||||
set_shell_modes(STDIN_FILENO, "shell");
|
||||
safe_termsize_invalidate_tty();
|
||||
signal_safe_termsize_invalidate_tty();
|
||||
}
|
||||
|
||||
// Ensure that fish owns the terminal, possibly waiting. If we cannot acquire the terminal, then
|
||||
@@ -5006,7 +5006,7 @@ fn reader_interactive_init() {
|
||||
set_shell_modes(STDIN_FILENO, "startup");
|
||||
}
|
||||
|
||||
safe_termsize_invalidate_tty();
|
||||
signal_safe_termsize_invalidate_tty();
|
||||
}
|
||||
|
||||
/// Return whether fish is currently unwinding the stack in preparation to exit.
|
||||
@@ -7026,9 +7026,11 @@ fn handle_completions(&mut self, token_range: Range<usize>, mut comp: Vec<Comple
|
||||
|
||||
comp.retain(|c| !c.replaces_token() || reader_can_replace(&tok, c.flags));
|
||||
|
||||
for c in &mut comp {
|
||||
if !will_replace_token && c.replaces_token() {
|
||||
c.flags |= CompleteFlags::SUPPRESS_PAGER_PREFIX;
|
||||
if !will_replace_token {
|
||||
for c in &mut comp {
|
||||
if c.replaces_token() {
|
||||
c.flags |= CompleteFlags::SUPPRESS_PAGER_PREFIX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::event::{enqueue_signal, is_signal_observed};
|
||||
use crate::prelude::*;
|
||||
use crate::reader::{safe_reader_handle_sigint, safe_reader_set_exit_signal};
|
||||
use crate::termsize::safe_termsize_invalidate_tty;
|
||||
use crate::reader::{signal_safe_reader_handle_sigint, signal_safe_reader_set_exit_signal};
|
||||
use crate::termsize::signal_safe_termsize_invalidate_tty;
|
||||
use crate::topic_monitor::{Generation, GenerationsList, Topic, topic_monitor_principal};
|
||||
use crate::tty_handoff::safe_mark_tty_invalid;
|
||||
use crate::tty_handoff::signal_safe_mark_tty_invalid;
|
||||
use crate::wutil::fish_wcstoi;
|
||||
use errno::{errno, set_errno};
|
||||
use fish_common::exit_without_destructors;
|
||||
@@ -86,14 +86,14 @@ extern "C" fn fish_signal_handler(
|
||||
match sig {
|
||||
libc::SIGWINCH => {
|
||||
// Respond to a winch signal by telling the termsize container.
|
||||
safe_termsize_invalidate_tty();
|
||||
signal_safe_termsize_invalidate_tty();
|
||||
}
|
||||
libc::SIGHUP | libc::SIGTERM => {
|
||||
// Exit unless the signal was trapped.
|
||||
if !observed {
|
||||
safe_reader_set_exit_signal(sig);
|
||||
signal_safe_reader_set_exit_signal(sig);
|
||||
if sig == libc::SIGHUP {
|
||||
safe_mark_tty_invalid();
|
||||
signal_safe_mark_tty_invalid();
|
||||
}
|
||||
}
|
||||
topic_monitor_principal().post(Topic::SigHupIntTerm);
|
||||
@@ -103,7 +103,7 @@ extern "C" fn fish_signal_handler(
|
||||
if !observed {
|
||||
CANCELLATION_SIGNAL.store(libc::SIGINT, Ordering::Relaxed);
|
||||
}
|
||||
safe_reader_handle_sigint();
|
||||
signal_safe_reader_handle_sigint();
|
||||
topic_monitor_principal().post(Topic::SigHupIntTerm);
|
||||
}
|
||||
libc::SIGCHLD => {
|
||||
@@ -273,7 +273,7 @@ pub fn signal_handle(sig: Signal) {
|
||||
sigaction(sig, &act, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
pub static signals_to_default: LazyLock<libc::sigset_t> = LazyLock::new(|| {
|
||||
pub static SIGNALS_TO_DEFAULT: LazyLock<libc::sigset_t> = LazyLock::new(|| {
|
||||
let mut set = MaybeUninit::uninit();
|
||||
unsafe { libc::sigemptyset(set.as_mut_ptr()) };
|
||||
for data in SIGNAL_TABLE.iter() {
|
||||
|
||||
@@ -256,7 +256,7 @@ pub fn termsize_update(parser: &Parser) -> Termsize {
|
||||
}
|
||||
|
||||
/// May be called form a signal handler (WINCH).
|
||||
pub fn safe_termsize_invalidate_tty() {
|
||||
pub fn signal_safe_termsize_invalidate_tty() {
|
||||
TTY_TERMSIZE_GEN_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ fn stubby_termsize() -> Option<Termsize> {
|
||||
assert_eq!(ts.last(), Termsize::defaults());
|
||||
|
||||
// Ok let's tell it. But it still doesn't update right away.
|
||||
let handle_winch = safe_termsize_invalidate_tty;
|
||||
let handle_winch = signal_safe_termsize_invalidate_tty;
|
||||
handle_winch();
|
||||
assert_eq!(ts.last(), Termsize::defaults());
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ pub fn deactivate_tty_protocols() {
|
||||
|
||||
// Called from a signal handler to mark the tty as invalid (e.g. SIGHUP).
|
||||
// This suppresses any further attempts to write protocols to the tty,
|
||||
pub fn safe_mark_tty_invalid() {
|
||||
pub fn signal_safe_mark_tty_invalid() {
|
||||
TTY_INVALID.store(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -149,16 +149,11 @@ fn wildcard_complete_internal(
|
||||
|
||||
// Note: out_completion may be empty if the completion really is empty, e.g. tab-completing
|
||||
// 'foo' when a file 'foo' exists.
|
||||
let local_flags = if full_replacement {
|
||||
flags | CompleteFlags::REPLACES_TOKEN
|
||||
} else {
|
||||
flags
|
||||
};
|
||||
if !out.add(Completion::new(
|
||||
out_completion.to_owned(),
|
||||
out_desc,
|
||||
m,
|
||||
local_flags,
|
||||
flags,
|
||||
)) {
|
||||
return WildcardResult::Overflow;
|
||||
}
|
||||
|
||||
@@ -346,7 +346,9 @@ complete -C'cd .'
|
||||
# Note that there is no kern.osproductversion under older OS X releases!
|
||||
#
|
||||
# NetBSD 10 does not support it.
|
||||
if test (uname) = NetBSD || { test (uname) = Darwin && test (sysctl kern.osproductversion 2>/dev/null | string match -r \\d+; or echo 10) -lt 12 }
|
||||
# Cygwin/MSYS does not support it when using ACL. And without ACL, a directory
|
||||
# cannot be made unreadable, making the test pointless. So either way, skip it
|
||||
if test (uname) = NetBSD || __fish_is_cygwin || { test (uname) = Darwin && test (sysctl kern.osproductversion 2>/dev/null | string match -r \\d+; or echo 10) -lt 12 }
|
||||
# Not supported. Satisfy the CHECKs below.
|
||||
echo fake/a
|
||||
echo fake/a/b
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
mkdir dir
|
||||
echo "#!/bin/sh" >dir/foo.exe
|
||||
echo "#!/bin/sh" >dir/foo.bar
|
||||
set PATH (pwd)/dir $PATH
|
||||
if ! cygwin_noacl ./
|
||||
chmod u+x dir/foo*
|
||||
end
|
||||
set -p PATH (pwd)/dir
|
||||
|
||||
# === Check that `complete` prefers to non-exe name, unless the user started
|
||||
# to type the extension
|
||||
@@ -36,6 +39,9 @@ complete -C"./dir/foo."
|
||||
|
||||
# === Check that if we have a non-exe and an exe file, they both show
|
||||
echo "#!/bin/sh" >dir/foo.bar.exe
|
||||
if ! cygwin_noacl ./
|
||||
chmod u+x dir/foo.bar.exe
|
||||
end
|
||||
complete -C"./dir/foo.ba"
|
||||
# CHECK: ./dir/foo.bar{{\t}}command
|
||||
# CHECK: ./dir/foo.bar.exe{{\t}}command
|
||||
|
||||
@@ -727,3 +727,9 @@ complete -c foo -a "foo\\"
|
||||
|
||||
complete -C
|
||||
# CHECKERR: complete: Can not get commandline in non-interactive mode
|
||||
|
||||
if string match -rq -- '^[a-z]+$' $USER
|
||||
set -l first_letter_wrong_case (string sub -l 1 -- $USER | string upper)
|
||||
string match -rq -- "$USER\t.*" (complete -C "echo ~$first_letter_wrong_case")
|
||||
or echo "`complete -C'echo ~$first_letter_wrong_case'` did not yield $USER"
|
||||
end
|
||||
|
||||
@@ -14,6 +14,9 @@ status job-control full
|
||||
sleep 1 &
|
||||
set -l pid (jobs -lp)
|
||||
kill -SIGSTOP $pid
|
||||
while jobs -l | grep -q running
|
||||
sleep .01
|
||||
end
|
||||
disown
|
||||
# CHECKERR: disown: job 1 ('sleep 1 &') was stopped and has been signalled to continue.
|
||||
echo $status
|
||||
|
||||
@@ -231,7 +231,14 @@ path filter -w stuff/*
|
||||
echo "=== test --perm exec"
|
||||
# CHECK: === test --perm exec
|
||||
|
||||
path filter --perm exec stuff/*
|
||||
begin
|
||||
path filter --perm exec stuff/*
|
||||
# Cygwin with ACL doesn't return non-readable files, so add them manually to pass the test
|
||||
if __fish_is_cygwin && ! set -q noacl
|
||||
echo stuff/exec
|
||||
echo stuff/writeexec
|
||||
end
|
||||
end | sort
|
||||
# CHECK: stuff/all
|
||||
# CHECK: stuff/exec
|
||||
# CHECK: stuff/readexec
|
||||
@@ -240,7 +247,13 @@ path filter --perm exec stuff/*
|
||||
echo "=== test -x"
|
||||
# CHECK: === test -x
|
||||
|
||||
path filter -x stuff/*
|
||||
begin
|
||||
path filter -x exec stuff/*
|
||||
if __fish_is_cygwin && ! set -q noacl
|
||||
echo stuff/exec
|
||||
echo stuff/writeexec
|
||||
end
|
||||
end | sort
|
||||
# CHECK: stuff/all
|
||||
# CHECK: stuff/exec
|
||||
# CHECK: stuff/readexec
|
||||
@@ -271,7 +284,12 @@ end
|
||||
echo "=== test --perm write,exec"
|
||||
# CHECK: === test --perm write,exec
|
||||
|
||||
path filter --perm write,exec stuff/*
|
||||
begin
|
||||
path filter --perm write,exec stuff/*
|
||||
if __fish_is_cygwin && ! set -q noacl
|
||||
echo stuff/writeexec
|
||||
end
|
||||
end | sort
|
||||
# CHECK: stuff/all
|
||||
# CHECK: stuff/writeexec
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#RUN: fish_indent=%fish_indent %fish %s
|
||||
#REQUIRES: command -v sphinx-build
|
||||
# Cygwin has some old version of python/sphinx/... triggering deprecation errors
|
||||
# (and MSYS doesn't have sphinx-build at all)
|
||||
#REQUIRES: %fish -c "not __fish_is_cygwin"
|
||||
|
||||
set -l workspace_root (status dirname)/../..
|
||||
set -l build_script $workspace_root/tests/test_functions/sphinx-shared.sh
|
||||
|
||||
30
tests/checks/tmux-complete4.fish
Normal file
30
tests/checks/tmux-complete4.fish
Normal file
@@ -0,0 +1,30 @@
|
||||
#RUN: %fish %s
|
||||
#REQUIRES: command -v tmux
|
||||
#REQUIRES: uname -r | grep -qv Microsoft
|
||||
|
||||
isolated-tmux-start -C '
|
||||
complete : -s c -l clip
|
||||
complete : -s q -l qrcode
|
||||
set -g fish_autosuggestion_enabled 0
|
||||
'
|
||||
touch somefile1
|
||||
touch somefile2
|
||||
|
||||
isolated-tmux send-keys C-l ': -c'
|
||||
|
||||
function tab
|
||||
isolated-tmux send-keys Tab
|
||||
tmux-sleep
|
||||
isolated-tmux capture-pane -p | awk '/./ { print "[" $0 "]" }'
|
||||
end
|
||||
|
||||
tab
|
||||
# CHECK: [prompt 0> : -cq]
|
||||
|
||||
tab
|
||||
# CHECK: [prompt 0> : -cq somefile]
|
||||
# CHECK: [somefile1 somefile2]
|
||||
|
||||
tab
|
||||
# CHECK: [prompt 0> : -cq somefile1]
|
||||
# CHECK: [somefile1 somefile2]
|
||||
@@ -2,10 +2,19 @@ function cygwin_nosymlinks --description \
|
||||
"Return 0 if Cygwin fakes symlinks, return 1 otherwise"
|
||||
|
||||
switch (__fish_uname)
|
||||
case "MSYS*"
|
||||
not string match -q "*winsymlinks*" -- "$MSYS"
|
||||
case "CYGWIN*"
|
||||
not string match -q "*winsymlinks*" -- "$CYGWIN"
|
||||
# Cygwin has various ways of creating symlinks but they should
|
||||
# all be recognized as such (with a few exceptions, which we do
|
||||
# not support in testing, see
|
||||
# https://cygwin.com/cygwin-ug-net/using.html#pathnames-symlinks)
|
||||
return 1
|
||||
case "MSYS*"
|
||||
# In addition to the standard Cygwin symlinks, MSYS2 also has
|
||||
# `deepcopy` mode, which is the default, and does not create
|
||||
# recognizable symlinks
|
||||
# (https://www.msys2.org/docs/symlinks/)
|
||||
not string match -q "*winsymlinks*" -- "$MSYS"
|
||||
or string match -q "*winsymlinks:deepcopy*" -- "$MSYS"
|
||||
case "*"
|
||||
return 1
|
||||
end
|
||||
|
||||
@@ -6,9 +6,9 @@ sources:
|
||||
spec:
|
||||
shell: bash
|
||||
command: |
|
||||
set -eo pipefail
|
||||
set -e
|
||||
# Check that we have latest stable.
|
||||
if rustup check | grep ^stable- | grep 'Update available'; then
|
||||
if rustup check | grep '^stable-.*[uU]pdate available'; then
|
||||
echo >&2 "Rust toolchain 'stable' is stale, please update it"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user