Smart search with :Subvert

#47

This is a transcript of screencast 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.