latest from 2026-04-02
2026-04-02vim[intermediate][deep-dive]

Macro 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

  1. Write the macro in a scratch buffer — open a new buffer and type out your intended keystrokes
  2. Test incrementally — select portions and execute with @"
  3. Refine and debug — edit the text directly, no re-recording required
  4. 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.