Macros
Now that you’ve learned how to refactor text using multiple cursors, the next step is to learn about macros and how to effectively use them for complex refactorings.
Introduction
Macros are a way to record your keystrokes and then replay them whenever you want to.
The two most important keys here are:
- Q which begins recording a macro into the register
Q
. - q which replays the macro from register
Q
.
You can have a lot of macros at your disposal in various registers, ready to use whenever. You use registers with macros just like usual.
For example, to record a macro into a specific register, press ” to select the register, then a character which will be the register where your macro is stored, and then Q. For instance:
- ” + e + Q to record a macro into the
e
register.
To stop recording, press Q
again.
Replay the macro by using register + q. For example:
- ” + e + q
Using macros
-
Place your cursor on the first line, after the dash:
1 <a class="font-thin px-3" /> 2 <a class="font-thin px-3" /> 3 <a class="font-thin px-3" /> 4 <a class="font-thin px-3" /> NOR file.html [+] 1 sel 1:16
-
Use Q to begin recording a macro, and press e to select the full
thin
word:1 <a class="font-thin px-3" /> 2 <a class="font-thin px-3" /> 3 <a class="font-thin px-3" /> 4 <a class="font-thin px-3" /> NOR file.html [+] 1 sel 1:19 [@]
-
Change it to
bold
using c to delete and insert mode, then typebold
and Esc to go back to normal mode:1 <a class="font-bold px-3" /> 2 <a class="font-thin px-3" /> 3 <a class="font-thin px-3" /> 4 <a class="font-thin px-3" /> NOR file.html [+] 1 sel 1:20 [@]
-
Use f + 3 to find to next 3, and then ; to collapse selection to a single cursor in order to select the
3
:1 <a class="font-bold px-3" /> 2 <a class="font-thin px-3" /> 3 <a class="font-thin px-3" /> 4 <a class="font-thin px-3" /> NOR file.html [+] 1 sel 1:24 [@]
-
Replace it with a
4
using r to enter replace mode which waits for the next character, and type4
:1 <a class="font-bold px-4" /> 2 <a class="font-thin px-3" /> 3 <a class="font-thin px-3" /> 4 <a class="font-thin px-3" /> NOR file.html [+] 1 sel 1:24 [@]
-
Press Q to stop recording the macro.
1 <a class="font-bold px-4" /> 2 <a class="font-thin px-3" /> 3 <a class="font-thin px-3" /> 4 <a class="font-thin px-3" /> NOR file.html [+] 1 sel 1:24 Recorded to register [@]
-
Okay, so the hard part is done!
Now go back to the
-
you started from by using F + - which finds the previous-
.Repeat this motion by using Alt + . and then collapse selection with ;:
1 <a class="font-bold px-4" /> 2 <a class="font-thin px-3" /> 3 <a class="font-thin px-3" /> 4 <a class="font-thin px-3" /> NOR file.html [+] 1 sel 1:15
-
Hit j to go down a line and create 2 more cursors on each
-
by pressing C 2 more times:1 <a class="font-bold px-4" /> 2 <a class="font-thin px-3" /> 3 <a class="font-thin px-3" /> 4 <a class="font-thin px-3" /> NOR file.html [+] 3 sels 4:15
-
And just press q (note the lowercase q) to repeat the motions you’ve recorded earlier.
1 <a class="font-bold px-4" /> 2 <a class="font-bold px-4" /> 3 <a class="font-bold px-4" /> 4 <a class="font-bold px-4" /> NOR file.html [+] 3 sels 4:24
Search and Select
We’ve just explored the very basics of how to use multiple cursors and macros, but in fact they’re a lot more powerful than seems at first glance.
Let’s consider the following file, which includes some repetition:
1 def calculate_area(length, width): 2 result = length * width 3 return result 4 5 def calculate_perimeter(length, width): 6 result = 2 * (length + width) 7 return result 8 9 def calculate_volume(length, width, height): 10 result = length * width * height 11 return result NOR file.py 1 sel 1:1 Loaded 1 file.
And we want every variable to be a maximum of 3 characters, and functions to be static methods on a class.
The final outcome will look like this:
1 class Calculator: 2 @staticmethod 3 def get_area(len, wid): 4 return len * wid 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 return 2 * (len + wid) 9 10 @staticmethod 11 def get_volume(len, wid, hei): 12 return len * wid * hei NOR file.py 1 sel 1:1 Loaded 1 file.
-
First, let’s rename each of the functions from
calculate_*
toget_*
.The easiest way to do search-and-replace in Helix is to first select the entire file with %.
1 def calculate_area(length, width): 2 result = length * width 3 return result 4 5 def calculate_perimeter(length, width): 6 result = 2 * (length + width) 7 return result 8 9 def calculate_volume(length, width, height): 10 result = length * width * height 11 return result NOR file.py [+] 1 sel 11:15
-
We’re going to use s which allows us to select inside of our selection.
Basically, it takes an input and will search for that input and create a cursor everywhere it finds it.
1 def calculate_area(length, width): 2 result = length * width 3 return result 4 5 def calculate_perimeter(length, width): 6 result = 2 * (length + width) 7 return result 8 9 def calculate_volume(length, width, height): 10 result = length * width * height 11 return result NOR file.py [+] 1 sel 11:15 select:
-
Type the following:
calculate
and then Enter.1 def calculate_area(length, width): 2 result = length * width 3 return result 4 5 def calculate_perimeter(length, width): 6 result = 2 * (length + width) 7 return result 8 9 def calculate_volume(length, width, height): 10 result = length * width * height 11 return result NOR file.py [+] 3 sels 1:13
As you see, it created a selection and a cursor on every single instance of the
calculate
word.We can now just press c to delete the contents of each selection, which also places us into insert mode, and type
get
then Esc to go back to normal more:1 def get_area(length, width): 2 result = length * width 3 return result 4 5 def get_perimeter(length, width): 6 result = 2 * (length + width) 7 return result 8 9 def get_volume(length, width, height): 10 result = length * width * height 11 return result NOR file.py [+] 3 sels 1:8
-
Now that we have a cursor on each function definition, lets create an empty line above each cursor by pressing O and then writing
@staticmethod
, finally going back to normal mode with Esc:1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 3 sels 1:14
-
Our goal is to rename the parameters to be 3 letters.
To do this, we’re going to first select the function definition as well as the first line of the body of each function, with j and then by using x twice:
1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 3 sels 3:26
-
Let’s use s now which will ask us for a prompt.
This time, we won’t just enter a string but we’ll enter a very small regex:
\w+
, which selects every word:1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 24 sels 2:3
-
This selected a few extra words than we wanted, but that’s okay! One of the selections is the “primary” selection which we can tell by the
2:3
in the left right corner, which means that our primary selection is on line 2 character 3.We can make the next selection our primary selection by using ), which is seen by the
2:12
in the counter, meaning that our primary selection is at theget_area
method:1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 24 sels 2:12
We can go back to the previous one with (:
1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 24 sels 2:3
-
By pressing Alt + ,, we can remove the primary selection, so that
def
is not selected:1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 23 sels 2:12
-
Removing that selection automatically made
get_area
the next selected region. We don’t want this one either, so we’ll remove it again with Alt + ,:1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 22 sels 2:19
Press ) a few times until you reach the next selection you want to remove. If you go over by one, just press ( to make the previous selection primary.
-
Let’s repeat the previous step until we only have the words
length
,width
andheight
selected:1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 14 sels 13:17
-
At this point, we can begin our removal process.
Go to the beginning of each word by pressing b, and then press l a couple of times until you are on the 4th character of each word:
1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 14 sels 13:15
-
Let’s press e to go to the end of each word which will highlight it:
1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 14 sels 13:17
-
Press d to delete it.
1 @staticmethod 2 def get_area(len, wid): 3 result = len * wid 4 return result 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 result = 2 * (len + wid) 9 return result 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 result = len * wid * hei 14 return result NOR file.py [+] 14 sels 13:15
-
Great we’re almost there!
Let’s actually undo this step so that we can look at an alternative way of accomplishing the same result.
Press
u
just once which will undo our replacement:1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 14 sels 13:17
-
An alternative way is to select the entire file again with %:
1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 1 sel 14:44
-
With the power of s and Regex, we can select the
length
,width
andheight
words by chaining them together with the “OR” operator.For instance, type the following:
length|width|height
:1 @staticmethod 2 def get_area(length, width): 3 result = length * width 4 return result 5 6 @staticmethod 7 def get_perimeter(length, width): 8 result = 2 * (length + width) 9 return result 10 11 @staticmethod 12 def get_volume(length, width, height): 13 result = length * width * height 14 return result NOR file.py [+] 14 sels 2:19 select:length|width|height
This is certainly simpler but it’s good to know both approaches.
-
Hit Enter to select and go to the beginning of each word with b
-
Move 3 characters to the right with lll
-
Press e to go to the end of each word and delete it:
1 @staticmethod 2 def get_area(len, wid): 3 result = len * wid 4 return result 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 result = 2 * (len + wid) 9 return result 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 result = len * wid * hei 14 return result NOR file.py [+] 14 sels 2:17
-
Lets to remove the variable declarations of
result
on each line, and instead directly return the calculation.Go ahead and select the entire file again with % then use the s to bring up the select prompt again.
-
Type
result =
which will select all instances ofresult =
:1 @staticmethod 2 def get_area(len, wid): 3 result = len * wid 4 return result 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 result = 2 * (len + wid) 9 return result 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 result = len * wid * hei 14 return result NOR file.py [+] 3 sels 3:10 select:result =
-
Enter to select, and then Press C to duplicate the selection on each line:
1 @staticmethod 2 def get_area(len, wid): 3 result = len * wid 4 return result 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 result = 2 * (len + wid) 9 return result 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 result = len * wid * hei 14 return result NOR file.py [+] 6 sels 4:10
-
By using Alt + (, we can rotate the contents of the selection backward:
1 @staticmethod 2 def get_area(len, wid): 3 return r len * wid 4 result =esult 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 return r 2 * (len + wid) 9 result =esult 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 return r len * wid * hei 14 result =esult NOR file.py [+] 6 sels 4:10
Try using Alt + ) to rotate the selection forward. You can keep on pressing the key but the difference between Alt + ) and Alt + ( won’t be obvious in this example, since we basically have the same selections repeated.
Regardless, press Alt + ) at least once to get into the following state:
1 @staticmethod 2 def get_area(len, wid): 3 return r len * wid 4 result =esult 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 return r 2 * (len + wid) 9 result =esult 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 return r len * wid * hei 14 result =esult NOR file.py [+] 6 sels 4:10
-
Collapse each cursor so that there is only a single selection with ;:
1 @staticmethod 2 def get_area(len, wid): 3 return r len * wid 4 result =esult 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 return r 2 * (len + wid) 9 result =esult 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 return r len * wid * hei 14 result =esult NOR file.py [+] 6 sels 4:10
-
Now, just press d twice to delete two characters on each of the 6 lines:
1 @staticmethod 2 def get_area(len, wid): 3 return len * wid 4 result sult 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 return 2 * (len + wid) 9 result sult 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 return len * wid * hei 14 result sult NOR file.py [+] 6 sels 4:10
-
Okay so at this point, we are almost there. We just need to delete the
result sult
line.To do this, use the s again and type
s
in the prompt. This will select thes
in each of your selections and place a cursor there:1 @staticmethod 2 def get_area(len, wid): 3 return len * wid 4 result sult 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 return 2 * (len + wid) 9 result sult 10 11 @staticmethod 12 def get_volume(len, wid, hei): 13 return len * wid * hei 14 result sult NOR file.py [+] 3 sels 4:10 select:s
-
Press Enter to select, and then press x to select the entire line and d to delete it. Voila!
1 @staticmethod 2 def get_area(len, wid): 3 return len * wid 4 5 @staticmethod 6 def get_perimeter(len, wid): 7 return 2 * (len + wid) 8 9 @staticmethod 10 def get_volume(len, wid, hei): 11 return len * wid * hei ~ NOR file.py [+] 3 sels 4:1
-
We’ve got just a few steps left. Namely, select the entire file again with %:
1 @staticmethod 2 def get_area(len, wid): 3 return len * wid 4 5 @staticmethod 6 def get_perimeter(len, wid): 7 return 2 * (len + wid) 8 9 @staticmethod 10 def get_volume(len, wid, hei): 11 return len * wid * hei ~ NOR file.py [+] 1 sel 11:25
-
Press > which will indent everything by 1 level. We need this since we’re going to nest it in a class, after all. You can also use < to de-dent the selection by 1 level too.
1 @staticmethod 2 def get_area(len, wid): 3 return len * wid 4 5 @staticmethod 6 def get_perimeter(len, wid): 7 return 2 * (len + wid) 8 9 @staticmethod 10 def get_volume(len, wid, hei): 11 return len * wid * hei ~ NOR file.py [+] 1 sel 11:27
-
Now, press O to create a newline above and enter Insert mode.
-
Finally, type
class Calculator:
and Esc to go back to normal mode.1 class Calculator: 2 @staticmethod 3 def get_area(len, wid): 4 return len * wid 5 6 @staticmethod 7 def get_perimeter(len, wid): 8 return 2 * (len + wid) 9 10 @staticmethod 11 def get_volume(len, wid, hei): 12 return len * wid * hei ~ NOR file.py [+] 1 sel 1:18
We’ve accomplished our goal 🥳!
Next steps
At your disposal is now one of the most powerful methods to manipulate text: Macros and Multiple Cursors. There are still ways to improve to become more powerful.
- Check out more refactoring examples.
- Learn more advanced text manipulation techniques with text objects and surround.
- Configure language server support to gain access to a plethora of powerful tools such as workspace symbol search, variable name refactoring and more.