Skip to content

Examples (vim)

kiryph edited this page Apr 4, 2019 · 58 revisions

fzf.vim repository

If you're not familiar with Vimscript or don't have time to write your own commands, check out fzf.vim project which provides a set of ready-made commands.

Basic tutorial

fzf#run() function is the core of Vim integration. It takes a single dictionary argument. At the very least, specify sink option to tell what it should do with the selected entry.

call fzf#run({'sink': 'e'})

Without source, fzf will use find command (or $FZF_DEFAULT_COMMAND if defined) to list the files under the current directory. When you select one, it will open it with :e command. If you want to open it in a new tab, you can pass :tabedit command instead as the sink.

call fzf#run({'sink': 'tabedit'})

fzf allows you to select multiple entries with --multi (or -m) option, and you can change its bottom-up layout with --reverse option. Such options can be specified as options.

call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})

Instead of using the default find command, you can use any shell command as the source. This will list the files managed by git.

call fzf#run({'source': 'git ls-files', 'sink': 'e'})

Pass a layout option if you don't want fzf window to take up the entire screen.

" up / down / left / right / window are allowed
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'})
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vsplit'})

source doesn't have to be an external shell command, you can pass a Vim array as the source. In the following example, we use the names of the open buffers as the source.

call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
            \               'bufname(v:val)'),
            \ 'sink': 'e', 'down': '30%'})

Or the names of color schemes.

call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
            \               'fnamemodify(v:val, ":t:r")'),
            \ 'sink': 'colo', 'left': '25%'})

The following table shows the available options.

Option name Type Description
source string External command to generate input to fzf (e.g. find .)
source list Vim list as input to fzf
sink string Vim command to handle the selected item (e.g. e, tabe)
sink funcref Reference to function to process each selected item
sink* funcref Similar to sink, but takes the list of output lines at once
options string Options to fzf
dir string Working directory
up/down/left/right number/string Use tmux pane with the given size (e.g. 20, 50%)
window (Neovim only) string Command to open fzf window (e.g. vertical aboveleft 30new)
launcher string External terminal emulator to start fzf with (GVim only)
launcher funcref Function for generating launcher string (GVim only)

Using fzf#wrap() function

:FZF command provided by default knows how to handle CTRL-T, CTRL-X, and CTRL-V and opens the selected file in a new tab, in a horizontal split, or in a vertical split respectively. And these key bindings can be configured via g:fzf_action. This is implemented using --expect option of fzf and the smart sink function. It also understands g:fzf_layout and g:fzf_history_dir. With fzf#wrap function, you can make your command support the options.

" Usage:
"   fzf#wrap([name string,] [opts dict,] [fullscreen boolean])

" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
" and opens fzf according to g:fzf_layout setting.
command! Buffers call fzf#run(fzf#wrap(
    \ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))

" This extends the above example to open fzf in fullscreen
" when the command is run with ! suffix (Buffers!)
command! -bang Buffers call fzf#run(fzf#wrap(
    \ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))

" You can optionally pass the name of the command as the first argument to
" fzf#wrap to make it work with g:fzf_history_dir
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
    \ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))

locate command integration

command! -nargs=1 -bang Locate call fzf#run(fzf#wrap(
      \ {'source': 'locate <q-args>', 'options': '-m'}, <bang>0))

:Locate / will list every file on the system. So make sure that you're using Go version of fzf which is significantly faster than the old Ruby version.

Open files in splits

Consider using CTRL-X/V/T key bindings of the default :FZF command instead.

" Open files in horizontal split
nnoremap <silent> <Leader>s :call fzf#run({
\   'down': '40%',
\   'sink': 'botright split' })<CR>

" Open files in vertical horizontal split
nnoremap <silent> <Leader>v :call fzf#run({
\   'right': winwidth('.') / 2,
\   'sink':  'vertical botright split' })<CR>

Choose color scheme

nnoremap <silent> <Leader>C :call fzf#run({
\   'source':
\     map(split(globpath(&rtp, "colors/*.vim"), "\n"),
\         "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')"),
\   'sink':    'colo',
\   'options': '+m',
\   'left':    30
\ })<CR>

Select buffer

function! s:buflist()
  redir => ls
  silent ls
  redir END
  return split(ls, '\n')

function! s:bufopen(e)
  execute 'buffer' matchstr(a:e, '^[ 0-9]*')

nnoremap <silent> <Leader><Enter> :call fzf#run({
\   'source':  reverse(<sid>buflist()),
\   'sink':    function('<sid>bufopen'),
\   'options': '+m',
\   'down':    len(<sid>buflist()) + 2
\ })<CR>

Simple MRU search


command! FZFMru call fzf#run({
\  'source':  v:oldfiles,
\  'sink':    'e',
\  'options': '-m -x +s',
\  'down':    '40%'})

Filtered v:oldfiles and open buffers

command! FZFMru call fzf#run({
\ 'source':  reverse(s:all_files()),
\ 'sink':    'edit',
\ 'options': '-m -x +s',
\ 'down':    '40%' })

function! s:all_files()
  return extend(
  \ filter(copy(v:oldfiles),
  \        "v:val !~ 'fugitive:\\|NERD_tree\\|^/tmp/\\|.git/'"),
  \ map(filter(range(1, bufnr('$')), 'buflisted(v:val)'), 'bufname(v:val)'))

Jump to tags (simple)

command! -bar Tags if !empty(tagfiles()) | call fzf#run({
\   'source': "sed '/^\\!/d;s/\t.*//' " . join(tagfiles()) . ' | uniq',
\   'sink':   'tag',
\ }) | else | echo 'Preparing tags' | call system('ctags -R') | FZFTag | endif

Jump to tags

This version better handles same tags across different files.

function! s:tags_sink(line)
  let parts = split(a:line, '\t\zs')
  let excmd = matchstr(parts[2:], '^.*\ze;"\t')
  execute 'silent e' parts[1][:-2]
  let [magic, &magic] = [&magic, 0]
  execute excmd
  let &magic = magic

function! s:tags()
  if empty(tagfiles())
    echohl WarningMsg
    echom 'Preparing tags'
    echohl None
    call system('ctags -R')

  call fzf#run({
  \ 'source':  'cat '.join(map(tagfiles(), 'fnamemodify(v:val, ":S")')).
  \            '| grep -v -a ^!',
  \ 'options': '+m -d "\t" --with-nth 1,4.. -n 1 --tiebreak=index',
  \ 'down':    '40%',
  \ 'sink':    function('s:tags_sink')})

command! Tags call s:tags()

Jump to tags in the current buffer

function! s:align_lists(lists)
  let maxes = {}
  for list in a:lists
    let i = 0
    while i < len(list)
      let maxes[i] = max([get(maxes, i, 0), len(list[i])])
      let i += 1
  for list in a:lists
    call map(list, "printf('%-'.maxes[v:key].'s', v:val)")
  return a:lists

function! s:btags_source()
  let lines = map(split(system(printf(
    \ 'ctags -f - --sort=no --excmd=number --language-force=%s %s',
    \ &filetype, expand('%:S'))), "\n"), 'split(v:val, "\t")')
  if v:shell_error
    throw 'failed to extract tags'
  return map(s:align_lists(lines), 'join(v:val, "\t")')

function! s:btags_sink(line)
  execute split(a:line, "\t")[2]

function! s:btags()
    call fzf#run({
    \ 'source':  s:btags_source(),
    \ 'options': '+m -d "\t" --with-nth 1,4.. -n 1 --tiebreak=index',
    \ 'down':    '40%',
    \ 'sink':    function('s:btags_sink')})
    echohl WarningMsg
    echom v:exception
    echohl None

command! BTags call s:btags()

Search lines in all open vim buffers

(require set hidden to prevent vim unload buffer)

function! s:line_handler(l)
  let keys = split(a:l, ':\t')
  exec 'buf' keys[0]
  exec keys[1]
  normal! ^zz

function! s:buffer_lines()
  let res = []
  for b in filter(range(1, bufnr('$')), 'buflisted(v:val)')
    call extend(res, map(getbufline(b,0,"$"), 'b . ":\t" . (v:key + 1) . ":\t" . v:val '))
  return res

command! FZFLines call fzf#run({
\   'source':  <sid>buffer_lines(),
\   'sink':    function('<sid>line_handler'),
\   'options': '--extended --nth=3..',
\   'down':    '60%'

Narrow ag results within vim

  • CTRL-X, CTRL-V, CTRL-T to open in a new split, vertical split, tab respectively.
  • CTRL-A to select all matches and list them in quickfix window
    • CTRL-D to deselect all
  • Ag without argument will list all the lines
function! s:ag_to_qf(line)
  let parts = split(a:line, ':')
  return {'filename': parts[0], 'lnum': parts[1], 'col': parts[2],
        \ 'text': join(parts[3:], ':')}

function! s:ag_handler(lines)
  if len(a:lines) < 2 | return | endif

  let cmd = get({'ctrl-x': 'split',
               \ 'ctrl-v': 'vertical split',
               \ 'ctrl-t': 'tabe'}, a:lines[0], 'e')
  let list = map(a:lines[1:], 's:ag_to_qf(v:val)')

  let first = list[0]
  execute cmd escape(first.filename, ' %#\')
  execute first.lnum
  execute 'normal!' first.col.'|zz'

  if len(list) > 1
    call setqflist(list)
    wincmd p

command! -nargs=* Ag call fzf#run({
\ 'source':  printf('ag --nogroup --column --color "%s"',
\                   escape(empty(<q-args>) ? '^(?=.)' : <q-args>, '"\')),
\ 'sink*':    function('<sid>ag_handler'),
\ 'options': '--ansi --expect=ctrl-t,ctrl-v,ctrl-x --delimiter : --nth 4.. '.
\            '--multi --bind=ctrl-a:select-all,ctrl-d:deselect-all '.
\            '--color hl:68,hl+:110',
\ 'down':    '50%'
\ })

Fuzzy search files in parent directory of current file

This command is very handy if you want to explore or edit the surrounding/neigbouring files of the file your currently editing. (e.g. files in the same directory)

function! s:fzf_neighbouring_files()
  let current_file =expand("%")
  let cwd = fnamemodify(current_file, ':p:h')
  let command = 'ag -g "" -f ' . cwd . ' --depth 0'

  call fzf#run({
        \ 'source': command,
        \ 'sink':   'e',
        \ 'options': '-m -x +s',
        \ 'window':  'enew' })

command! FZFNeigh call s:fzf_neighbouring_files()

Fuzzy search the arglist (:Args)

command! -bang Args call fzf#run(fzf#wrap('args',
    \ {'source': map([argidx()]+(argidx()==0?[]:range(argc())[0:argidx()-1])+range(argc())[argidx()+1:], 'argv(v:val)')}, <bang>0))

Fuzzy search netrwhist (:Netrwhist)

function! MyUniq(lst)
    return filter(a:lst, 'count(a:lst, v:val) == 1')
command! -bang Netrwhist call fzf#run(fzf#wrap('netrw_dirhist',
    \ {'source': 
    \ !exists('g:netrw_dirhist_cnt')
    \   ?"tail -n +3 ".g:netrw_home.".netrwhist | cut -d \"'\" -f2- | rev | cut -d \"'\" -f2- | rev | awk '!seen[$0]++'"
    \   :MyUniq(map(range(1,g:netrw_dirhist_cnt), 'g:netrw_dirhist_{v:val}'))
    \ }, <bang>0))

This uses the command line tools tail, cut, rev, and awk which are most likely available under macOS and Linux.

The default value of g:netrw_dirhistmax is 10. You might be interested in increasing this value when you use this feature more often:

let g:netrw_dirhistmax = 1000
You can’t perform that action at this time.