A variety of workflows using fzf
fzf has been one of the show cases of a successful, relatively recent command line tools. It is a relatively recently developed tool which seemingly brought a true sea-change to how many people use and navigate within their terminal. 1 On its surface, it provides a simple idea — don’t just look for exact words, fuzz them a little instead — but executes it so well that it truly changes the experience of interacting with your shell.
There are already a bunch of ways of working with fzf floating around on various blogs,
but I decided to still add my voice to the choir.
This is for two reasons:
space on the web does not seem too scarce,
and the kilobytes that this page occupies should fit.
And, fzf
, being a tool true to the Unix philosophy of doing one thing well,
integrates nicely into all sorts of other applications.
The combinatorial possibility space of combining applications is huge,
so maybe I found a use case you have not considered yet.
What we will cover in the following sections is:
- basic
fzf
ideas - integration with some (
zsh
) shell tools (notably its history, man-pages, tab completion) - integration with
yay
(though this can of course be adopted forpacman
)
What we will not be covering in the following sections,
but would love to get into whenever I find the time is a basic integration with tmux
(session selection), and more advanced integrations within vim
(for file and buffer navigation, full-text search, and zettelkasten navigation).
If you are interested in those, for now,
you will have to make do with rummaging through my dotfiles.
Some are already documented a bit but since dotfiles are often in flux,
the docs will never be fully up-to-date, I fear.
Look especially for the tmux
-session chooser in the tmux
module,
and some :Fzf
-commands in the nvim
module to find out more.
The basics
If you just enter fzf
as a command,
the program initiates find
in the current working directory and displays a list of all found files to fuzzily search through.
While that can be useful, we want more flexibility by connecting it to other tools we already use.
So, how does that work?
Basically, fzf
can be fed any list of values through its stdin
, which it will then allow you to filter fuzzily.
As mentioned, by default it is fed by the find
program.
It will then pass through to its stdout
your selection (or selections, if multiple).
Of course, we can then use this selection to pass into other programs to actually do something with it.
In the version above, it would for example be a good idea to feed the results into cat
or less
:
fzf | less
or fzf | cat
Or, we could directly invoke vim
with the output of the command, so that we can edit the note we want.
Since we pass the file for vim
to open as an argument, we can do so with the following command,
which gets the results from fzf and passes them along:
vim "$(fzf)"
.
So that is the very basic usage of fzf,
but what if we don’t want to use the files of our current directory as choices?
By passing something to fzf
’s stdin
, it will use those choices.
For example, find ~/documents/notes/ -type f | fzf | xargs less
will let you search through the names of your notes,
regardless of which directory you are currently in.
Of course, we can pass anything into fzf
, not just the contents of directories.
How about searching through a little to-do list for example?
If we have access to it in plain text, we can just do cat ~/documents/todo.md | fzf | less
to select an entry to display.
Or we can go further:
nvim --headless "+%s/^$(cat ~/todo.md | fzf)/[x] \0/" +wq
The convoluted string above loads the content of your to-do list into fzf
and waits for you to select an entry;
then it starts neovim
without showing you its interface,
searches for the entry in your file and prepends it with [x]
to mark it being done.
Although please be aware that the above way of doing it serves mainly as a demonstration of what is possible and is not the most efficient way of accomplishing the task! 2 With a bit of tinkering, these concepts, by the way, are basically already enough to get started building a full-text search engine for your plain text files.
Before we look at some examples,
I think highlighting the preview window of fzf
may also be useful.
The preview window is a powerful concept —
it simply runs another command for whatever entry you currently have selected and displays its output together with your results.
Examples might be to display the contents of text files as you are choosing them
(useful for the notes search we created above),
or showing the contents of directories as you hover over them
(useful for the basic fzf
+ find
functionality if you want to use it for navigation).
To get a preview into our notes for the function we created earlier,
a simple way to accomplish it is:
find ~/documents/notes -type f | fzf --preview='cat {}'
.
This will: 1. Execute the directory-independent search as before; 2. Call cat
with the currently selected entry as its argument (passed along instead of {}
).
The contents of our notes are now displayed directly next to its entry so that we can have an idea of what they contain. 3
Also take a look into fzf
’s manual with man fzf
.
It is very well documented and possesses a ton of flags and options with which its behavior can be changed to your liking.
Armed with this knowledge, let’s dive into some examples —
from basic uses to more advanced integrations.
Basic shell integrations
We will now walk through creating fzfhistory
, fzfman
, and fzfyay
to search through our shell history, man-pages, and repository packages respectively.
Of course, you can name the commands whatever you want —
but grouping them into the fzf
-namespace has been working well for my semi-porous brain.
Fuzzy history
In your shell, if you have set it up, you can see a history of all your previously typed commands. Now, when I want to remember one of the more rare commands that you just need every now and again, this list is really useful.
But scrolling through its entries one by one doesn’t seem practical,
and if I wanted to grep
through it I would need to know the exact command I typed previously —
and that’s the reason I am looking in here in the first place!
So whenever you find yourself thinking, ‘how about a less exact version of this’,
you already know fzf
can come to your rescue.
The fzfhistory
command is just a simple alias that I have defined as follows:
alias fzfhistory="history | fzf --tac --height 20"
,
and history
in turn is aliased to:
alias history="fc -l -d -D 0"
,
which invokes the standard zsh
history and shows it to you from the very first entry,
with time-stamp, execution time.
The result is a function I can quickly invoke whenever I have a vague memory of a command once used, and which is quick to explore.
Of course, you could extend this functionality to also run the command you select, substituting your shell of choice: 4
zsh -c $(fzfhistory | cut -f7- -d' ')
Fuzzy manpages
How about searching all available man-page topics in a fuzzy way? This search kind of mirrors what we have been doing previously, but it involves a bit more manipulation of our results.
The full code to achieve it is as follows:
fzfman() {
man "$(apropos --long "$1" | fzf | awk '{print $2, $1}' | tr -d '()')"
}
Now, this time our command is a function since it is a little more involved than the previous aliases.
Still, it’s no rocket science: apropos --long
parses all possible topics from our current man-page database, to which we can pass an initial topic as an argument if we so desire.
5
We then pass the whole list to fzf
which is fortunately quite fast —
it works basically instantaneously for my 25668 man-page topics, after they have been initially built.
The selected topic is then passed from fzf
to awk
for column-based wrangling.
We take an initial string like fzf (1) - a command-line fuzzy finder
and switch its first to space separated ‘columns’ around.
In this example, we get (1) fzf
.
We finally remove the parentheses, and pass the result to man
as its argument.
And just like that we arrive at our desired man-page!
Fuzzy package management
Alright, we now have some of the basics out of the way.
To be able to fuzzily search packages is not much more advanced,
but I want to have a preview with the package information displayed as well —
so we will now get into using the fzf
’s preview window.
alias fzfyay="yay -Slq | fzf -m --preview 'yay -Si {1}' | xargs -ro yay -S"
Once again, the yay -Slq
will feed all packages in the repositories to fzf
through stdin
.
6
Now fzf
itself gets a bit more advanced.
First off, we use -m
to allow the user to select multiple entries which will then all be passed on.
Then, we instruct fzf
to create a preview windows, which it fills with the package information for whatever entry we have currently selected.
It will re-run this command to populate the preview window whenever our selected entry changes.
Lastly, we take everything that fzf
spits out (remember, it could be multiple arguments),
and send it to yay -S
again, to begin their installation.
I won’t go too deep into an explanation of xargs
and its options,
but suffice to say that this combination allows us to use yay
interactively,
and will not run anything if we did not select anything with fzf
.
Done!
As a bonus, we can also fuzzy-fy the removal of existing packages with the following commands:
alias fzfyayrns="yay -Qeq | fzf -m --preview 'yay -Qi {1}' | xargs -ro yay -Rns"
It’s not too different from installation,
but uses your locally installed packages as listing and information sources instead.
Of course, similar results can also be achieved with pacman
if you are not using yay
—
the syntax is basically congruent between the two programs (thanks yay
developers!)
Fuzzy shell completion
Fuzzy shell completion is quite a bit more involved than our basic examples so far.
In fact, while basic completion is possible to achieve relatively quickly,
I would suggest you do the same thing as I am currently:
use aloxaf’s impressive fzf-tab
plugin!
Be aware that the following will only work for the zsh
shell.
You install it and source its main file from wherever you installed it:
source /install/location/fzf-tab.plugin.zsh
, and you are done.
Read the accompanying instructions carefully, and you have a basic working tab completion with fzf
.
If you look into the configuration section, there are some examples for adding additional, really useful completions.
For example, to get a preview of the various processes running on your system when invoking kill
,
you can use the:
# give a preview of commandline arguments when completing `kill`
zstyle ':completion:*:*:*:*:processes' command "ps -u $USER -o pid,user,comm,cmd -w -w"
zstyle ':fzf-tab:complete:kill:argument-rest' extra-opts --preview=$extract'ps --pid=$in[(w)1] -o cmd --no-headers -w -w' --preview-window=down:3:wrap
This arcane snippet of code is zsh-completion
code, and will invoke the fzf-tab
program as a completer.
If you ignore the zsh
-specific configurations above,
you can see that it boils down to basically invoking the preview window again.
Only this time its population is quite a bit more involved, using a few zsh
-specific possibilities to end up with something like the following:
-
Its license points to 2013 as the start of development, the first git commits stem from around 2014. ↩︎
-
There are other ways to accomplish this, notably with
sed
, which would be more efficient. Mainly this is to mirror the above steps going fromless
tovim
and to show that you can pipe pretty much anywhere. We could, for example accomplish something similar with (gnu)sed
by usingsed -ie "s/^$(cat ~/todo.md | fzf)/[x] \0/" ~/todo.md
instead. ↩︎ -
Usually, you would also have more space available to display the contents. With the tiny frame being recorded for this animation, it does look a bit weird. ↩︎
-
I consciously decided against this however, since I would rather have the safety of manually typing it before running anything. ↩︎
-
The fact that we can either decide to pass in a default argument and thereby pre-filter the list, or simply wade through the whole possibility space of all man-pages is actually quite useful for us. Implementing such a pre-filter argument is not hard for many programs and is generally useful when building something for
fzf
. ↩︎ -
-Slq
meanssync
, i.e. get information from the repos,list
list all available packges,quiet
hide extraneous information and only show the package names. ↩︎