From 5cd2ef903a98a6d0d7fe5935f9b4fb0bc0d016ce Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Tue, 4 Mar 2025 16:57:59 +0100 Subject: [PATCH 01/32] key: Add super modifier Fixes #11217 (cherry picked from commit 9f5e1736a860ac5e42b00bd6ebee37f6559495be) --- doc_src/cmds/bind.rst | 2 +- src/input_common.rs | 1 + src/key.rs | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc_src/cmds/bind.rst b/doc_src/cmds/bind.rst index d7088f060..e93cf4a22 100644 --- a/doc_src/cmds/bind.rst +++ b/doc_src/cmds/bind.rst @@ -23,7 +23,7 @@ If both ``KEYS`` and ``COMMAND`` are given, ``bind`` adds (or replaces) a bindin If only ``KEYS`` is given, any existing binding in the given ``MODE`` will be printed. ``KEYS`` is a comma-separated list of key names. -Modifier keys can be specified by prefixing a key name with a combination of ``ctrl-``, ``alt-`` and ``shift-``. +Modifier keys can be specified by prefixing a key name with a combination of ``ctrl-``, ``alt-``, ``shift-`` and ``super-`` (i.e. the "windows" or "command" key). For example, pressing :kbd:`w` while holding the Alt modifier is written as ``alt-w``. Key names are case-sensitive; for example ``alt-W`` is the same as ``alt-shift-w``. ``ctrl-x,ctrl-e`` would mean pressing :kbd:`ctrl-x` followed by :kbd:`ctrl-e`. diff --git a/src/input_common.rs b/src/input_common.rs index c8e2f1012..687816795 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -560,6 +560,7 @@ pub(crate) fn terminal_protocols_disable_ifn() { fn parse_mask(mask: u32) -> Modifiers { Modifiers { + sup: (mask & 8) != 0, ctrl: (mask & 4) != 0, alt: (mask & 2) != 0, shift: (mask & 1) != 0, diff --git a/src/key.rs b/src/key.rs index 27d6e2316..d18c7d2bb 100644 --- a/src/key.rs +++ b/src/key.rs @@ -54,6 +54,7 @@ pub struct Modifiers { pub ctrl: bool, pub alt: bool, pub shift: bool, + pub sup: bool, } impl Modifiers { @@ -62,6 +63,7 @@ const fn new() -> Self { ctrl: false, alt: false, shift: false, + sup: false, } } pub(crate) const ALT: Self = { @@ -271,6 +273,7 @@ pub(crate) fn parse_keys(value: &wstr) -> Result, WString> { _ if modifier == "ctrl" => modifiers.ctrl = true, _ if modifier == "alt" => modifiers.alt = true, _ if modifier == "shift" => modifiers.shift = true, + _ if modifier == "super" => modifiers.sup = true, _ => { return Err(wgettext_fmt!( "unknown modifier '%s' in '%s'", @@ -407,6 +410,9 @@ fn from(key: Key) -> Self { if key.modifiers.ctrl { res.insert_utfstr(0, L!("ctrl-")); } + if key.modifiers.sup { + res.insert_utfstr(0, L!("super-")); + } res } From ae3532e9eccda2e3203c929ed256b0a1c7e3dc31 Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Fri, 14 Mar 2025 16:21:50 +0100 Subject: [PATCH 02/32] screen: Fix crash if prompt contains backspace fish_wcwidth_visible can return -1, so usize::try_from fails. Fixes #11280 (cherry picked from commit c03de2086ac46ad9a79928407395e49d3dadb723) --- src/screen.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/screen.rs b/src/screen.rs index ad6f43b2b..158512e1e 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -1636,7 +1636,11 @@ fn measure_run_from( width = next_tab_stop(width); } else { // Ordinary char. Add its width with care to ignore control chars which have width -1. - width += usize::try_from(fish_wcwidth_visible(input.char_at(idx))).unwrap(); + if let Ok(ww) = usize::try_from(fish_wcwidth_visible(input.char_at(idx))) { + width += ww; + } else { + width = width.saturating_sub(1); + } } idx += 1; } @@ -1682,7 +1686,8 @@ fn truncate_run( curr_width = measure_run_from(run, 0, None, cache); idx = 0; } else { - let char_width = usize::try_from(fish_wcwidth_visible(c)).unwrap(); + // FIXME: In case of backspace, this would remove the last width. + let char_width = usize::try_from(fish_wcwidth_visible(c)).unwrap_or(0); curr_width -= std::cmp::min(curr_width, char_width); run.remove(idx); } From ffbf957fa38ce18d23aa7054196705126e7016fc Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Thu, 13 Mar 2025 07:32:37 +0100 Subject: [PATCH 03/32] Quote only unique completions, use backslashes for ambiguous ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 29dc307111 (Insert some completions with quotes instead of backslashes, 2024-04-13) breaks some workflows. Given touch '[test] file1' touch '[test] file2' ls tes we insert completions quoted, which is inconvenient when using globs. This implicit quoting feature is somewhat minor. But quotes look nicer, so let's try to keep them. Either way, users can ask for it by starting a token with «"». Use quoting only when we insert unique completions. Closes #11271 (cherry picked from commit 9f79fe17fcc125e03f51d1417423ccd149aa5b8d) --- src/builtins/complete.rs | 3 ++- src/reader.rs | 28 ++++++++++++++++++++++++---- src/tests/complete.rs | 4 +++- src/tests/reader.rs | 1 + tests/checks/complete.fish | 2 +- 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/builtins/complete.rs b/src/builtins/complete.rs index 4014ec208..e744570f4 100644 --- a/src/builtins/complete.rs +++ b/src/builtins/complete.rs @@ -517,7 +517,8 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> next.flags, faux_cmdline, &mut tmp_cursor, - false, + /*append_only=*/ false, + /*is_unique=*/ false, ); // completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE diff --git a/src/reader.rs b/src/reader.rs index b8149c3b7..e3294f5f3 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -3777,6 +3777,7 @@ fn pager_selection_changed(&mut self) { &self.cycle_command_line, &mut cursor_pos, false, + /*is_unique=*/ false, // don't care ), }; @@ -4414,6 +4415,7 @@ fn get_autosuggestion_performer( &search_string, &mut cursor, /*append_only=*/ true, + /*is_unique=*/ false, ); } result @@ -5597,6 +5599,7 @@ pub fn completion_apply_to_command_line( command_line: &wstr, inout_cursor_pos: &mut usize, append_only: bool, + is_unique: bool, ) -> WString { let add_space = !flags.contains(CompleteFlags::NO_SPACE); let do_replace_token = flags.contains(CompleteFlags::REPLACES_TOKEN); @@ -5621,7 +5624,7 @@ pub fn completion_apply_to_command_line( } let mut escape_flags = EscapeFlags::empty(); - if append_only || !add_space { + if append_only || !is_unique || !add_space { escape_flags.insert(EscapeFlags::NO_QUOTED); } if no_tilde { @@ -5869,7 +5872,12 @@ fn try_insert(&mut self, c: Completion, tok: &wstr, token_range: Range) { // If this is a replacement completion, check that we know how to replace it, e.g. that // the token doesn't contain evil operators like {}. if !c.flags.contains(CompleteFlags::REPLACES_TOKEN) || reader_can_replace(tok, c.flags) { - self.completion_insert(&c.completion, token_range.end, c.flags); + self.completion_insert( + &c.completion, + token_range.end, + c.flags, + /*is_unique=*/ true, + ); } } @@ -6005,7 +6013,12 @@ fn handle_completions(&mut self, token_range: Range) -> bool { if prefix_is_partial_completion { flags |= CompleteFlags::NO_SPACE; } - self.completion_insert(&common_prefix, token_range.end, flags); + self.completion_insert( + &common_prefix, + token_range.end, + flags, + /*is_unique=*/ false, + ); self.cycle_command_line = self.command_line.text().to_owned(); self.cycle_cursor_pos = self.command_line.position(); } @@ -6049,7 +6062,13 @@ fn handle_completions(&mut self, token_range: Range) -> bool { /// \param token_end the position after the token to complete /// \param flags A union of all flags describing the completion to insert. See the completion_t /// struct for more information on possible values. - fn completion_insert(&mut self, val: &wstr, token_end: usize, flags: CompleteFlags) { + fn completion_insert( + &mut self, + val: &wstr, + token_end: usize, + flags: CompleteFlags, + is_unique: bool, + ) { let (elt, el) = self.active_edit_line(); // Move the cursor to the end of the token. @@ -6065,6 +6084,7 @@ fn completion_insert(&mut self, val: &wstr, token_end: usize, flags: CompleteFla el.text(), &mut cursor, /*append_only=*/ false, + is_unique, ); self.set_buffer_maintaining_pager(&new_command_line, cursor, false); } diff --git a/src/tests/complete.rs b/src/tests/complete.rs index 21f267e30..cd6312228 100644 --- a/src/tests/complete.rs +++ b/src/tests/complete.rs @@ -160,7 +160,8 @@ macro_rules! unique_completion_applies_as { completions[0].flags, cmdline, &mut cursor, - false, + /*append_only=*/ false, + /*is_unique=*/ true, ); assert_eq!(newcmdline, L!($applied), "apply result mismatch"); }; @@ -224,6 +225,7 @@ macro_rules! unique_completion_applies_as { L!("mv debug debug"), &mut cursor_pos, true, + /*is_unique=*/ false, ); assert_eq!(newcmdline, L!("mv debug Debug/")); diff --git a/src/tests/reader.rs b/src/tests/reader.rs index 2b0450a91..be5b4ec9c 100644 --- a/src/tests/reader.rs +++ b/src/tests/reader.rs @@ -57,6 +57,7 @@ macro_rules! validate { &line, &mut cursor_pos, $append_only, + /*is_unique=*/ false, ); assert_eq!(result, expected); assert_eq!(cursor_pos, out_cursor_pos); diff --git a/tests/checks/complete.fish b/tests/checks/complete.fish index 1db09fd59..9ea39f260 100644 --- a/tests/checks/complete.fish +++ b/tests/checks/complete.fish @@ -10,7 +10,7 @@ complete -c complete_test_alpha3 --no-files -w 'complete_test_alpha2 extra2' complete -C'complete_test_alpha1 arg1 ' # CHECK: complete_test_alpha1 arg1 complete --escape -C'complete_test_alpha1 arg1 ' -# CHECK: 'complete_test_alpha1 arg1 ' +# CHECK: complete_test_alpha1\ arg1\{{ }} complete -C'complete_test_alpha2 arg2 ' # CHECK: complete_test_alpha1 extra1 arg2 complete -C'complete_test_alpha3 arg3 ' From 76d2419228627764876d1b001c3b74e6222499bb Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Fri, 14 Mar 2025 18:53:48 +0100 Subject: [PATCH 04/32] Downgrade $TERM warnings to a term-support flog The chances that xterm-256color breaks anything are miniscule. In the features we use, there are basically no differences, especially when you consider that we decode keys independently. E.g. tmux-256color has differences, but they are either just taste questions (xterm's clear_screen will also clear scrollback), or they're just... not actually different? Terminfo will claim that it uses a different cursor_up and exit_attribute_mode, but it also understands the xterm ones, and it sends a different key_home, but we decode that even with TERM=xterm-256color. In some cases, terminfo is also just outright *wrong* and will claim something does not support italics when it does. So, since the differences are very likely to simply not matter, throwing a warning is more confusing than it is helpful. (cherry picked from commit 642ec399ca17bbde973dc20461335396fe922e4c) --- src/env_dispatch.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/env_dispatch.rs b/src/env_dispatch.rs index c495edbd1..9bdba7fcb 100644 --- a/src/env_dispatch.rs +++ b/src/env_dispatch.rs @@ -554,19 +554,16 @@ fn init_curses(vars: &EnvStack) { if curses::setup(None, |term| apply_term_hacks(vars, term)).is_none() { if is_interactive_session() { let term = vars.get_unless_empty(L!("TERM")).map(|v| v.as_string()); - // We do not warn for xterm-256color at all, we know that one. - if term != Some("xterm-256color".into()) { - if let Some(term) = term { - FLOG!( - warning, - wgettext_fmt!("Could not set up terminal for $TERM '%ls'. Falling back to hardcoded xterm-256color values", term) - ); - } else { - FLOG!( - warning, - wgettext!("Could not set up terminal because $TERM is unset. Falling back to hardcoded xterm-256color values") - ); - } + if let Some(term) = term { + FLOG!( + term_support, + wgettext_fmt!("Could not set up terminal for $TERM '%ls'. Falling back to hardcoded xterm-256color values", term) + ); + } else { + FLOG!( + term_support, + wgettext!("Could not set up terminal because $TERM is unset. Falling back to hardcoded xterm-256color values") + ); } } From 5771085280fbfc79fa78d81ffcf5dde53487f25c Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Sun, 16 Mar 2025 19:15:28 +0100 Subject: [PATCH 05/32] CHANGELOG --- CHANGELOG.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 763d738b6..97e814c2c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,16 @@ +fish 4.0.2 (released March ??, 2025) +==================================== + +This release of fish includes the following improvements compared to fish 4.0.1: + +- Key combinations using the super (Windows/command) key can now actually be bound using the :kbd:`super-` prefix (:issue:`11217`). +- A crash involving backspace characters was fixed (:issue:`11280`). +- Fish 4.0.0 switched completions to be quoted instead of backslash-escaped, + now it only does so if the completion is unambiguous to make continuing the token easier (:issue:`11271`). +- The warning when the terminfo database couldn't be found has been downgraded to a log message. + This is because fish will fall back to what xterm-256color would do, + which in the majority of cases would just work (:issue:`11277`, :issue:`11290`). + fish 4.0.1 (released March 12, 2025) ==================================== From c7efbf590e9c9e622b722d4309e00a39655df5f5 Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Mon, 17 Mar 2025 19:52:09 +0100 Subject: [PATCH 06/32] function: Also error for read-only var in positional arg We have this hack where any positional arguments are taken as argument names if "--argument-names" is given, and that didn't check for read-only variables. Fixes #11295 (cherry picked from commit d203ee4d531b10cb6204d7b327b0ecd88fa30d1b) --- src/builtins/function.rs | 8 ++++++++ tests/checks/function.fish | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/builtins/function.rs b/src/builtins/function.rs index 4bf4a81b2..28b64a4f8 100644 --- a/src/builtins/function.rs +++ b/src/builtins/function.rs @@ -92,6 +92,14 @@ fn parse_cmd_opts( // A positional argument we got because we use RETURN_IN_ORDER. let woptarg = w.woptarg.unwrap().to_owned(); if handling_named_arguments { + if is_read_only(&woptarg) { + streams.err.append(wgettext_fmt!( + "%ls: variable '%ls' is read-only\n", + cmd, + woptarg + )); + return STATUS_INVALID_ARGS; + } opts.named_arguments.push(woptarg); } else { streams.err.append(wgettext_fmt!( diff --git a/tests/checks/function.fish b/tests/checks/function.fish index bf71723be..058a036d3 100644 --- a/tests/checks/function.fish +++ b/tests/checks/function.fish @@ -183,6 +183,11 @@ function foo --argument-names status; end echo status $status # CHECK: status 2 +function foo --argument-names foo status; end +# CHECKERR: {{.*}}function.fish (line {{\d+}}): function: variable 'status' is read-only +# CHECKERR: function foo --argument-names foo status; end +# CHECKERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ + functions -q foo echo exists $status # CHECK: exists 1 From 8e7885783644d1d828d8049ebf2af3401f92ee41 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Wed, 19 Mar 2025 09:12:03 +0100 Subject: [PATCH 07/32] Fix Vi mode delete key bindings while numlock is active Commit 8bf8b10f68 (Extended & human-friendly keys, 2024-03-30) add bindings that obsolete the terminfo-based `bind -k` invocations. The `bind -k` variants were still left around[^*]. Unfortunately it forgot to add the new syntax for some special keys in Vi mode. This leads to issues if a terminal that supports the kitty keyboard protocol sends an encoding that differs from the traditional one. As far as I can tell, this happens when capslock or numlock is active. Let's add the new key names and consistently mark `bind -k` invocations as deprecated. Fixes #11303 [^*]: Support for `bind -k` will probably be removed in a future release - it leads to issues like https://github.com/fish-shell/fish-shell/issues/11278 where it's better to fail early. (cherry picked from commit 733f7042679e97bd8176d4958ed704f0dd92b2dd) --- CHANGELOG.rst | 1 + .../functions/__fish_shared_key_bindings.fish | 2 +- share/functions/fish_vi_key_bindings.fish | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 97e814c2c..88b3d1486 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,7 @@ fish 4.0.2 (released March ??, 2025) This release of fish includes the following improvements compared to fish 4.0.1: - Key combinations using the super (Windows/command) key can now actually be bound using the :kbd:`super-` prefix (:issue:`11217`). +- :kbd:`delete` in Vi mode works again if numlock is active (:issue:`11303`). - A crash involving backspace characters was fixed (:issue:`11280`). - Fish 4.0.0 switched completions to be quoted instead of backslash-escaped, now it only does so if the completion is unambiguous to make continuing the token easier (:issue:`11271`). diff --git a/share/functions/__fish_shared_key_bindings.fish b/share/functions/__fish_shared_key_bindings.fish index cff6d7d7d..aabf9fd72 100644 --- a/share/functions/__fish_shared_key_bindings.fish +++ b/share/functions/__fish_shared_key_bindings.fish @@ -120,7 +120,7 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod bind --preset $argv alt-enter "commandline -i \n" expand-abbr bind --preset $argv ")" self-insert expand-abbr # Closing a command substitution. bind --preset $argv ctrl-space 'test -n "$(commandline)" && commandline -i " "' - bind --preset $argv -k nul 'test -n "$(commandline)" && commandline -i " "' + $legacy_bind --preset $argv -k nul 'test -n "$(commandline)" && commandline -i " "' # Shift-space behaves like space because it's easy to mistype. bind --preset $argv shift-space 'commandline -i " "' expand-abbr diff --git a/share/functions/fish_vi_key_bindings.fish b/share/functions/fish_vi_key_bindings.fish index cb8428e17..56ab5baa3 100644 --- a/share/functions/fish_vi_key_bindings.fish +++ b/share/functions/fish_vi_key_bindings.fish @@ -109,18 +109,24 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish' bind -s --preset -M insert ctrl-n accept-autosuggestion # Vi/Vim doesn't support these keys in insert mode but that seems silly so we do so anyway. - bind -s --preset -M insert -k home beginning-of-line - bind -s --preset -M default -k home beginning-of-line - bind -s --preset -M insert -k end end-of-line - bind -s --preset -M default -k end end-of-line + bind -s --preset -M insert home beginning-of-line + $legacy_bind -s --preset -M insert -k home beginning-of-line + bind -s --preset -M default home beginning-of-line + $legacy_bind -s --preset -M default -k home beginning-of-line + bind -s --preset -M insert end end-of-line + $legacy_bind -s --preset -M insert -k end end-of-line + bind -s --preset -M default end end-of-line + $legacy_bind -s --preset -M default -k end end-of-line # Vi moves the cursor back if, after deleting, it is at EOL. # To emulate that, move forward, then backward, which will be a NOP # if there is something to move forward to. bind -s --preset -M default x delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive' bind -s --preset -M default X backward-delete-char - bind -s --preset -M insert -k dc delete-char forward-single-char backward-char - bind -s --preset -M default -k dc delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive' + bind -s --preset -M insert delete delete-char forward-single-char backward-char + $legacy_bind -s --preset -M insert -k dc delete-char forward-single-char backward-char + bind -s --preset -M default delete delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive' + $legacy_bind -s --preset -M default -k dc delete-char 'set fish_cursor_end_mode exclusive' forward-single-char backward-char 'set fish_cursor_end_mode inclusive' # Backspace deletes a char in insert mode, but not in normal/default mode. bind -s --preset -M insert backspace backward-delete-char From 18c231de2978235f5869a8d42ef8f38cfc4b9f9f Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Wed, 19 Mar 2025 09:39:04 +0100 Subject: [PATCH 08/32] Fix concurrent setlocale() in string escape tests In our C++ implementation, these tests were run serially. As pointed out in https://github.com/fish-shell/fish-shell/issues/11254#issuecomment-2735623229 we run them in parallel now, which means that one test could be changing the global locale used by another. In theory this could be fine because all tests are setting setting the global locale to the same thing but the existence of a lock suggests that setlocale() is not guaranteed to be atomic, so it's possible that another thread uses a temporarily-invalid locale. Fixes #11254 (cherry picked from commit 1d78c8bd4295262a3118f478e6b3a7c7536fa282) --- src/tests/string_escape.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/tests/string_escape.rs b/src/tests/string_escape.rs index ba8ee7534..4428d679c 100644 --- a/src/tests/string_escape.rs +++ b/src/tests/string_escape.rs @@ -1,3 +1,5 @@ +use std::sync::MutexGuard; + use crate::common::{ escape_string, str2wcstring, unescape_string, wcs2string, EscapeFlags, EscapeStringStyle, UnescapeStringStyle, ENCODE_DIRECT_BASE, ENCODE_DIRECT_END, @@ -10,21 +12,21 @@ /// wcs2string is locale-dependent, so ensure we have a multibyte locale /// before using it in a test. -fn setlocale() { - let _guard = LOCALE_LOCK.lock().unwrap(); +fn setlocale() -> MutexGuard<'static, ()> { + let guard = LOCALE_LOCK.lock().unwrap(); #[rustfmt::skip] const UTF8_LOCALES: &[&str] = &[ "C.UTF-8", "en_US.UTF-8", "en_GB.UTF-8", "de_DE.UTF-8", "C.utf8", "UTF-8", ]; if crate::libc::MB_CUR_MAX() > 1 { - return; + return guard; } for locale in UTF8_LOCALES { let locale = std::ffi::CString::new(locale.to_owned()).unwrap(); unsafe { libc::setlocale(libc::LC_CTYPE, locale.as_ptr()) }; if crate::libc::MB_CUR_MAX() > 1 { - return; + return guard; } } panic!("No UTF-8 locale found"); @@ -100,7 +102,7 @@ fn test_escape_var() { } fn escape_test(escape_style: EscapeStringStyle, unescape_style: UnescapeStringStyle) { - setlocale(); + let _locale_guard = setlocale(); let seed: u128 = 92348567983274852905629743984572; let mut rng = get_seeded_rng(seed); @@ -174,7 +176,7 @@ fn str2hex(input: &[u8]) -> String { /// string comes back through double conversion. #[test] fn test_convert() { - setlocale(); + let _locale_guard = setlocale(); let seed = get_rng_seed(); let mut rng = get_seeded_rng(seed); let mut origin = Vec::new(); From 542793a53421ea6d9a9b0b6d46cb7c6deb3ea03f Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Tue, 25 Mar 2025 09:58:32 +0100 Subject: [PATCH 09/32] completions/git: fix arg completion for third-party git commands, again Commit 50e595503e (completions/git: fix completions for third-party git commands, 2025-03-03) wasn't quite right, as we can see in the linked reproduction: $ fish_trace=1 complete -C 'git machete add --onto ' ----> complete -C git-machete\ add\n--onto\ The recursive completion invocation contains a spurious newline, which means that "--onto" is the command name. The newline is produced by "string escape -- add --onto" inside a command substitution. Fix this by interpreting newlines as list separators, and then joining by spaces. Fixes #11319 (cherry picked from commit 360cfdb7ae7af9a73cc3f357a78bd35b5b12e829) --- share/completions/git.fish | 4 ++-- tests/checks/git.fish | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/share/completions/git.fish b/share/completions/git.fish index 592d358b2..89057e342 100644 --- a/share/completions/git.fish +++ b/share/completions/git.fish @@ -2571,9 +2571,9 @@ function __fish_git_complete_custom_command -a subcommand set -e cmd[1] # Drop "git". set -l subcommand_args if argparse -s (__fish_git_global_optspecs) -- $cmd - set subcommand_args $argv[2..] # Drop the subcommand. + set subcommand_args (string escape -- $argv[2..]) # Drop the subcommand. end - complete -C "git-$subcommand $(string escape -- $subcommand_args) "(commandline -ct) + complete -C "git-$subcommand $subcommand_args "(commandline -ct) end # source git-* commands' autocompletion file if exists diff --git a/tests/checks/git.fish b/tests/checks/git.fish index 5e3d71cf3..4af64cd4f 100644 --- a/tests/checks/git.fish +++ b/tests/checks/git.fish @@ -51,6 +51,10 @@ complete -C'git frobnicate --onto ' #CHECK: onto1 #CHECK: onto2 +complete -C'git frobnicate graft --onto ' +#CHECK: onto1 +#CHECK: onto2 + complete -C'git ' | grep '^add'\t # (note: actual tab character in the check here) #CHECK: add Add file contents to the staging area From 3fc245d829a470d2aaaa5a86c020ee69b59979eb Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Thu, 27 Mar 2025 15:56:09 +0100 Subject: [PATCH 10/32] docs: Readd bind -k to the docs Fixes #11329 (cherry picked from commit d88f5ddbaf5d8c60da2da7a5f7d4a316eb6240c3) --- doc_src/cmds/bind.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc_src/cmds/bind.rst b/doc_src/cmds/bind.rst index e93cf4a22..8b4f2599c 100644 --- a/doc_src/cmds/bind.rst +++ b/doc_src/cmds/bind.rst @@ -99,6 +99,12 @@ The following options are available: **-s** or **--silent** Silences some of the error messages, including for unknown key names and unbound sequences. +**-k KEY_NAME** or **--key KEY_NAME** + This looks up KEY_NAME in terminfo and binds that sequence instead of a key that fish would decode. + To view a list of the terminfo keys fish knows about, use ``bind --key-names`` or ``bind -K``. + This is deprecated and provided for compatibility with older fish versions. You should bind the keys directly. + Instead of ``bind -k sright`` use ``bind shift-right``, instead of ``bind -k nul`` use ``bind ctrl-space`` and so on. + **-h** or **--help** Displays help about using this command. From f127323c337d3b524e0e986c3a3ae150725c60cd Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Wed, 26 Mar 2025 20:38:31 +0100 Subject: [PATCH 11/32] Prevent commandline modification inside abbreviation callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consider command line modifications triggered from fish script via abbreviation expansion: function my-abbr-func commandline -r "" echo expanded end abbr -a foo --function my-abbr-func Prior to commit 8386088b3d (Update commandline state changes eagerly as well, 2024-04-11), we'd silently ignore the command line modification. This is because the abbreviation machinery runs something similar to if my-abbr-func commandline -rt expanded end except without running "apply_commandline_state_changes()" after "my-abbr-func", so the «commandline -r ""» update is lost. Commit 8386088b3d applies the commandline change immediately in the abbrevation function callback, invalidating abbrevation-expansion state. The abbreviation design does not tell us what should happen here. Let's ignore commandline modifications for now. This mostly matches historical behavior. Unlike historical behavior we also ignore modifications if the callback fails: function my-abbr-func commandline -r "" false end Remove the resulting dead code in editable_line. See #11324 (cherry picked from commit 11c7310f17ea2e8507ca4fc0f7444ef6a22a071f) --- src/builtins/commandline.rs | 11 +++++++---- src/builtins/read.rs | 6 +++++- src/parser.rs | 3 +++ src/reader.rs | 17 +++++++++++++++-- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/builtins/commandline.rs b/src/builtins/commandline.rs index 2a84743df..8fe6ba09a 100644 --- a/src/builtins/commandline.rs +++ b/src/builtins/commandline.rs @@ -55,6 +55,7 @@ enum TokenMode { /// \param buff the original command line buffer /// \param cursor_pos the position of the cursor in the command line fn replace_part( + parser: &Parser, range: Range, insert: &wstr, insert_mode: AppendMode, @@ -86,9 +87,9 @@ fn replace_part( out.push_utfstr(&buff[range.end..]); if search_field_mode { - commandline_set_search_field(out, Some(out_pos)); + commandline_set_search_field(parser, out, Some(out_pos)); } else { - commandline_set_buffer(Some(out), Some(out_pos)); + commandline_set_buffer(parser, Some(out), Some(out_pos)); } } @@ -465,7 +466,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) } line_offset + new_coord }; - commandline_set_buffer(None, Some(new_pos)); + commandline_set_buffer(parser, None, Some(new_pos)); } else { streams.out.append(sprintf!( "%d\n", @@ -631,7 +632,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) .saturating_add_signed(isize::try_from(new_pos).unwrap()), current_buffer.len(), ); - commandline_set_buffer(None, Some(new_pos)); + commandline_set_buffer(parser, None, Some(new_pos)); } else { streams .out @@ -652,6 +653,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ); } else if positional_args == 1 { replace_part( + parser, range, args[w.wopt_index], append_mode, @@ -662,6 +664,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) } else { let sb = join_strings(&w.argv[w.wopt_index..], '\n'); replace_part( + parser, range, &sb, append_mode, diff --git a/src/builtins/read.rs b/src/builtins/read.rs index 7051897f3..e45f60aec 100644 --- a/src/builtins/read.rs +++ b/src/builtins/read.rs @@ -234,7 +234,11 @@ fn read_interactive( // Keep in-memory history only. reader_push(parser, L!(""), conf); - commandline_set_buffer(Some(commandline.to_owned()), None); + let _modifiable_commandline = scoped_push_replacer( + |new_value| std::mem::replace(&mut parser.libdata_mut().readonly_commandline, new_value), + false, + ); + commandline_set_buffer(parser, Some(commandline.to_owned()), None); let mline = { let _interactive = scoped_push_replacer( diff --git a/src/parser.rs b/src/parser.rs index b6ca73835..7afca7f58 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -276,6 +276,9 @@ pub struct LibraryData { /// Whether we are currently interactive. pub is_interactive: bool, + /// Whether the command line is closed for modification from fish script. + pub readonly_commandline: bool, + /// Whether to suppress fish_trace output. This occurs in the prompt, event handlers, and key /// bindings. pub suppress_fish_trace: bool, diff --git a/src/reader.rs b/src/reader.rs index e3294f5f3..087b41035 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -955,6 +955,9 @@ pub fn reader_schedule_prompt_repaint() { } pub fn reader_execute_readline_cmd(parser: &Parser, ch: CharEvent) { + if parser.libdata().readonly_commandline { + return; + } if let Some(data) = current_data() { let mut data = Reader { parser, data }; let CharEvent::Readline(readline_cmd_evt) = &ch else { @@ -1033,7 +1036,10 @@ pub fn commandline_get_state(sync: bool) -> CommandlineState { /// Set the command line text and position. This may be called on a background thread; the reader /// will pick it up when it is done executing. -pub fn commandline_set_buffer(text: Option, cursor_pos: Option) { +pub fn commandline_set_buffer(parser: &Parser, text: Option, cursor_pos: Option) { + if parser.libdata().readonly_commandline { + return; + } { let mut state = commandline_state_snapshot(); if let Some(text) = text { @@ -1044,7 +1050,10 @@ pub fn commandline_set_buffer(text: Option, cursor_pos: Option) current_data().map(|data| data.apply_commandline_state_changes()); } -pub fn commandline_set_search_field(text: WString, cursor_pos: Option) { +pub fn commandline_set_search_field(parser: &Parser, text: WString, cursor_pos: Option) { + if parser.libdata().readonly_commandline { + return; + } { let mut state = commandline_state_snapshot(); assert!(state.search_field.is_some()); @@ -4891,6 +4900,10 @@ fn expand_replacer( |new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value), false, ); + let _readonly_commandline = scoped_push_replacer( + |new_value| std::mem::replace(&mut parser.libdata_mut().readonly_commandline, new_value), + true, + ); let mut outputs = vec![]; let ret = exec_subshell( From 459e9b784743fa3578b2dbd318faf12f71b03492 Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Wed, 2 Apr 2025 16:57:32 +0200 Subject: [PATCH 12/32] completions/cargo: Speed up This does two things: - it stops completing cargo- tools because `cargo --list` already includes them. This speeds up loading especially with a long $PATH - it stops using `cargo search` for `cargo add` and install. this removes a network call, which may be unexpected and can take a long time Fixes #11347 (cherry picked from commit 18371fbd4ea4bb49de2039019395e6211a38b90e) --- share/completions/cargo.fish | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/share/completions/cargo.fish b/share/completions/cargo.fish index 0061d2ad2..947fc5ecf 100644 --- a/share/completions/cargo.fish +++ b/share/completions/cargo.fish @@ -3,9 +3,6 @@ ## --- WRITTEN MANUALLY --- set -l __fish_cargo_subcommands (cargo --list 2>&1 | string replace -rf '^\s+([^\s]+)\s*(.*)' '$1\t$2' | string escape) -# Append user-installed extensions (e.g. cargo-foo, invokable as `cargo foo`) to the list of subcommands (à la git) -set -la __fish_cargo_subcommands (complete -C'cargo-' | string replace -rf '^cargo-(\w+).*' '$1') - complete -c cargo -f -c cargo -n __fish_use_subcommand -a "$__fish_cargo_subcommands" complete -c cargo -x -c cargo -n '__fish_seen_subcommand_from help' -a "$__fish_cargo_subcommands" @@ -53,27 +50,6 @@ end complete -c cargo -n '__fish_seen_subcommand_from run test build debug check' -l package \ -xa "(__fish_cargo_packages)" -# Look up crates.io crates matching the single argument provided to this function -function __fish_cargo_search - if test (string length -- "$argv[1]") -le 2 - # Don't waste time searching for strings with too many results to realistically - # provide a meaningful completion within our results limit. - return - end - - # This doesn't do a prefix search, so bump up the limit a tiny bit to try and - # get enough results to show something. - cargo search --color never --quiet --limit 20 -- $argv[1] 2>/dev/null | - # Filter out placeholders and "... and xxx more crates" - string match -rvi '^\.\.\.|= "0.0.0"|# .*(reserved|yanked)' | - # Remove the version number and map the description - string replace -rf '^([^ ]+).*# (.*)' '$1\t$2' -end - -# Complete possible crate names by search the crates.io index -complete -c cargo -n '__fish_seen_subcommand_from add install' -n '__fish_is_nth_token 2' \ - -a "(__fish_cargo_search (commandline -ct))" - ## --- AUTO-GENERATED WITH `cargo complete fish` --- # Manually massaged to improve some descriptions complete -c cargo -n __fish_use_subcommand -l explain -d 'Run `rustc --explain CODE`' From de154065fe7414f8dcda4aa624304243ec72ec17 Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Wed, 2 Apr 2025 16:59:21 +0200 Subject: [PATCH 13/32] fish_print_hg_root: Don't break if $PWD includes newlines Fixes #11348 (cherry picked from commit 5e25cdaa6f09b634d2a8ea8ac508c7d7f7f91020) --- share/functions/fish_print_hg_root.fish | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/functions/fish_print_hg_root.fish b/share/functions/fish_print_hg_root.fish index 65625f9ea..50df1e3c6 100644 --- a/share/functions/fish_print_hg_root.fish +++ b/share/functions/fish_print_hg_root.fish @@ -7,16 +7,16 @@ function fish_print_hg_root # Find an hg directory above $PWD # without calling `hg root` because that's too slow set -l root - set -l dir (pwd -P 2>/dev/null) + set -l dir "$(pwd -P 2>/dev/null)" or return 1 - while test $dir != / + while not contains -- "$dir" "" / . if test -f $dir'/.hg/dirstate' echo $dir/.hg return 0 end # Go up one directory - set dir (string replace -r '[^/]*/?$' '' $dir) + set dir (path dirname -- $dir) end return 1 From 6af0378916afad201facb1fcccf0fee961130ef7 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sun, 30 Mar 2025 08:33:24 +0200 Subject: [PATCH 14/32] Don't insert text from keys like super-i While at it, use declaration order for modifiers. (cherry picked from commit 35ae0bf1f216a866066775db640282e3c50353d0) --- src/input_common.rs | 2 +- src/key.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/input_common.rs b/src/input_common.rs index 687816795..ca7508acc 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -560,10 +560,10 @@ pub(crate) fn terminal_protocols_disable_ifn() { fn parse_mask(mask: u32) -> Modifiers { Modifiers { - sup: (mask & 8) != 0, ctrl: (mask & 4) != 0, alt: (mask & 2) != 0, shift: (mask & 1) != 0, + sup: (mask & 8) != 0, } } diff --git a/src/key.rs b/src/key.rs index d18c7d2bb..7b8346ff5 100644 --- a/src/key.rs +++ b/src/key.rs @@ -72,10 +72,10 @@ const fn new() -> Self { m }; pub(crate) fn is_some(&self) -> bool { - self.ctrl || self.alt || self.shift + *self != Self::new() } pub(crate) fn is_none(&self) -> bool { - !self.is_some() + *self == Self::new() } } From bf455bc31614a6b08524569c6cf0f84e76135829 Mon Sep 17 00:00:00 2001 From: memchr Date: Tue, 25 Mar 2025 10:59:33 +0000 Subject: [PATCH 15/32] completions/btrfs: add new options and commands Also add completion for balance filters (cherry picked from commit 95b93c6bff632a7a49b117d944df89cf5ae5cbe9) --- share/completions/btrfs.fish | 121 ++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 23 deletions(-) diff --git a/share/completions/btrfs.fish b/share/completions/btrfs.fish index e45ecdfa0..a483b81e7 100644 --- a/share/completions/btrfs.fish +++ b/share/completions/btrfs.fish @@ -102,33 +102,36 @@ complete -f -c btrfs -n $restore -s S -l symlink -d 'Restore symbolic links' complete -f -c btrfs -n $restore -s v -l verbose -d Verbose complete -f -c btrfs -n $restore -s i -l ignore-errors -d 'Ignore errors' complete -f -c btrfs -n $restore -s o -l overwrite -d Overwrite -complete -f -c btrfs -n $restore -s t -d 'Tree location' -complete -f -c btrfs -n $restore -s f -d 'Filesystem location' -complete -f -c btrfs -n $restore -s u -l super -d 'Super mirror' -complete -f -c btrfs -n $restore -s r -l root -d 'Root objectid' +complete -f -c btrfs -n $restore -s t -r -d 'Tree location' +complete -f -c btrfs -n $restore -s f -r -d 'Filesystem location' +complete -f -c btrfs -n $restore -s u -l super -r -d 'Super mirror' +complete -f -c btrfs -n $restore -s r -l root -r -d 'Root objectid' complete -f -c btrfs -n $restore -s d -d 'Find dir' complete -f -c btrfs -n $restore -s l -l list-roots -d 'List tree roots' complete -f -c btrfs -n $restore -s D -l dry-run -d 'Only list files that would be recovered' -complete -f -c btrfs -n $restore -l path-regex -d 'Restore only filenames matching regex' +complete -f -c btrfs -n $restore -l path-regex -r -d 'Restore only filenames matching regex' complete -f -c btrfs -n $restore -s c -d 'Ignore case (--path-regex only)' # btrfs send complete -f -c btrfs -n $send -s e -d '' -complete -f -c btrfs -n $send -s p -d 'Send an incremental stream from to ' -complete -f -c btrfs -n $send -s c -d 'Use this snapshot as a clone source for an incremental send' -complete -f -c btrfs -n $send -s f -d 'Output is normally written to stdout' +complete -f -c btrfs -n $send -s p -r -d 'Send an incremental stream from to ' +complete -f -c btrfs -n $send -s c -r -d 'Use this snapshot as a clone source for an incremental send' +complete -f -c btrfs -n $send -s f -r -d 'Output is normally written to stdout' complete -f -c btrfs -n $send -l no-data -d 'send in NO_FILE_DATA mode' complete -f -c btrfs -n $send -s v -l verbose -d 'Enable verbose output to stderr' complete -f -c btrfs -n $send -s q -l quiet -d 'Suppress all messages, except errors' +complete -f -c btrfs -n $send -l proto -a '0 1 2' -r -d 'Use send protocol version' +complete -f -c btrfs -n $send -l proto -l compressed-data -d 'Send compressed data directly' # btrfs receive complete -f -c btrfs -n $receive -s v -d 'Increase verbosity about performed actions' complete -f -c btrfs -n $receive -s q -l quiet -d 'Suppress all messages, except errors' -complete -f -c btrfs -n $receive -s f -d 'Read the stream from FILE instead of stdin' +complete -f -c btrfs -n $receive -s f -r -d 'Read the stream from FILE instead of stdin' complete -f -c btrfs -n $receive -s e -d 'Terminate after receiving an marker in the stream' complete -f -c btrfs -n $receive -s C -l chroot -d 'Confine the process to using chroot' -complete -f -c btrfs -n $receive -s E -l max-errors -d 'Terminate when NUMBER errors occur' -complete -f -c btrfs -n $receive -s m -d 'The root mount point of the destination filesystem' +complete -f -c btrfs -n $receive -s E -l max-errors -r -d 'Terminate when NUMBER errors occur' +complete -f -c btrfs -n $receive -s m -r -d 'The root mount point of the destination filesystem' +complete -f -c btrfs -n $receive -l force-decompress -r -d 'Always decompress data' complete -f -c btrfs -n $receive -l dump -d 'Dump stream metadata' # btrfs help @@ -147,9 +150,11 @@ complete -f -c btrfs -n $subvolume -a show -d 'Show more information about the s complete -f -c btrfs -n $subvolume -a sync -d 'Wait until given subvolume(s) are completely removed from the filesystem.' # btrfs subvolume create complete -f -c btrfs -n '__btrfs_command_groups subvolume create' -s i -d 'Add subvolume to a qgroup (can be given multiple times)' +complete -f -c btrfs -n '__btrfs_command_groups subvolume create' -s p -d 'Create any missing parent directories' # btrfs subvolume delete complete -f -c btrfs -n '__btrfs_command_groups subvolume delete' -s c -l commit-after -d 'Wait for transaction commit at the end of the operation' complete -f -c btrfs -n '__btrfs_command_groups subvolume delete' -s C -l commit-each -d 'Wait for transaction commit after deleting each subvolume' +complete -f -c btrfs -n '__btrfs_command_groups subvolume delete' -s R -l recursive -d 'Delete subvolumes beneath each subvolume recursively' complete -f -c btrfs -n '__btrfs_command_groups subvolume delete' -s v -l verbose -d 'Verbose output of operations' # btrfs subvolume list complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s o -d 'Print only subvolumes below specified path' @@ -164,8 +169,8 @@ complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s s -d 'List on complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s r -d 'List readonly subvolumes (including snapshots)' complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s d -d 'List deleted subvolumes that are not yet cleaned' complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s t -d 'Print the result as a table' -complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s G -d 'Filter the subvolumes by generation' -complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s C -d 'Filter the subvolumes by ogeneration' +complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s G -r -d 'Filter the subvolumes by generation' +complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -s C -r -d 'Filter the subvolumes by ogeneration' complete -f -c btrfs -n '__btrfs_command_groups subvolume list' -l sort -d 'List the subvolume in order' -a '{gen,ogen,rootid,path}' # btrfs subvolume snapshot complete -f -c btrfs -n '__btrfs_command_groups subvolume snapshot' -s r -d 'Create a readonly snapshot' @@ -194,6 +199,7 @@ complete -f -c btrfs -n $filesystem -a defragment -d 'Defragment a file or a dir complete -f -c btrfs -n $filesystem -a resize -d 'Resize a filesystem' complete -f -c btrfs -n $filesystem -a label -d 'Get or change the label of a filesystem' complete -f -c btrfs -n $filesystem -a usage -d 'Show detailed information about internal filesystem usage.' +complete -f -c btrfs -n $filesystem -a mkswapfile -d 'Create a new swapfile' # btrfs filesystem df complete -f -c btrfs -n '__btrfs_command_groups filesystem df' -s b -l raw -d 'Show raw numbers in bytes' complete -f -c btrfs -n '__btrfs_command_groups filesystem df' -s h -l human-readable -d 'Show human friendly numbers, base 1024' @@ -230,9 +236,12 @@ complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s v -d ' complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s r -d 'Defragment files recursively' complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s c -d 'Compress the file while defragmenting' -ra '{zlib,lzo,zstd}' complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s f -d 'Flush data to disk immediately after defragmenting' -complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s s -d 'Defragment only from NUMBER byte onward' -complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s l -d 'Defragment only up to LEN bytes' -complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s t -d 'Target extent SIZE hint' +complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s s -r -d 'Defragment only from NUMBER byte onward' +complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s l -r -d 'Defragment only up to LEN bytes' +complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s t -r -d 'Target extent SIZE hint' +complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -l step -r -d 'Defragment in steps of SIZE' +complete -f -c btrfs -n '__btrfs_command_groups filesystem defragment' -s L -l level -r -d 'Specify compression levels' + # btrfs filesystem usage complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s b -l raw -d 'Show raw numbers in bytes' complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s h -l human-readable -d 'Show human friendly numbers, base 1024' @@ -244,6 +253,11 @@ complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s m -l mbytes complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s g -l gbytes -d 'Show sizes in GiB, or GB with --si' complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s t -l tbytes -d 'Show sizes in TiB, or TB with --si' complete -f -c btrfs -n '__btrfs_command_groups filesystem usage' -s T -d 'Show data in tabular format' +# btrfs filesystem mkswapfile +complete -f -c btrfs -n '__btrfs_command_groups filesystem mkswapfile' -s s -l size -r -d 'Swapfile size' +complete -f -c btrfs -n '__btrfs_command_groups filesystem mkswapfile' -s U -l uuid -r -d 'UUID for the swapfile' +# btrfs filesystem resize +complete -f -c btrfs -n '__btrfs_command_groups filesystem resize' -l enqueue -d 'Wait for other exclusive operations' # btrfs balance complete -f -c btrfs -n $balance -a start -d 'Balance chunks across the devices' @@ -252,13 +266,23 @@ complete -f -c btrfs -n $balance -a cancel -d 'Cancel running or paused balance' complete -f -c btrfs -n $balance -a resume -d 'Resume interrupted balance' complete -f -c btrfs -n $balance -a status -d 'Show status of running or paused balance' # btrfs balance start -complete -f -c btrfs -n '__btrfs_command_groups balance start' -s d -d 'Act on data chunks with FILTERS' -complete -f -c btrfs -n '__btrfs_command_groups balance start' -s m -d 'Act on metadata chunks with FILTERS' -complete -f -c btrfs -n '__btrfs_command_groups balance start' -s s -d 'Act on system chunks with FILTERS (only under -f)' +function __btrfs_balance_filters + set -l profiles raid{0,1{,c3,c4},10,5,6} dup single + set -l btrfs_balance_filters \ + profiles=$profiles\t"Balances only block groups with the given profiles" \ + convert=$profiles\t"Convert selected block groups to given profile" \ + usage= devid= vrange= limit= strips= soft + set -l prefix (commandline -tc | string replace -r '^-d' -- '' | string match -rg '^(.*?)?[^,]*$' -- $token) + printf "%s\n" "$prefix"$btrfs_balance_filters +end +complete -f -c btrfs -n '__btrfs_command_groups balance start' -s d -ra '(__btrfs_balance_filters)' -d 'Act on data chunks with FILTERS' +complete -f -c btrfs -n '__btrfs_command_groups balance start' -s m -ra '(__btrfs_balance_filters)' -d 'Act on metadata chunks with FILTERS' +complete -f -c btrfs -n '__btrfs_command_groups balance start' -s s -ra '(__btrfs_balance_filters)' -d 'Act on system chunks with FILTERS (only under -f)' complete -f -c btrfs -n '__btrfs_command_groups balance start' -s v -d 'Be verbose' complete -f -c btrfs -n '__btrfs_command_groups balance start' -s f -d 'Force a reduction of metadata integrity' complete -f -c btrfs -n '__btrfs_command_groups balance start' -l full-balance -d 'Do not print warning and do not delay start' complete -f -c btrfs -n '__btrfs_command_groups balance start' -l background -l bg -d 'Run the balance as a background process' +complete -f -c btrfs -n '__btrfs_command_groups balance start' -l enqueue -d 'Wait for other exclusive operations' # btrfs balance status complete -f -c btrfs -n '__btrfs_command_groups balance status' -s v -d 'Be verbose' @@ -279,6 +303,10 @@ complete -f -c btrfs -n '__btrfs_command_groups device scan' -s u -l forget -d ' # btrfs device stats complete -f -c btrfs -n '__btrfs_command_groups device stats' -s c -l check -d 'Return non-zero if any stat counter is not zero' complete -f -c btrfs -n '__btrfs_command_groups device stats' -s z -l reset -d 'Show current stats and reset values to zero' +complete -f -c btrfs -n '__btrfs_command_groups device stats' -s T -d "Print stats in a tabular form" +# btrfs device remove +complete -f -c btrfs -n '__btrfs_command_groups device remove' -l enqueue -d 'Wait for other exclusive operations' +complete -f -c btrfs -n '__btrfs_command_groups device remove' -l force -d 'Skip the safety timeout for removing multiple devices' # btrfs device usage complete -f -c btrfs -n '__btrfs_command_groups device usage' -s b -l raw -d 'Show raw numbers in bytes' complete -f -c btrfs -n '__btrfs_command_groups device usage' -s h -l human-readable -d 'Show human friendly numbers, base 1024' @@ -295,12 +323,18 @@ complete -f -c btrfs -n $scrub -a start -d 'Start a new scrub. If a scrub is alr complete -f -c btrfs -n $scrub -a cancel -d 'Cancel a running scrub' complete -f -c btrfs -n $scrub -a resume -d 'Resume previously canceled or interrupted scrub' complete -f -c btrfs -n $scrub -a status -d 'Show status of running or finished scrub' +complete -f -c btrfs -n $scrub -a limit -d 'Show or set scrub limits on devices of the given filesystem' +# btrfs scrub limit +complete -f -c btrfs -n '__btrfs_command_groups scrub limit' -s d -l devid -d 'Select the device by DEVID to apply the limit' +complete -f -c btrfs -n '__btrfs_command_groups scrub limit' -s l -l limit -d 'Set the limit of the device' +complete -f -c btrfs -n '__btrfs_command_groups scrub limit' -s a -l all -d 'Apply the limit to all devices' # btrfs scrub start complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s B -d 'Do not background' complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s d -d 'Stats per device (-B only)' complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s q -d 'Be quiet' complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s r -d 'Read only mode' complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s R -d 'Raw print mode, print full data instead of summary' +complete -f -c btrfs -n '__btrfs_command_groups scrub start' -l limit -d 'Set the scrub throughput limit' complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s c -d 'Set ioprio class (see ionice(1) manpage)' complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s n -d 'Set ioprio classdata (see ionice(1) manpage)' complete -f -c btrfs -n '__btrfs_command_groups scrub start' -s f -d 'Force starting new scrub' @@ -321,6 +355,9 @@ complete -f -c btrfs -n $rescue -a chunk-recover -d 'Recover the chunk tree by s complete -f -c btrfs -n $rescue -a super-recover -d 'Recover bad superblocks from good copies' complete -f -c btrfs -n $rescue -a zero-log -d 'Clear the tree log. Usable if it\'s corrupted and prevents mount.' complete -f -c btrfs -n $rescue -a fix-device-size -d 'Re-align device and super block sizes' +complete -f -c btrfs -n $rescue -a clear-ino-cache -d 'Remove leftover items pertaining to the deprecated inode cache feature' +complete -f -c btrfs -n $rescue -a clear-space-cache -d 'Completely remove the on-disk data of free space cache of given version' +complete -f -c btrfs -n $rescue -a clear-uuid-tree -d 'Clear the UUID tree' # btrfs rescue chunk-recover complete -f -c btrfs -n '__btrfs_command_groups rescue chunk-recover' -s y -d 'Assume an answer of YES to all questions' complete -f -c btrfs -n '__btrfs_command_groups rescue chunk-recover' -s v -d 'Verbose mode' @@ -337,6 +374,8 @@ complete -f -c btrfs -n $inspect_internal -a min-dev-size -d 'Get the minimum si complete -f -c btrfs -n $inspect_internal -a dump-tree -d 'Dump tree structures from a given device' complete -f -c btrfs -n $inspect_internal -a dump-super -d 'Dump superblock from a device in a textual form' complete -f -c btrfs -n $inspect_internal -a tree-stats -d 'Print various stats for trees' +complete -f -c btrfs -n $inspect_internal -a list-chunks -d 'Enumerate chunks on all devices' +complete -f -c btrfs -n $inspect_internal -a map-swapfile -d 'Find device-specific physical offset of file that can be used for hibernation' # btrfs inspect-internal inode-resolve complete -f -c btrfs -n '__btrfs_command_groups inspect-internal inode-resolve' -s v -d 'Verbose mode' # btrfs inspect-internal logical-resolve @@ -351,20 +390,50 @@ complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s d complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s r -l roots -d 'Print only short root node info' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s R -l backups -d 'Print short root node info and backup root info' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s u -l uuid -d 'Print only the uuid tree' -complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s b -l block -d 'Print info from the specified BLOCK only' -complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s t -l tree -d 'Print only tree with the given ID' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s b -l block -r -d 'Print info from the specified BLOCK only' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -s t -l tree -r -d 'Print only tree with the given ID' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l follow -d 'Use with -b, to show all children tree blocks of ' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l noscan -d 'Do not scan the devices from the filesystem' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l bfs -d 'Breadth-first traversal of the trees, print nodes, then leaves' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l dfs -d 'Depth-first traversal of the trees' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l hide-names -d 'Print placeholder instead of names' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l csum-headers -d 'Print b-tree node checksums in headers' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-tree' -l csum-items -d 'Print checksums stored in checksum items' + # btrfs inspect-internal dump-super complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -s f -l full -d 'Print full superblock information, backup roots etc.' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -s a -l all -d 'Print information about all superblocks' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -s s -l super -d 'Specify which SUPER-BLOCK copy to print out' -ra '{0,1,2}' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -s F -l force -d 'Attempt to dump superblocks with bad magic' complete -f -c btrfs -n '__btrfs_command_groups inspect-internal dump-super' -l bytenr -d 'Specify alternate superblock OFFSET' +# btrfs inspect-internal logical-resolve +complete -f -c btrfs -n '__btrfs_command_groups logical-resolve' -s P -d 'Print inodes instead of resolving paths' +complete -f -c btrfs -n '__btrfs_command_groups logical-resolve' -s o -d 'Ignore offsets, find all references to an extent' +complete -f -c btrfs -n '__btrfs_command_groups logical-resolve' -s s -r -d 'Set internal buffer size for storing file names' # btrfs inspect-internal tree-stats complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -s b -d 'Show raw numbers in bytes' +# btrfs inspect-internal list-chunks +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l sort -ra 'devid pstart lstart usage length' -d 'Sort by a column (ascending)' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l raw -d 'Show raw numbers in bytes' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l human-readable -d 'Show human friendly numbers, base 1024' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l iec -d 'Use 1024 as a base (KiB, MiB, GiB, TiB)' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l si -d 'Use 1000 as a base (kB, MB, GB, TB)' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l kbytes -d 'Show sizes in KiB, or kB with --si' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l mbytes -d 'Show sizes in MiB, or MB with --si' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l gbytes -d 'Show sizes in GiB, or GB with --si' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal list-chunks' -l tbytes -d 'Show sizes in TiB, or TB with --si' +# btrfs inspect-internal map-swapfile +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal map-swapfile' -l resume-offset -s r -d 'Print the value suitable as resume offset for /sys/power/resume_offset.' +# btrfs inspect-internal tree-stats +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -s t -r -d 'Print stats only for the given treeid' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l raw -s b -d 'Show raw numbers in bytes' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l human-readable -d 'Show human friendly numbers, base 1024' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l iec -d 'Use 1024 as a base (KiB, MiB, GiB, TiB)' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l si -d 'Use 1000 as a base (kB, MB, GB, TB)' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l kbytes -d 'Show sizes in KiB, or kB with --si' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l mbytes -d 'Show sizes in MiB, or MB with --si' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l gbytes -d 'Show sizes in GiB, or GB with --si' +complete -f -c btrfs -n '__btrfs_command_groups inspect-internal tree-stats' -l tbytes -d 'Show sizes in TiB, or TB with --si' # btrfs property complete -f -c btrfs -n $property -a get -d 'Get a property value of a btrfs object' @@ -381,9 +450,12 @@ complete -f -c btrfs -n '__btrfs_command_groups property list' -s t -d 'List pro complete -f -c btrfs -n $quota -a enable -d 'Enable subvolume quota support for a filesystem.' complete -f -c btrfs -n $quota -a disable -d 'Disable subvolume quota support for a filesystem.' complete -f -c btrfs -n $quota -a rescan -d 'Trash all qgroup numbers and scan the metadata again with the current config.' +# btrfs quota enable +complete -f -c btrfs -n '__btrfs_command_groups quota enable' -s s -l simple -d 'Use simple quotas' # btrfs quota rescan -complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s s -d 'Show status of a running rescan operation' -complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s w -d 'Wait for rescan operation to finish' +complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s s -l status -d 'Show status of a running rescan operation' +complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s w -l wait -d 'Wait for rescan operation to finish' +complete -f -c btrfs -n '__btrfs_command_groups quota rescan' -s W -l wait-norescan -d 'Wait for rescan to finish without starting it' # btrfs qgroup complete -f -c btrfs -n $qgroup -a assign -d 'Assign SRC as the child qgroup of DST' @@ -391,6 +463,7 @@ complete -f -c btrfs -n $qgroup -a remove -d 'Remove a child qgroup SRC from DST complete -f -c btrfs -n $qgroup -a create -d 'Create a subvolume quota group.' complete -f -c btrfs -n $qgroup -a destroy -d 'Destroy a quota group.' complete -f -c btrfs -n $qgroup -a show -d 'Show subvolume quota groups.' +complete -f -c btrfs -n $qgroup -a clear-stale -d 'Clear all stale qgroups whose subvolume does not exist' complete -f -c btrfs -n $qgroup -a limit -d 'Set the limits a subvolume quota group.' # btrfs qgroup assign complete -f -c btrfs -n '__btrfs_command_groups qgroup assign' -l rescan -d 'Schedule qutoa rescan if needed' @@ -424,5 +497,7 @@ complete -f -c btrfs -n $replace -a cancel -d 'Cancel a running device replace o complete -f -c btrfs -n '__btrfs_command_groups replace start' -s r -d 'Only read from if no other zero-defect mirror exists' complete -f -c btrfs -n '__btrfs_command_groups replace start' -s f -d 'Force using and overwriting ' complete -f -c btrfs -n '__btrfs_command_groups replace start' -s B -d 'Do not background' +complete -f -c btrfs -n '__btrfs_command_groups replace start' -l enqueue -d "Wait if there's another exclusive operation running" +complete -f -c btrfs -n '__btrfs_command_groups replace start' -s K -l nodiscard -d 'Do not perform TRIM on DEVICES' # btrfs replace status complete -f -c btrfs -n '__btrfs_command_groups replace status' -s 1 -d 'Only print once until the replace operation finishes' From 84c03c6f26beaf09f8fb627e96c7e9278ea40aad Mon Sep 17 00:00:00 2001 From: Jonathan Palardy Date: Tue, 25 Mar 2025 23:14:27 -0700 Subject: [PATCH 16/32] completions/git: Added autostash option to git merge (cherry picked from commit 269ed5ddf431076a4cf24f65c54ad02623441a0e) --- share/completions/git.fish | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/completions/git.fish b/share/completions/git.fish index 89057e342..12a5a1edb 100644 --- a/share/completions/git.fish +++ b/share/completions/git.fish @@ -1798,6 +1798,8 @@ complete -f -c git -n '__fish_git_using_command merge' -l rerere-autoupdate -d ' complete -f -c git -n '__fish_git_using_command merge' -l no-rerere-autoupdate -d 'Do not use previous conflict resolutions' complete -f -c git -n '__fish_git_using_command merge' -l abort -d 'Abort the current conflict resolution process' complete -f -c git -n '__fish_git_using_command merge' -l continue -d 'Conclude current conflict resolution process' +complete -f -c git -n '__fish_git_using_command merge' -l autostash -d 'Before starting merge, stash local changes, and apply stash when done' +complete -f -c git -n '__fish_git_using_command merge' -l no-autostash -d 'Do not stash local changes before starting merge' ### merge-base complete -f -c git -n __fish_git_needs_command -a merge-base -d 'Find a common ancestor for a merge' From b71027f62237f6528ce2b7a52b1bed07ba48b76a Mon Sep 17 00:00:00 2001 From: memchr Date: Wed, 26 Mar 2025 05:46:37 +0000 Subject: [PATCH 17/32] completions/git: add --filter option supported subcommands: - clone - fetch - submodule update - rev-list (cherry picked from commit 795d6b6c4005122ba1460108736483de6ae660eb) --- share/completions/git.fish | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/share/completions/git.fish b/share/completions/git.fish index 12a5a1edb..033516279 100644 --- a/share/completions/git.fish +++ b/share/completions/git.fish @@ -899,6 +899,15 @@ function __fish_git_is_rebasing test -e (__fish_git rev-parse --absolute-git-dir)/rebase-merge end +function __fish_git_filters + printf "%s\n" \ + blob:none\t"omits all blobs" \ + blob:limit=\t"omits blobs by size" \ + object:type={tag,commit,tree,blob}\t"omit object which are not of the requested type" \ + sparse:oid=\t"omit blobs not required for a sparse checkout" \ + tree:\t"omits all blobs and trees" +end + # general options complete git -f -l help -s h -d 'Display manual of a Git command' complete git -f -n __fish_git_needs_command -l version -s v -d 'display git version' @@ -1033,6 +1042,7 @@ complete -f -c git -n '__fish_git_using_command fetch' -l unshallow -d 'Convert complete -f -c git -n '__fish_git_using_command fetch' -l refetch -d 'Re-fetch without negotiating common commits' complete -f -c git -n '__fish_git_using_command fetch' -l negotiation-tip -d 'Only report commits reachable from these tips' -kxa '(__fish_git_commits; __fish_git_branches)' complete -f -c git -n '__fish_git_using_command fetch' -l negotiate-only -d "Don't fetch, only show commits in common with the server" +complete -f -c git -n '__fish_git_using_command fetch' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server' # TODO other options @@ -1347,6 +1357,7 @@ complete -f -c git -n '__fish_git_using_command clone' -s o -l origin -d 'Use a complete -f -c git -n '__fish_git_using_command clone' -s b -l branch -d 'Use a specific branch instead of the one used by the cloned repository' complete -f -c git -n '__fish_git_using_command clone' -l depth -d 'Truncate the history to a specified number of revisions' complete -f -c git -n '__fish_git_using_command clone' -l recursive -d 'Initialize all submodules within the cloned repository' +complete -f -c git -n '__fish_git_using_command clone' -l filter -ra '(__fish_git_filters)' -d 'Partial clone by requesting a subset of objects from server' ### commit complete -c git -n __fish_git_needs_command -a commit -d 'Record changes to the repository' @@ -1595,6 +1606,7 @@ complete -c git -n '__fish_git_using_command log rev-list' -l bisect complete -c git -n '__fish_git_using_command log rev-list' -l stdin -d 'Read commits from stdin' complete -c git -n '__fish_git_using_command log rev-list' -l cherry-mark -d 'Mark equivalent commits with = and inequivalent with +' complete -c git -n '__fish_git_using_command log rev-list' -l cherry-pick -d 'Omit equivalent commits' +complete -f -c git -n '__fish_git_using_command rev-list' -l filter -ra '(__fish_git_filters)' -d 'Omits objects from the list of printed objects' complete -c git -n '__fish_git_using_command log' -l left-only complete -c git -n '__fish_git_using_command log' -l right-only complete -c git -n '__fish_git_using_command log' -l cherry @@ -2280,6 +2292,7 @@ complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subco complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from update' -s N -l no-fetch -d "Don't fetch new objects from the remote" complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from update' -l remote -d "Instead of using superproject's SHA-1, use the state of the submodule's remote-tracking branch" complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from update' -l force -d "Discard local changes when switching to a different commit & always run checkout" +complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from update' -l filter -ra '(__fish_git_filters)' -d 'Request a subset of objects from server' complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from add' -l force -d "Also add ignored submodule path" complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from deinit' -l force -d "Remove even with local changes" complete -f -c git -n '__fish_git_using_command submodule' -n '__fish_seen_subcommand_from deinit' -l all -d "Remove all submodules" From 1a58d3f08bf473d29eea96905f5f9a450c489f30 Mon Sep 17 00:00:00 2001 From: memchr Date: Mon, 24 Mar 2025 08:08:58 +0000 Subject: [PATCH 18/32] completions/cryptsetup: complete device mapping names The commands 'close', 'resize', and 'status' each take 'name' as their solo argument. Signed-off-by: memchr (cherry picked from commit 5012bcb97607c7faa770992e4db98921f6314fc8) --- share/completions/cryptsetup.fish | 3 +++ 1 file changed, 3 insertions(+) diff --git a/share/completions/cryptsetup.fish b/share/completions/cryptsetup.fish index 7a50c52dc..269db941b 100644 --- a/share/completions/cryptsetup.fish +++ b/share/completions/cryptsetup.fish @@ -94,3 +94,6 @@ complete -c cryptsetup -l veracrypt-query-pim -d "Query Personal Iteration Multi complete -c cryptsetup -l verbose -s v -d "Shows more detailed error messages" complete -c cryptsetup -l verify-passphrase -s y -d "Verifies the passphrase by asking for it twice" complete -c cryptsetup -l version -s V -d "Print package version" + +# subcommands +complete -c cryptsetup -n "__fish_seen_subcommand_from close status resize" -f -r -a "(path basename /dev/mapper/* | string match -v control)" From ff0980c4c16090641029618b8fb13433edf18b63 Mon Sep 17 00:00:00 2001 From: memchr Date: Mon, 24 Mar 2025 07:38:45 +0000 Subject: [PATCH 19/32] completions/systemd-analyze: add new options and subcommands options: - instance - image - image-policy - tldr - unit - table - no-legend - detailed - scale-svg - malloc subcommands: - filesystems - compare-versions - inspect-elf - fdstore - has-tpm2 - pcrs - srk - architectures - smbios11 fix a typo in timespan completion Signed-off-by: memchr (cherry picked from commit 3744c02a01386fd75d24653e8b2ba8c53f0ad7bb) --- share/completions/systemd-analyze.fish | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/share/completions/systemd-analyze.fish b/share/completions/systemd-analyze.fish index 7e1c103c8..6e097ebaa 100644 --- a/share/completions/systemd-analyze.fish +++ b/share/completions/systemd-analyze.fish @@ -16,9 +16,18 @@ complete -c systemd-analyze -l to-pattern -d 'dot: show relationships matching r complete -c systemd-analyze -l fuzz -x -d 'critical-chain: also show units which finished timespan earlier than last unit in same level' complete -c systemd-analyze -l man -xa no -d 'Do not invoke man to verify the existence of man pages' complete -c systemd-analyze -l generators -d 'Invoke unit generators' +complete -c systemd-analyze -l instance -r -d 'Fallback instance name for template units' complete -c systemd-analyze -l root -xa "(__fish_complete_directories)" -d 'With cat-files, show config files underneath the specified root path' +complete -c systemd-analyze -l image -r -d 'With cat-files, show config files inside the specified image path' +complete -c systemd-analyze -l image-policy -d 'Disk image dissection policy' complete -c systemd-analyze -l iterations -x -d 'calendar: show number of iterations the calendar expression will elapse next' complete -c systemd-analyze -l base-time -x -d 'calendar: show next iterations relative to the specified point in time' +complete -c systemd-analyze -l tldr -d 'cat-config: skip comments, empty lines and section headers' +complete -c systemd-analyze -l unit -r -d "condition: evaluate Condition and Assert assignments in unit file" +complete -c systemd-analyze -l table -d 'plot: output raw time data in a table' +complete -c systemd-analyze -l no-legend -d 'plot: exclude legends/hints' +complete -c systemd-analyze -l detailed -d "plot: show activation timestamps details in SVG plot" +complete -c systemd-analyze -l scale-svg -r -d "plot: stretch the x-axis of the plot" complete -c systemd-analyze -s H -l host -xa "(__fish_complete_user_at_hosts)" -d 'Execute the operation on a remote host' complete -c systemd-analyze -s M -l machine -xa "(__fish_systemd_machines)" -d 'Execute operation on a VM or container' complete -c systemd-analyze -s h -l help -d 'Print a short help and exit' @@ -34,6 +43,7 @@ complete -c systemd-analyze -n __fish_use_subcommand -a critical-chain -d "Print complete -c systemd-analyze -n "__fish_seen_subcommand_from critical-chain" -a "(__fish_systemd_units)" complete -c systemd-analyze -n __fish_use_subcommand -a dump -d "Output serialization of server state" +complete -c systemd-analyze -n __fish_use_subcommand -a malloc -d "Output internal memory state of D-Bus service" complete -c systemd-analyze -n __fish_use_subcommand -a plot -d "Output SVG graphic showing service initialization" complete -c systemd-analyze -n __fish_use_subcommand -a dot -d "Output dependency graph in dot(1) format" complete -c systemd-analyze -n __fish_use_subcommand -a unit-paths -d "List all directories from which unit files may be loaded" @@ -41,15 +51,25 @@ complete -c systemd-analyze -n __fish_use_subcommand -a exit-status -d "List exi complete -c systemd-analyze -n __fish_use_subcommand -a capability -d "List Linux capabilities along with their numeric IDs" complete -c systemd-analyze -n __fish_use_subcommand -a condition -d "Evaluate Condition and Assert assignments" complete -c systemd-analyze -n __fish_use_subcommand -a syscall-filter -d "List system calls contained in the specified system call set" +complete -c systemd-analyze -n __fish_use_subcommand -a filesystems -d "List filesystems" complete -c systemd-analyze -n __fish_use_subcommand -a calendar -d "Normalize repetitive calendar events and calculate when they elapse next" complete -c systemd-analyze -n __fish_use_subcommand -a timestamp -d "Parse timestamp and output the normalized form" -complete -c systemd-analyze -n __fish_use_subcommand -a timestamp -d "Parse time span and output the normalized form" +complete -c systemd-analyze -n __fish_use_subcommand -a timespan -d "Parse time span and output the normalized form" complete -c systemd-analyze -n __fish_use_subcommand -a cat-config -d "Show contents of a config file" complete -c systemd-analyze -n "__fish_seen_subcommand_from cat-config" -F +complete -c systemd-analyze -n __fish_use_subcommand -a compare-versions -d "Compare two version strings" complete -c systemd-analyze -n __fish_use_subcommand -a verify -d "Check unit files for correctness" complete -c systemd-analyze -n "__fish_seen_subcommand_from verify" -F complete -c systemd-analyze -n __fish_use_subcommand -a security -d "Analyze security settings of specified service units" complete -c systemd-analyze -n "__fish_seen_subcommand_from security" -a "(__fish_systemctl_services)" +complete -c systemd-analyze -n __fish_use_subcommand -a inspect-elf -d "Parse and print ELF object packaging metadata" +complete -c systemd-analyze -n __fish_use_subcommand -a fdstore -d "List contents of service unit's file descriptor store" +complete -c systemd-analyze -n __fish_use_subcommand -a image-policy -d "Analyze image policy string" +complete -c systemd-analyze -n __fish_use_subcommand -a has-tpm2 -d "Report TPM2 support" +complete -c systemd-analyze -n __fish_use_subcommand -a pcrs -d "Show known TPM2 PCRs" +complete -c systemd-analyze -n __fish_use_subcommand -a srk -d "Read Storage Root Key from TPM2 device" +complete -c systemd-analyze -n __fish_use_subcommand -a architectures -d "List known CPU architectures" +complete -c systemd-analyze -n __fish_use_subcommand -a smbios11 -d "Show SMBIOS Type #11 strings passed to the system" From f9a03215b80a017094b37746c71ace331ec4cdf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Martinez?= Date: Tue, 18 Mar 2025 22:39:52 +0000 Subject: [PATCH 20/32] Add `wlr-randr` completions (cherry picked from commit ea8e122fada5412b2bfea4958d2288d45009a7aa) --- share/completions/wlr-randr.fish | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 share/completions/wlr-randr.fish diff --git a/share/completions/wlr-randr.fish b/share/completions/wlr-randr.fish new file mode 100644 index 000000000..2d99ba64c --- /dev/null +++ b/share/completions/wlr-randr.fish @@ -0,0 +1,58 @@ +# `wlr-randr` completions. +# See: https://gitlab.freedesktop.org/emersion/wlr-randr + +function __fish_print_wlr-randr_outputs --argument-names exclude + if command -q jq + wlr-randr --json | jq \ + --raw-output \ + --arg exclude "$exclude" \ + '.[] | select(.name != $exclude) | "\(.name)\t\(.make), \(.model)"' + end +end + +function __fish_get_wlr-randr-current_output + set -l last_output + set -l tokens (commandline -xpc) + + while set -q tokens[1] + if test "$tokens[1]" = --output + set last_output "$tokens[2]" + set -e tokens[1] + else if string match -qr -- '^--output=' "$tokens[1]" + set last_output (string replace -r -- '--output=(.*)' '$1' "$tokens[1]") + end + + set -e tokens[1] + end + + printf "%s" $last_output +end + +function __fish_complete_wlr-randr_modes + if command -q jq + set -l output (__fish_get_wlr-randr-current_output) + + wlr-randr --json | jq \ + --raw-output \ + --arg output "$output" \ + --arg preferred_str Preferred \ + --arg empty_str '' \ + '.[] | select(.name == $output) | .modes[] | "\(.width)x\(.height)\t\(if .preferred then $preferred_str else $empty_str end)"' + end +end + +complete -c wlr-randr -f +complete -c wlr-randr -s h -l help -d 'Show help' +complete -c wlr-randr -l json -d 'Print as JSON' +complete -c wlr-randr -l dryrun -d 'Dry run' +complete -c wlr-randr -l output -x -d Output -a '(__fish_print_wlr-randr_outputs)' +complete -c wlr-randr -l on -d 'Turn on' +complete -c wlr-randr -l off -d 'Turn off' +complete -c wlr-randr -l mode -x -d Mode -a '(__fish_complete_wlr-randr_modes)' +complete -c wlr-randr -l pos -r -d Position +complete -c wlr-randr -l left-of -x -d 'Relative left position' -a '(__fish_print_wlr-randr_outputs (__fish_get_wlr-randr-current_output))' +complete -c wlr-randr -l right-of -x -d 'Relative right position' -a '(__fish_print_wlr-randr_outputs (__fish_get_wlr-randr-current_output))' +complete -c wlr-randr -l above -x -d 'Relative top position' -a '(__fish_print_wlr-randr_outputs (__fish_get_wlr-randr-current_output))' +complete -c wlr-randr -l below -x -d 'Relative bottom position' -a '(__fish_print_wlr-randr_outputs (__fish_get_wlr-randr-current_output))' +complete -c wlr-randr -l transform -x -d Transformation -a 'normal\t 90\t 180\t 270\t flipped\t flipped-90\t flipped-180\t flipped-270\t' +complete -c wlr-randr -l scale -x -d Scale From b5877ebe44d9981dd30491268c0c3073fe557cc0 Mon Sep 17 00:00:00 2001 From: Ilya Grigoriev Date: Tue, 14 Jan 2025 22:46:08 -0800 Subject: [PATCH 21/32] jj completions: use dynamic completions by default, also fix This uses jj's dynamic completions when possible. This avoids an annoying problem. After 04a4e5c4, jj's dynamic completions (see the second paragraph of ) do not work very well in fish if the user puts `COMPLETE=fish jj | source` in their `~/.config/fish/config.fish`. When the user types `jj `, they are instead overridden by fish's built-in non-dynamic completions. The difference is subtle. One problem I saw is that `jj new ` works as expected (and shows revisions) while `jj new -A ` becomes broken (and shows files). If the user puts `COMPLETE=fish jj | source` in `~/.config/fish/completions/jj.fish` there is no problem. However, users might be confused if they run `COMPLETE=fish jj | source` or put it in their config and it works in a broken fashion. I certainly was. Meanwhile, I checked that if the user has `jj completion fish | source` in their `config.fish`, executing `COMPLETE=fish jj __this_command_does_not_exist | source` afterwards still works correctly. Let me know if there's a better approach to this problem. (cherry picked from commit 932010cd04e8d757ead0c8287b6407ebd2582b78) --- share/completions/jj.fish | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/share/completions/jj.fish b/share/completions/jj.fish index 8ff5afc10..d1225816d 100644 --- a/share/completions/jj.fish +++ b/share/completions/jj.fish @@ -1 +1,10 @@ -jj util completion fish | source +# The reason for `__this-command-does-not-exist` is that, if dynamic completion +# is not implemented, we'd like to get an error reliably. However, the +# behavior of `jj` without arguments depends on the value of a config, see +# https://jj-vcs.github.io/jj/latest/config/#default-command +if set -l completion (COMPLETE=fish jj __this-command-does-not-exist 2>/dev/null) + # jj is new enough for dynamic completions to be implemented + printf %s\n $completion | source +else + jj util completion fish | source +end From 05ae55b17223ece060d9095a40a12788ce00bb7c Mon Sep 17 00:00:00 2001 From: Farhood Etaati Date: Sat, 18 Jan 2025 12:39:21 +0330 Subject: [PATCH 22/32] Adds git subtree completion Closes #11063 (cherry picked from commit 48306409efd642ccf032aafff25c82c163b5eb4e) --- share/completions/git-subtree.fish | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 share/completions/git-subtree.fish diff --git a/share/completions/git-subtree.fish b/share/completions/git-subtree.fish new file mode 100644 index 000000000..92dc08b79 --- /dev/null +++ b/share/completions/git-subtree.fish @@ -0,0 +1,27 @@ +complete -f -c git -a subtree -d 'Manage git subtrees' +# Git subtree common completions +complete -f -c git -n '__fish_git_using_command subtree' -s q -l quiet -d 'Suppress output' +complete -f -c git -n '__fish_git_using_command subtree' -s d -l debug -d 'Debug output' +complete -f -c git -n '__fish_git_using_command subtree' -s P -l path -d 'Path to the subtree' + +# Git subtree subcommands +complete -f -c git -n '__fish_git_using_command subtree' -a add -d "Add a new subtree to the repository" +complete -f -c git -n '__fish_git_using_command subtree' -a merge -d "Merge changes from a subtree into the repository" +complete -f -c git -n '__fish_git_using_command subtree' -a split -d "Extract a subtree from the repository" +complete -f -c git -n '__fish_git_using_command subtree' -a pull -d "Fetch and integrate changes from a remote subtree" +complete -f -c git -n '__fish_git_using_command subtree' -a push -d "Push changes to a remote subtree" + +# Completions for push and split subcommands +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -l annotate -d 'Annotate the commit' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -s b -l branch -d 'Branch to split' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -l ignore-joins -d 'Ignore joins during history reconstruction' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -l onto -d 'Specify the commit ID to start history reconstruction' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split' -l rejoin -d 'Merge the synthetic history back into the main project' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split; and __fish_contains_opt rejoin' -l squash -d 'Merge subtree changes as a single commit' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split; and __fish_contains_opt rejoin' -l no-squash -d 'Do not merge subtree changes as a single commit' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from push split; and __fish_contains_opt rejoin' -s m -l message -d 'Use the given message as the commit message for the merge commit' + +# Completion for add and merge subcommands +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from add merge' -l squash -d 'Merge subtree changes as a single commit' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from add merge' -l no-squash -d 'Do not merge subtree changes as a single commit' +complete -f -c git -n '__fish_git_using_command subtree; and __fish_seen_subcommand_from add merge' -s m -l message -d 'Use the given message as the commit message for the merge commit' From ba49981f17755c231034cd5ced6d49409f1450c6 Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Sat, 15 Mar 2025 10:52:04 +0100 Subject: [PATCH 23/32] tests: Just check that the version starts with a digit Our versions look like 4.0.0 4.0b1 4.0.1-535-abfef-dirty But packagers may want to add more information here, and we don't really care. Note that we no longer ever set the version to "unknown" since 5abd0e46f50449d42febfe343dcfab6efcaa8d74. Supersedes #11173 (cherry picked from commit 411a396fa9fff97480de37192fa37e83b36b4784) --- tests/checks/version.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/checks/version.fish b/tests/checks/version.fish index 398239d1d..33c5590e7 100644 --- a/tests/checks/version.fish +++ b/tests/checks/version.fish @@ -1,2 +1,2 @@ #RUN: %fish -v -# CHECK: fish, version {{[-.gabcdeflphat0-9]*(irty)?}} +# CHECK: fish, version {{[0-9].*}} From a7f717c59c3e25f339e9a26a8d44edaf262ecfa0 Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Wed, 2 Apr 2025 17:06:55 +0200 Subject: [PATCH 24/32] CHANGELOG for 4.0.2 --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 88b3d1486..97206aaf2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,11 @@ This release of fish includes the following improvements compared to fish 4.0.1: - The warning when the terminfo database couldn't be found has been downgraded to a log message. This is because fish will fall back to what xterm-256color would do, which in the majority of cases would just work (:issue:`11277`, :issue:`11290`). +- The cargo completions have been sped up, especially by no longer looking up crates online (:issue:`11347`). +- The hg prompt no longer errors if $PWD includes a newline (:issue:`11348`). +- Super-modified keys are no longer inserted +- Various additions to completions, including git (:issue:`11322`, :issue:`11323`), btrfs (:issue:`11320`), cryptsetup (:issue:`11315`), systemd-analyze (:issue:`11314`), + wl-randr (:issue:`11301`), jj (:issue:`11046`) fish 4.0.1 (released March 12, 2025) ==================================== From d88d3122c8dfced330e731f8973ab5011f88b4c8 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Fri, 4 Apr 2025 14:09:05 +0200 Subject: [PATCH 25/32] Fix crash when history pager is closed before search With bind ctrl-r 'sleep 1' history-pager typing ctrl-r,escape crashes fish in the history pager completion callback, because the history pager has already been closed. Prior to 55fd43d86c (Port reader, 2023-12-22), the completion callback would not crash open a pager -- which causes weird races with the user input. Apparently this crash as been triggered by running "playwright", and -- while that's running typing ctrl-r ligh escape. Those key strokes were received while the kitty keyboard protocol was active, possibly a race. Fixes #11355 (cherry picked from commit c94e30293aa1a7642d302cd239f776e09ec094f9) --- CHANGELOG.rst | 1 + src/reader.rs | 4 +++- tests/pexpects/bind.py | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 97206aaf2..cbe237cbe 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ This release of fish includes the following improvements compared to fish 4.0.1: - The cargo completions have been sped up, especially by no longer looking up crates online (:issue:`11347`). - The hg prompt no longer errors if $PWD includes a newline (:issue:`11348`). - Super-modified keys are no longer inserted +- A crash when closing the ``ctrl-r`` search has been fixed. - Various additions to completions, including git (:issue:`11322`, :issue:`11323`), btrfs (:issue:`11320`), cryptsetup (:issue:`11315`), systemd-analyze (:issue:`11314`), wl-randr (:issue:`11301`), jj (:issue:`11046`) diff --git a/src/reader.rs b/src/reader.rs index 087b41035..87c39211b 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -4831,7 +4831,9 @@ fn fill_history_pager( if search_term != zelf.pager.search_field_line.text() { return; // Stale request. } - let history_pager = zelf.history_pager.as_mut().unwrap(); + let Some(history_pager) = zelf.history_pager.as_mut() else { + return; // Pager has been closed. + }; history_pager.direction = direction; match direction { SearchDirection::Forward => { diff --git a/tests/pexpects/bind.py b/tests/pexpects/bind.py index fdbe9b629..fec9f2952 100644 --- a/tests/pexpects/bind.py +++ b/tests/pexpects/bind.py @@ -345,7 +345,7 @@ send("\x1b") expect_str("foo") send("\x1b[A") expect_str("bind escape 'echo foo'") -sendline("") +sendline("bind --erase escape") expect_prompt() send(" a b c d\x01") # ctrl-a, move back to the beginning of the line @@ -394,6 +394,11 @@ expect_prompt() sendline("commandline -f and") expect_prompt() +sendline("bind ctrl-g 'sleep 1' history-pager") +expect_prompt() +send("\x07") # ctrl-g +send("\x1b[27u") # escape, to close pager + # Check that the builtin version of `exit` works # (for obvious reasons this MUST BE LAST) sendline("function myexit; echo exit; exit; end; bind ctrl-z myexit") From d95b662542e9b903dabbc44a53804746653772bb Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Tue, 8 Apr 2025 17:13:18 +0200 Subject: [PATCH 26/32] docs: Fix string-match glob examples `?` no longer is a wildcard. See #11361 (cherry picked from commit eb4a0b25600fc84ef651651de24ca4c202b33d4f) --- doc_src/cmds/string-match.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc_src/cmds/string-match.rst b/doc_src/cmds/string-match.rst index 4e5b18378..1e1b2bc80 100644 --- a/doc_src/cmds/string-match.rst +++ b/doc_src/cmds/string-match.rst @@ -52,13 +52,13 @@ Match Glob Examples :: - >_ string match '?' a + >_ string match 'a' a a >_ string match 'a*b' axxb axxb - >_ string match -i 'a??B' Axxb + >_ string match -i 'a*B' Axxb Axxb >_ string match -- '-*' -h foo --version bar @@ -67,7 +67,7 @@ Match Glob Examples -h --version - >_ echo 'ok?' | string match '*\?' + >_ echo 'ok?' | string match '*?' ok? # Note that only the second STRING will match here. @@ -79,7 +79,7 @@ Match Glob Examples foo foo2 - >_ string match 'foo?' 'foo1' 'foo' 'foo2' + >_ string match 'foo*' 'foo1' 'foo' 'foo2' foo1 foo2 From d622949d260da952091dece0ee90abe51e0e76f2 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Fri, 11 Apr 2025 09:48:28 +0200 Subject: [PATCH 27/32] Fix kill-selection crash when selection start is out-of-bounds This part of the code could use some love; when we happen to clear the selected text, we should end the selection. But that's not how it works today. This is fine for Vi mode, because Vi mode never deletes in visual mode. Let's fix the crash for now. Fixes #11367 (cherry picked from commit af3b49bf9c66575da20bbcf4f6e006390e1cf2c7) --- src/reader.rs | 5 ++++- tests/pexpects/bind.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/reader.rs b/src/reader.rs index 87c39211b..d62be6d47 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -3705,8 +3705,11 @@ fn clear_pager(&mut self) { fn get_selection(&self) -> Option> { let selection = self.selection?; - let start = selection.start; + let start = std::cmp::min(selection.start, self.command_line.len()); let end = std::cmp::min(selection.stop, self.command_line.len()); + if start == end { + return None; + } Some(start..end) } diff --git a/tests/pexpects/bind.py b/tests/pexpects/bind.py index fec9f2952..2c43ca04a 100644 --- a/tests/pexpects/bind.py +++ b/tests/pexpects/bind.py @@ -28,6 +28,21 @@ expect_prompt(increment=False) send("\f") expect_prompt(increment=False) +# Test that kill-selection after selection is cleared doesn't crash +sendline("bind ctrl-space begin-selection") +expect_prompt() +sendline("bind ctrl-w kill-selection end-selection") +expect_prompt() +send("echo 123") +# Send Ctrl-Space using CSI u encoding +send("\x1b[32;5u") +# Send Ctrl-C to clear the command line +send("\x1b[99;5u") +# Send Ctrl-W which used to crash +send("\x1b[119;5u") +sendline("bind --erase ctrl-space ctrl-w") +expect_prompt() + # Fish should start in default-mode (i.e., emacs) bindings. The default escape # timeout is 30ms. # From 4f810809c8930f94b13021122197eeeb27bb0f29 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Wed, 16 Apr 2025 09:44:26 +0200 Subject: [PATCH 28/32] Fix builtin test assigning wrong range to "! -d /" (Rust port regression) Fixes #11387 (cherry picked from commit c740c656a8397b10fa31b42ac8d54942b9899d8e) --- src/builtins/test.rs | 3 ++- src/builtins/tests/test_tests.rs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/builtins/test.rs b/src/builtins/test.rs index 7dfeabce7..d512f1502 100644 --- a/src/builtins/test.rs +++ b/src/builtins/test.rs @@ -684,10 +684,11 @@ fn parse_4_arg_expression( if let Token::UnaryBoolean(token) = first_token { let subject = self.parse_3_arg_expression(start + 1, end)?; + let range = start..subject.range().end; UnaryOperator { subject, token, - range: start..end, + range, } .into_some_box() } else if first_token == Token::ParenOpen { diff --git a/src/builtins/tests/test_tests.rs b/src/builtins/tests/test_tests.rs index 8346a50e6..5140b0958 100644 --- a/src/builtins/tests/test_tests.rs +++ b/src/builtins/tests/test_tests.rs @@ -103,6 +103,12 @@ fn test_test() { assert!(run_test_test(1, &["!", "15", "-ge", "10"])); assert!(run_test_test(0, &["!", "!", "15", "-ge", "10"])); + assert!(run_test_test(0, &[ + "(", "-d", "/", ")", + "-o", + "(", "!", "-d", "/", ")", + ])); + assert!(run_test_test(0, &["0", "-ne", "1", "-a", "0", "-eq", "0"])); assert!(run_test_test(0, &["0", "-ne", "1", "-a", "-n", "5"])); assert!(run_test_test(0, &["-n", "5", "-a", "10", "-gt", "5"])); From 3191ac13e553d71cc2b1526121aa352efc8a3d89 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Mon, 14 Apr 2025 23:51:34 +0200 Subject: [PATCH 29/32] Reduce parse_codepoint responsibilities, fixing alt in single-byte locale? This also changes the single-byte locale code path to treat keyboard input like "\x1ba" as alt-a instead of "escape,a". I can't off-hand reproduce a problem with "LC_ALL=C fish_key_reader", I guess we always use a UTF-8 locale if available? (cherry picked from commit b061178606a36edfaaab36b13b708992407daebc) --- src/input_common.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/input_common.rs b/src/input_common.rs index ca7508acc..fbea70847 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -704,19 +704,23 @@ fn try_readch(&mut self, blocking: bool) -> Option { } match self.parse_codepoint( &mut state, - &mut key, &mut seq, - &buffer, - i, + &buffer[..i + 1], &mut consumed, - &mut have_escape_prefix, ) { - ControlFlow::Continue(codepoint_complete) => { - if codepoint_complete && i + 1 == buffer.len() { + ControlFlow::Continue(/*codepoint_complete=*/ false) => (), + ControlFlow::Continue(/*codepoint_complete=*/ true) => { + if have_escape_prefix && i != 0 { + have_escape_prefix = false; + let c = seq.as_char_slice().last().unwrap(); + key = Some(Key::from(alt(*c))); + } + if i + 1 == buffer.len() { break true; } } ControlFlow::Break(()) => { + self.push_front(CharEvent::from_check_exit()); break false; } } @@ -793,15 +797,12 @@ fn parse_escape_sequence( fn parse_codepoint( &mut self, state: &mut mbstate_t, - out_key: &mut Option, out_seq: &mut WString, buffer: &[u8], - i: usize, consumed: &mut usize, - have_escape_prefix: &mut bool, ) -> ControlFlow<(), bool> { let mut res: char = '\0'; - let read_byte = buffer[i]; + let read_byte = *buffer.last().unwrap(); if crate::libc::MB_CUR_MAX() == 1 { // single-byte locale, all values are legal // FIXME: this looks wrong, this falsely assumes that @@ -823,7 +824,6 @@ fn parse_codepoint( -1 => { FLOG!(reader, "Illegal input"); *consumed += 1; - self.push_front(CharEvent::from_check_exit()); return ControlFlow::Break(()); } -2 => { @@ -841,16 +841,12 @@ fn parse_codepoint( if let Some(res) = char::from_u32(codepoint) { // Sequence complete. if !fish_reserved_codepoint(res) { - if *have_escape_prefix && i != 0 { - *have_escape_prefix = false; - *out_key = Some(alt(res)); - } *consumed += 1; out_seq.push(res); return ControlFlow::Continue(true); } } - for &b in &buffer[*consumed..i] { + for &b in &buffer[*consumed..] { out_seq.push(encode_byte_to_char(b)); *consumed += 1; } From bc3e3ae0292eddde6e05f21d96e765ffdee2d782 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Tue, 15 Apr 2025 00:28:47 +0200 Subject: [PATCH 30/32] builtin read: always handle out-of-range codepoints (Rust port regression) As mentioned in https://github.com/fish-shell/fish-shell/pull/9688#discussion_r1155089596, commit b77d1d0e2be (Stop crashing on invalid Unicode input, 2024-02-27), Rust's char type doesn't support arbitrary 32-bit values. Out-of-range Unicode codepoints would cause crashes. That commit addressed this by converting the encoded bytes (e.g. UTF-8) to special private-use-area characters that fish knows about. It didn't bother to update the code path in builtin read that relies on mbrtowc as well. Fix that. Move and rename parse_codepoint() and rename/reorder its input/output parameters. Fixes #11383 (cherry picked from commit d9ba27f58fedf96d5737e6ef6c454b76a0fbc2ce) --- src/builtins/read.rs | 67 +++++++++------------ src/input_common.rs | 129 ++++++++++++++++++++--------------------- tests/checks/read.fish | 5 ++ 3 files changed, 98 insertions(+), 103 deletions(-) diff --git a/src/builtins/read.rs b/src/builtins/read.rs index e45f60aec..1ec26c5f0 100644 --- a/src/builtins/read.rs +++ b/src/builtins/read.rs @@ -12,8 +12,9 @@ use crate::env::Environment; use crate::env::READ_BYTE_LIMIT; use crate::env::{EnvVar, EnvVarFlags}; +use crate::input_common::decode_input_byte; use crate::input_common::terminal_protocols_disable_ifn; -use crate::libc::MB_CUR_MAX; +use crate::input_common::DecodeState; use crate::nix::isatty; use crate::reader::commandline_set_buffer; use crate::reader::ReaderConfig; @@ -23,7 +24,6 @@ use crate::wcstringutil::split_about; use crate::wcstringutil::split_string_tok; use crate::wutil; -use crate::wutil::encoding::mbrtowc; use crate::wutil::encoding::zero_mbstate; use crate::wutil::perror; use libc::SEEK_CUR; @@ -341,70 +341,61 @@ fn read_one_char_at_a_time( split_null: bool, ) -> Option { let mut exit_res = STATUS_CMD_OK; - let mut eof = false; let mut nbytes = 0; + let mut unconsumed = vec![]; + loop { - let mut finished = false; - let mut res = '\x00'; let mut state = zero_mbstate(); - while !finished { + let chars_read = buff.len(); + let res = loop { let mut b = [0_u8; 1]; match read_blocked(fd, &mut b) { Ok(0) | Err(_) => { - eof = true; - break; + break None; } _ => {} } let b = b[0]; - + unconsumed.push(b); nbytes += 1; - if MB_CUR_MAX() == 1 { - res = char::from(b); - finished = true; - } else { - let sz = unsafe { - mbrtowc( - std::ptr::addr_of_mut!(res).cast(), - std::ptr::addr_of!(b).cast(), - 1, - &mut state, - ) - } as isize; - if sz == -1 { + let mut consumed = 0; + match decode_input_byte(buff, &mut state, &unconsumed, &mut consumed) { + DecodeState::Incomplete => continue, + DecodeState::Complete => { + unconsumed.clear(); + break Some(buff.as_char_slice().last().unwrap()); + } + DecodeState::Error => { state = zero_mbstate(); - } else if sz != -2 { - finished = true; + unconsumed.clear(); } } - } + }; if nbytes > READ_BYTE_LIMIT.load(Ordering::Relaxed) { + // Historical behavior: do not include the codepoint that made us overflow. + buff.truncate(chars_read); exit_res = STATUS_READ_TOO_MUCH; break; } - if eof { + let Some(&res) = res else { + // EOF + if buff.is_empty() { + exit_res = STATUS_CMD_ERROR; + } + break; + }; + if res == if split_null { '\0' } else { '\n' } { + buff.pop(); break; } - if !split_null && res == '\n' { - break; - } - if split_null && res == '\0' { - break; - } - - buff.push(res); if nchars > 0 && nchars <= buff.len() { break; } } - if buff.is_empty() && eof { - exit_res = STATUS_CMD_ERROR; - } - exit_res } diff --git a/src/input_common.rs b/src/input_common.rs index fbea70847..ebe050135 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -21,7 +21,6 @@ use crate::wutil::encoding::{mbrtowc, mbstate_t, zero_mbstate}; use crate::wutil::fish_wcstol; use std::collections::VecDeque; -use std::ops::ControlFlow; use std::os::fd::RawFd; use std::os::unix::ffi::OsStrExt; use std::ptr; @@ -702,14 +701,14 @@ fn try_readch(&mut self, blocking: bool) -> Option { _ => 0, }); } - match self.parse_codepoint( - &mut state, + match decode_input_byte( &mut seq, + &mut state, &buffer[..i + 1], &mut consumed, ) { - ControlFlow::Continue(/*codepoint_complete=*/ false) => (), - ControlFlow::Continue(/*codepoint_complete=*/ true) => { + DecodeState::Incomplete => (), + DecodeState::Complete => { if have_escape_prefix && i != 0 { have_escape_prefix = false; let c = seq.as_char_slice().last().unwrap(); @@ -719,7 +718,7 @@ fn try_readch(&mut self, blocking: bool) -> Option { break true; } } - ControlFlow::Break(()) => { + DecodeState::Error => { self.push_front(CharEvent::from_check_exit()); break false; } @@ -794,65 +793,6 @@ fn parse_escape_sequence( } } - fn parse_codepoint( - &mut self, - state: &mut mbstate_t, - out_seq: &mut WString, - buffer: &[u8], - consumed: &mut usize, - ) -> ControlFlow<(), bool> { - let mut res: char = '\0'; - let read_byte = *buffer.last().unwrap(); - if crate::libc::MB_CUR_MAX() == 1 { - // single-byte locale, all values are legal - // FIXME: this looks wrong, this falsely assumes that - // the single-byte locale is compatible with Unicode upper-ASCII. - res = read_byte.into(); - out_seq.push(res); - return ControlFlow::Continue(true); - } - let mut codepoint = u32::from(res); - let sz = unsafe { - mbrtowc( - std::ptr::addr_of_mut!(codepoint).cast(), - std::ptr::addr_of!(read_byte).cast(), - 1, - state, - ) - } as isize; - match sz { - -1 => { - FLOG!(reader, "Illegal input"); - *consumed += 1; - return ControlFlow::Break(()); - } - -2 => { - // Sequence not yet complete. - return ControlFlow::Continue(false); - } - 0 => { - // Actual nul char. - *consumed += 1; - out_seq.push('\0'); - return ControlFlow::Continue(true); - } - _ => (), - } - if let Some(res) = char::from_u32(codepoint) { - // Sequence complete. - if !fish_reserved_codepoint(res) { - *consumed += 1; - out_seq.push(res); - return ControlFlow::Continue(true); - } - } - for &b in &buffer[*consumed..] { - out_seq.push(encode_byte_to_char(b)); - *consumed += 1; - } - ControlFlow::Continue(true) - } - fn parse_csi(&mut self, buffer: &mut Vec) -> Option { let mut next_char = |zelf: &mut Self| zelf.try_readb(buffer).unwrap_or(0xff); // The maximum number of CSI parameters is defined by NPAR, nominally 16. @@ -1322,6 +1262,65 @@ fn has_lookahead(&self) -> bool { } } +pub(crate) enum DecodeState { + Incomplete, + Complete, + Error, +} + +pub(crate) fn decode_input_byte( + out_seq: &mut WString, + state: &mut mbstate_t, + buffer: &[u8], + consumed: &mut usize, +) -> DecodeState { + use DecodeState::*; + let mut res: char = '\0'; + let read_byte = *buffer.last().unwrap(); + if crate::libc::MB_CUR_MAX() == 1 { + // single-byte locale, all values are legal + // FIXME: this looks wrong, this falsely assumes that + // the single-byte locale is compatible with Unicode upper-ASCII. + res = read_byte.into(); + out_seq.push(res); + return Complete; + } + let mut codepoint = u32::from(res); + match unsafe { + mbrtowc( + std::ptr::addr_of_mut!(codepoint).cast(), + std::ptr::addr_of!(read_byte).cast(), + 1, + state, + ) + } as isize + { + -1 => { + FLOG!(reader, "Illegal input"); + *consumed += 1; + return Error; + } + -2 => { + // Sequence not yet complete. + return Incomplete; + } + _ => (), + } + if let Some(res) = char::from_u32(codepoint) { + // Sequence complete. + if !fish_reserved_codepoint(res) { + *consumed += 1; + out_seq.push(res); + return Complete; + } + } + for &b in &buffer[*consumed..] { + out_seq.push(encode_byte_to_char(b)); + *consumed += 1; + } + Complete +} + /// A simple, concrete implementation of InputEventQueuer. pub struct InputEventQueue { data: InputData, diff --git a/tests/checks/read.fish b/tests/checks/read.fish index 2f6c9a454..dd1fd69b3 100644 --- a/tests/checks/read.fish +++ b/tests/checks/read.fish @@ -400,3 +400,8 @@ echo foo | read -n -1 # CHECKERR: echo foo | read -n -1 # CHECKERR: ^ # CHECKERR: (Type 'help read' for related documentation) + +printf \xf9\x98\xb1\x83\x8b | read -z out_of_range_codepoint +set -S out_of_range_codepoint +# CHECK: $out_of_range_codepoint: set in global scope, unexported, with 1 elements +# CHECK: $out_of_range_codepoint[1]: |\Xf9\X98\Xb1\X83\X8b| From c88e6827b755cde07f3879f6218cf46beab5817b Mon Sep 17 00:00:00 2001 From: David Adam Date: Sat, 19 Apr 2025 00:06:31 +0800 Subject: [PATCH 31/32] CHANGELOG: work on 4.0.2 --- CHANGELOG.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cbe237cbe..c84cba345 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,22 +1,22 @@ fish 4.0.2 (released March ??, 2025) ==================================== -This release of fish includes the following improvements compared to fish 4.0.1: +This release of fish fixes a number of issues identified in fish 4.0.1: -- Key combinations using the super (Windows/command) key can now actually be bound using the :kbd:`super-` prefix (:issue:`11217`). -- :kbd:`delete` in Vi mode works again if numlock is active (:issue:`11303`). -- A crash involving backspace characters was fixed (:issue:`11280`). -- Fish 4.0.0 switched completions to be quoted instead of backslash-escaped, - now it only does so if the completion is unambiguous to make continuing the token easier (:issue:`11271`). -- The warning when the terminfo database couldn't be found has been downgraded to a log message. - This is because fish will fall back to what xterm-256color would do, - which in the majority of cases would just work (:issue:`11277`, :issue:`11290`). -- The cargo completions have been sped up, especially by no longer looking up crates online (:issue:`11347`). -- The hg prompt no longer errors if $PWD includes a newline (:issue:`11348`). -- Super-modified keys are no longer inserted -- A crash when closing the ``ctrl-r`` search has been fixed. -- Various additions to completions, including git (:issue:`11322`, :issue:`11323`), btrfs (:issue:`11320`), cryptsetup (:issue:`11315`), systemd-analyze (:issue:`11314`), - wl-randr (:issue:`11301`), jj (:issue:`11046`) +- Completions are quoted, rather than backslash-escaped, only if the completion is unambiguous. Continuing to edit the token is therefore easier (:issue:`11271`). This changes the behavior introduced in 4.0.0 where all completions were quoted. +- The warning when the terminfo database can't be found has been downgraded to a log message. fish will act as if the terminal behaves like xterm-256color, which is correct for the vast majority of cases (:issue:`11277`, :issue:`11290`). +- Key combinations using the super (Windows/command) key can now (actually) be bound using the :kbd:`super-` prefix (:issue:`11217`). This was listed in the release notes for 4.0.1 but did not work correctly. +- :doc:`function ` is stricter about argument parsing, rather than allowing additional parameters to be silently ignored (:issue:`11295`). +- Using parentheses in the :doc:`test ` builtin works correctly, following a regression in 4.0.0 where they were not recognized (:issue:`11387`). +- :kbd:`delete` in Vi mode when Num Lock is active will work correctly (:issue:`11303`). +- Abbreviations cannot alter the command-line contents, preventing a crash (:issue:`11324`). +- Improvements to various completions, including new completions for ``wl-randr`` (:issue:`11301`), performance improvements for ``cargo`` completions by avoiding network requests (:issue:`11347`), and other improvements for ``btrfs`` (:issue:`11320`), ``cryptsetup`` (:issue:`11315`), ``git`` (:issue:`11319`, :issue:`11322`, :issue:`11323`), ``jj`` (:issue:`11046`), and ``systemd-analyze`` (:issue:`11314`). +- The Mercurial (``hg``) prompt can handle working directories that contain an embedded newline, rather than producing errors (:issue:`11348`). +- A number of crashes have been fixed. Triggers include prompts containing backspace characters (:issue:`11280`), history pager search (:issue:`11355`), invalid UTF-8 in :doc:`read ` (:issue:`11383`), and the ``kill-selection`` binding (:issue:`11367`). +- A race condition in the test suite has been fixed (:issue:`11254`), and a test for fish versioning relaxed to support downstream distributors' modifications (:issue:`11173`). +- Small improvements to the documentation (:issue:`11264`, :issue:`11329`, :issue:`11361`). + +-------------- fish 4.0.1 (released March 12, 2025) ==================================== From f1456f97076543f4a7de2777e2a8c8fa664ce7dd Mon Sep 17 00:00:00 2001 From: David Adam Date: Sun, 20 Apr 2025 21:11:52 +0800 Subject: [PATCH 32/32] Release 4.0.2 --- CHANGELOG.rst | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c84cba345..7ff40cade 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,4 +1,4 @@ -fish 4.0.2 (released March ??, 2025) +fish 4.0.2 (released April 20, 2025) ==================================== This release of fish fixes a number of issues identified in fish 4.0.1: diff --git a/Cargo.lock b/Cargo.lock index 756223471..726882d94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,7 +112,7 @@ dependencies = [ [[package]] name = "fish" -version = "4.0.1" +version = "4.0.2" dependencies = [ "bitflags", "cc", diff --git a/Cargo.toml b/Cargo.toml index 087fe5da7..240f348a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ debug = true [package] name = "fish" -version = "4.0.1" +version = "4.0.2" edition.workspace = true rust-version.workspace = true default-run = "fish"