fish-shell/share/functions/__fish_complete_suffix.fish
Johannes Altmanninger 93cb0e2abb __fish_complete_suffix: enable fuzzy completion, simplify
fish's internal completion logic is much smarter than the globbing in this
function, so let's just reuse "complete -C", and filter directories and
files with the given suffix.

Thanks to @Kratacoa for reporting on Gitter.

Using "complete -C" works well no prefix is given. Since in this repository
only the openocd completions pass a prefix, I left the prefix-case as is.
It could probably be improved and simplified as well.  The prefix argument was
introduced to avoid cd's side effects inside a completion. Using cd is tempting
though because it would allow to use the same logic as without a prefix.
2020-08-08 22:59:41 +02:00

106 lines
3.8 KiB
Fish

#
# Find files that complete $argv[1], has the suffix $argv[2], and
# output them as completions with the optional description $argv[3] Both
# $argv[1] and $argv[3] are optional, if only one is specified, it is
# assumed to be the argument to complete. If $argv[4] is present, it is
# treated as a prefix for the path, i.e. in lieu of $PWD.
#
function __fish_complete_suffix -d "Complete using files"
# Variable declarations
set -l comp
set -l suff
set -l desc
set -l files
set -l prefix ""
switch (count $argv)
case 1
set comp (commandline -ct)
set suff $argv
set desc ""
case 2
set comp $argv[1]
set suff $argv[2]
set desc ""
case 3
set comp $argv[1]
set suff $argv[2]
set desc $argv[3]
case 4
set comp $argv[1]
set suff $argv[2]
set desc $argv[3]
set prefix $argv[4]
# Only directories are supported as prefixes, and to use the same logic
# for both absolute prefixed paths and relative non-prefixed paths, $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 '/$' $prefix
set prefix $prefix/
end
end
# Simple and common case: no prefix, just complete normally and filter out unwanted suffixes.
if test -z $prefix
set -l suffix (string escape --style=regex -- $suff)
# Use normal file completions. Any valid command works here as, as long as it has no
# user-defined completions. The builtin ":" should work.
set files (complete -C ": $comp" | string match -r "^.*(?:$suffix|/)\$")
else
# Strip leading ./ as it confuses the detection of base and suffix
# It is conditionally re-added below.
set base $prefix(string replace -r '^("\')?\\./' '' -- $comp | string trim -c '\'"') # " make emacs syntax highlighting happy
set -l all
set -l dirs
# If $comp 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 all (string match -r -- ".*"(string escape --style=regex -- $suff) $all)
if not string match -qr '/$' -- $suff
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 $all $dirs
if string match -qr '^\\./' -- $comp
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 -- . "$desc"
set desc "\t$desc"
end
if string match -qr -- . "$prefix"
set prefix (string escape --style=regex -- $prefix)
set files (string replace -r -- "^$prefix" "" $files)
end
printf "%s$desc\n" $files
end
end