Long-time vim or neovim users are probably already aware that visually selecting a block of text then pressing CTRL + A in vim will result in any numbers in the selected block of text being incremented by 1. This works even if the block contains non-numeric text: each group of digits gets treated as a number and is incremented.1
For example, here’s a video that shows what happens when you select some text in vim and then use CTRL + A to increment the values:
(It’s also a fact that a lot of vim users learned about this functionality the terribly hard way: accidentally pressing CTRL + A then later realizing that all the numbers in their document were off-by-one for some unknown reason.)
But in all honesty, this isn’t a very useful mapping because it’s rare (at least in the programming world) to have numeric and text content completely separate: you usually have numbers in certain, key places while also possibly having numbers intermixed with the remainder of the text in the document. And we’ll often need to only increment certain numeric values but not the rest.
Here’s how you can use increment only numbers matching against a regular expression (including multiple numbers on the same line) while leaving the rest intact:
- Write a regex that matches against only the numbers you want to change. By doing this in normal mode, we can get vim to highlight the matches as we edit the regular expression, allowing us to visually confirm that the regex matches the numbers we want to increment. To do this, just use our trusty, old friend
:s/foo(you can match against numeric content by using
\d\+to select all consecutive digits)
- For the second half of the
bar, the replacement value), we’ll use the magic of a vim expression to increment (or otherwise manipulate) the matching value. Remember that regex capture groups are made with
(match here), match group 0 is the entirety of the match, and our manually captured groups (via the parentheses) are then counted from left-to-right from number 1 onward. The magic bits are
\=submatch(n)+1which replaces the nth match group with the incremented value.
Here’s an example where we want to insert some text in the middle of a numbered/indexed structured body of text then update all the indexes afterwards by bumping them up by one:
We have some subtitles in the SRT format and we want to insert a new caption in the middle, then update all the caption numbers but not the timestamps to reflect the insertion in the middle of the list. We have this text to start with:
1 00:00:03,400 --> 00:00:06,177 In this episode, we'll be talking about the importance of strong typing in programming. 2 00:00:010,000 --> 00:00:11,200 Strongly-typed languages have many benefits over their loosely-typed counterparts. 3 00:00:11,500 --> 00:00:13,655 Using strongly-typed languages can actually make you more productive.
And we want to insert the following subtitles between
2, but not have to increment all the indexes that come after one-by-one by hand, which is time-consuming, error-prone, and a chore:
00:00:06,600 --> 00:00:09,220 Hang on to your hats because this is going to be fun!
We’ll do this by pasting the text where we want it to go, selecting the remainder of the text (where we need to increment the subtitle index number), and then using the vim expression
:s/^\d\+$/\=submatch(0)+1/g to match a line that contains only numeric content (so it’ll match the subtitle index number but not the timestamps, which we absolutely don't want to inadvertently increment in the process):
As you can see, it’s simply a matter of selecting the text you want to replace in (in our case, everything past the captions we just entered) and then coming up with a regex that matches only the numbers we want to replace but not the numbers we don’t. If we had used CTRL + A here instead, we would have ended up with the first timestamp in each caption in our selection incremented, in addition to incrementing the index.
I think the syntax for this one is easy enough to remember that you probably don’t need a plugin or a custom key mapping to do this for you. The trickiest part is just the regex, and odds are in most cases judicious application of
^ (start of line),
$ (end of line), and whitespace will probably suffice to get you a regex that matches only the values you need. Unlike some other vim expressions that have really inscrutable names or incantations, using
\=submatch(4) or whatever) a few times is probably all it will take for you to memorize the syntax and soon enough it’ll be second nature.
If you enjoyed this tip, consider subscribing to blog posts via RSS or via email from the sidebar to the right and follow me on twitter @mqudsi for more fun hacking or programming stuff! (If you’re an emacs user, it’s highly unlikely I’ll have any text editing hacks for you at any time, unfortunately!)
To be pedantic, only the first group-of-digits/number on each line gets incremented; like many vim commands this only works on the first match per line of text unless some sort
/gglobal modifier is used. ↩