Increment or decrement visual selection in vim
Posted on 25 August 2014 in vim
vim can increment and decrement digits under the cursor with the <C-A>
<C-X>
bindings, respectively. Go try it out and then come back.
Now that you are back, let's extend this functionality to visual
selections. First let's learn a command you can find in :help submatch
.
:s/\d\+/\=submatch(0)+1/
This is a type of substitution called sub-replace-expression. A normal
substitution looks like :s/{pattern}/{string}/
and replaces {pattern}
with {string}
. Here we have the \=
changes the {string}
part to an
expression to be evaluated. Our pattern in this case is \d\+
. The \d
matches a digit and the \+
says to match one or more digit so something like
555
is matched once and not 3 times.
So what our sub-replace-expression is going to do is find a number and then
substitute it with the evaluation of submatch(0)+1
. submatch(0)
simply
returns the matched text (ie the \d\+
). The +1
adds one to it!
Of course, this only operates on the first occurrence of \d\+
in the current
line. We can make this operate on every \d\+
on a line with the g
flag for
:s
:s/\d\+/\=submatch(0)+1/g
Go ahead and give that a try.
It feels like we are close now. This won't work quite right, but at this point you might be tempted to add this mapping to your vimrc
vnoremap <C-a> :s/\d\+/\=submatch(0)+1/g
What we want this to do is to increment all numbers in a visual selection. It does do that, but it does something else as well. Can you figure out what's going wrong?
The problem is that the way substitute works on visual selections (ie
:'<,'>s
) is to operate on the whole line that contains the visual selection
so if you select only some of the numbers on a line, the numbers on the line
that are not selected will also be incremented. We can fix this by adding
something to the {pattern}
. We want to match not just digits but digits
that are in the visual selection. We can do that with \%V
vnoremap <C-a> :s/\%V\d\+/\=submatch(0)+1/g
Now we are getting pretty close. There's one problem here depending on the
exact behavior that you want. What about negative values? Do you want -6 to
increment to -5 or to increment to -7? Personally, I want it to increment to
-5. If you do, too, we have to add even more to our {pattern}
to include
negative signs along with our digit. What we need to do is add, in front of
the \d\+
either 0 or 1 -
's. To say 0 or 1, we use \=
in our pattern. In
the pattern, \=
means 0 or 1. In the replacement string, \=
means
evaluate an expression.
So here's our final mapping:
vnoremap <C-a> :s/\%V-\=\d\+/\=submatch(0)+1/g
To decrement, just subtract 1 instead of adding. Have you added this mapping to your vimrc? Have you made it better?