Author: Eiko

Tags: vim, grep, vimgrep, search, multiple files, refactor, cdo, copen, cfdo, quickfix, regular expression, regex

Time: 2024-09-10 20:31:50 - 2024-10-23 04:46:49 (UTC)

Vim Grep: The Magical Wand of Insight and Transformation

vimgrep is a built in vim functionality that few people know about. They belongs to advanced magic, but they are actually easy to use and extremely powerful.

It is a search spell that casts your vision across multiple dimensions of files, revealing the hidden patterns of the text within. Together with commands like :copen, :cdo, you can easily transform your code with minimal effort.

Introduction

Assume you are in a magical world with tens or hundreds or even more magical modules. You want to replace a function let’s say Resource with Asset in all of them, or along the way moving some modules in and out.

You can do it manually, but it will take a lot of time and effort, consulting magical compilers and spell checkers multiple times along the way.

Some wizards know that we can use grep to examine the modules with given pattern, like this

grep -Irl "Resource" .

this is cool and does give you all modules that contain the word Resource, but you will have to turn back to your magical editor in and out.

Now if you have vimgrep, you can do it in a few keystrokes, within the magical editor vim itself, type one of the following commands

:vimgrep /Resource/ **/*.hs

:vimgrep /Resource/ `find . -type f`

" for only searching the current file, use `%` for the current file
:vimgrep /Resource/ %

Yes, you can match a pattern across a lot of files, the vimgrep accepts a pattern and a list of files, which can be generated by a matching pattern or command.

When you press Enter, you will immediately be brought to the pattern it finds, it opens the correct file and places the cursor at the correct position! You can now do the work you want to do, like replacing Resource with Asset. When you think you are done with the current magical module, you can press :cn to go to the next match, or :cp to go to the previous match.

But that is still a pretty manual process, no worry, we have more magic to help you.

  • The :copen command opens a quickfix list window, that shows all the matches, you can easily navigate to the match you want to work on.

  • Now if you put :cdo s/Resource/Asset or :cfdo %s/Resource/Asset/g in the command line, it will replace all Resource with Asset in all the matches in the quickfix window, at once! If you want to confirm each replacement, you can use :cdo s/Resource/Asset/c or :cfdo %s/Resource/Asset/gc

The difference between cdo and cfdo

  • :cdo will apply the command to all the matches in the quickfix window.

  • :cfdo will apply the command to all the files in the quickfix window.

Cdo is transformation magic, it is more than replacing strings!

There is much more you can do with these commands than just replacing strings, any commands can be used in :cdo {cmd}.

  • For example you can even run a macro on all matches!

    :cdo normal @a
  • Another example, adding a comment (in haskell its --) above all matching lines

    :cdo normal O-- This is a comment
  • Or even delete all matching lines, very easily

    :cdo normal dd
  • This also means you can match a very big pattern for example all functions starting with a given name, and perform replacements inside these functions only!

If you want to update changes immediately, you can pipe the cdo command to update command, like this

:cdo %s/Resource/Asset/g | update

Or you can just save all changes using :wa.

Common Search Patterns

If you are not satisfied with just searching a keyword, you can use regular expressions to search for more complex patterns. Here are some common patterns you can use:

  • \b matches a word boundary,

    • \bResource\b will match the word Resource but not Resources or SomeResource.
  • \< and \>: word boundary, match the beginning and end of a word

  • .: match any one character,

    • for example a.b will match a1b, a2b
    • foo. will match foo1, foo2, etc.
  • ^ and $: match the beginning and end of a line,

    • for example return 0;$ will match return 0; at the end of a line.
  • *, +, ?: match zero or more, one or more, zero or one of the preceding character, but in vim you need to escape them with \, like \*, \+, \?.

  • \s, \S for whitespace, non-whitespace,

  • \d, \D for digit, non-digit,

    • for example \d will match 1, 2, etc.
  • \w, \W for word character, non-word character,

    • for example \w will match a, b, etc.
  • []: match any one character in the brackets,

    • for example \[abc\] will match a, b, or c.
  • {}: match the preceding character for a specific number of times,

    • for example a\{3\} will match aaa.
  • () and |: group characters, match one of the characters,

    • for example \(a\|b\) will match a or b.

Note that there are some differences between the regular expressions in vim and other tools, this is because vim want to priotize actual characters like ()[]{}*+?, so the characters representing regular expressions need to be escaped. You can check :help pattern for more details.

Alternative Option

(to be added)