Ctrl-P in the Terminal with fzf

Extend fzf to automatically change directories and open files when selected.

I use the vim plugin Ctrl-P. Ctrl-P is a convenient file search plugin, which allows regex searching for files in the directory tree.

I also use fzf, which provides similar file search functionality, but for the terminal. fzf also has a vim plugin, which I assume is similar to Ctrl-P, though I haven't used it. By default, the file search mode of fzf is bound to the key binding ctrl-t.

In my opinion, fzf's file search is missing one key feature. When you select the file or directory path from the search window, it is simply pasted into the terminal. This is useful for some applications, but most of the time I either want to edit the file (if the selected path points to a file) or change to the directory (if the path points to a directory).

Luckily, fzf is very extensible. I use zsh as my shell, so I wrote my own ZLE widget to implement the feature. The code for the widget is below.

# This is the same functionality as fzf's ctrl-t, except that the file or
# directory selected is now automatically cd'ed or opened, respectively.
fzf-open-file-or-dir() {
  local cmd="command find -L . \
    \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
    -o -type f -print \
    -o -type d -print \
    -o -type l -print 2> /dev/null | sed 1d | cut -b3-"
  local out=$(eval $cmd | fzf-tmux --exit-0)

  if [ -f "$out" ]; then
    $EDITOR "$out" < /dev/tty
  elif [ -d "$out" ]; then
    cd "$out"
    zle reset-prompt
  fi
}
zle     -N   fzf-open-file-or-dir
bindkey '^P' fzf-open-file-or-dir

The lines containing the assignments to cmd and out are adapted from fzf's built-in file search widget. The if statement that follows implements the new behaviour.

One of the interesting parts of the new code is the line $EDITOR "$out" </dev/tty. It opens the selected file with your $EDITOR. The redirection of input from /dev/tty is required for my $EDITOR, vim, to properly read input from the terminal, since it's being called non-interactively. On Linux systems, /dev/tty is an alias for "the controlling terminal of the current process" (see the man page). I've only tried this with vim; your results with other editors may vary.

Another interesting piece of code is the line zle reset-prompt. This command simply does what it says: it resets the user's prompt after we've cd'ed.

I assigned the widget to the key binding ctrl-p, so now I can use that same binding for the same purpose in both vim and the terminal.