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