Fuzzy Finding in Vim with fzf
UPDATED FEBRUARY 2020
Following on from fuzzy finding in Bash with fzf, this post will discuss usage of the fzf tool within the Vim and Neovim editors.
Please do read fuzzy finding in Bash with fzf to first gain insight about the fzf tool. That post notes how to install, configure and then use fzf within the Bash shell.
Fuzzy finding is a powerful search technique that can be just as useful inside an editor as it is at the command-line. Useful fuzzy finding operations within an editor, such as Vim, may include: opening a project file in a far-away location by short fuzzy name, switching to an open buffer by fuzzy name or opening a file by fuzzy tag.
As always, feel free to refer to my bashrc and vimrc files to view my fzf configurations.
Requirements
For the best user experience, please use a modern version of Vim or Neovim that provides a built-in terminal. For Vim that means using version 8.1 or later, and for Neovim that means using version 0.2 or later. GUI-based Vims, such as gVim and MacVim, especially benefit since versions pre-terminal integration, when running fzf, would spawn an ugly external terminal window which was highly unpleasant.
I recommend installing and updating Vim, or Neovim, using Homebrew on macOS and Linux.
For example, to install Neovim using Brew:
brew install neovim
And to update Neovim:
brew upgrade neovim
Plugin
The fzf tool provides basic Vim integration out of the box.
However, I strongly recommend installing and using the fzf.vim plugin which smartly wraps the fzf command-line tool whilst also providing a number of powerful Vim commands. Note, both fzf and fzf.vim were created and are maintained by Junegunn Choi, hence, they will always be synchronized if installed and updated simultaneously.
Just like the command-line tool, fzf.vim provides responsive real-time updates as fuzzy text is entered.
If using vim-plug, please add the
following to your ~/.vimrc
file:
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
Then run :PlugInstall
to install both the fzf command-line tool and
associated Vim plugin.
You may wonder, if one has already installed the fzf command-line tool, why bother with installing it again via a Vim plugin? Basically to maintain runtime synchronization between the Vim plugin and command-line tool.
If using a different plugin manager, please adjust the above statements appropriately.
Usage
With fzf.vim installed we can now focus on how to use fzf within Vim.
Hint, use the ESC key to quickly dismiss a fzf window in Vim. To do the same in
Neovim add the following to your ~/.vimrc
:
if has("nvim")
" Escape inside a FZF terminal window should exit the terminal window
" rather than going into the terminal's normal mode.
autocmd FileType fzf tnoremap <buffer> <Esc> <Esc>
endif
Files command
The :Files
command is a fuzzy file finder. Without an argument :Files
will
recursively search files that match the supplied fuzzy query from the current
directory downwards.
Note, when the fzf command line tool is configured to use the fd tool, Git ignores will be respected, which can greatly increase fuzzy find performance. Please refer to fuzzy finding in Bash with fzf for details about how to configure fzf to use fd.
Refine a search by entering in more characters at the prompt. Use the UP and
DOWN keys to move the selection line. Hit the ENTER key to edit a selected file
in the current window, or use TAB to first select multiple files and then hit
ENTER to edit all the selected files. To open the selected file(s) in a new
tab, rather than the current window, use Control-t
instead of ENTER.
Similarly, use Control-x
or Control-v
to open file selections in new
horizontal or vertical splits.
Example key mapping
Typing :Files
soon becomes a burden, instead please use a mapping like the
following.
let mapleader = " "
nnoremap <silent> <Space><Space> :Files<CR>
Hitting SPACE SPACE quickly brings up the fuzzy file finder. An alternative
would be to map Control-p
to :Files
if transitioning over from the
CtrlP plugin.
Screenshot
Sibling files
Sometimes one may need to edit a sibling file of the currently edited file, that being a file in the same directory of the current file.
This SPACE DOT mapping will open the fuzzy finder just for the directory containing the currently edited file.
nnoremap <silent> <Space>. :Files <C-r>=expand("%:h")<CR>/<CR>
Project navigation
Modern project frameworks, such as Ruby on Rails and Create React App, are opinionated in regards to directory structure and project layout.
Those opinionated project layouts, as created by the above listed frameworks and others, can form the basis for fuzzy project navigation.
In a Rails application, controller files are located in app/controllers
,
whilst models are located in app/models
and lastly views are located in
app/views
. With that in mind the following mappings will provide quick fuzzy
access to controller, model and view files.
nnoremap <silent> <Space>ec :Files app/controllers<CR>
nnoremap <silent> <Space>em :Files app/models<CR>
nnoremap <silent> <Space>ev :Files app/views<CR>
A React application, on the other hand, may be navigated with the following mappings.
nnoremap <silent> <Space>ec :Files src/components<CR>
nnoremap <silent> <Space>et :Files src/__tests__/components<CR>
When dealing with multiple frameworks I recommend scoping such key mappings by
searching for a distinguishing file, that way key mappings can be reused such
as the <Space>ec
mapping noted above.
For example:
if filereadable('config/routes.rb')
" This looks like a Rails app, add Rails specific mappings here.
elseif filereadable('src/index.js')
" This looks like a React app, add React specific mappings here.
endif
Note, the vim-rails and vim-projectionist plugins can also be used to navigate projects. All these project navigation techniques are complimentary, they can happily co-exist.
Buffers command
The :Buffers
command is used to quickly switch to an open buffer.
Example key mapping
nnoremap <silent> <Space>b :Buffers<CR>
GFiles? command
The :GFiles?
command is used to display the status of the current Git
repository whilst also allowing easy navigation to modified files.
Example key mapping
nnoremap <silent> <Space>g :GFiles?
Tags command
The :Tags
and :BTags
commands are used to navigate a project by fuzzy
tag. The :BTags
command will limit navigation to the tags associated with the current buffer
whilst :Tags
will use the complete project tags.
Example key mapping
nnoremap <silent> <Space>] :Tags<CR>
nnoremap <silent> <Space>B :BTags<CR>
Commits command
The :Commits
and :BCommits
commands are used to explore a project’s
Git history. The :BCommits
command will limit
exploration to the history associated with the current buffer whilst the
:Commits
command will explore the complete history of the project.
Example configuration and key mappings
let g:fzf_commits_log_options = '--graph --color=always
\ --format="%C(yellow)%h%C(red)%d%C(reset)
\ - %C(bold green)(%ar)%C(reset) %s %C(blue)<%an>%C(reset)"'
nnoremap <silent> <Space>C :Commits<CR>
nnoremap <silent> <Space>c :BCommits<CR>
The g:fzf_commits_log_options
option customizes the appearance of Git log
command used by the :Commits
and :BCommits
commands.
Screenshot
Pattern search with the Rg command
ripgrep is an excellent command-line text search utility that I have previously posted about.
The fzf.vim plugin provides access
to ripgrep with the :Rg
and :Rg!
commands. Both commands should be
launched with a search term, for example :Rg my_search_term
. The bang
version, :Rg!
, launches a fullscreen window whilst :Rg
launches a separate
window.
Once a search has been completed, fzf can be used to filter the results to just
those of interest. If only one selection is chosen then that match will be
opened, otherwise if multiple selections are chosen then the first match will
be opened along with the
quickfix window listing
all matches. Note, use Alt-a
and Alt-d
to select and deselect all matches.
Example key mapping
nnoremap <Space>/ :Rg<Space>
The preview window can be toggled with the QUESTION MARK key.
Most Recently Used Files
Sometimes one may want to open an out-of-project file that had been previously edited in Vim. Such a need is best served by an MRU (most-recently-used) cache that can provide access to a list of recently opened files.
The fzf.vim plugin unfortunately does not provide any MRU functionality, however, the fzf-mru plugin does provide a bridge between fzf and a MRU cache.
If using vim-plug, please add the
following to your ~/.vimrc
file:
Plug 'pbogut/fzf-mru.vim'
Run :PlugInstall
to install the plugin. If using a different plugin manager,
please adjust the above statement appropriately.
Example key mapping
nnoremap <silent> <Space>m :FZFMru<CR>
The SPACE m key combination will launch the fzf window with a list of recently opened-by-Vim files.
Other commands
The fzf.vim plugin also provides the following useful commands.
-
:Lines
- list lines of all open buffers, then navigate to the selection -
:BLines
- list lines of the current buffer, then navigate to the selection -
:Marks
- list all marks, then navigate to the selection -
:Snippets
- list UltiSnips snippets, then run the selected snippet -
:Maps
- list all mappings -
:Helptags
- explore Vim’s:help
documentation, then navigate to the selected topic -
:Colors
- list color schemes, then changecolorscheme
to the selection
fzf in a Floating or Popup Window
By default, fzf in Vim displays as a split window across the lower part of the Vim workspace. However, with the release of Neovim 0.4 in September 2019 and Vim patch release 8.2.091 in February 2020, it is now possible to display fzf in an floating window over the top of the current workspace.
Here is an example user configuration, in ~/.vimrc
, that displays fzf in a
floating window when run in a modern version of Vim or Neovim:
if has('nvim-0.4.0') || has("patch-8.2.0191")
let g:fzf_layout = { 'window': {
\ 'width': 0.9,
\ 'height': 0.7,
\ 'highlight': 'Comment',
\ 'rounded': v:false } }
else
let g:fzf_layout = { "window": "silent botright 16split enew" }
endif
Screenshot
Why not just use CtrlP
The CtrlP plugin is the precursor to most Vim fuzzy finding plugins. It is an excellent plugin which is still actively maintained.
I used CtrlP for many years. I even posted how to turbocharge the CtrlP Vim plugin.
So why am I not using CtrlP anymore? Answer, performance.
Even with the turbocharging noted above CtrlP can still slowdown when navigating huge source trees or when navigating a very large tags file. Neovim especially, when running the cpsm CtrlP matcher, can be sluggish due to the cost of Python RPCs (remote-procedure-calls) as noted in this issue.
The fzf.vim plugin is highly performant, even when dealing with very large source trees or tags files. Pattern matching is asynchronous and progressive, so it usually feels very fast and rarely laggy.
I will note, if you are a CtrlP user, and its performance is fine to you, then
there really is no need to change over to fzf.vim unless some of its unique
features, such as :Commits
or :Rg
, appeal to you.
Conclusion
I have found fzf-based fuzzy finding, when editing in Vim, to be a productivity game-changer.
So I encourage you to be curious and to not be afraid to go down the fzf rabbit hole