fzf + vim + tmux
2014. 04. 24.

fzf - "fuzzy finder for your shell"

fzf is a Ruby script I've been working on for the past months. Basically It's a curses-based Unix filter that allows you to quickly select an item from a list using fuzzy matching pattern. For example, when you search for /usr/local/bin/git, you don't type in the exact path, but you can just haphazardly type in characters such as urlcbigt. The idea of fuzzy finder has been around for quite a while, and it has become extremely popular among Vim community by the emergence of the excellent Vim plugins such as Command-T and ctrlp.vim. Being able to open files using fuzzy pattern is so convenient that now I could never imagine living without it.

The natural progression was that I started to want to have this fuzzy finder everywhere, not just on Vim. And not just for selecting files, but for anything. Unable to find the right solution for my needs, I decided to write fzf for myself. It was initially released as a very small script around 370 lines of code, but since then many people have continuously shown interest and suggested lots of genuine ideas that have now become parts of fzf. Now after 6 months from its initial release, it has grown to have more than 1200 lines of code and it's distributed with its own Vim plugin, keybindings for bash and zsh and even fuzzy-auto-completion for bash. The idea itself also has become pretty popular that now I see clones being written in Go and Python.

Using fzf in Vim

Vim follows the Unix philosophy and encourages you to use external commands whenever appropriate. This idea is well summarized in the following excerpt from Vim Koans.

A Markdown acolyte came to Master Wq to demonstrate his Vim plugin.

“See, master,” he said, “I have nearly finished the Vim macros that translate Markdown into HTML. My functions interweave, my parser is a paragon of efficiency, and the results nearly flawless. I daresay I have mastered Vimscript, and my work will validate Vim as a modern editor for the enlightened developer! Have I done rightly?”

Master Wq read the acolyte’s code for several minutes without saying anything. Then he opened a Markdown document, and typed:

:%!markdown

HTML filled the buffer instantly. The acolyte began to cry.

I guess you see the point. The thing is, there's really no reason not to try fzf in Vim, except that we already have a variety of very well-written native Vim plugins with the same goal. There are pros and cons to using fzf in lieu of native ones that I'm going to delve into in the following sections.

Pros.

Consistency

By choosing to use fzf in Vim, you use the same fuzzy finder interface regardless of whether you're in Vim or not. Consistenty matters.

Non-blocking

One of the benefits of using fzf is that it does not block while the list is being populated. The benefit is clear when you have a very large list. Thanks to its non-blocking nature, you don't have to wait tens of seconds until the entire list is ready. You just start typing in right away.

For the discussion to be complete, I must mention that Unite.vim can also process the list asynchronously using vimproc.vim. However, the performance is simply no match for fzf (you'll see). Please note that I'm not trying to argue that you should choose fzf over Unite.vim, which is much more than just a fuzzy finder.

Fast

fzf is fast. Ruby is not the language that is particularly famous for its performance (actually it's the opposite), but it's still several times faster than Vimscript and fzf benefits from that. More important is that fzf feels so fast because of its smart caching of the intermediate results. The idea is so simple yet very effective. Typing in characters to fuzzy finder is the process of narrowing down the search space. fzf takes advantage of this fact and only looks at the much-smaller, already narrowed-down list whenever possible instead of going through the entire list.

However, inevitably, as the size of the list grows, search becomes proportionally slow. Searching is a CPU-intensive task, and since at the time of this writing it's almost impossible to find a personal computer with a humble single core CPU (even my phone has a quad-core), we can greatly reduce search time by parallelizing it across the cores. As a matter of fact, the search function of Command-T is written in multi-threaded C, so for a very large list, Command-T can outperform fzf whose search is single-threaded if we ignore the time spent being blocked until the entire list is ready.

So you may ask why fzf searches with a single thread. It is due to the sad fact that multi-threaded program written in Ruby cannot saturate more than a single core of CPU in the presence of the notorious GIL (global interpreter lock). The only option we have left is to use multiple processes instead of threads. Doing so is perfectly feasible but the result is likely to be sub-optimal because of the unavoidable overhead of inter-process communication. However, we can minimize the overhead by employing a very fast serialization method like MessagePack. You can currently check out the experimental multi-process version here which requires MessagePack. As I prefer to keep the installation process as simple as possible and the benefit of the parallelizaion is only noticeable when the list is extremely large (> 100k) which I believe to be quite rare, I'm not planning to release this version anytime soon. But, we'll see.

So how fast is it really, compared to the other fuzzy finders? Click here and see it for yourself.

Edit: After the post was published, the scanning performance of Unite.vim has been significantly improved. You can see the discussion here.

Extended-search mode

Extended-search mode of fzf is a nice improvement to the basic fuzzy matching behavior. It's not a full-fledged regular expression matcher, but it's simpler and easier to use.

Cons.

It's not all roses of course.

Terminal-only

This is certainly the most obvious drawback of fzf. fzf is a command-line program and you simply cannot run it on GVim. I don't find it a problem since I'm always on terminal, but obviously not everyone's like me and if you prefer GVim, fzf is just not an option there.

Edit: This is no more the case. I updated the Vim plugin for fzf to use an external terminal emulator such as xterm, or urxvt, to launch fzf even when you're on GVim.

Slower startup time

fzf is an external process to Vim and theoretically should take longer to start compared to the native plugins. It's a natural trade-off for being a general-purpose tool. However, you may not find it a problem because the difference is usually not very noticeable on modern computers.

Lack of features

fzf was designed to be a Unix filter and no more than that. It simply reads the input and filters it by the given fuzzy pattern. That's all it does. You cannot dynamically change the source or the action to be performed on the matches. This is straightforward if you think about the usage of a typical Unix filter:

ls | grep hello > world

Obviously grep does not provide us with a way to change its input or the direction of its output. fzf is just like that and this intentional decoupling is what makes fzf (and Unix filters in general) truly extensible and composable. But in the context of Vim Plugin, this nature can be considered to be limiting.

Fullscreen-only

A curses program only works in fullscreen mode. Which means that fzf will take up the whole screen while a native plugin will use split-window and occupy only a part of the screen. You may find it annoying as it completely hides the current context of the editor. However, not all hope is lost. Dispatch.vim by Tim Pope inspired me to make use of tmux splits, and now the Vim plugin of fzf automatically splits the window to start fuzzy finder there not to take up too much screen real estate. As you can see in the following GIF, fzf as a Vim plugin, running inside a tmux session, pretty much resembles the native ones and I'm really happy with it.

Closing notes

Until recently I wasn't sure if fzf as a Vim plugin could replace ctrlp.vim or Command-T. But I was able to implement all the features I needed (selecting file, open buffer, or color scheme) using fzf, and the result has been quite pleasant. If you're somehow interested in giving it a try, check out the documentation. fzf#run() is all you need to know.

» capture | close