Vim Macros
Macro recording, execution, and advanced patterns.
Macro Fundamentals
" RECORDING MACROS
q\{register} " Start recording to register (a-z)
...commands...
q " Stop recording
" PLAYING MACROS
@\{register} " Play macro from register
@@ " Repeat last played macro
\{count}@\{register} " Play macro count times
" BASIC EXAMPLE
qa " Start recording to 'a'
0 " Go to line start
i# <Esc> " Insert "# "
j " Move down
q " Stop recording
10@a " Comment 10 lines
" REGISTERS STORE MACROS
" Macros are just text in registers!
"ap " Paste macro as text
yy"aY " Yank text into macro register
" VIEW MACRO CONTENT
:reg a " Show register 'a' content
:echo @a " Echo register 'a'
Constructing Robust Macros
" RELIABLE MACRO PATTERN
" 1. Start at known position (0 or ^)
" 2. Use motions that work on any line
" 3. End positioned for next iteration
" GOOD: Position-independent
qa
0 " Always start at column 0
f: " Find colon (works if colon exists)
s: <Esc> " Replace with ": "
j " Move to next line
q
" BAD: Position-dependent
qa
5l " Assumes cursor position
cw " May not be on word
q
" HANDLING LINE ENDINGS
qa
^ " First non-blank
y$ " Yank to end
o<Esc>p " New line, paste
j " Next line
q
" USE SEARCH FOR FLEXIBILITY
qa
/pattern<CR> " Find pattern (anywhere)
cwreplacement<Esc> " Change word
q
" FAIL GRACEFULLY
" Macros stop on first error
" This is useful! Run 100@a - stops when pattern not found
Recursive Macros
" RECURSIVE MACRO = calls itself
" Runs until error (end of file, pattern not found, etc.)
" BASIC RECURSIVE PATTERN
qaq " Clear register 'a' first!
qa
...commands...
j " Move to next line
@a " Call self
q
" Start execution
@a " Runs until j fails (end of file)
" EXAMPLE: Number all lines
qaq " Clear
:let i=1<CR> " Initialize counter
qa
^ " Start of content
I<Ctrl+R>=i<CR>. <Esc> " Insert number
:let i+=1<CR> " Increment
j@a " Next line, recurse
q
gg@a " Start from line 1
" EXAMPLE: Delete every other line
qaq
ddj " Delete line, skip one
@a " Recurse
q
@a " Start
" SAFETY: Macros stop on error
" No infinite loops - stops when command fails
Editing Macros
" MACROS ARE JUST TEXT
" Paste, edit, yank back
" VIEW AND EDIT
:reg a " See macro content
"ap " Paste macro as text
" ... edit the text ...
0"ay$ " Yank line back to register 'a'
dd " Delete the scratch line
" DIRECT REGISTER EDIT
:let @a = "0i# \<Esc>j" " Set register directly
" Use \<Esc> for escape, \<CR> for enter
" APPEND TO MACRO
qA " Uppercase = append to existing
...more commands...
q
" COMMON EDITS
" Fix wrong motion:
:let @a = substitute(@a, 'w', 'W', '')
" Add prefix:
:let @a = "0" . @a
" Add suffix:
:let @a = @a . "j"
" SPECIAL CHARACTERS
" \<Esc> Escape key
" \<CR> Enter key
" \<Tab> Tab
" \<C-R> Ctrl+R
" \<C-A> Ctrl+A
" EXAMPLE: Build macro from scratch
:let @a = "0f:lcw: \<Esc>j"
@a " Execute built macro
Macros with Global Command
" :global (or :g) APPLIES COMMAND TO MATCHING LINES
" RUN MACRO ON MATCHING LINES
:g/pattern/normal @a
" EXAMPLE: Add semicolon to function declarations
" Record macro: A;<Esc>
:g/^function/normal @a " Add ; to function lines
" EXAMPLE: Comment all error lines
:g/ERROR/normal I#
" EXAMPLE: Delete matching lines (no macro needed)
:g/DEBUG/d
" INVERSE: Run on NON-matching lines
:v/KEEP/d " Delete lines NOT matching KEEP
" COMBINED PATTERNS
:g/^server:/normal @a " Apply to YAML server keys
:g/^\s*#/d " Delete all comments
:v/^\s*$/d " Delete blank lines (inverse)
" ADVANCED: Chain commands
:g/TODO/normal 0r✓j " Mark TODOs as done
" WITH RANGES
:10,50g/pattern/normal @a " Lines 10-50 only
:'<,'>g/pattern/normal @a " Visual selection only
Infrastructure Automation Macros
" CONVERT CSV TO YAML
" Input: hostname,10.50.1.60,vault
" Output: - name: hostname
" ip: 10.50.1.60
" role: vault
qa
0 " Start of line
i- name: <Esc> " Insert prefix
f, " Find comma
s<CR> ip: <Esc> " Replace with newline + ip:
f, " Find comma
s<CR> role: <Esc> " Replace with newline + role:
jo<Esc> " Add blank line, move down
q
" GENERATE HOST ENTRIES
" Input: vault-01
" Output: 10.50.1.60 vault-01 vault-01.inside.domusdigitalis.dev
qa
0 " Start
I10.50.1.<Esc> " Add IP prefix
A <Esc> " Add space at end
yiw " Yank hostname
$p " Paste at end
a.inside.domusdigitalis.dev<Esc> " Add domain
j " Next line
q
" FIX SSH CONFIG INDENTATION
" Input: HostName value (wrong indent)
" Output: HostName value (4 spaces)
qa
^ " First non-blank
d0 " Delete leading space
I <Esc> " Insert 4 spaces
j " Next line
q
" WRAP YAML VALUES IN QUOTES
qa
f:w " Find colon, move to value
i"<Esc> " Insert opening quote
$a"<Esc> " Append closing quote
j " Next line
q
" ADD ATTRIBUTE MARKERS
" Input: server: 10.50.1.60
" Output: server: {vault-ip}
qa
f:w " Move to value
ciw{vault-ip}<Esc> " Replace with attribute
j
q
AsciiDoc Editing Macros
" CONVERT MARKDOWN TO ASCIIDOC HEADERS
" Input: ## Section Title
" Output: == Section Title
qa
0f#r=;r= " Replace first two # with =
f#x " Delete any remaining #
j
q
" ADD SOURCE BLOCK
" Input: code line
" Output: [source,bash]
" ----
" code line
" ----
qa
O[source,bash]<CR>----<Esc> " Add header above
jo----<Esc> " Add footer below
j
q
" CONVERT INLINE CODE TO ASCIIDOC
" Input: Use `command` here
" Output: Use `command` here (same - AsciiDoc uses backticks too)
" But for +monospace+:
qa
f`r+;r+ " Replace backticks with +
j
q
" WRAP LINE AS ADMONITION
" Input: Important message here
" Output: TIP: Important message here
qa
ITIP: <Esc> " Insert at start
j
q
" CREATE XREF FROM URL
" Input: https://example.com/page.html
" Output: xref:page.adoc[Page]
qa
0 " Start
cwxref:<Esc> " Replace protocol
f/l " Move past ://
dt. " Delete up to extension
s.adoc[<Esc> " Replace with .adoc[
A]<Esc> " Add closing ]
j
q
Visual Mode and Macros
" APPLY MACRO TO VISUAL SELECTION
" Select lines visually, then:
:'<,'>normal @a " Apply macro to each line
" EXAMPLE: Comment selected lines
V5j " Select 5 lines
:'<,'>normal I# <CR> " Insert # at start of each
" EXAMPLE: Add quotes to selection
V} " Select paragraph
:'<,'>normal I"<CR> " Add opening quote
gv " Reselect
:'<,'>normal A"<CR> " Add closing quote
" BLOCK VISUAL MODE
<Ctrl+V>jjj " Block select column
I " Insert at start of block
#<Space><Esc> " Adds to ALL selected lines
" RECORD WITH VISUAL
" Macros can include visual selection
qa
viw " Visual select word
y " Yank
j0 " Next line, start
q
" VISUAL + GLOBAL
:'<,'>g/pattern/normal @a " Apply to matches in selection
Debugging Macros
" SEE WHAT'S IN THE MACRO
:reg a " Display register content
:echo @a " Echo (interprets escapes)
"ap " Paste to see in buffer
" STEP THROUGH MACRO
" No built-in step mode, but:
" 1. Paste macro: "ap
" 2. Visually execute line by line
" 3. Fix issues
" COMMON ISSUES
" Problem: Macro doesn't repeat
" Cause: Not ending in correct position
" Fix: Add j or movement to end
" Problem: Macro works once, fails on repeat
" Cause: Position-dependent commands
" Fix: Start with 0 or ^
" Problem: Macro inserts wrong text
" Cause: Forgot to leave insert mode
" Fix: Ensure <Esc> after insert
" Problem: Special characters literal
" Cause: Using literal <Esc> in :let
" Fix: Use \<Esc> in :let commands
" REBUILD MACRO FROM SCRATCH
qaq " Clear register
qa " Start fresh recording
" TEST INDIVIDUAL PIECES
" Record small parts, test, combine:
qa " Record first part
q
@a " Test
qA " Append more
q
@a " Test combined
Macro Gotchas
" WRONG: Macro with count at end
qa
100@a " Record plays the macro!
q
" CORRECT: Don't include count in recording
qa
...commands...
q " Stop first
100@a " THEN add count
" WRONG: Using jj for escape
qa
itext<jj> " jj is literal if using imap
q
" CORRECT: Use literal Escape
qa
itext<Esc> " Press actual Escape key
" WRONG: Assuming search starts from cursor
qa
/pattern " May find before cursor wrapping
q
" CORRECT: Consider wrap behavior
:set nowrapscan " Stop at end of file (optional)
" WRONG: Macro changes register during playback
qa
dd " Deletes to default register
p " Pastes what was deleted, not original
q
" CORRECT: Use named registers or black hole
qa
"_dd " Delete to black hole
"ap " Paste from named register
q
" WRONG: Expecting @@ to replay modified macro
@a " Play original
:let @a = @a . "j" " Modify
@@ " Plays ORIGINAL (last played)
" CORRECT: Use @a for modified macro
@a " Play the modified version
Quick Reference
" ESSENTIAL MACRO COMMANDS
qa " Record to 'a'
q " Stop recording
@a " Play 'a'
@@ " Repeat last macro
5@a " Play 5 times
" MACRO EDITING
"ap " Paste macro (to edit)
"ay$ " Yank line to macro
:let @a = "..." " Set macro directly
qA " Append to macro 'a'
" WITH :global
:g/pattern/normal @a " Apply to matches
:v/pattern/normal @a " Apply to non-matches
:'<,'>g/./normal @a " Apply to visual selection
" ROBUST MACRO PATTERN
0 " 1. Known start position
f\{char} " 2. Find target
c\{motion}text<Esc> " 3. Make change
j " 4. Position for next
@a " 5. (Optional) recurse
" SPECIAL KEY SEQUENCES (:let)
\<Esc> " Escape
\<CR> " Enter
\<Tab> " Tab
\<C-R> " Ctrl+R