Interactive Fuzzy Finding in Vim without Plugins
Tags: vim, terminal, fzf
Published: 2021-07-25 03:11:02

---
title: Interactive Fuzzy Finding in Vim without Plugins
published: true
description: Interactive Fuzzy Finding with FZF in pure Vim
tags: vim, terminal, fzf
---

## Table of Contents

- [Overview](#overview)
- [The Setup](#the-setup)
- [Bonus](#bonus)
- [Caveat](#caveat)
- [Summary](#summary)

## Overview

[FZF](https://github.com/junegunn/fzf) is a great command-line fuzzy-finder and there is [fzf.vim](https://github.com/junegunn/fzf.vim) plugin that integrates with Vim to provide features, like [`:Files`](https://github.com/junegunn/fzf.vim/blob/e34f6c129d39b90db44df1107c8b7dfacfd18946/doc/fzf-vim.txt#L118) to fuzzy search over files and [`:Rg`](https://github.com/junegunn/fzf.vim/blob/e34f6c129d39b90db44df1107c8b7dfacfd18946/doc/fzf-vim.txt#L124) to fuzzy search over text using [`ripgrep`](https://github.com/BurntSushi/ripgrep).

Recently, however, I have been experimenting with a plugin-free Vim setup and while [`:find`](http://vimdoc.sourceforge.net/htmldoc/editing.html#: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](http://vimdoc.sourceforge.net/htmldoc/quickfix.html#quickfix).

The flow is:

1. Call `fzf`
1. Format the output to make it compatible with [`errorformat`](http://vimdoc.sourceforge.net/htmldoc/quickfix.html#errorformats)
1. Write the results to a temporary file
1. Load the results into Vim's quickfix list with [`:cfile`](http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:cfile) or [`:cgetfile`](http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:cgetfile), so that we can navigate through the results with [`:cnext`](http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:cnext)/`:cprevious` or [`:copen`](http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:copen)
1. Clean up temporary file

Here is what the final vimscript looks like:

```viml
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()`](http://vimdoc.sourceforge.net/htmldoc/eval.html#tempname())
- `execute 'silent !fzf --multi ' . '| awk ''{ print $1":1:0" }'' > ' . fnameescape(l:tempname)`
  - Call `fzf` with `--multi` to allow for selecting multiple files
  - Pipe to `awk` to append `:1:0` to fzf results to make them `errorformat`-compatible.
    - Note: you can drop this `awk` command if you `set errorformat+=%f` in your vimrc, but I found `%f` to capture a lot of false-positives from other programs' outputs and therefore `:cnext`/`:cprevious` don't function on these false-positive results.
  - Finally, direct the results into the temp file
- `execute 'cfile ' . l:tempname`
  - Load results from temp file into quickfix list and jump to the 1st result.
    - Note #1: you may use `:cgetfile` to only load results into quickfix list without jumping to the 1st result.
    - Note #2: you may replace `:cfile`/`:cgetfile` with `:lfile`/`:lgetfile` to 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 `:Files` in vim
- `nnoremap <leader>ff :Files<cr>`
  - Normal-mode mapping so that we can trigger this flow with `<leader>ff`

## 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_):

```viml
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:

- `:Rg` without 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 to `fzf` for 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](http://vimdoc.sourceforge.net/htmldoc/various.html#:!) 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!