One of the most powerful feature we have is the macro. We can record anything with macros and replay them, we can even say “replay it N times”. It’s a big beast, we can even do nested macros, but be careful, that can hurt your brain.
We have two parts:
- Recording
- Replaying
Recording
With q
, we can start and stop recoding our macro.
q{0-9a-zA-Z"} Record typed characters into register {0-9a-zA-Z"}
(uppercase to append). The 'q' command is disabled
while executing a register, and it doesn't work inside
a mapping and |:normal|.
Note: If the register being used for recording is also
used for |y| and |p| the result is most likely not
what is expected, because the put will paste the
recorded macro and the yank will overwrite the
recorded macro.
Note: The recording happens while you type, replaying
the register happens as if the keys come from a
mapping. This matters, for example, for undo, which
only syncs when commands were typed.
q Stops recording.
Implementation note: The 'q' that stops recording is
not stored in the register, unless it was the result
of a mapping
A quick and easily digestible summary: With q{letter}
we can record and store
our macro in the provided register. It’s important to remember, it stores the
content in a register. Later we will talk about registers, and they are using
the same backend, actually recording is only storing the content in a register
and replay executes the content of a register, even if it’s a “macro” or not.
Replaying
@{0-9a-z".=*+} Execute the contents of register {0-9a-z".=*+} [count]
times. Note that register '%' (name of the current
file) and '#' (name of the alternate file) cannot be
used.
The register is executed like a mapping, that means
that the difference between 'wildchar' and 'wildcharm'
applies, and undo might not be synced in the same way.
For "@=" you are prompted to enter an expression. The
result of the expression is then executed.
See also |@:|.
It’s pretty self-explanatory, stop recording and save the result in the previously defined register.
Examples
It’s a bit hard to produce a useful and easy example for that. Let’s say we have a Go file with a lot of function and we want to break long function definitions into multiple lines, but we don’t want or can’t use the Options pattern.
func FunctionOne(ctx context.Context, name string, veryLongVariableName int, fancyExtra string, wantToDoIt bool) (bool, error) {
// ... secret code lies here.
}
func ShortFunction(ctx context.Context, name string) string {
// ... secret code lies here.
}
func FunctionTwo(ctx context.Context, definitelyNotName string, definitelyNotVeryLongVariableName int, definitelyNotFancyExtra string, definitelyDonNotWantToDoIt bool) (bool, error) {
// ... secret code lies here.
}
Now imagine we have a file with 30 functions like these. Of course we can use a lot of tools, formatters, whatever we can, but we can do it with macros.
A good practice to start a macro always with 0
(beginning of the line) or ^
(first non-space character in the line).
There are a lot of options, but we will walk through a way that can demonstrate how macros are working (without nested macros).
First stand on the first function (anywhere) and start a macro, save it in
register l
, to do this we can use ql
, now at the bottom of the editor a
small test tells us we are in recording mode recording @l
, if you see that
line, it means you are in recording. It can happen accidentally for example if
you want to quit with :q
, but you did not press :
for some reasons.
So we are in macro recording mode. First we move our cursor to the beginning of
the line with 0
, because it’s a macro and we don’t want to mess up with wrong
starting position. It’s part of the macro, so when we execute a macro, it will
always do that.
Let’s break the parentheses first.
- Move the cursor to
(
withf
and go to insert mode witha
(i
before the cursor,a
after the cursor) and hit a single newline (I’ll mark it with<cr>
) and exit insert mode (<esc>
). - Move to the closing pair with
f)
, go to insert mode withi
, add a,
and a newline, and leave insert mode.
0f(a<cr><esc>f)i,<cr><esc>
Don’t worry, if you are doing it, not just reading, you will see all the changes real time on that line, so you don’t have to remember the whole sequence to do that.
No we have, if we leave the macro as it is, the result will be:
func FunctionOne(
ctx context.Context, name string, veryLongVariableName int, fancyExtra string, wantToDoIt bool,
) (bool, error) {
Not we have to add a line break after all ,
. To do that we have to move one
line up first because right now we are on the 3rd line. Go to command mode (:
)
and replace all ,
with a ,<cr><tab>
(you can ignore the tab if your vim
does an autoformat on save, or you call an autoformat when you are done).
k:s/, /,\r\t/g<cr>
At this point we can stop recording with q
.
Let’s see what’s in the register l
now:
:registers l
Type Name Content
c "l 0f(a^M^[f)i,^M^[k:s/, /,\r\t/g^M
Wonderful, we can even save this in out vimrc
.
Fine, we are done with one function, but we have another 29. Wait, not all of the functions are long, we want to break only long functions. Does not matter, we have a nice macro, we can use it to on ranges, or even pattern matching.
We want to reformat function signatures where the line is longer than 80
characters and we can use ^func .\{75\}
as a pattern to do that.
:g/^func .\{75\}/norm! @l
Now we can see our nicely formatted code:
func FunctionOne(
ctx context.Context,
name string,
veryLongVariableName int,
fancyExtra string,
wantToDoIt bool,
) (bool, error) {
// ... secret code lies here.
}
func ShortFunction(ctx context.Context, name string) string {
// ... secret code lies here.
}
func FunctionTwo(
ctx context.Context,
definitelyNotName string,
definitelyNotVeryLongVariableName int,
definitelyNotFancyExtra string,
definitelyDonNotWantToDoIt bool,
) (bool, error) {
// ... secret code lies here.
}
That’s it, we are done. I know it’s not as easy to digest as c
, but mostly
because it’s a beast and can do a lot. If someone can learn to use vim without
mouse and use proper actions (like not pressing backspace 30 times to delete
something), macro is just a wrapper with q[register]...q
and @[register]
to
automate things.
Extra
We can execute macros while we are recoding macros, but make sure you are not executing the content of the same register you are using to store your new recording. For example we can split up the macro into two separate macros:
k
:0f(a<cr><esc>f)i,<cr><esc>
l
:k@k:s/, /,\r\t/g<cr>
That way we have a macro in register k
that breaks only the parentheses, and
we have a macro in l
that moves one line up, executes the macro in register
k
, and breaks the line at all ,
characters. In this case it does not seem
useful, but it is possible, that you want to record a macro that does something,
and calls a “submacro” N times like 20@l
. For example formatting HTML from raw
text and we want to open a <div>
with class="block"
, put the next 4
paragraphs in <p>
, and close the <div>
. After that we can call the “outer”
macro 30 times and each call will create a new <div>
with a bunch of <p>
blocks in it.
A few macros from my vimrc
-- mark todo item as done
vim.fn.setreg("x", "0t]rx0")
-- mark todo item as not done
vim.fn.setreg("c", "0t]r 0")
-- break long function names
vim.fn.setreg("l", "0f(a\n\27f)i,\n\27k:s/, /,\\r\\t/g\n")
-- break go error chain
vim.fn.setreg("m", "f:li\n \27")