Vimgrep Tips and Tricks

January 4, 2022

When I started using Vim, I wished that Vim had a powerful in-file search feature that other popular IDEs / editors have. How could I search for the files that contain the string "echo"? How could I search for only the .js files containing the string "const"?

It turns out that Vim does come with a powerful in-file search right out of the box.

There are two main in-file searches in Vim: :grep and :vimgrep. The former uses an external grep command and the latter is built into Vim. This article will cover how to use the :vimgrep command. Maybe in the future I will write about :grep.

Why Learn Vimgrep

The ability to perform complex searches quickly can boost your productivity.

It is true that the Vim ecosystem today contains many useful plugins, some of them are search-related plugins like ctrlp, denite, and fzf.vim. These plugins are convenient and powerful, so why bother learning vimgrep?

Just because it is old doesn't mean it is no good. There are a few advantages of learning vimgrep.

First, the :vimgrep command is built into Vim, so you don't have to worry about installing dependencies (and all the issues that might come with it). If you ever had to use vanilla Vim (ex: when you're in an SSH, or using someone else's computer, or in your mobile phone, etc), you can be sure that :vimgrep will always be there.

Second, vimgrep uses Vim's built-in regex engine (remember 'verymagic'? :D). This may sound like a con to some people, but to me, this is a huge pro. Using the same, consistent regex engine as Vim itself means there is zero friction between performing a Vim search (/) and using :vimgrep. Your brain won't have to switch to a different mode. The less you have to use your brain for editing, the more you can use it for the more fun stuff!

Vimgrep has some downsides. The biggest one is that because it loads all the search results into memory, if you have a large search result, it can slow down Vim. However, if you're working on a small / medium project, it is fast enough.

Basic Usage

Did you know that :vimgrep shorthand is :vim? That's right! A :vim command inside Vim? How meta!

Anyway, from now on, I'll refer to it as :vim in the remainder of this article.

The :vim command follows the following syntax:

:vim /pattern/flag path
  • pattern is your search pattern.
  • flag is a flag that you can pass to the search pattern.
  • path is the file argument. You can pass multiple arguments.

Ok, enough theory. You're here to learn some vimgrep, aren't you? Let's go!

Searching for a String Inside a Particular File

If you need to find the string "hello" inside all ruby files (.rb) inside app/controllers/ directory, run the following command:

:vim /hello/ app/controllers/**/*.rb

I use the wildcards * and double wildcards (globstar) ** a lot. The globstar, **, searches recursively (it will match things like app/controllers/dir/, app/controllers/some/dir/, app/controllers/file). The wildcard, *, matches any string of any length. In this case, *.rb matches any string that ends with .rb (like hello_controller.rb, whatever.rb).

Btw, do not confuse the wildcards with the asterisk in regex. These two are different things. Regex asterisk pattern means zero or more of subsequent pattern (ex: a* means zero or more "a"), while the wildcard does not require a subsequent pattern (* means any string of any length).

If you want to learn about globs, check out https://mywiki.wooledge.org/glob.

The :vim search displays the results in quickfix. If you aren't familiar with it, think of it as a set of items. In this case, it is a set of search results.

After running the search command, run :copen to open the quickfix window.

If you're brand new to quickfix and aren't sure how to interact with it, here are some useful quickfix commands to get you started immediately:

:copen        Open the quickfix window
:cclose       Close the quickfix window
:cnext        Go to the next location
:cprevious    Go to the previous location

The above list is by no means comprehensive. I suggest you learn about quickfix if you have time. It shouldn't take long. It's a useful skill to have in your Vim toolbelt. To learn more about quickfix, check out :h quickfix.

Searching for a String Inside a Particular File Extension

If you need to look for the string "echo" but only inside a .sh file:

:vim /echo/ **sh

The globstar is very useful if you have a nested directory structure. Here, it will match both ./some_file.sh and ./some/really/long/dir/then/file.sh.

If you only want to search in adjacent files (like ./hey.sh and ./some_file.sh, not ./some_dir/then/file.sh), instead of using the globstar, use a single wildcard: *sh.

Searching for a String Inside Files Ending With Particular Extensions

If you need to look for the string "echo" but only inside either a .sh or a .rb file:

:vim /echo/ **{sh,rb}

Passing Multiple Files to Search Inside of

Note that earlier I said that vimgrep accepts multiple file arguments. We have been passing it only one argument so far. However, we can totally pass it with more than one argument!

If you need to look for the string "echo" inside either .sh or .rb:

:vim /echo/ **sh **rb

If you need to look for the string "echo" inside of either app/controllers/ or Rakefile:

:vim /echo/ app/controllers/** Rakefile

You are not limited to only two file arguments, you can pass it as many arguments as you want. If you need to look for "echo" inside of either app/controllers/ directory, Rakefile in the current directory, a .json in the current directory, and a sh file somewhere inside the bin/ directory:

:vim /echo/ app/controllers/** Rakefile *.json bin/**/*sh

Finding Multiple Matches in the Same Line

When we did :vim /echo/ app/controllers/**, Vim returns the first match on each line.

That means if we have a line that contains multiple keywords, like echo "I like to echo echo echo", the search result only displays one result instead of four. Using the pattern /echo/ only matches the first "echo" of that line.

What if we want our search result to display all four of them?

To catch all of the "echo" strings whenever it occurs multiple times in a line, we need to use the global flag (g).

:vim /echo/g app/controllers/**

Now it will match all 4 "echo" occurrences in that line.

I like the global flag and I use it in over 80% of my vimgrep searches.

Fuzzy Search a String Inside a Particular File

Vimgrep is also capable of running a fuzzy search. We need to pass it a f flag. To fuzzy search the string "echo" inside a .sh file:

:vim /echo/fg  **sh

This will fuzzy search all lines inside **sh pattern for strings that resemble "echo".

So how does a vimgrep fuzzy search differ from a regular search? In addition to matching a literal "echo" string, it would also match something like: puts "Checking Homebrew..." because the "ec" in "Checking" and "Ho" in "Homebrew" constructs an "echo".

Search a Regular Expression Pattern in a Particular File

Vimgrep accepts regular expression in your keyword search pattern. If want to search for either "echo" or "ecko" inside a .sh file:

:vim /ec[hk]o/g **sh

The [] in [hk] is a character set syntax. In this case, it will match either "h" or "k" letters.

If you need to search for a text surrounded by a single quote (like 'hello', or 'foo', or '1234') inside a .sh file:

:vim /'[^']*'/g **sh

There is so much more you can do with regex. I won't cover how to use regex here - but I want to show you that vimgrep works great with regex. If you want to learn more regex, I like to go to https://www.regular-expressions.info/tutorial.html.

Search for a String Inside a Particular Directory

The double star (globstar) ** and the wildcard can be used at the start, in the middle, or at the end. It can also be used multiple times in a file pattern.

If you want to search for "echo" inside the /controllers/ directory:

:vim /echo/g **/controllers/**

If you want to search for "echo" inside the /controllers/ directory and inside a file that begins with "shipment" and end with "rb" (ex: shipment_domestic.rb, shipment_incoming.rb):

:vim /echo/g **/controllers/**/shipment*rb

Search for a String Inside a Different Directory Than Your Current Working Directory

Vimgrep searches in your working directory. But what if you need to search for the string "echo" inside a different directory? Easy. Just go to that other directory, then do the search!

Vim has a :cd command that changes the directory you are currently in.

:cd somewhere/else
:vim /echo/g **js

When you're done, just :cd back to your previous directory.

Search in the Current File

You can use Vim's filename expansion to shortcut your file pattern search. % in Vim represents the current active buffer (the file you're currently on).

If you need to search for "echo" the current file:

:vim /echo/g %

For more: :help :_%

Using Other Search Commands

There are times when we need to perform a more advanced search. We may need to use other commands, like find. No problem. To search for "echo" inside all files whose names start with "docker" using the find command:

:vim /echo/g `find . -type f -name 'docker*'

For more on how find works, check out man find.

The git ls-files is another useful shell command for git-related searches. Assuming that you are inside a git repository, to search for "echo" inside of all modified files only:

:vim /echo/g `git ls-files --modified`

For more on how git ls-files works, check out man git-ls-files.

Searching in-Files Within Arglist

The argument list (arglist) is a Vim feature wherein Vim stores a list of files.

The gist of arglist is, if you open Vim with multiple files (ex: vim file1.js file2.rb file3.py), Vim collects these files inside the arglist. To see them, run :args.

Arglist does not necessarily have to be populated on start too. You can create your own arglist while in Vim by running :args file1.js file2.rb file3.py. To see them, run :args.

Once you have a list of files, you can quickly go to the next or previous arglist files with :next or :prev.

Ok, so arglist is a collection of files. So how does arglist relate to vimgrep?

Usually when performing a task, you would gather all the relevant files first. Once you have all your relevant files in a collection, you can very quickly navigate between them. Arglist is a very useful feature for that.

Vim has a number of file expansion shortcuts. Just like how % represents the current buffer, Vim has one for arglist too! ## represents the current arglist.

So if we want to search within our arglist files for the string "echo":

:vim /echo/g ##

Why is this useful? I mean, it looks like I'm adding an extra step to the search process: first, I need to gather the files to an arglist and second, I run the vimgrep command. That's two steps altogether. Why can't I just run :vim /echo/g file1.js file2.rb file3.py and be done with only one step?

Speaking from experience, often I find myself needing to perform multiple keyword searches within the same set of files, like:

:vim /echo1/g file1.js file2.rb file3.py
:vim /foo2/g file1.js file2.rb file3.py
:vim /bar3/g file1.js file2.rb file3.py

I find it painful each time I have to re-type the same set of files. This is where arglist can save time!

Why not collect an arglist first:

:arglist file1.js file2.rb file3.py

Then reuse it in subsequent vimgrep searches?

Now I can just run:

:vim /echo1/g ##
:vim /foo2/g ##
:vim /bar3/g ##

If you need to perform different keyword searches against the same set of files, arglist can save you time.

If you're curious about how ## works, check out :h :_##. If you want to learn more arglist, check out :h :arglist.

Quickly Get the Last Search Pattern

Sometimes I need to search for a complicated pattern. Before I enter that in the :vim command, I like to test it with search (/) first.

For example, if you want to search for a string surrounded by a single quote, while excluding the single quotes, you could do '\zs[^']*\ze'. But this pattern may not be intuitive at first. When using a semi-complicated pattern, I like to test if it does what I think it does, so before I run :vim /'\zs[^']*\ze'/g **sh, I would usually do a quick search command /'\zs[^']*\ze' to test if it works.

Once I confirm that the pattern meets my expectations, I would then enter it to the :vim command. But do I really want to re-type '\zs[^']*\ze' all over again? I mean, look at those brackets and single quotes and backslashes... I could easily mistype them when I am typing them on the :vim command.

Moreover, what if my pattern is a lot longer, like \(["']\)\(\%(\1\@!.\)*\)\1? Oh boy, look at those backslashes, parentheses, and brackets... what are the chances of me retyping that correctly in the first try?

Luckily, there is a trick that allows you to "paste" your most recently used search command. After typing :vim /, type Ctrl-r then /. The secret is that Ctrl-r when used in insert mode (or in this case, command-line mode) invokes the Vim registers. Here we ask Vim for the value from the search register (/).

This is where the :vim command has an advantage over the regular :grep command. The search command (/) uses Vim regex flavor, which :vim also uses. But the :grep command doesn't use Vim regex flavor (it uses whatever grep external command you set up; for more, check out :h 'grepprg'). Using the same regex flavor for / then :vim command means zero friction and a buttery-smooth search experience!

Albeit, performance-wise, :grep is faster, but if speed is not a big issue (it isn't really noticeable in most cases), :vim offers a better user experience.

Conclusion

This is a good place to stop. You've learned a number of cool tricks for the :vim program. I wish I knew half of these tricks when I started Vim. Don't just speed read this and leave. Take your time going through each command. Go through each one of them - tweak it, break it, and understand it. Make it your goal to be able to perform them without much mental effort. This is by no means a comprehensive list of :vimgrep tips and tricks. There are so many other combinations that you can do with vimgrep. Don't stop learning!

Vim is a great editor even without plugins. Vim is a universal program that can be found/installed in practically any machine. There may be times when you can't use search plugins. This will be the time where your vanilla Vim knowledge will shine. Don't let the lack of plugins cripple your Vim productivity. Learn to use Vim with and without plugins. Learn :vimgrep and also learn :grep.

Happy Vimming!