When trying to explain why Vim is so amazing, people often say things like “you don’t have to use the mouse” and “modal editing” and “commands are composable” and such. These are all pretty nice things that make coding faster and easier. They also just so happen to lead to one of the most powerful and criminally underpublicized features of Vim: macros.
Macros are a form of “programmatic editing”, or using code to change your text. Macros are to pasting what normal mode is to insert mode. Pasting inserts text into your code. Macros run text as if it were a sequence of commands. Otherwise, they’re identical, down to even using the same registers to store text/keystrokes. If you write a macro to register a, you can type ”ap to paste the raw text of your macro to the buffer. If you yank the text “ZZ” into register a, @a will save and exit your window. This isomorphism has two consequences:
Macros generalize commands: commands automate simple tasks, and macros automate complex ones.
Imagine we have a rails project with the following HTML:
<ul id="soup_options"><li id="apple">Apple Soup</li><li id="cotton">Cotton Soup</li><li id="bees">Bee Soup</li>...</ul>
As part of a refactor, you are asked to convert the text in the list items to I18n. Depending on the setup, one way to do this would be with the following macro:
^"ayi""bcit<%=t 'soup_options.<c-R>a'%><c-C><c-W>wo<c-R>a: <c-R>b<c-C><c-W>wj
Where <c-X> is ctrl+X.
I am bad at making gifs
Let’s dissect the macro we just used:
^ Move to beginning of line | Prefix"ayi" Copy in quotes (id) to reg a |"bcit Change in tag (text) to reg b |<%=t 'soup_options. Text |<c-R>a Paste reg a |'%> Text |<c-C> Exit insert mode | Body<c-W>w Switch to next window |o Newline |<c-R>a: Paste reg a |<c-R>b Paste reg b |<c-C> Exit |<c-W>w Switch to next window |j Down one line | Suffix
The first thing is that there’s a clear structure to the macro:
The body ends up being the most complex part, and there are a couple of special things worth noting about it.
Registers are useful in vim commands; in macros they are absolutely vital. Registers are effectively your ‘variables’ in the macro, letting you store information for future transformations. Here we end up using two, one to store the id and one to store the text.
Similarly, marks are extremely useful, especially uppercase ones. While we used two windows here, we could have also placed a mark mA in our html file and mB in our I18n file and switched using that.
Because of this, it’s usually a good idea to reserve a few registers for use as macros and a few registers for use by macros.
Since the lines are all slightly different, we can’t rely on count-based commands to always be correct. Fortunately, vim has powerful relative motions and selection commands we can use instead. yi” and cit use text objects as invariants. We’re interested in the id and the li value, and “the first text inside quotes” and “the text inside the tags” will always describe them. Note that there’s multiple possible invariants to choose here, and that different contexts will change which ones are valid or invalid. Let’s say we instead had the following:
<li id="apple">Apple Soup</li><li id='cotton'>Cotton Soup</li><li class="foo" id="bees">Bee Soup</li>
yi” will yank “apples”, but “cotton” is in single quotes. We could do yi’ for “cotton”, but that would fail for “apples”. One invariant here is f=2lyw, or “yank the word starting two characters after the first =”. That would break for the third line (copying “foo” instead of “bees”). We could fix this by replacing f= with f>F= (last = before the first >).
(Other invariants here could be /id= or f>B3w (ew). Use the simplest one that matches your context.)
You start recording a new macro by pressing q[a-z] and end it by pressing q. You can append to an existing macro with q[A-Z]. You can also create macros by using “[a-z]y to copy a string of text directly into a register. A common way to write macros is this:
Usually the issue is an incorrect invariant or a relative motion, and it should be easy to fix.
When directly editing a macro, you can use ctrl-V in insert mode to insert a special character literally. You can use this to add ctrl commands, escape, etc.
Medium needs an inline-code format.
:h complex-repeat:h multi-repeat:h registers:h i_CTRL-V