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 (
--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:
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.
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.
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.
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.
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 :)
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 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
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
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̵.
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
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.
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 :)