Nov 3, '18

Project search your feelings.

One of the biggest productivity hurdles for me when switching to Vim was getting a good project-wide find-and-replace workflow down. Most editors have a simple CMD-SHIFT-F to find in project. However, as with most things in Vim, it's not obvious where to start. This is the workflow I've come to love, and I hope maybe you can take something from it 🤙

Before we start, you should have a basic understanding of substitution and regex to get the most out of this workflow.

Project Searching Tools

Out-of-the-box, Vim provides us with the :grep and :vimgrep commands. The former uses the external grep CLI tool installed on your system, but allows you to search from within Vim, and populates linked results into the quickfix list for easier refactoring (more on this later). The latter uses Vim's internal pattern matching, which is more consistent with / searching, but can be quite slow. Aside from performance concerns, both tools lack some key features present in modern GUI-based editors, like the ability to respect your project's gitignore, smart-case searching, etc.

For reasons like these, Vim allows us to customize the external tool used by the :grep command, and there are other search wrapper plugins that can do even more for your workflow. The first step though, is to choose a project searching tool that's going to be used under the hood. Ideally, we want something that is performant, easy to use, has powerful features for more advanced code searches, and sane defaults for the most basic searches (like respecting your project's .gitignore).

Two of the more popular tools these days are ag and rg. Both of these can be integrated into Vim quite easily, but first let's focus on how to search using one of these tools. Personally, I'm more familiar with ag, so we'll be using it from here on out.

Ag Basics

By default, ag will respect your .gitignore, and allow you to regex search anywhere recursively within your current working directory. Here's a few examples of how you would use ag on the command line:

ag 'function store'

Will search for function store anywhere within your current working directly.

ag 'func.*store' app/Http/Controllers

Will regex search within the app/Http/Controllers path specifically.

ag -Q 'function store(Request $request)'

The -Q option will search for a literal string, instead of pattern matching with regex.

ag -U 'extends Model'

The -U option will search everywhere, despite what's in your .gitignore.

Feel free to explore other options via ag --help; It's a very powerful tool, and can be just as useful on the command line as it is in Vim! That said, now we need to figure out how to integrate ag into Vim.

Vim Integration

As mentioned earlier, Vim provides us with :grep which can actually be wired up to use ag or rg. However, I'm going to suggest a more intuitive solution using a plugin called fzf.vim. Not only is fzf.vim one of the best fuzzy finders available, but it also provides us with some really nice :Ag and :Rg wrapper commands that really improve the project searching experience in Vim. For example:

:Ag func.*store

Is the same as running the following on the command line:

ag 'func.*store'

However, it will pipe results into fzf's extended search mode, which allows you to narrow down the results in real-time using an expressive syntax. For example, you could then type:

^app !Controllers

Which would narrow your results to those starting with app in the path, ignoring results in the Controllers folder.

This narrowing of results in real-time is what makes this workflow so intuitive. Most other search solutions require you to precisely think ahead to all the possibilities without any visual feedback or opportunity to refine the output. Did you forget about that extensions folder? Traditionally, you would have to modify and re-run your original search command over again until you are happy with the results. However, the real-time nature of fzf's extended search mode allows you to visually see the narrowing of results as you type. Though ag is powerful enough without fzf.vim, having the ability to narrow further after you're already looking at the results can be a huge time saver.

Downsides to Fzf.vim

There are a few downsides to fzf.vim's :Ag out-of-the-box though. By default, it only allows you to pass a search regex. If you want to pass raw command line options or a specific path through to ag, you will need to hook into fzf#vim#ag_raw(), which is included but not setup out-of-the-box.

Furthermore, :Ag escapes all quotes and treats them as part of your query, so it can be confusing when you wrap your query in quotes as you would on the command line:

:Ag 'function index'

You would expect the same results as on the command line, but nothing is returned.

Make Fzf.vim Great Again

To remedy the above, I've written a plugin called vim-agriculture 🚜 to allow the passing of raw command line options to your search tool via :AgRaw or :RgRaw. It even smart quotes your input, so all of the following will work as expected:

:AgRaw func.*index
:AgRaw 'func.*index'
:AgRaw -Q 'function index()' app/Http/Controllers

As soon as you pass any options or quotes, it gets out of your way and passes all of your input directly to your search tool, giving you access to everything you can do on the command line 👌

And as a bonus, it provides a few handy mappings which you can throw into your .vimrc:

nmap <Leader>/ <Plug>AgRawSearch
vmap <Leader>/ <Plug>AgRawVisualSelection
nmap <Leader>* <Plug>AgRawWordUnderCursor

Refactoring in Quickfix Window

Once you are looking at the results of your project search, you can open a result simply by hitting Enter. This is great for one-off changes, but if you are doing a larger refactor across multiple files, the quickfix list can be a nice way to keep track of your refactor. The beauty of this quickfix list is that it is presented as a buffer in a separate window, so you can use it as you would a notepad or checklist as you refactor your files.

To populate the quickfix list with the results from your project search, you can mark individual results with the Tab key, or mark all results with ALT-A, then hit Enter to populate the quickfix list. Once populated, you can again open a result by hitting Enter. However, the real power of the quickfix list is that you have the ability to perform a batch command on everything listed in it.

Enter :cdo and :cfdo 🔥

:cdo %s/chew/bacca/gc

Using :cdo will perform the above substitution on every valid entry in the quickfix list. Similarily, :cfdo will perform the subtitution on each file in the quickfix list. You can also pipe :update to ensure each file gets written:

:cfdo %s/chew/bacca/gc | update

And this isn't just for substitutions, but for any command you wish to run on your result set. For example, you could delete all the files in the quickfix list:

:cfdo silent !rm %

This Technological Terror

...is nothing compared to the power of the force, but hopefully you can see how easy and powerful project searching can be with tools like ag, fzf, and agriculture 🚜. Here's a few related bonus tips: