Macro

The . repeat command repeats only the last change. And it gets overwritten with every change. The q command allows you to record a sequence of commands and execute them later whenever you need. You can make it recursive, add a count prefix, combine it with Command-line mode commands and so on. Powerful indeed!

With so many built-in features, sometimes it isn't easy to choose. I prefer the substitute command to macros if both of them can be used for the given problem, especially if the processing doesn't require multiple lines to be considered at once for the solution. That said, macros are more flexible, having an inherent advantage of being able to easily integrate numerous Vim commands. Also, macros allow you to progress in smaller chunks, which might be easier compared to a complicated regexp based solution.

Documentation links:

Macro usage steps

Here's a rough overview of the q command usage. Working examples will be discussed in later sections.

  1. Press q to start the recording
  2. Use any alphanumeric character as the register to store the recording (for example, a)
  3. Execute command sequence to accomplish the required task
  4. Press q again to stop the recording
  5. Press @a (the register used in step 2) to execute the recorded command sequence
    • 5@a execute the macro 5 times
    • @@ repeat the last executed macro

info Command-line area will show recording @a after step 2 and this indicator vanishes after step 4.

info Note that these registers are shared across recording, delete and yank commands. You'll see how this helps to modify a recording later, but you should also be careful not to mix them if you want separate recording and paste use cases. As mentioned earlier in the Normal mode chapter, uppercase registers will append to existing content in lowercase registers.

info See also vi.stackexchange: Can I repeat a macro with the "dot operator"? (one of the solutions will allow you to use the . command to execute a macro immediately after recording as well).

Example 1

The qwceHello^[q macro recording clears text till the end of the word and inserts Hello. Here's a breakdown of this command sequence:

  • q start recording
  • w register used to save the macro
  • ce change till the end of the word
  • Hello insert these characters
  • ^[ this is a single character that denotes the Esc key
    • in other words, press Esc key for this step, don't type the ^ and [ characters
    • you'll see this representation if you paste the contents of the "w register using "wp
  • q stop recording

After you've recorded the macro, you can execute this command sequence anywhere else you need it. For example, if the cursor is on the fourth character of the text Hi-there and @w is pressed, you'll get Hi-Hello.

Modifying a macro

As mentioned earlier, registers are shared across recording, delete and yank commands. When you call a macro using @, the register content is treated as the sequence of commands to be executed. So, editing a register's content will automatically update the behavior of the macro as well. Knowing that you can modify a macro also helps if you make a mistake — you can choose to finish the recording and update later instead of restarting the recording.

Suppose you want to use 'Hello!' instead of Hello for the macro discussed in the previous section. Here's one possible way to make the changes:

  • "wp paste the contents of "w register
    • you should get ceHello^[
  • ce'Hello'!^[ modify the text as needed
  • "wy update the contents of "w register after visually selecting the modified text or using motion commands in Normal mode

After you've modified the register contents, check if it is working as expected. For example, if the cursor is on the fourth character of the text Hi-there and @w is pressed, you should now get Hi-'Hello'!.

info In case you wish to create a new macro from scratch by just typing the required text instead of using the q command, you'll find Ctrl+v (or the Ctrl+q alias) useful to insert keys like Esc and Enter. To do so, press Ctrl+v followed by the required key. You'll get ^[ for Esc, ^M for Enter and so on.

info let @w = "ce'Hello'!^[" adding this line to the vimrc file will load the "w register with the given text at startup.

Example 2

Suppose you forgot to add curly braces for single statement control structures in a Perl program:

# syntax error
if($word eq reverse $word)
    print "$word is a palindrome\n";

# corrected code
if($word eq reverse $word)
{
    print "$word is a palindrome\n";
}

qpo{^[jo}^[q is one way to do it:

  • qp start recording and use register p
  • o open a new line
  • { insert the { character
  • ^[ go back to Normal mode (Esc key)
  • j move down one line
  • o open another line
  • } insert the } character
  • ^[ go back to Normal mode (Esc key)
  • q stop recording

Having a macro will help you apply this correction whenever you forget braces for single statement control structures.

info Note that { and } will be indented based on style settings for that particular filetype.

Example 3

I used F`r[f`s]()^["*P macro to replace `:h <topic>` with a hyperlink to the corresponding online help page for this ebook. Assume the cursor is somewhere within the :h <topic> text portion surrounded by backticks (markdown formatting for inline code). This has to be changed to [:h <topic>](link) (markdown formatting for hyperlinks).

  • F` move cursor to the starting backtick
  • r[ replace backtick with [
  • f` move cursor to the ending backtick
  • s]() replace backtick with ]()
  • ^[ go back to Normal mode (Esc key)
  • "*P paste contents of the last highlighted text selection
    • note the use of uppercase P to paste content to the left of the cursor

Once the macro was recorded, I just had to select the url from the browser for each help topic and execute the macro. I used n to navigate in the markdown files after using :h as the search pattern.

Motion and Filter

If you have to apply the same macro for text portions that are next to each other, you can add motion commands at the end of the macro for reaching the next text portion. The motion command could be arrow motions, searching using / and so on. Doing so will allow you to use a count prefix to apply the macro for all the text portions in one shot. This assumes that you can easily count the number of text portions. For example, consider this Python snippet where you want to change single line definitions to multiple lines:

def square(n): return n ** 2
def cube(n): return n ** 3
def isodd(n): return n % 2 == 1

You can do a recording as usual, select these lines visually (or use a range) and then apply the macro using normal @d in Command-line mode. Or, you could add a motion to automatically go to the next line and use a count prefix as described below.

qd0f:lr^M>>o^[jq is one way to achieve this:

  • 0f:l Move to beginning of the line and then move the cursor to the character after the first occurrence of : (which is a space character in the above snippet)
    • this also assumes that there won't be any : character as part of the function arguments
  • r^M replace the space character with a newline character
  • >> indent the line
    • note that this won't be required if indentation is automatically applied based on Python syntax
  • o^[ open a new line and go back to Normal mode
  • j move to the next line (this makes it possible to use the count prefix)

After recording, you can use 3@d on the first line to get the output as shown below:

def square(n):
    return n ** 2

def cube(n):
    return n ** 3

def isodd(n):
    return n % 2 == 1

Suppose the Python function definitions discussed above aren't next to each other but can be found anywhere in the Python script file. In such cases, if you are able to reliably identify the lines using a regexp filter, you can use the :g command.

  • qdf:lr^M>>o^[q simplified macro
    • 0 not required since the cursor starts at the beginning
    • no need to move to the next line
  • :g/^def .*): / normal @d apply the macro for filtered lines
  • :%s/^def .*):\zs \(.*\)/\r\t\1\r/ if you are comfortable with regexp, you could also just use the substitution command like this one instead

Recursive recording

Suppose it isn't easy to count the number of text portions and filtering is complicated too. In such cases, you might be able to use recursive recording that continues to execute the macro until one of the steps fails. Similar to recursive function calls, you have to call the macro from within the recording. Consider this Python snippet where you want to change single line definitions to multiple lines:

def square(n): return n ** 2
def cube(n): return n ** 3
def isodd(n): return n % 2 == 1
print(square(12))

qr0f:lr^M>>o^[j@rq is one way to achieve this. The only addition here is @r at the end of the recording compared to the solution discussed in the previous section. For the fourth line with print() function, the macro will stop when it doesn't find the : character. It would've stopped even if a : was found, provided it was the last character, since the l motion would've failed.

Using @r on the first line of the above snippet would give the following output:

def square(n):
    return n ** 2

def cube(n):
    return n ** 3

def isodd(n):
    return n % 2 == 1

print(square(12))

warning info Note that the register being used here must be empty before you start the recording, otherwise you might see some unwanted changes when you type @r while recording. To ensure this register is empty, you can use qrq (i.e. record an empty macro) before you record the recursive macro.

info If the :s command is part of the recording and you do not want the macro to stop if the search pattern isn't found, you can use the e flag.

Here are some more examples:

Exercise

Given the following text:

# Introduction
# Normal mode
# Command Line mode
# Visual mode

Use a macro (or the substitute command if you prefer) to get the modified text as shown below:

* [Introduction](#introduction)
* [Normal mode](#normal-mode)
* [Command Line mode](#command-line-mode)
* [Visual mode](#visual-mode)

Further Reading