fish-shell/share/completions/env.fish
Johannes Altmanninger 6b5ad163d3 Fix double expansion of tokenized command line
Commit 798527d79a (completions: fix double evaluation of tokenized
commandline, 2024-01-06) fixed some completions such as the "watchexec"
ones by adding "string escape" here:

	set argv (commandline -opc | string escape) (commandline -ct)

This fixed double evaluation when we later call `complete -C"$argv"`.

Unfortunately -- searching for "complete -C" and
"__fish_complete_subcommand" -- it seems like that commit missed some
completions such as sudo.  Fix them the same way.

Alternatively, we could defer expansion of those arguments (via
--tokens-raw), since the recursive call to completion will expand
them anyway, and we don't really need to know their value.

But there are (contrived) examples where we do want to expand first,
to correctly figure out where the subcommand starts:

	sudo {-u,someuser} make ins

By definition, the tokens returned by `commandline -opc` do not
contain the token at cursor (which we're currently completing).
So the expansion should not hurt us. There is an edge case where
cartesian product expansion would produce too many results, and we
pass on the unexpanded input. In that case the extra escaping is very
unlikely to have negative effects.

Fixes # 11041
Closes # 11067

Co-authored-by: kerty <g.kabakov@inbox.ru>
2025-01-19 18:29:07 +01:00

123 lines
5.2 KiB
Fish

set -l is_gnu
if env --version &>/dev/null
set is_gnu --is-gnu
end
# Returns 0 if we're after `env` and all previous tokens have an equal sign or were switches
function __fish_env_defining_vars
set last ""
for token in (commandline -cxp)[2..] # 2.. excludes `env`. -cx already ignores the variable being completed.
# Is a switch, defined an env variable, or was a variable name to unset (after -u)
string match -r -- '^-|=' $token || string match -rq -- '^(-u|--unset)$' "$last" || return 1
set last $token
end
end
# Returns 0 if we're after `env` and all previous tokens have not yet contained an equal sign
# Prevents `env` completions from completing payload completions.
function __fish_env_not_yet_vars
not string match -qe -- = (commandline -c)
end
# Generate a list of possible variable names to redefine, excluding any already redefined.
function __fish_env_redefine_vars
set -l vars (set --names -x)
set cmdline "$(commandline -xp)"
for var in $vars
if not string match -eq -- $var= $cmdline
echo $var=
end
end
end
# Generate a list of possible variable names to define from completion history
function __fish_env_names_from_history
set -l token (commandline -ct)
# Since this is always going to be a best-effort kind of thing, limit this to uppercased variables by convention.
# This prevents us from having to parse quotes to figure out what was part of the payload and what wasn't.
for var in (history search --prefix "env " | string match -ra '\b([A-Z0-9_]+)=' --groups-only)
echo $var=
end
end
# Generate a list of possible completions for the current variable name from history
function __fish_env_values_from_history
string match -rq -- "(?<name>.+)=(?<value>.*)" (commandline -ct); or return 1
# Caveat lector: very crude multi-word tokenization handling below!
set -l rname (string escape --style=regex -- $name)
set -l search (string trim -c \'\" "$value")
set -l rsearch "$(string escape --style=regex -- $search)"
# Search multi-word values with quotes
set -l query '.*\b'$name'=([\'"])('$rsearch'.+?)\1.*'
set -l matches (history search --prefix "env " | string replace -rfa $query '$2')
# Search multi-word values without quotes
set -l query '.*\b'$name'=('$rsearch'[^\'" ]+).*'
set -a matches (history search --prefix "env " | string replace -rfa $query '$1')
# Display results without quotes
set matches (printf "%s\n" $matches | sort -u)
printf "$name=%s\n" $matches
end
# Get the text after all env arguments and variables, so we can complete it as a regular command
function __fish_env_remaining_args -V is_gnu
set -l argv (commandline -xpc | string escape) (commandline -ct)
if set -q is_gnu[1]
argparse -s i/ignore-environment u/unset= help version -- $argv 2>/dev/null
or return 0
else
argparse -s 0 i P= S= u= v -- $argv 2>/dev/null
or return 0
end
# argv[1] is `env` or an alias.
set -e argv[1]
# Remove all VAR=VAL arguments up to the first that isn't
while set -q argv[1]
if string match -q '*=*' -- $argv[1]
or string match -q -- '-*' $argv[1]
set -e argv[1]
else
break
end
end
string join \n -- $argv
# Return true if there is a subcommand.
test -n "$argv[1]"
end
# Generate a completion for the executable to execute under `env`
function __fish_complete_env_subcommand
if set -l argv (__fish_env_remaining_args)
complete -C "$argv"
end
end
# Complete the name of the variable from current definitions
complete -c env -n '__fish_env_defining_vars; and not string match -eq = -- (commandline -ct)' -a "(__fish_env_redefine_vars)" -f -d "Redefine variable"
# Complete the name of the variable from history
complete -c env -n '__fish_env_defining_vars; and not string match -eq = -- (commandline -ct)' -a "(__fish_env_names_from_history)" -f -d Historical
# Complete the value for FOO= from history
# TODO: NO_ESCAPE when that becomes available
complete -c env -n '__fish_env_defining_vars; and string match -eq = -- (commandline -ct)' -a "(__fish_env_values_from_history)" -f
# Complete normally after we are done with `env` stuff
complete -c env -fa "(__fish_complete_env_subcommand)"
if set -q is_gnu
complete -c env -n __fish_env_not_yet_vars -s i -l ignore-environment -d "Start with an empty environment"
complete -c env -n __fish_env_not_yet_vars -s u -l unset -d "Unset environment variable" -x -a "(set --names -x)"
complete -c env -n __fish_env_not_yet_vars -l help -d "Display help and exit"
complete -c env -n __fish_env_not_yet_vars -l version -d "Display version and exit"
else
complete -c env -n __fish_env_not_yet_vars -s 0 -d "End output lines with NUL"
complete -c env -n __fish_env_not_yet_vars -s i -d "Start with empty environment"
complete -c env -n __fish_env_not_yet_vars -s P -d "Provide an alternate PATH"
complete -c env -n __fish_env_not_yet_vars -s S -d "Split argument into args on ' '"
complete -c env -n __fish_env_not_yet_vars -s u -d "Unset environment variable" -x -a "(set --names -x)"
complete -c env -n __fish_env_not_yet_vars -s v -d "Verbose output on processing"
end