Michael Cordell's Blog

Better Fuzzy Search with Ctrl-P in Vim

27 Jan 2015

For those familiar with Sublime Text and have since moved to vim you probably miss the Cmd+P feature that allowed you to find a file in your working directory through a fuzzy match. The two main plugin contenders for fuzzy file finding in vim would arguably be Ctrl-P and Command-T. Personally I perfer Ctrl-P because it does not have a compiled extension and it offers a few niceties such as MRU and buffer switcher. Here’s a gif of it in action:

CtrlP!

A common technique when using Ctrl-P is to set it up to use it in conjuction with the_silver_searcher as detailed in this blog by Thoughtbot. However, I noticed that often the order of the results are not quite up to par with Command-T or Sublime.

“Fixing” results from Ag and Ctrl-P

To get a better fuzzy finder with silver searcher and Ctrl-P the solution lies with another vim plugin called matcher. This plugin does require a compiled executable, which is kind of a drag in light of my above requirements. But, it is much easier to compile than the Command-T extension IMHO.

To get it set up with vim and silver searcher:

  1. Clone the repo and or install it with your vim plugin manager.
  2. Navigate into the repo folder and run make and then make install
  3. Open up your Vimrc and add the following
" The Silver Searcher
if executable('ag')
  " Use ag over grep
  set grepprg=ag\ --nogroup\ --nocolor

  " Use ag in CtrlP for listing files. Lightning fast and respects .gitignore
  let g:ctrlp_user_command = 'ag %s -l --nocolor -g ""'
endif

This should already be there if you are using silver searcher, but I’m including it for clarity.

Then add the following (slightly modified from the README on the matcher repo):

if executable('matcher')
    let g:ctrlp_match_func = { 'match': 'GoodMatch' }

    function! GoodMatch(items, str, limit, mmode, ispath, crfile, regex)

      " Create a cache file if not yet exists
      let cachefile = ctrlp#utils#cachedir().'/matcher.cache'
      if !( filereadable(cachefile) && a:items == readfile(cachefile) )
        call writefile(a:items, cachefile)
      endif
      if !filereadable(cachefile)
        return []
      endif

      " a:mmode is currently ignored. In the future, we should probably do
      " something about that. the matcher behaves like "full-line".
      let cmd = 'matcher --limit '.a:limit.' --manifest '.cachefile.' '
      if !( exists('g:ctrlp_dotfiles') && g:ctrlp_dotfiles )
        let cmd = cmd.'--no-dotfiles '
      endif
      let cmd = cmd.a:str

      return split(system(cmd), "\n")

    endfunction
end

Close vim or re-source the .vimrc and you should be good to go! Happy fuzzy matching!

comments powered by Disqus