How FZF and ripgrep improved my workflow
I stopped writing on Medium.com and now write on my own website instead. This post will remain here for reference but will no longer benefit from updates, you can find this post here: https://sidneyliebrand.io/blog/how-fzf-and-ripgrep-improved-my-workflow
Today I want to talk about fzf and ripgrep, two tools I use all the time when working in Vim and the terminal. They have become an absolutely vital part of my workflow. Ever since I started using them I can’t imagine myself functioning without them anymore.
What is FZF?
FZF is a fuzzy finder for your terminal, it is a command line application that filters each line from given input with a query that the user types. When the query changes, the results update in realtime.
After finding the file you’re looking for, hitting enter
prints the highlighted entry. You can combine this with your $EDITOR
variable to search for- and edit a file for example.
Of course this is only a simple example. The possibilities with FZF are endless. There are countless ways in which you can use it filter input and use that in another command. We’ll dive more into that later.
What about ripgrep?
As it already says in the name, it is another grep
program. Ripgrep is written in rust and one of its primary goals is to be the fastest grep
of them all. It performs amazing even in a larger code base.
Ripgrep has many options to explore, there are way to many to list here.
Some of the options I use most often with ripgrep are:
--files
— List files which ripgrep will search instead of searching them--hidden
— Show hidden (.file
) files--no-ignore-vcs
— Show files ignored by your VCS--vimgrep
— Results are returned on a single line in vimgrep format
The problems they solve
Both these tools can be combined in various scenario’s that would have otherwise taken multiple long commands to execute. This ranges from killing processes to managing plugins to being able to find (in) files.
These actions are usually involved when I try to do something more complex:
- googling the right command
- look around for the right line in the output
- refine grep pattern
- retrying the command
At this point you’ll realize that you’re not actually searching for something anymore. You find yourself looking for ways to perform your search instead :/ This is only one of many scenarios however. Another common one is:
Killing processes
One example is stopping an out of control process. First you have to find the process ID by issuing some command like:
$ ps -ef | grep [PROCESS NAME]
Which is then followed by a kill
command with one of the process IDs you want to kill. The downsides to this are that I have to use two commands. Filter the output before seeing it or knowing how it looks and issuing an extra command to actually stop the process.
To make this easier, I wrote a small wrapper (first in zsh, later migrated to fish) called kp
. It lists processes using ps -ef
and pipes it to fzf
.
This command opens an FZF window with your processes. FZF has an option to allow selecting multiple entries (-m
flag). When enter
is pressed, both marked (light red >
symbols) processes will be shut down. When changing your query, selected entries will stay selected. This is convenient for killing different processes in a single run.
After killing some processes, the command will rerun itself. I can use escape
to exit from this specific window.
Installing brew plugins
Another use case is to install, update or purge brew plugins from your system. When you are looking for a brew package, a common pattern is to use brew search
together with grep
to find out if it exists.
After that you’ll most likely run a command like: brew install [PACKAGE]
to install it. Another pattern is to use the brew leaves
command to list installed packages which can be updated or removed.
I created a small wrapper for each of these actions. One for installing, another for updating and one for deleting brew packages:
bip
— Brew Install Plugin, install one or more plugins (zsh, fish)bup
— Brew Update Plugin, update multiple installed plugins (zsh, fish)bcp
— Brew Clean Plugin, delete multiple installed plugins (zsh, fish)
Whenever I have to do anything with brew, it is completely painless and it works quite well for package discovery too.
Finding binaries
One mythical beast known to anyone who has ever worked in a terminal is the $PATH
variable. Often, a shell script will tell you to “Add me to your $PATH” so that the script will become available in your shell. This makes sense but can leave you with a messed up shell path or duplicate entries. It could cause all kinds of weirdness and slowness in your terminal.
My solution to this is a simple path explorer called fp
(zsh, fish). It invokes FZF with a list of folders populated using $PATH
.
Of course there are more than 3 paths in my list but I cropped the gif for brevity here. When I press enter
on the/bin
entry, I see a list of executables inside that folder. Either find what you’re looking for or go back.
Going back to the overview is as easy as pressing escape
. This will take you back to the directory listing. Pressing escape
in the overview will exit the command completely.
Checking features on caniuse.com
Additionally, I’ve written a post before on how to combine Caniuse with FZF. It allows me to quickly find out wether I should stay away from some Web API or not. this small tool also allows me to query features that have been added or deprecated recently.
The cani
command (zsh, fish) itself uses another ruby script (ciu
) I wrote to actually provide the data and format it properly. The data is fetched once then cached for a day. So you’ll have fresh data on a daily basis :)
This mixture of shell + ruby has since been ported to a Ruby Gem :)
Vim
Since I spend a lot of my time in Vim trying to find a file either by name, or by some code inside a certain file. Streamlining that process is very important. Every context switch you have to make adds overhead and the possibility of losing focus of what you are trying to find. Therefore it should be as mindless as possible, e.g: press a key, type query, press enter to go to matching file.
Finding files wasn’t too much of an issue here. There is a long list of Vim plugins that offer file searching using fuzzy matching or MRU algorithms. Two examples of this are CtrlP and Command-T. I used CtrlP which always managed to do the job. But after playing around with FZF in the terminal I wondered if it could be applied to Vim as well.
FZF.vim
FZF has a small builtin Vim interface that already works, but it comes without any existing functionality. The author of FZF also wrote this plugin. It is a small wrapper that provides common functionality. This includes listing files, buffers, tags, git logs and much more!
Fuzzy searching in file paths
Coming from CtrlP the first thing I needed was a replacement for fuzzy-finding files. The solution was to use the :Files
command provided by FZF.vim. This lists files using your $FZF_DEFAULT_COMMAND
environment variable. It opens the currently highlighted file on enter
.
Since I was already so used to the ctrl-p
mapping from the CtrlP plugin, I mapped the :Files
command to it like this:
nnoremap <C-p> :Files<Cr>
FZF will not use ripgrep by default so you’ll have to modify $FZF_DEFAULT_COMMAND
if you want FZF to use ripgrep. Of course this is exactly what I wanted! After some tweaking I ended up with the following command (in fish syntax):
set -gx FZF_DEFAULT_COMMAND 'rg --files --no-ignore-vcs --hidden'# equivalent bash / zsh:
# export FZF_DEFAULT_COMMAND='rg --files --no-ignore-vcs --hidden'
In my case it happens that I do want to edit or search for something in a file that is ignored by my VCS or in a hidden file. The options ensure that all files inside the directory are listed (except those ignored in a ~/.rgignore
file).
Finding content in specific files
Last but not least I wanted to find files based on what was inside of a file. This is useful to see where a class or function is used for example.
The name of this command is :Ag
and as you can guess, it relies on ag
to grep inside files. ag
is a nice and fast tool too but since I am already using ripgrep, I’d rather use that over installing another dependency. T̵h̵i̵s̵ ̵m̵e̵a̵n̵s̵ ̵w̵e̵’̵l̵l̵ ̵h̵a̵v̵e̵ ̵t̵o̵ ̵d̵o̵ ̵s̵o̵m̵e̵ ̵m̵a̵n̵u̵a̵l̵ ̵t̵w̵e̵a̵k̵i̵n̵g̵.
UPDATE 27–09–2018
As pointed out by Casey McGinty in the comments, FZF.vim now has the command :Rg
built-in (see :h fzf-vim-commands
). The old section on how to do it still exists but is now irrelevant.
Despite the update to the plugin, I’ll leave the code snippet here for reference:
This one I mapped to ctrl-g
, right next to ctrl-f
for the :Files
command.
nnoremap <C-g> :Rg<Cr>
The nice thing about this command is that you can select multiple files. When selecting multiple files, pressing enter
will load the files in a quickfix list for batch editing using cdo
for example.
Conclusion
As I mentioned at the start of my post, these tools have become a vital part of my workflow. I use them while barely noticing their presence and they take a lot of complexity away from the task at hand. This allows me to focus on what matters instead of finding out how to do something which should be trivial.
Wether it be killing services / processes, installing brew packages, finding a glitch in my path or a feature set in caniuse, I can do it in fewer keystrokes with more fine-grained control. I even use FZF as a standalone filter sometimes when I have to find something in line-based command output, skipping (rip)grep all together :)
Hopefully you are also able to reduce some of the strain in your workflow with FZF using some of the tips above. If you are using FZF in another way, leave a comment! I’d love to hear about it and learn what others are doing with these two fantastic tools.
Happy fuzzy finding :)