fish-shell/share/functions/__fish_complete_suffix.fish
lengyijun aa65856ee0 Fixes #8924 via __fish_complete_suffix overhaul
Before:
* hand write arg parse
* only accepts one suffix

After:
* use `arg_parse` to parse args
* accepts multi suffixes

Closes #9611.
2023-03-12 22:07:44 -05:00

84 lines
3.8 KiB
Fish

# Find files ending in any of the non-switch arguments and output them as completions.
# * --description provides the description that should be part of each generated completion,
# * --prefix=DIR makes __fish_complete_suffix behave as if it were called in DIR rather than $PWD
# * --complete=PREFIX only lists files that begin with PREFIX
#
# Files matching the above preconditions are printed first then other, non-matching files
# are printed for fallback purposes. As such, it is imperative that `complete` calls that
# shell out to `__fish_complete_suffix` are made with a `-k` switch to ensure sort order
# is preserved.
function __fish_complete_suffix -d "Complete using files"
set -l _flag_prefix ""
set -l _flag_complete (commandline -ct)
argparse 'prefix=' 'description=' 'complete=' -- $argv
set -l suff (string escape --style=regex -- $argv)
# Simple and common case: no prefix, just complete normally and sort matching files first.
if test -z $_flag_prefix
# Use normal file completions.
set files (complete -C "__fish_command_without_completions $_flag_complete")
set -l files_with_suffix (string match -r -- (string join "|" "^.*"$suff\$) $files)
set -l directories (string match -r -- '^.*/$' $files)
set files $files_with_suffix $directories $files
else
# Only directories are supported as prefixes, and to use the same logic
# for both absolute prefixed paths and relative non-prefixed paths, $_flag_prefix
# must terminate in a `/` if it is present, so it can be unconditionally
# prefixed to any path to get the desired result.
if not string match -qr '/$' $_flag_prefix
set _flag_prefix $_flag_prefix/
end
# Strip leading ./ as it confuses the detection of base and suffix
# It is conditionally re-added below.
set base $_flag_prefix(string replace -r '^("\')?\\./' '' -- $_flag_complete | string trim -c '\'"') # " make emacs syntax highlighting happy
set -l all
set -l files_with_suffix
set -l dirs
# If $_flag_complete is "./ma" and the file is "main.py", we'll catch that case here,
# but complete.cpp will not consider it a match, so we have to output the
# correct form.
# Also do directory completion, since there might be files with the correct
# suffix in a subdirectory.
set all $base*
set files_with_suffix (string match -r -- (string join "|" ".*"$suff) $all)
if not string match -qr '/$' -- $argv
set dirs $base*/
# The problem is that we now have each directory included twice in the output,
# once as `dir` and once as `dir/`. The runtime here is O(n) for n directories
# in the output, but hopefully since we have only one level (no nested results)
# it should be fast. The alternative is to shell out to `sort` and remove any
# duplicate results, but it would have to be a huge `n` to make up for the fork
# overhead.
for dir in $dirs
set all (string match -v (string match -r '(.*)/$' -- $dir)[2] -- $all)
end
end
set files $files_with_suffix $dirs $all
if string match -qr '^\\./' -- $_flag_complete
set files ./$files
else
# "Escape" files starting with a literal dash `-` with a `./`
set files (string replace -r -- "^-" "./-" $files)
end
end
if set -q files[1]
if string match -qr -- . "$_flag_description"
set _flag_description "\t$_flag_description"
end
if string match -qr -- . "$_flag_prefix"
set prefix (string escape --style=regex -- $_flag_prefix)
set files (string replace -r -- "^$prefix" "" $files)
end
printf "%s$_flag_description\n" $files
end
end