Yet Another Inappropriate Use of Macros

Tim Chase posted a clever little :substitute command on Twitter:

As always, my instinct was immediately to see if you can do it as a macro1.

You can. Here’s how:

qqqqq/\v(\\d)+\zs\\dEnterc2l{2}Esc@qq@qqq/\\d{\d}{2}EnterCtrl-All3x@qq@q

(Yes, this is actually two recordings. Is it possible to do it with just one? If it is, it’s difficult!)

See it in action

If you want to try it out but this seems like too much TYPING to you, here’s a couple of :let commands you can paste into your Vim command-line to set up the macros:

:let @q='/\v(\\d)+\zs\\d'."\<CR>c2l{2}\<Esc>@q"
:let @w="/\\\\d{\\d}{2}\<CR>\<C-A>ll3x@w"

You can then play them back by typing@q@w.

So how does this work? Let’s see:

qqq
This is just recursive macro boilerplate. In order to RECURSE, we need to clear out the current contents of the register we’re going to record into, and the quickest way to do that is to start and then immediately stop recording into them. Here, we clear out the"qregister.
qq
More boilerplate! Start recording into register"q
/\v(\\d)+\zs\\d Enter
First we jump to the END of one of the sequences of repeated \ds by searching for one or more \ds followed by another \d , using \zs to place the cursor on that final \d.
c2l{2} Esc
Then we change the \d into {2}. So e.g. \d\d\d\d will be changed to \d\d\d{2}.
@q
Now we recurse by playing back the macro in register"q. Because we cleared it out earlier, this does nothing when we are recording the macro, but when we’re playing it back, it will jump back to the start of the macro.
q
Then we end the recording.
@q
And playback the macro. It will continue repeating until there are no more repeated \ds in the buffer, at which point the search will fail and the macro will stop.

After the above, something like \d\d\d\d will have been changed to \d{2}{2}{2}: a single \d followed by a series of {2}s. And the number of {2}s is one fewer than the number we need in our final {N}.

Next we need to convert the {2}{2}{2} into our desired {4}:

qq
Start recording again. We don’t bother to clear out the"qregister this time because the macro in it currently has no effect: we already removed all the search matches by running our previous recording, so running it again while we’re recording this macro won’t do anything.
/\\d{\d}{2} Enter
Search for a \d, followed by any number inside curly brackets {N} (such as one of the {2}s we inserted with the first macro), followed by another {2}.
<C-A>
Now the cursor is on a location that looks like ...\d{2}{2}.... We consolidate the two repetition atoms by adding one to the first withCtrl-Aand…
ll3x
…removing the second. If we started with a match of\d{2}{2}{2}, now we have \d{3}{2}.
@qq@q
Now we just need to repeat the above until we run out of search matches. This is the same as in the first recording: @q to recurse (we just ignore the “Pattern not found” message: it doesn’t affect our macro), q to stop the recording, and @q to playback the macro.

And now \d{2}{2}{2} has been changed to \d{4}!

This macro-based solution is particularly satisfying when run on a sufficiently slow computer/terminal, as the edits kind of flutter through the file like a domino rally. If your computer is really slow, or if your file is really big, and if you have no JOY in your soul, you might find this undesirable. :set lazyredraw to make the macro BORING and FAST.


  1. I dunno, I think I have a condition. ↩︎