Editor macros are a secret weapon for editing text. While they are hard to learn, no other tool offers such broad speed and power for automating changes. A skilled macro wielder can make huge changes to a codebase with ease.
Here are some inspirational patterns to demonstrate major structural refactorings using Vim macros and search/replace commands.
Reorder A List Of Numbers
A change left me with a file with unordered numbers. The numbers needed to be increasing, without changing the actual order of the lines. Macros are my first choice to accomplish a repeated action.
The commands:
/;
- Search for ‘;’n
- Go to next ‘;’qq
- Start macro in ‘q’ registerdb
- Delete backwardsN
- Search for previous ‘;’yb
- Yank backwardsn
- Search for next ‘;’P
- Paste backwardsCtrl+a
- Increment numbern
- Go to next ‘;’n
- Go to next ‘;’q
- Stop Recording macro33@q
- Replay 33 times the ‘q’ macro
This only took me a few seconds to type! I was able to run this across the whole file by replaying it the number of lines minus one. Because I saved it in the ‘q’ register, I was able to keep it saved for the whole session, which involved refactoring several dozen similar files. I got to re-use the saved macro at least 20 more times in that three hour session!
I don’t think this macro is very complex. I would only leave it in the register
for a single session, and remake it in the future if needed. But if I wanted to
save it, running "qp
spits out: dbNybnP^ann
.
Extract Interface From Class
Let’s extract an interface from a class. A region-based search and replace is a great fit for this task. Since we are not matching or reordering several blocks of text, several search/replace steps work well. First, duplicate the whole class body to the top of the file. Delete all lines that are not the function signature lines. Remove all public keywords and add in semicolons on each line ending. Finish up with adding the interface name and adding it to the class.
:11,37t8
- Copy the class body to line 8:10,35g!/public/d
- Delete every line not containing the word public from line 10 to 35:10,12s/public//g
- Delete all the public keywords in lines 10-12:10,12s/$/;/g
- End each line with a semi-colon:8
- Jump to line 8 (and add interface and name):14
- Jump to line 14 (and add interface name)
Change a Function Signature
Let’s change a function signature to move a parameter into a generic and not have to cast. I want to change a line like this
m.Name = (NameType)getEnum(typeof(NameType), req.Name);
into this:
m.Name = getEnum<NameType>(req.Name);
Normally, this is a very manual task, requiring many edits.
In this case, a search/replace is possible, but probably a lot more complex.
We’d have to identify and remove the (NameType)
, locate the opening
(
of the parameter list, put in the NameType
inside a pair of
<>
, then remove the first parameter and comma. A macro record/playback
fits this task easily.
We perform those steps above, but use the f/F commands to find markers in the line. This allows the playback to work on any line with the same markers.
/getEnum
- Search forgetEnum
qq
- Start macro inq
- ``F(```` - Backwards find
(
- ``da(```` - Delete whole block in
()
f(
- Find next(
P
- Paste backwards one characterr>
- Replace character with>
F(
- Backwards find(
r<
- Replace character with<
f(
- Find next(
l
- Left one characterdf
- Delete up to and including next spacen
- Search nextgetEnum
q
- Stop macro recording5@q
- Replay macro 5 times
Update SQL Query Fields
I needed to replace the names in a set of sql queries to sanitize some data to hand off to a customer. I grabbed a set of Star Wars names and copied it to my sql file. They were tab AND newline separated.
:1,26s/\t//g
- Replace tabs with newlines:1,100s/^\W*//g
- Delete leading whitespace:1,100s/\W*$//g
- Delete trailing whitespace:101
- Jump to line 101qq
- Start macro inq
register:-100
- Jump 100 lines up2yw
- Yank (copy) two words:+100
- Jump down 100 linesf)
- Jump to next)
f'
- Jump to next'
l
- Move rightvi'
- Select text in quotesp
- Paste clipboard:+1
- Jump down one lineq
- End macro99@q
- Replay macro 99 times
Conclusion
I typically use a macro when a search/replace would require using several
groups. I use relative line jumps (:+1
) and liberal uses of the search
(/?
and nN
) and find (fF
) commands for navigating. The search
and find commands let my macro work the same on many different shapes of lines.
I also end each macro with a step for finding the “next” line to operate on. By
ensuring each iteration finishes by going to the “next” line, I can repeat the
command with the built-in command repeat without manually executing each one.
Learning how to record and playback macros is a complex skill. With practice, it becomes second-nature. My muscle memory grows stronger with each new macro. Several of these I wrote without a mistake, almost as fast as I can type.
Like any other skill, the first few dozen times you write a macro it won’t work right, or will mangle the text. That is okay! If you are setting out to learn macros, allow yourself to be slow while you master the skill. Touch-typing can be many times faster than hand-writing or hunt-and-peck, but learning takes a long time. Macros can save you huge amounts of time, but you have to allow yourself to be slow while you master the skill.