From d4c999d62e426044d4b59edd3608809069569f77 Mon Sep 17 00:00:00 2001 From: bitraid Date: Thu, 27 Feb 2025 12:36:57 +0200 Subject: [PATCH 1/3] [fish] Fix for file/dir names containing newlines CTRL-T/ALT-C now works correctly when selecting files or directories that contain newlines in their names. When external commands defined by $FZF_CTRL_T_COMMAND/$FZF_ALT_C_COMMAND are used (for example the fd command with -0 switch), the --read0 option must also be set through $FZF_CTRL_T_OPTS/$FZF_ALT_C_OPTS. --- shell/key-bindings.fish | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish index 5083309f74f..658addbb25f 100644 --- a/shell/key-bindings.fish +++ b/shell/key-bindings.fish @@ -96,12 +96,12 @@ function fzf_key_bindings set -lx FZF_DEFAULT_OPTS (__fzf_defaults \ "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" \ - "$FZF_CTRL_T_OPTS --multi") + "$FZF_CTRL_T_OPTS --multi --print0") set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" set -lx FZF_DEFAULT_OPTS_FILE - if set -l result (eval (__fzfcmd) --query=$fzf_query) + if set -l result (eval (__fzfcmd) --query=$fzf_query | string split0) # Remove last token from commandline. commandline -t '' for i in $result @@ -155,12 +155,12 @@ function fzf_key_bindings set -lx FZF_DEFAULT_OPTS (__fzf_defaults \ "--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" \ - "$FZF_ALT_C_OPTS --no-multi") + "$FZF_ALT_C_OPTS --no-multi --print0") set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" - if set -l result (eval (__fzfcmd) --query=$fzf_query) + if set -l result (eval (__fzfcmd) --query=$fzf_query | string split0) cd -- $result commandline -rt -- $prefix end From 84aff1b0345efc6238e8dc91e49e03122c28f698 Mon Sep 17 00:00:00 2001 From: bitraid Date: Fri, 28 Feb 2025 11:48:11 +0200 Subject: [PATCH 2/3] [fish] Fix whitespace/regex characters in command line This is a rewrite of __fzf_parse_commandline function, that fixes the following issues, when CTRL-T/ALT-C is used and current command line token contains: - Escaped newlines (\n): This never worked correctly, but after 282884a, the string would split, and the script would enter an infinite loop while trying to set $dir. - Escaped bell (\a, \cg), backspace (\b), form feed (\v, \cl), carriage return (\r), vertical tab (\v, \ck): walker-root would not set correctly for existing directories containing any of those characters. - Regular expression special characters (^, +, ? etc): $dir would not be be stripped from $fzf_query if it contained any of those characters. The lowest supported fish version is v3.1b. For optimal operation, the function uses more recent commands when supported by the running version. Specifically, for versions equal or newer than: - v3.2.0: Sets variables using PCRE2 capture groups of `string match --regex` when needing to preserve any trailing newlines and simultaneously omit the extra newline that is appended by `string collect -N`. - v3.5.0: Uses the builtin path command for path normalization, dirname extraction and existing directories check. - v4.0.0: Uses the --tokens-expanded option of commandline, for expansion and dealing with unbalanced quotes and incomplete escape sequences. It also uses the regex style of string-escape, to prepare variable contents for regex operations. This is not used in older versions, because they don't escape newlines. --- shell/key-bindings.fish | 104 +++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish index 658addbb25f..72b420794e4 100644 --- a/shell/key-bindings.fish +++ b/shell/key-bindings.fish @@ -42,45 +42,73 @@ function fzf_key_bindings end function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix' + set -l fzf_query '' + set -l prefix '' set -l dir '.' - set -l query - set -l commandline (commandline -t | string unescape -n) - # Strip -option= from token if present - set -l prefix (string match -r -- '^-[^\s=]+=' $commandline) - set commandline (string replace -- "$prefix" '' $commandline) - - # Enable home directory expansion of leading ~/ - set commandline (string replace -r -- '^~/' '\$HOME/' $commandline) - - # Escape special characters, except for the $ sign of valid variable names, - # so that the original string with expanded variables is returned after eval. - set commandline (string escape -n -- $commandline) - set commandline (string replace -r -a -- '\\\\\$(?=[\w])' '\$' $commandline) - - # eval is used to do shell expansion on paths - eval set commandline $commandline - - # Combine multiple consecutive slashes into one. - set commandline (string replace -r -a -- '/+' '/' $commandline) - - if test -n "$commandline" - # Strip trailing slash, unless $dir is root dir (/) - set dir (string replace -r -- '(?^-[^\s=]+=)?(?[\s\S]*?(?=\n?$)$)' \ + (commandline --current-token --tokens-expanded | string collect -N) + else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 + # fish v3.2.0 - v3.7.1 (last v3) + string match -q -r -- '(?^-[^\s=]+=)?(?[\s\S]*?(?=\n?$)$)' \ + (commandline --current-token --tokenize | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '') + else + # fish older than v3.2.0 (v3.1b1 - v3.1.2) + set -l -- cl_token (commandline --current-token --tokenize | string collect -N) + set -- prefix (string match -r -- '^-[^\s=]+=' $cl_token) + set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '') + end - # Set $dir to the longest existing filepath - while not test -d "$dir" - # If path is absolute, this can keep going until ends up at / - # If path is relative, this can keep going until entire input is consumed, dirname returns "." - set dir (dirname -- $dir) + if test -n "$fzf_query" + # Normalize path in $fzf_query, set $dir to the longest existing directory. + if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \) + # fish v3.5.0 and newer + set -- fzf_query (path normalize -- $fzf_query) + set -- dir $fzf_query + while not path is -d $dir + set -- dir (path dirname $dir) + end + else + # fish older than v3.5.0 (v3.1b1 - v3.4.1) + if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 + # fish v3.2.0 - v3.4.1 + string match -q -r -- '(?^[\s\S]*?(?=\n?$)$)' \ + (string replace -r -a -- '(?<=/)/|(?[\s\S]*)' $fzf_query + else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 + # fish v3.2.0 - v3.7.1 (last v3) + string match -q -r -- '^/?(?[\s\S]*?(?=\n?$)$)' \ + (string replace -- "$dir" '' $fzf_query | string collect -N) + else + # fish older than v3.2.0 (v3.1b1 - v3.1.2) + set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N) + eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '') + end end end @@ -95,13 +123,13 @@ function fzf_key_bindings set -l prefix $commandline[3] set -lx FZF_DEFAULT_OPTS (__fzf_defaults \ - "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" \ + "--reverse --walker=file,dir,follow,hidden --scheme=path" \ "$FZF_CTRL_T_OPTS --multi --print0") set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" set -lx FZF_DEFAULT_OPTS_FILE - if set -l result (eval (__fzfcmd) --query=$fzf_query | string split0) + if set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0) # Remove last token from commandline. commandline -t '' for i in $result @@ -154,13 +182,13 @@ function fzf_key_bindings set -l prefix $commandline[3] set -lx FZF_DEFAULT_OPTS (__fzf_defaults \ - "--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" \ + "--reverse --walker=dir,follow,hidden --scheme=path" \ "$FZF_ALT_C_OPTS --no-multi --print0") set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" - if set -l result (eval (__fzfcmd) --query=$fzf_query | string split0) + if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0) cd -- $result commandline -rt -- $prefix end From 19fe205e630496daf8223d710768a99886836d19 Mon Sep 17 00:00:00 2001 From: bitraid Date: Wed, 16 Apr 2025 10:43:45 +0300 Subject: [PATCH 3/3] [fish] Improve option prefix processing - Support single-letter options without = such as -fFILEPATH - fish v3.3.0 and newer: Disable option prefix if -- is preceded --- shell/key-bindings.fish | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish index 72b420794e4..0004c380c5d 100644 --- a/shell/key-bindings.fish +++ b/shell/key-bindings.fish @@ -51,20 +51,26 @@ function fzf_key_bindings set -l -- fish_major (string match -r -- '^\d+' $version) set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2] + # fish v3.3.0 and newer: Don't use option prefix if " -- " is preceded. + set -l -- match_regex '(?[\s\S]*?(?=\n?$)$)' + set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S' + if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3 + or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p)) + set -- match_regex "(?$prefix_regex)?$match_regex" + end + # Set $prefix and expanded $fzf_query with preserved trailing newlines. if test "$fish_major" -ge 4 # fish v4.0.0 and newer - string match -q -r -- '(?^-[^\s=]+=)?(?[\s\S]*?(?=\n?$)$)' \ - (commandline --current-token --tokens-expanded | string collect -N) + string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N) else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 # fish v3.2.0 - v3.7.1 (last v3) - string match -q -r -- '(?^-[^\s=]+=)?(?[\s\S]*?(?=\n?$)$)' \ - (commandline --current-token --tokenize | string collect -N) + string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N) eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '') else # fish older than v3.2.0 (v3.1b1 - v3.1.2) set -l -- cl_token (commandline --current-token --tokenize | string collect -N) - set -- prefix (string match -r -- '^-[^\s=]+=' $cl_token) + set -- prefix (string match -r -- $prefix_regex $cl_token) set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N) eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '') end