fzf and rg

Everyone who works in the shell needs a faster way to get around. fzf and rg in zsh makes for a great combination. I’ve been using this setup for a while now and it allows me to jump around my filesystem.

$ brew install fzf rg

To install useful key bindings and fuzzy completion:

$ $(brew --prefix)/opt/fzf/install

Adding a couple of lines to .zshrc

# Setting rg as the default source for fzf
export FZF_DEFAULT_COMMAND='rg --files --no-ignore --hidden --follow -g "!{.git,node_modules}/*" 2> /dev/null'

# To apply the command to CTRL-T as well
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"

And we’re done!

Fuzzy history search

$ <crtl> + r
$
   7063  bundle
   7064  bundle exec middleman build
   7065  time bundle exec middleman build
   7066  bundle install
   7067  bundle purge
   7068  bundle --help
   7069  bundle clean
   7070  bundle clean --force
   7071  bundle exec middleman server
   7115  bundle exec middleman
   7161  bundle exec middleman version
   7186  bundle exec middleman server
   7196  bundle install
   7197  bundle exec middleman server
   7265  bundle install
   7266  bundle update
   7267  gem install bundler --pre
   7269  bundle exec middleman build
>  7409  bundle exec middleman server
  129/7409 +S
> bundle

Fuzzy file search

$ <ctrl> + t
$
> zshrc
\ 1678/972367
> .zshrc
  .zshrc.zwc
  dotfiles/zsh/.zshrc
  .oh-my-zsh/templates/zshrc.zsh-template
  .oh-my-zsh/plugins/redis-cli/_redis-cli
  .oh-my-zsh/themes/crcandy.zsh-theme
  .oh-my-zsh/plugins/react-native/README.md
  .oh-my-zsh/plugins/react-native/_react-native
  .oh-my-zsh/plugins/react-native/react-native.plugin.zsh
  .oh-my-zsh/plugins/arcanist/README.md
  .oh-my-zsh/plugins/archlinux/README.md
  .oh-my-zsh/plugins/rsync/rsync.plugin.zsh
  .oh-my-zsh/plugins/arcanist/arcanist.plugin.zsh
  .oh-my-zsh/plugins/archlinux/archlinux.plugin.zsh
  .oh-my-zsh/plugins/mercurial/README.md
  .oh-my-zsh/plugins/dircycle/dircycle.plugin.zsh
  .oh-my-zsh/plugins/mercurial/mercurial.plugin.zsh
  .oh-my-zsh/themes/tjkirch.zsh-theme

Fuzzy directory go to

$ <alt> + c
$
> chrome
| 16635/70115
> Library/Caches/Google/Chrome
  Applications/Chrome Apps.localized
  Library/Caches/Google/Chrome/Default
  Applications/Chrome Canary Apps.localized
  Library/Application Support/Google/Chrome
  Library/Caches/Google/Chrome/Default/Cache
  Library/Application Support/Google/Chrome/pnacl
  Library/Application Support/Google/Chrome Canary
  Library/Caches/Google/Chrome/Default/Media Cache
  Library/Caches/Google/Chrome/Default
  Library/Caches/Google/Chrome/Crashpad
  Library/Caches/Google/Chrome/Default/PnaclTranslationCache
  Library/Application Support/Google/Chrome/EVWhitelist
  Library/Application Support/Google/Chrome/PepperFlash
  Library/Caches/Google/Chrome/ShaderCache
  Library/Caches/Google/Chrome/WidevineCDM
  Applications/Chrome Canary Apps.localized/app_list.app
  Library/Caches/Google/Chrome/Crashpad/new
  Library/Caches/Google/Chrome/OriginTrials

Why these two together

rg earned its spot by being fast in the cases that matter: it walks directories in parallel, respects .gitignore so a search inside a repository skips node_modules instead of drowning you in it, and passes over binary files without being asked. It assumes you are searching a source tree, which you almost always are.

fzf earns its spot by being the universal picker. It reads candidate lines from stdin, shows a fuzzy-matched list, and prints your selection to stdout. That contract means it composes with everything.

The two config lines above weld them together. FZF_DEFAULT_COMMAND tells fzf how to gather candidates when invoked bare, and rg --files is a better gatherer than the stock find: list every file (--files), include dotfiles (--hidden), skip the ignore rules so nothing is invisible (--no-ignore), follow symlinks (--follow), but keep .git and node_modules out (-g "!{.git,node_modules}/*"). Pointing FZF_CTRL_T_COMMAND at the same command puts CTRL-T into that same pool of candidates.

Pipes

Once both tools speak stdin and stdout, the combinations are free:

$ rg --line-number TODO | fzf     # pick a match, get the file and line
$ vim "$(fzf)"                     # open whichever file you fuzzy-pick
$ git log --oneline | fzf          # find a commit without scrolling

The three bindings cover most of the daily use: CTRL-R over shell history with a preview of the full command, CTRL-T to drop a path into the command you are typing, and ALT-C to cd into a fuzzy-picked directory, which is each of the transcripts above in a single keystroke.