# # 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 # 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 # echo "base: $base" > /dev/tty # echo "suffix: $suff" > /dev/tty 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. `eval` is used since $suff may be passed in # as {.foo,.bar} and we want to expand that. eval "set all $base*$suff" if not string match -qr '/$' -- $suff eval "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 end # Another problem is that expanded paths are not matched, either. # So an expression like $HOME/foo*.zip will expand to /home/rdahl/foo-bar.zip # but that no longer matches the expression at the command line. if string match -qr '[${}*~]' -- $comp set -l expanded eval "set expanded $comp" set files (string replace -- $expanded $comp $files) end if set -q files[1] if string match -qr -- . "$desc" set desc "\t$desc" end if string match -qr -- . "$prefix" # Ideally, only replace in the beginning of the string, but we have no # way of doing a pcre2 escape so we can use a regex replace instead set files (string replace $prefix "" $files) end printf "%s$desc\n" $files #| sort -u end end