Enhancing search and replace with Vim

Vim is a powerful text editor, appreciated for a large number of plugins, syntax highlighter and the small memory requirements, it can also run in console or virtual terminal flawless. Once configured, customized and get accustomed with keyboard’s combinations, it reveals great features for every programmer. One of them is the advanced use of search & replace command.

Replace it with what I do!

Regular expressions are an important part of the technical background for every programmer because they allow to solve complex problems related to string matching with ease, and a powerful editor, as Vim is, embedded that feature within the search and search & replace commands.

The magic of Vim doesn’t stop here, with the last command mentioned Vim added the ability to call a function from its internal programming language (VimL) to perform a more sophisticated replacement, let’s explain it through examples.

One of the simplest issues is the line numbering with left padded spaces, useful to number our notes in an ASCII file or to create an ordered list in Markdown (removing the padded spaces):

:%s/^/\=printf('%5d.', line('.'))

The printf() function introduced by the escaped equal sign \=, requires an output format as first argument and as many parameters as requested by that string. Practically it has the same implementation seen in a bunch of other programming languages, to get more info just type :help printf in the gVim command line.

Affecting it the entire buffer, it is a good idea to write it in a new one and paste the result back to the document in order to have a one-based index, otherwise with some little change we can apply it to a selection without switching back and forth:

:'<,'>s/^/\=printf('%5d.', line('.') - line("'<") + 1)

Rejecting lines

Let’s think a slightly different list where a dash or any other symbol is preceding every item, we want to replace that symbol with a number. That can be easily achieved by changing the search expression in the command above, so let’s imagine that this list has also empty lines among items that shouldn’t be count.

To filter these empty lines, or just those starting with space, we need the proper regular expression and an external counter.

To make things easier I will make use of a function: a function in Vim is defined into the ~/.vimrc file or in a file under ~/.vim/plugin. A third way is to explicit it directly in the command line, writing it line by line or just as a single line:

:exe "function! NotEmptyLinesCounter()\n let g:counter += 1\n return g:counter\n endfunction"

Now let’s initialize the counter:

:let counter=0

And update the command:

:%s/^\S/\=printf('%5d.', NotEmptyLinesCounter())

This last solution is really interesting: imagining a long list of counted items, let’s say in a Markdown document, inserting a new one just after the 4th element we can adjust the counter instead of update manually the other 4+1 items:

:let counter = 5 | .,$s/^\ \d\+\./\=printf('%5d.', NotEmptyLinesCounter())

Scattered numbers

Unfortunately, the algorithms above work fine only when the values to increase are well coupled with the progressing line number or the external counter (i.e. sorted lists).

To alter an existing number applying calculations, we have to introduce capturing groups and the submatch() function.

In brief capturing groups allow to referer the content caught between the parenthesis () in a regular expression. To handle that content in the replacing side of the substitute command we use the escaped ordinal number representing the group within the regular expression: \1 for the first group, \2 for the second and so on.

Inside the printf() function instead, we have to use the submatch() function providing the bare number as argument:

:%s/\(\d\+)/\=printf('%d', str2nr(submatch(1)) + 1)

This version looks even more flexible and doesn’t have to rely on an external counter.

Let’s complete this set of examples with an interesting addiction to the printf argument in order to apply the command on the whole text and at same time affecting only those numbers greater than 40:

:%s/\(\d\+)/\=printf('%d', str2nr(submatch(1)) > 40 ? (str2nr(submatch(1)) + 10) : str2nr(submatch(1)))

Of course, as the logic grows inside the printf argument’s list, become more comfortable to create an external function, better within a plugin file.

Final thoughts

This is just a taste about the power of the Vim text editor, it has been around for years and represents a milestone for any Linux user. Although its logic is hard to understand at a first approach, learning it step by step will provide huge benefits in the future, allowing to solve difficult and tedious tasks regarding text handling in a very eclectic and clever way.


Creative Commons License This work is licensed under a Creative Commons Attribution-NoCommercial-ShareAlike 4.0 International License


Leave a Comment