/today
No. 006Macro Composition: Building Complex Vim Automations
Learn advanced macro techniques: building macros from scratch, editing macro content directly, combining macros with Ex commands, and creating recursive patterns for complex text transformations.
Macro Composition: Building Complex Vim Automations
Most Vim users record macros the traditional way: qa, fumble through some commands, hope nothing breaks, then q. But you shouldn't compose macros on the fly — if a macro is more than three keystrokes, compose it directly in a scratch buffer.
Macros are text. Once you internalize this, everything changes. Running a macro is simply instructing Vim to take the contents of a register and execute them as keystrokes. This means you can write, edit, debug, and version-control your macros like any other code.
Constructing Macros as Text
Instead of recording, build macros by setting register contents directly:
:let @q = 'I// ^[A (added)^[j'
The ^[ represents the Escape key. To insert special keys like Escape and Enter, use Ctrl+v followed by the key — you'll get ^[ for Escape, ^M for Enter.
Better yet, use single quotes to avoid escaping nightmares. Single quotes allow you to type almost everything literally, whereas double-quotes require escaping characters like backslashes and quotes.
The Iterative Macro Development Process
- Write the macro in a scratch buffer — open a new buffer and type out your intended keystrokes
- Test incrementally — select portions and execute with
@" - Refine and debug — edit the text directly, no re-recording required
- Load into a register —
:let @q = 'your-macro-text'
Consider this macro for wrapping function calls with error handling:
ciw^Rtry { ^[pA } catch(e) { console.error(e); }^[f{%
Breaking it down:
ciw— change inner word (replace function name)^R— paste from register- Build the try-catch structure
f{%— jump to opening brace, then to matching closing brace
Macro Editing and Repair
Registers are shared across recording, delete and yank commands. When you call a macro, the register content is treated as commands. So editing a register automatically updates the macro behavior.
To inspect macro content:
:reg q " View register q contents
"qp " Paste macro content to edit
After editing, reload with :let @q = 'new-content'. Macros are just text stored in named registers, so treat them like any other text object.
Combining Macros with Ex Commands
You needn't restrict yourself to single-keystroke vi commands when composing macros. You can include Ex commands as well.
Powerful pattern: macro + range operation:
:let @f = 'I ^[>>j' " Add 4 spaces and indent
:15,25norm @f " Apply to lines 15-25
:%norm @f " Apply to entire file
Or embed substitutions within macros:
:let @c = ':s/function/async function/^Mj'
Recursive Macros for Complex Patterns
Recursion occurs when we execute a macro while recording it. Clear the register first with qqq.
Classic recursive macro pattern — process until end of file:
:let @r = '0/pattern^M<operations>j@r'
The recursion breaks when the search fails (no more matches), making it self-terminating.
Pro Tips for Macro Composition
Use mnemonics to remember macros — if you have a macro for modifying functions use f register (qf), n (qn) for numbers. Name it with whatever first thing comes to mind.
Don't forget you can prepend a count — 6@a would run the macro 6 times. Compose macros so they make sense when run multiple times.
For persistent macros, Vim automatically saves registers to viminfo and restores them at startup. Or explicitly save to vimrc:
" Permanent macro for CSS property formatting
let @c = 'I ^[A;^[j'
Pro Tip
To append to an existing macro instead of re-recording, use a capital letter. If you recorded with qa...q, append with qA...q. This lets you iteratively build complex macros without losing your work.
Example
" Convert array destructuring to object destructuring
:let @d = 'f[ci[{^[f]s}^['
" Before: const [a, b, c] = getData()
" After: const {a, b, c} = getData()
" Apply to multiple lines:
:10,20norm @d
" Or make it recursive to handle nested structures:
:let @D = 'f[ci[{^[f]s}^[j@D'
Macros aren't magic spells you record once and hope work. They're code. Write them, debug them, version them. Your future self will thank you when that 47-keystroke transformation just works.