Existing by coincidence, programming deliberately
Along with all the fun, creative stuff
it enables you to do,
programming sometimes requires you to carry out
boring and repetitive editing operations.
If those operations are uniformly applicable,
it's straightforward to automate them
using regular expressions
and a tool like sed
,
or :%s/foo/bar/g
in vim-speak.
But sometimes a regex can't express
the pattern you want to match against
and on those occasions,
vim macros can come to the rescue.
Let's look at a concrete example
to see what I mean.
JavaScript fat arrow functions
have different semantics for this
to regular functions,
binding it to the value of this
from their parent context.
If you want to bulk edit
a bunch of function expressions
to fat arrows,
you must also inspect the body of each function
to see whether it references this
.
A simple regular expression match can't do that,
so instead you need
to step through the matches one-by-one
and decide whether to apply the change
after eyeballing each particular block of code.
Consider this code that uses mocha to run some unit tests:
describe('a unit test suite', function () {
let result;
before(function (done) {
result = foo(done);
});
it('returned the correct result', function () {
assert.equal(result, 'expected result');
});
it('flaky test', function () {
this.retries(3);
assert.didNotThrow(bar);
});
});
Here,
the final test calls this.retries
,
so is not suitable
to be replaced by a fat arrow.
Additionally,
we must be careful to preserve
the optional done
argument
in the before
callback.
In the real world of course,
the example might be hundreds of lines long
and spread over multiple source modules.
Doing it manually
is tedious busywork.
The first step to bulk editing this with macros is to search for a match pattern. For our example code, we can do that by typing the following in vim's command mode:
/function
...then hitting Enter
.
That will land the cursor on the first function in our listing:
describe('a unit test suite', function () {
It doesn't reference this
,
so can be replaced with a fat arrow.
We'll do that while recording a macro,
so the same macro
can be applied to subsequent functions too.
Macros are recorded
by typing q
in command mode,
followed by a character
to identify which register
it should be stored in.
As we're replacing functions here,
let's use the letter f
:
qf
We're now in macro-recording mode
and every action we take
will be added to the macro,
including both movement and edits.
The first action we need to perform
is to delete the function
keyword
using dw
.
Next we want to move the cursor
to the closing parenthesis,
but it's important to do so in a way
that will work for
all subsequent macro invocations.
We can't simply use l
because that wouldn't move past
the optional done
argument.
Likewise w
would behave inconsistently
for the same reason.
By moving straight to the closing parenthesis
using %
instead,
it ensures we'll always skip past
any intervening parameters.
With the cursor positioned
over the closing parenthesis,
we can append text with a
and add the fat arrow.
Then we can return to command mode with Escape
and stop recording the macro with q
.
The macro is now stored in register f
and the edited line of code
looks like this:
describe('a unit test suite', () => {
To recap, the full sequence of keystrokes that we went through to record this macro were:
qfdw%a =>^[q
(where ^[
is the Escape
key)
At this point we're all set,
and can begin stepping
through the remaining functions
with n
.
That takes the cursor
to the next function
in our example listing,
which is the before
callback:
before(function (done) {
This is another valid case for replacement,
so we can apply the macro
that we just recorded
by typing @
followed by the appropriate register letter,
in our case f
:
@f
In one fell swoop, our macro is applied and the line is changed to a fat arrow expression:
before((done) => {
Pressing n
again
moves the cursor on
to the next function:
it('returned the correct result', function () {
Once more
we have a valid candidate for replacement,
so can invoke our macro a second time.
We can do that with @f
again,
or we can use the "previous macro" shortcut:
@@
Either way, the edited line now looks like so:
it('returned the correct result', () => {
Pressing n
a further time
brings us to the function
that we don't want to to update
because it uses this
:
it('flaky test', function () {
this.retries(3);
We can skip past this one with n
and continue working our way
through the rest of the codebase
in a similar fashion.
Often
the macro you create
will be for a particular task
and you can forget about it
after that task is completed.
But occasionally
there will be macros
that you want to re-use
in the future
and it makes sense
to add those
to your .vimrc
file,
to save the effort
of recording them again
every time
you need to apply them.
Doing that is easy.
Continuing with our previous example,
let's assume
the macro we want to save
is stored in register f
.
We don't want to lose it
by terminating the session,
so we open the .vimrc
file
in the same session instead:
:e ~/.vimrc
Next,
we add a line to the .vimrc
file
where we're going to save
the macro definition:
let @f = ''
Now we want to
print the contents of register f
between the two quote characters.
We can do that
with the cursor over
the opening quote
by typing the following
in command mode:
"fp
Or if the cursor is over the closing quote of course:
"fP
Here f
is just the register name,
so if our macro had been saved
in a different register
we'd replace f
with another letter instead.
Regardless,
the line will now look like this:
let @f = 'dw%a =>^['
(except the ^[
will be a real
Unicode Escape
character,
decimal 27
/
hex 1B
)
In a fresh editing session
you can now check
the macro has been saved
by finding a function
and typing @f
in command mode.
All being well,
you should see it change
to a fat arrow expression.
So, in summary:
Record macros with q
followed by a register letter.
Both movement and edits will be included in the macro.
Finish recording by pressing q
again.
Invoke a macro by pressing @
followed by a register letter.
Invoke the last-used macro
by pressing @@
.
Macros are stored in registers
so can be printed using "xp
.
Save any commonly-used macros
to your .vimrc
file
using the syntax let @x = '...'
.