shellzshfzf

Make fzf `**<TAB>` completion only return files for specific command (caveat inside!)


I'm trying to make fzfs autocompletion invoked on cat **<TAB> return only files (directories does not make sense for cat). According to docs it's the matter of defining _fzf_complete_cat(), so my first try was

_fzf_complete_cat() {
  _fzf_complete -- "$@" < <(
    fd --type f --hidden --follow --no-ignore --exclude .git 
  )
}

however, there's a major flaw: this completion does not take into account the path's part before **, e.g. if I am in ~/subdir and type cat ~/another/**<TAB> it will not recurse from there, but rather from ~/subdir.

I understand the trick is around these lines from fzf/shell/completion.zsh, namely, the way _fzf_[path|dir]_completion() handles it. So I tried smth like that (essentially, repeating the logic of the completion.zsh):

_custom_fzf_compgen_path() {
  fd --hidden --follow --no-ignore --exclude ".git" . "$1"
}

_custom_fzf_path_completion() {
  __fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
    "-m" "" " "
}

_fzf_complete_cat() {
  _custom_fzf_path_completion "$prefix" "$1"
}

and it almost works, messing up in the very end with zle reset-prompt from here.

TL;DR: I need **<TAB> for cat command work similarly to _fzf_[path|dir]_completion() but return only files.

Appreciate any ideas! Thanks!


Solution

  • I ended up going down the initial path of tweaking _fzf_complete_cat(). It uses a wonderful function __fzf_generic_path_completion() which takes care of splitting the path yped before **<TAB> (e.g. cat ~/some_other_dir/**<TAB>) and finding the base of it. Problem lied around extra zle reset-prompt in here, so I had to create my own __fzf_generic_path_completion() without it.

    Full working solution

    __custom_fzf_generic_path_completion() {
      local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
      base=$1
      lbuf=$2
      cmd=$(__fzf_extract_command "$lbuf")
      compgen=$3
      fzf_opts=$4
      suffix=$5
      tail=$6
    
      setopt localoptions nonomatch
      eval "base=$base"
      [[ $base = *"/"* ]] && dir="$base"
      while [ 1 ]; do
        if [[ -z "$dir" || -d ${dir} ]]; then
          leftover=${base/#"$dir"}
          leftover=${leftover/#\/}
          [ -z "$dir" ] && dir='.'
          [ "$dir" != "/" ] && dir="${dir/%\//}"
          matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
            echo -n "${(q)item}$suffix "
          done)
          matches=${matches% }
          if [ -n "$matches" ]; then
            LBUFFER="$lbuf$matches$tail"
          fi
          # REMOVED HERE zle reset-prompt
          break
        fi
        dir=$(dirname "$dir")
        dir=${dir%/}/
      done
    }
    
    _custom_fzf_compgen_path() {
      # Add here your favorite command to search for files. $1 is the starting
      # point.
      fd --type f --hidden --follow --no-ignore --exclude ".git" . "$1"
    }
    
    _custom_fzf_path_completion() {
      __custom_fzf_generic_path_completion "$1" "$2" _custom_fzf_compgen_path \
        "-m" "" " "
    }
    
    _fzf_complete_cat() {
      # $prefix is the variable set by fzf here https://github.com/junegunn/fzf/blob/master/shell/completion.zsh#L302
      _custom_fzf_path_completion "$prefix" "$1"
    }