Table of Contents
Overview
FZF is a great command-line fuzzy-finder and there is fzf.vim plugin that integrates with Vim to provide features, like :Files to fuzzy search over files and :Rg to fuzzy search over text using ripgrep.
Recently, however, I have been experimenting with a plugin-free Vim setup and while :find is sufficient for some use-cases, I found myself quitting vim and running fzf to find deeply nested files in new or large projects.
There has to be a simple way to integrate a command-line program with a command-line editor, right?
The Setup
This setup is simple and it leverages a feature in vim called quickfix.
The flow is:
- Call
fzf - Format the output to make it compatible with
errorformat - Write the results to a temporary file
- Load the results into Vim's quickfix list with
:cfileor:cgetfile, so that we can navigate through the results with:cnext/:cpreviousor:copen - Clean up temporary file
Here is what the final vimscript looks like:
function! FZF() abort
let l:tempname = tempname()
" fzf | awk '{ print $1":1:0" }' > file
execute 'silent !fzf --multi ' . '| awk ''{ print $1":1:0" }'' > ' . fnameescape(l:tempname)
try
execute 'cfile ' . l:tempname
redraw!
finally
call delete(l:tempname)
endtry
endfunction
" :Files
command! -nargs=* Files call FZF()
" \ff
nnoremap <leader>ff :Files<cr>
A quick breakdown:
-
let l:tempname = tempname()- Generate a path to a temporary file and store it in a variable.
- See
:h tempname()
-
execute 'silent !fzf --multi ' . '| awk ''{ print $1":1:0" }'' > ' . fnameescape(l:tempname)- Call
fzfwith--multito allow for selecting multiple files - Pipe to
awkto append:1:0to fzf results to make themerrorformat-compatible. - Note: you can drop this
awkcommand if youset errorformat+=%fin your vimrc, but I found%fto capture a lot of false-positives from other programs' outputs and therefore:cnext/:cpreviousdon't function on these false-positive results. - Finally, direct the results into the temp file
- Call
-
execute 'cfile ' . l:tempname- Load results from temp file into quickfix list and jump to the 1st result.
- Note #1: you may use
:cgetfileto only load results into quickfix list without jumping to the 1st result. - Note #2: you may replace
:cfile/:cgetfilewith:lfile/:lgetfileto use location list instead of quickfix list. Location lists are window-specific, whereas quickfix lists are global. So if you prefer to have different set of results per vim window, then use:lfile/:lgetfile.
-
call delete(l:tempname)- Clean up by deleting the temp file
-
command! -nargs=* Files call FZF()- Invoke
FZF()function when we call:Filesin vim
- Invoke
-
nnoremap <leader>ff :Files<cr>- Normal-mode mapping so that we can trigger this flow with
<leader>ff
- Normal-mode mapping so that we can trigger this flow with
Bonus
While :set grepprg=rg\ --vimgrep is again sufficient for most of my use-cases, those who have used :Rg in fzf.vim will appreciate the interactive fuzzy grepping experience and the ability to preview results before opening files in vim.
Well, here is a similar experience with pure vim (obviously, fzf and rg binaries are still required):
function! RG(args) abort
let l:tempname = tempname()
let l:pattern = '.'
if len(a:args) > 0
let l:pattern = a:args
endif
" rg --vimgrep <pattern> | fzf -m > file
execute 'silent !rg --vimgrep ''' . l:pattern . ''' | fzf -m > ' . fnameescape(l:tempname)
try
execute 'cfile ' . l:tempname
redraw!
finally
call delete(l:tempname)
endtry
endfunction
" :Rg [pattern]
command! -nargs=* Rg call RG(<q-args>)
" \fs
nnoremap <leader>fs :Rg<cr>
This offers the same experience where:
-
:Rgwithout arguments will load all text into vim and allow users to interactively type and preview results before selecting files -
:Rg [pattern]will pre-filter results to just ones that match[pattern]before passing them tofzffor further fuzzy searching.
Caveat
In my testing, I found one major caveat that did not impact me too much, but it is still worth calling out here:
Executing shell commands in vim with bangs, like :!fzf, is not meant to be interactive (at least, not out of the box). This could be a problem in GVim/MacVim. The vim docs mention the following workaround:
On Unix the command normally runs in a non-interactive shell. If you want an interactive shell to be used (to use aliases) set 'shellcmdflag' to "-ic".
Setting shellcmdflag=-ic could incur a time penalty, depending on your shell startup/initialization times.
Summary
Vim is extremely versatile and customizable.
With some knowledge of vim concepts (e.g. quickfix, :cfile/:lfile) and a little bit of (vim & bash) scripting, you can achieve a richer experience and pleasant integrations to enhance your productivity in a way that suits you and your workflow.
I hope you enjoyed this post and I hope it inspires you to develop and share your productivity tips and tricks in vim.
Happy hacking!
Top comments (1)
I use
fzfas a plugin, but I also have a couple of shell scripts to start things up. My most used one I callvrgand it allows me to search for something using whatever seach tool I have installed, select the matches I want throughfzf(if installed) and open them all in my configuredEDITORbecause I love being generic.It's a bit pointless, tbh, because the same script that installs
vrgalso installs all the dependencies I prefer. I could have made it a quick alias! But it's become a workflow habit.