Repository: sharkdp/fd (language: rust)

Issue:
Better integration with zsh
Currently autocomplete by `Tab` for `[<path>]` argument does not works.


Discussion / hints:
Thank you for reporting this. Currently, autocompletion files are auto-generated. I'm not sure if this can be fixed by configuring `clap`. Any help would definitely be appreciated.
It looks like this is related to https://github.com/kbknapp/clap-rs/issues/568
@sharkdp I just installed fd via `sudo dpkg -i fd_8.0.0_amd64.deb` but got no auto-completion items for the fish shell. Is this issue the same as this one, or a new issue?

**edit**: I've re-tested and auto-completion does work, so nevermind this.
That's something else. Could you please open a new ticket?
I have written a native Zsh completion script for personal use (actually, I had no idea there was an autogenerated completion script), and I have been thinking of sharing it with [zsh-users/zsh-completions](https://github.com/zsh-users/zsh-completions) or here, and I found this issue. I took existing `_find` completion as example, so the completion offers the letters for `-t`, or commands and options for `-x`, and directories for the second positional argument, for example.

Here it is working:

[![asciicast](https://asciinema.org/a/Tru8hppyuDNpGEGu6UP6cgTK0.svg)](https://asciinema.org/a/Tru8hppyuDNpGEGu6UP6cgTK0)

I can create a PR if you are interested. I have fd 7.5.0 installed though, I would need to check if there are missing options for newer versions, but shouldn't be a problem.


> so the completion offers the letters for `-t`, or commands and options for `-x`, and directories for the second positional argument, for example.

That sounds fantastic!

> I can create a PR if you are interested. I have fd 7.5.0 installed though, I would need to check if there are missing options for newer versions, but shouldn't be a problem.

I'd be glad to replace the auto-generated file with the hand-written, if it's up to date. I guess a few changes will be necessary to the build/deployment system as well. See for example:

https://github.com/sharkdp/fd/blob/74c3431a2bf74ce78c26974f3cc07570e1d3baf3/ci/before_deploy.bash#L148

Fix patch:
diff --git a/build.rs b/build.rs
index 735ad74ec..92ca750ec 100644
--- a/build.rs
+++ b/build.rs
@@ -26,6 +26,5 @@ fn main() {
     let mut app = build_app();
     app.gen_completions("fd", Shell::Bash, &outdir);
     app.gen_completions("fd", Shell::Fish, &outdir);
-    app.gen_completions("fd", Shell::Zsh, &outdir);
     app.gen_completions("fd", Shell::PowerShell, &outdir);
 }
diff --git a/ci/before_deploy.bash b/ci/before_deploy.bash
index ee7bddac0..8041415b6 100755
--- a/ci/before_deploy.bash
+++ b/ci/before_deploy.bash
@@ -40,7 +40,7 @@ pack() {
     # various autocomplete
     cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".bash "$tempdir/$package_name/autocomplete/${PROJECT_NAME}.bash-completion"
     cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".fish "$tempdir/$package_name/autocomplete"
-    cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/_"$PROJECT_NAME" "$tempdir/$package_name/autocomplete"
+    cp contrib/completion/_"$PROJECT_NAME" "$tempdir/$package_name/autocomplete"
 
     # archiving
     pushd "$tempdir"
@@ -145,7 +145,7 @@ EOF
     # completions
     install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
     install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.fish "$tempdir/usr/share/fish/completions/$PROJECT_NAME.fish"
-    install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/_$PROJECT_NAME "$tempdir/usr/share/zsh/vendor-completions/_$PROJECT_NAME"
+    install -Dm644 contrib/completion/_"$PROJECT_NAME" "$tempdir/usr/share/zsh/vendor-completions/_$PROJECT_NAME"
 
     # Control file
     mkdir "$tempdir/DEBIAN"
diff --git a/contrib/completion/_fd b/contrib/completion/_fd
new file mode 100644
index 000000000..20f8144ca
--- /dev/null
+++ b/contrib/completion/_fd
@@ -0,0 +1,265 @@
+#compdef fd
+
+##
+# zsh completion function for fd
+#
+# Based on ripgrep completion function.
+# Originally based on code from the zsh-users project — see copyright notice
+# below.
+
+autoload -U is-at-least
+
+_fd() {
+  local curcontext="$curcontext" no='!' ret=1
+  local -a context line state state_descr _arguments_options fd_types fd_args
+  local -A opt_args
+
+  if is-at-least 5.2; then
+    _arguments_options=( -s -S )
+  else
+    _arguments_options=( -s )
+  fi
+
+  fd_types=(
+    {f,file}'\:"regular files"'
+    {d,directory}'\:"directories"'
+    {l,symlink}'\:"symbolic links"'
+    {e,empty}'\:"empty files or directories"'
+    {x,executable}'\:"executable (files)"'
+    {s,socket}'\:"sockets"'
+    {p,pipe}'\:"named pipes (FIFOs)"'
+  )
+
+  # Do not complete rare options unless either the current prefix
+  # matches one of those options or the user has the `complete-all`
+  # style set. Note that this prefix check has to be updated manually to account
+  # for all of the potential negation options listed below!
+  if
+    # (--[bpsu]* => match all options marked with '$no')
+    [[ $PREFIX$SUFFIX == --[bopsu]* ]] ||
+    zstyle -t ":complete:$curcontext:*" complete-all
+  then
+    no=
+  fi
+
+  # We make heavy use of argument groups here to prevent the option specs from
+  # growing unwieldy. These aren't supported in zsh <5.4, though, so we'll strip
+  # them out below if necessary. This makes the exclusions inaccurate on those
+  # older versions, but oh well — it's not that big a deal
+  fd_args=(
+    + '(hidden)' # hidden files
+    {-H,--hidden}'[search hidden files/directories]'
+
+    + '(no-ignore-full)' # all ignore files
+    '(no-ignore-partial)'{-I,--no-ignore}"[don't respect .(git|fd)ignore and global ignore files]"
+    $no'(no-ignore-partial)*'{-u,--unrestricted}'[alias for --no-ignore, when repeated also alias for --hidden]'
+
+    + no-ignore-partial # some ignore files
+    "(no-ignore-full --no-ignore-vcs)--no-ignore-vcs[don't respect .gitignore files]"
+    "!(no-ignore-full --no-global-ignore-file)--no-global-ignore-file[don't respect the global ignore file]"
+
+    + '(case)' # case-sensitivity
+    {-s,--case-sensitive}'[perform a case-sensitive search]'
+    {-i,--ignore-case}'[perform a case-insensitive search]'
+
+    + '(regex-pattern)' # regex-based search pattern
+    '(no-regex-pattern)--regex[perform a regex-based search (default)]'
+
+    + '(no-regex-pattern)' # non-regex-based search pattern
+    {-g,--glob}'[perform a glob-based search]'
+    {-F,--fixed-strings}'[treat pattern as literal string instead of a regex]'
+
+    + '(match-full)' # match against full path
+    {-p,--full-path}'[match the pattern against the full path instead of the basename]'
+
+    + '(follow)' # follow symlinks
+    {-L,--follow}'[follow symbolic links to directories]'
+
+    + '(abs-path)' # show absolute paths
+    '(long-listing)'{-a,--absolute-path}'[show absolute paths instead of relative paths]'
+
+    + '(null-sep)' # use null separator for output
+    '(long-listing)'{-0,--print0}'[separate search results by the null character]'
+
+    + '(long-listing)' # long-listing output
+    '(abs-path null-sep max-results exec-cmds)'{-l,--list-details}'[use a long listing format with file metadata]'
+
+    + '(max-results)' # max number of results
+    '(long-listing exec-cmds)--max-results=[limit number of search results to given count and quit]:count'
+    '(long-listing exec-cmds)-1[limit to a single search result and quit]'
+
+    + '(fs-errors)' # file-system errors
+    $no'--show-errors[enable the display of filesystem errors]'
+
+    + '(fs-traversal)' # file-system traversal
+    $no"--one-file-system[don't descend into directories on other file systems]"
+    '!--mount'
+    '!--xdev'
+
+    + dir-depth # directory depth
+    '(--exact-depth -d --max-depth)'{-d+,--max-depth=}'[set max directory depth to descend when searching]:depth'
+    '!(--exact-depth -d --max-depth)--maxdepth:depth'
+    '(--exact-depth --min-depth)--min-depth=[set directory depth to descend before start searching]:depth'
+    '(--exact-depth -d --max-depth --maxdepth --min-depth)--exact-depth=[only search at the exact given directory depth]:depth'
+
+    + filter-misc # filter search
+    '*'{-t+,--type=}"[filter search by type]:type:(($fd_types))"
+    '*'{-e+,--extension=}'[filter search by file extension]:extension'
+    '*'{-E+,--exclude=}'[exclude files/directories that match the given glob pattern]:glob pattern'
+    '*'{-S+,--size=}'[limit search by file size]:size limit:->size'
+    '(-o --owner)'{-o+,--owner=}'[filter by owning user and/or group]:owner and/or group:->owner'
+
+    + ignore-file # extra ignore files
+    '*--ignore-file=[add a custom, low-precedence ignore-file with .gitignore format]: :_files'
+
+    + '(filter-mtime-newer)' # filter by files modified after than
+    '--changed-within=[limit search to files/directories modified within the given date/duration]:date or duration'
+    '!--change-newer-than=:date/duration'
+    '!--newer=:date/duration'
+
+    + '(filter-mtime-older)' # filter by files modified before than
+    '--changed-before=[limit search to files/directories modified before the given date/duration]:date or duration'
+    '!--change-older-than=:date/duration'
+    '!--older=:date/duration'
+
+    + '(color)' # colorize output
+    {-c+,--color=}'[declare when to colorize search results]:when to colorize:((
+      auto\:"show colors if the output goes to an interactive console (default)"
+      never\:"do not use colorized output"
+      always\:"always use colorized output"
+    ))'
+
+    + '(threads)'
+    {-j+,--threads=}'[set the number of threads for searching and executing]:number of threads'
+
+    + '(exec-cmds)' # execute command
+    '(long-listing max-results)'{-x+,--exec=}'[execute command for each search result]:command: _command_names -e:*\;::program arguments: _normal'
+    '(long-listing max-results)'{-X+,--exec-batch=}'[execute command for all search results at once]:command: _command_names -e:*\;::program arguments: _normal'
+
+    + other
+    '!(--max-buffer-time)--max-buffer-time=[set amount of time to buffer before showing output]:time (ms)'
+
+    + '(about)' # about flags
+    '(: * -)'{-h,--help}'[display help message]'
+    '(: * -)'{-v,--version}'[display version information]'
+
+    + path-sep # set path separator for output
+    $no'(--path-separator)--path-separator=[set the path separator to use when printing file paths]:path separator'
+
+    + search-path
+    $no'(--base-directory)--base-directory=[change the current working directory to the given path]:directory:_files -/'
+    $no'(*)*--search-path=[set search path (instead of positional <path> arguments)]:directory:_files -/'
+
+    + args # positional arguments
+    '1: :_guard "^-*" pattern'
+    '(--search-path)*:directory:_files -/'
+  )
+
+  # Strip out argument groups where unsupported (see above)
+  is-at-least 5.4 ||
+  fd_args=( ${(@)args:#(#i)(+|[a-z0-9][a-z0-9_-]#|\([a-z0-9][a-z0-9_-]#\))} )
+
+  _arguments $_arguments_options : $fd_args && ret=0
+
+  case ${state} in
+    owner)
+      compset -P '(\\|)\!'
+      if compset -P '*:'; then
+        _groups && ret=0
+      else
+        if
+          compset -S ':*' ||
+          # Do not add the colon suffix when completing "!user<TAB>
+          # (with a starting double-quote) otherwise pressing tab again
+          # after the inserted colon "!user:<TAB> will complete history modifiers
+          [[ $IPREFIX == (\\|\!)*  && ($QIPREFIX == \"* && -z $QISUFFIX) ]]
+        then
+          _users && ret=0
+        else
+          local q
+          # Since quotes are needed when using the negation prefix !,
+          # automatically remove the colon suffix also when closing the quote
+          if [[ $QIPREFIX == [\'\"]* ]]; then
+            q=${QIPREFIX:0:1}
+          fi
+          _users -r ": \t\n\-$q" -S : && ret=0
+        fi
+      fi
+      ;;
+
+    size)
+      if compset -P '[-+][0-9]##'; then
+        local -a suff=(
+          'B:bytes'
+          'K:kilobytes  (10^3  = 1000   bytes)'
+          'M:megabytes  (10^6  = 1000^2 bytes)'
+          'G:gigabytes  (10^9  = 1000^3 bytes)'
+          'T:terabytes  (10^12 = 1000^4 bytes)'
+          'Ki:kibibytes  ( 2^10 = 1024   bytes)'
+          'Mi:mebibytes  ( 2^20 = 1024^2 bytes)'
+          'Gi:gigibytes  ( 2^30 = 1024^3 bytes)'
+          'Ti:tebibytes  ( 2^40 = 1024^4 bytes)'
+        )
+        _describe -t units 'size limit units' suff -V 'units'
+      elif compset -P '[-+]'; then
+        _message -e 'size limit number (full format: <+-><number><unit>)'
+      else
+        _values 'size limit prefix (full format: <prefix><number><unit>)' \
+          '\+[file size must be greater or equal to]'\
+          '-[file size must be less than or equal to]' && ret=0
+      fi
+      ;;
+  esac
+
+  return ret
+}
+
+_fd "$@"
+
+# ------------------------------------------------------------------------------
+# Copyright (c) 2011 Github zsh-users - http://github.com/zsh-users
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the zsh-users nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL ZSH-USERS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# ------------------------------------------------------------------------------
+# Description
+# -----------
+#
+#  Completion script for fd
+#
+# ------------------------------------------------------------------------------
+# Authors
+# -------
+#
+#  * smancill (https://github.com/smancill)
+#
+# ------------------------------------------------------------------------------
+
+# Local Variables:
+# mode: shell-script
+# coding: utf-8-unix
+# indent-tabs-mode: nil
+# sh-indentation: 2
+# sh-basic-offset: 2
+# End:
+# vim: ft=zsh sw=2 ts=2 et


Test patch: