Using VIM Repetition and Macros

Vim has a terse syntax and focus on editing structured text. Vim in itself is almost a programming language for editing text in the same way that regexes are a programming language for matching patterns.

Each command Tn vim is like a command in any other programming language. “ d [motion]” - the delete function takes a motion parameter. Vim is actually an IDE for editing text. Knowing that VIM replaced line-oriented editors with a full-screen editor helps understand the emphasis on efficient movement and changes, especially focused on edits that happen on one line. (that’s why the “U” command exists. :help U ).

Basic Repetition

Basic command repetition is done with “ . ” (period) which repeats the last edit. I am forever using “ df,…. ” (delete find comma, repeat) to delete a bunch of parameters from a function list. If I were smarter I might use “ dt) ” (delete until right-paren) but using the period repetition is already a big win.

Repetition is extremely useful but it has a pretty severe limit- if you need to repeat an edit that doesn’t fit into exactly one of the basic editing commands or and edit that spans multiple lines, you can’t use repetition and usually have to upgrade to using vim macros.

Why Use Macros

Macros are easy in the way that bash shell scripts are easy. You just do your normal shell commands (or vim commands) and then save and play back those same commands later. Start macro recording with “ q [register]” usually: “ qa ” and play back with “ @a ” to play a register or “ @@ ” to repeat the last played back macro.

I looked for some real live code to practice on and came up with the Chromium Browser’s notification_provider.cc.

bool NotificationProvider::ShowText(const WebNotification& notification, int id) { DCHECK(!notification.isHTML()); ViewHostMsg_ShowNotification_Params params; params.is_html = false; params.origin = GURL(view_->webview()->mainFrame()->url()).GetOrigin(); params.icon_url = notification.iconURL(); params.title = notification.title(); params.body = notification.body(); params.direction = notification.direction(); params.notification_id = id; params.replaceId = notification.replaceId(); return Send(new ViewHostMsg_ShowDesktopNotification(view_->routing_id(), params)); }

Let’s pretend that we need to rename all the params.icon_url to match the case of the function that is on the right (ie: “ icon_url ” becomes “ iconURL ”).

If I were to do this normally, I’d navigate to the “ iconURL ” line, copy that word and paste it over the “ icon_url ” spot. Pretty easy to do with mouse and keyboard, double-click, ctrl-c, double-click, ctrl-v.

Pretty common operation, and easy to do for one function, but it would be a pain if we had to do it in multiple places.

Basic Macro

Now let’s do the same thing but with a vim macro: “ /iconURL<cr>qa02f.lyt(0f.plct= <esc>q ”

Actually, let’s explain the macro that I would have recorded

/iconURL<cr> - find iconURL ... nothing strange here qa - record (q) into register (a) 0 - go to beginning of line 2f. - find the 2nd "." l - cursor over one yt( - yank until the paren 0f. - back to the beginning, find the first "." p - paste it l - cursor over one ct= <esc> - change until the "=" with a space q - end macro recording

This is likely different from your normal interactive editing pattern. You’ll notice the excessive use of “0” to go to the beginning of the line. Using “0” tends to make macros “durable” in that you know you are starting from a known-good spot.

There is also a lot of exploitation of the structured nature of this text. The lines in question all have “ . ”, “ = ”, and “ ( ” so a lot of the edits are based on those key structural points. Instead of “ xxxxxx ” to delete text a bunch of text, you would use dt( to delete until the parenthesis. By exploiting the structure of the text, you can repeat the edit on similarly structured lines.

So… play along at home and load the same text up in your editor. Record the macro then undo all your changes and play them back. If you play back the macro on the same line, everything should work out fine. The question is whether it works for more than just the line you recorded it on.

Playing Back Macros

Macros are recorded with “ q<register><commands>q ” and played back with “ @<register> ”. By convention people will usually use the “ a ” register because it’s quick and easy but you can actually record and playback macros into and out of any register you’d like.

Move down to the “replace_id” line and play back the same macro ( @a ). You should see that as expected, “replace_id” is replaced with “replaceId”. If it’s not you need to try to figure out why, usually by re-recording a similar macro or trying a different macro tactic entirely.

It takes practice to record good macros but they save a lot of time and use all the same editing commands you already know.

Macro “Musts”

Macros just replay the exact keystrokes you use when editing, so what could be so difficult about macros?

The two tricky parts about recording useful macros are both related to repeatability.

You’ll see in the above example macro I continually go back to the beginning of the line with 0 . In some cases this is not strictly necessary but by starting at the beginning on the line forcing all commands to be relative to the beginning of the line, it is much more likely that the macro will work on subsequent lines.

Another common tactic I’ll use is to include either a “ j ” or “ n ” as the last command in a macro. This allows the macro to be easily repeated on every line that is necessary.

“ j ” will let you run through a contiguous block of text very quickly (ie: messing with log files).

“ n ” lets you mach search terms easier (ie: search for and modify function calls).

In both cases if the macro takes care of moving itself to the next spot you can run it back to back with 100@a (run macro a 100 times) or @@ (special notation to run the last macro again).

Ways to use Macros

The final “hail to macros” suggestion I’d like to make is around recording different macros and binding them to different keys. Bind the macro you just recorded to “F1” by typing :map <f1> @a . Now when you press F1 it’s as if you typed “ @a ” to run your macro.

I am forever doing stuff like this when making complicated edits across many files. As an example, I might need to go through and tag certain function parameters as “isNumber( … )”, or “isURL( … )” or “isHTML( … )”. The thing that requires brain-power is determining what validation needs to go on the function parameters, but the edits are pretty structured.

I’ll queue up the “add isNumber()” macro into F1, the “isURL()” macro into F2 and “isHTML()” into F3. Then it’s simply a matter of:

scroll scroll scroll, F1, F1, F3, F2, scroll, scroll, F3, F3, etc...

Working with the same code above we can record the isNumber(…) macro (and leave the isHTML, isURL up to you). Reset the text and record the following macro:

qa - record (q) into register (a) 0 - go to beginning of line f= - find "equals" laisNumber( <esc> - add "isNumber" $x - delete final ";" A );<esc> - append the closing function stuff 0 - back to the beginning, just for good luck q - end macro recording

Now you record similar isNumber, and isURL macros into “b” and “c” respectively. Finally:

:map <F1> @a :map <F2> @b :map <F3> @c

Now you can move through the block of code and easily tag each line as “number, URL, HTML” depending on what it needs to be. This macro is relatively simple and there are likely better / easier ways to do perform this specific task, so don’t get bound up in this specific example but instead focus on the overall strategy of using your brain for what it is good for and using macros to take care of the editing / typing parts.

Hopefully this gives you a taste of how and why you can integrate vim macros into your workflow. Once you’ve mastered macros you’ll probably find a lot of uses for them. I find that I reach for sed and awk a lot less because with vim I have a lot of the same power (sometimes even more!), and I can preview my changes a few times until I get it working right and then repeat it as necessary.