Smart search with :Subvert
#47
The :Subvert
command lets us create a particular style of regular expressions with ease. It’s great for matching irregular singular and plural words in plain English and also for variable names that come in snake_case
and MixedCase
forms.
This is part one of a three-part series on Tim Pope’s abolish plugin.
:Subvert - a supercharged search DSL
Vim’s search command is case-sensitive by default. I can demonstrate with this text by searching for
/pumpkin
It matches this occurrence in lowercase, but not the mixedcase occurrence here. Vim provides a few options that allow you to tune the case-sensitivity of the search command, such as ignorecase
and smartcase
. But I want to show you an alternative approach, as used by Tim Pope’s abolish plugin.
This plugin adds a command called :Subvert
. I’ll call it with the text pumpkin
:
:Subvert /pumpkin
That matches both occurrences. But watch this: if I upcase the letter ‘k’, it no longer matches. Clearly, there’s some kind of case-sensitivity going on here. We can inspect the pattern by pasting it from the search-register:
/<C-r>/ \v\C%(PUMPKIN|Pumpkin|pumpkin)
The \C
(big-C) switch enforces case-sensitivity, overriding the value of settings such as ignorecase
. Then we get three variations of our specified text: uppercase, mixed-case, and lowercase. Nothing else will match.
You get the idea?
Now let’s try something a bit different. The :Subvert
command can accept a comma-seperated list of alternatives, wrapped in braces. Let’s try searching for a few different words with a single command:
:Subvert/{pumpkin,mouse,user} \v\C%(PUMPKIN|Pumpkin|pumpkin|MOUSE|Mouse|mouse|USER|User|user)
This time, the generated pattern includes three variations for each of the specified words. That’s nifty!
Consider this: the word ‘mouse’ has an irregular plural: ‘mice’. With the subvert command, we can easily generate a pattern that will match the singular and plural forms.
:S/m{ouse,ice}
The letter ’m' is common to both alternatives, so I’ve specified it outside of the comma-separated list. Admittedly, it doesn’t save a lot of typing in this case!
Variable names
The subvert command is also handy if you want to find a variable name that appears in both MixedCase
and snake_case
. For example, in ruby, module names and class names are given in MixedCase
, while snake_case
is used for naming the corresponding files.
require "parslet" require "vimprint/parser/insert_mode" require "vimprint/parser/visual_mode" require "vimprint/parser/cmdline_mode" module VimPrint class Parser < Parslet::Parser include InsertMode include VisualMode include CmdlineMode ... end end
If we run the :Subvert
command with the snake cased insert_mode
variable, it automatically matches the mixed case version too.
:S/insert_mode \v\C%(INSERT_MODE|insert_mode|InsertMode)
Again, we have three variations: SNAKE_UPPERCASE
, snake_case
, and MixedCase
.
If we wanted to match these other variables, we could modify our :Subvert
command to use a comma-separated list:
:S/{insert,visual,cmdline}_mode
Now here’s another neat little trick: the cr
mapping lets us quickly mutate between different styles of variable. If I press:
crc
, it transforms the variable to camel case. (As a mnemonic, you can think of it as coerce to camel-case.)cr-
uses dashes to separate the words (I’ll undo that)crm
switches to mixed case
If you’ve also installed Tim Pope’s repeat plugin, then the dot command will work as you would expect.
crs
switches back to snake case
How cool is that?
Using :Subvert across multiple files
So far, we’ve been using the :Subvert
command to search inside the current buffer, but we can easily make it search across multiple files. We just have to specify the path of the directory we want to look inside:
:S /vimprint/ lib/**/*.rb
Boom!
:copen
It populates the quickfix list, just as though we’d run the :vimgrep
command.
Of course, we could achieve the same effect by pasting the pattern register into the search field of the :vimgrep
command. We’re working with standard Vim regexes throughout, so the patterns generated by :Subvert
will work with any of Vim’s pattern based commands.
Outro
It’s unlikely that you would compose such a complex pattern as this by hand, but the :Subvert
command makes it trivially easy. I like to think that it provides a domain-specific language for generating a particular style of regular expression.