Skip to content

Commit 84aff1b

Browse files
committed
[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.
1 parent d4c999d commit 84aff1b

File tree

1 file changed

+66
-38
lines changed

1 file changed

+66
-38
lines changed

Diff for: shell/key-bindings.fish

+66-38
Original file line numberDiff line numberDiff line change
@@ -42,45 +42,73 @@ function fzf_key_bindings
4242
end
4343

4444
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
45+
set -l fzf_query ''
46+
set -l prefix ''
4547
set -l dir '.'
46-
set -l query
47-
set -l commandline (commandline -t | string unescape -n)
4848

49-
# Strip -option= from token if present
50-
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
51-
set commandline (string replace -- "$prefix" '' $commandline)
52-
53-
# Enable home directory expansion of leading ~/
54-
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
55-
56-
# Escape special characters, except for the $ sign of valid variable names,
57-
# so that the original string with expanded variables is returned after eval.
58-
set commandline (string escape -n -- $commandline)
59-
set commandline (string replace -r -a -- '\\\\\$(?=[\w])' '\$' $commandline)
60-
61-
# eval is used to do shell expansion on paths
62-
eval set commandline $commandline
63-
64-
# Combine multiple consecutive slashes into one.
65-
set commandline (string replace -r -a -- '/+' '/' $commandline)
66-
67-
if test -n "$commandline"
68-
# Strip trailing slash, unless $dir is root dir (/)
69-
set dir (string replace -r -- '(?<!^)/$' '' $commandline)
49+
# Set variables containing the major and minor fish version numbers, using
50+
# a method compatible with all supported fish versions.
51+
set -l -- fish_major (string match -r -- '^\d+' $version)
52+
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
53+
54+
# Set $prefix and expanded $fzf_query with preserved trailing newlines.
55+
if test "$fish_major" -ge 4
56+
# fish v4.0.0 and newer
57+
string match -q -r -- '(?<prefix>^-[^\s=]+=)?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
58+
(commandline --current-token --tokens-expanded | string collect -N)
59+
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
60+
# fish v3.2.0 - v3.7.1 (last v3)
61+
string match -q -r -- '(?<prefix>^-[^\s=]+=)?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
62+
(commandline --current-token --tokenize | string collect -N)
63+
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
64+
else
65+
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
66+
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
67+
set -- prefix (string match -r -- '^-[^\s=]+=' $cl_token)
68+
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
69+
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
70+
end
7071

71-
# Set $dir to the longest existing filepath
72-
while not test -d "$dir"
73-
# If path is absolute, this can keep going until ends up at /
74-
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
75-
set dir (dirname -- $dir)
72+
if test -n "$fzf_query"
73+
# Normalize path in $fzf_query, set $dir to the longest existing directory.
74+
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
75+
# fish v3.5.0 and newer
76+
set -- fzf_query (path normalize -- $fzf_query)
77+
set -- dir $fzf_query
78+
while not path is -d $dir
79+
set -- dir (path dirname $dir)
80+
end
81+
else
82+
# fish older than v3.5.0 (v3.1b1 - v3.4.1)
83+
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
84+
# fish v3.2.0 - v3.4.1
85+
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
86+
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
87+
else
88+
# fish v3.1b1 - v3.1.2
89+
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
90+
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
91+
end
92+
set -- dir $fzf_query
93+
while not test -d "$dir"
94+
set -- dir (dirname -z -- "$dir" | string split0)
95+
end
7696
end
7797

78-
if test "$dir" = '.'; and test (string sub -l 2 -- $commandline) != './'
79-
# If $dir is "." but commandline is not a relative path, this means no file path found
80-
set fzf_query $commandline
81-
else
82-
# Also remove trailing slash after dir, to "split" input properly
83-
set fzf_query (string replace -r -- "^$dir/?" '' $commandline)
98+
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
99+
# Strip $dir from $fzf_query - preserve trailing newlines.
100+
if test "$fish_major" -ge 4
101+
# fish v4.0.0 and newer
102+
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
103+
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
104+
# fish v3.2.0 - v3.7.1 (last v3)
105+
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
106+
(string replace -- "$dir" '' $fzf_query | string collect -N)
107+
else
108+
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
109+
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
110+
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
111+
end
84112
end
85113
end
86114

@@ -95,13 +123,13 @@ function fzf_key_bindings
95123
set -l prefix $commandline[3]
96124

97125
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
98-
"--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" \
126+
"--reverse --walker=file,dir,follow,hidden --scheme=path" \
99127
"$FZF_CTRL_T_OPTS --multi --print0")
100128

101129
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
102130
set -lx FZF_DEFAULT_OPTS_FILE
103131

104-
if set -l result (eval (__fzfcmd) --query=$fzf_query | string split0)
132+
if set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
105133
# Remove last token from commandline.
106134
commandline -t ''
107135
for i in $result
@@ -154,13 +182,13 @@ function fzf_key_bindings
154182
set -l prefix $commandline[3]
155183

156184
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
157-
"--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" \
185+
"--reverse --walker=dir,follow,hidden --scheme=path" \
158186
"$FZF_ALT_C_OPTS --no-multi --print0")
159187

160188
set -lx FZF_DEFAULT_OPTS_FILE
161189
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
162190

163-
if set -l result (eval (__fzfcmd) --query=$fzf_query | string split0)
191+
if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0)
164192
cd -- $result
165193
commandline -rt -- $prefix
166194
end

0 commit comments

Comments
 (0)