Substitution in Vim with expressions
Substitution in vim is very efficient, especially with the help of expressions. This is especially useful for calculations. I discovered this usage when I wanted to insert a list of numbers to the beginning of each line. But before go to the example, let’s take a look at expressions in vim substitution.
The expressions in vim are marked with “=”. This tells vim to treat the contents after “=” as expressions rather than literal texts. For example, vim will treat \=1+1
as 2 rather than the text 1+1
. Using \=
, I can insert numbers in the beginning of each line with expressions.
s/^/\=line(".")/
line(".")
stands for the line number of the current line. I use ^
to capture the beginning of the line and add line numbers before each line and the output is:
19text1
20text2
21text3
\=
can combine with printf()
to achieve nicer output. To have a number before each line is good, but it looks better with a trailing dot and space .
, so that it looks like a numbered list. I can do this with the following line.
s/^/\=printf("%s. ", line("."))/
%s
tells vim that there is a string, and the string is the following argument line(".")
, the line number of the current line. Also, there is a dot and a space .
after the string. This gives the following output.
33. text1
34. text2
35. text3
Vim can also do simple calculations with \=
. For example, sometimes I want to insert the relative line number rather than the absolute line number. To do that, I first enter the Visual mode with V
, and select the lines I want to work with. Type the colon :
in the commandline will give us :'<,'>
. This shows that the operations will be done within the selected part. The line number of the first line under selection is line("'<")
, and the number of the last line is line("'>")
. Also, the line number of the current line is “line(”.")". Therefore, the relative line number of the current line is line(".")-line("'<")+1
. I added 1 to the number because the first line should also be 1. The previous command is revised into:
s/^/\=printf("%s. ", line(".")-line("'<"))/
And the output is:
1. text1
2. text2
3. text3
A tricky point in calculations in the expressions is that it cannot handle divisions, because the division sign /
is the same as the separator /
used in vim substitution. I can use #
to surround the patterns that vim needs to findand put the substituted contents after the second #
. For instance, I can divide the relative line numbers by 2, so that the line numbers would be 0, 1, 1, 2, 2, 3, 3…
s#^#\=printf("%s. ", (line(".")-line("'<"))/2)
0. text1
1. text2
1. text3
2. text4
2. text5
Now I know eough about expressions in vim substitutions, and I can go back to my original task to add numbers to the beginning of each line, and most importantly, I do not want the empty lines to interfere the numbering. So, what I have is:
text1
text2
text3
And what I want is:
1. text1
2. text2
3. text3
Basic calculation can do that. I can divide the relative line numbers by 2 and add 1, and the command is:
s#^.#\=printf("%s. ", (line('.')-line("'<"))/2+1)
Also, to capture the non-empty lines, I use ^.
. That is to say, only add numbers on lines which have a character after the beginning of the line.
However, the outputis like the following. Because with ^.
, I substituted the beginning of the line together with the first character!
1. ext1
2. ext2
3. ext3
I have to add the first character back by submatch(0)
. submatch()
returns matches from the pattern. With number 0, it returns the whole match, and with 1 to 9, it returns the first to the 9th submatch. So the command is:
s#\(^.\)#\=printf("%s. %s", (line('.')-line("'<"))/2+1, submatch(0))
Ta-dah! Here is the result!
1. text1
2. text2
3. text3
Useful links;