Vim macros

JUNE 1, 2023  ·  1090 WORDS

Whether it's fixing a CSV file or refactoring code, I often encounter situations that require repetitive text manipulation. If the task is relatively small, I simply handle it manually. However, there are times when manual execution becomes impractical. In those instances, I've found vim macros to be an amazing tool!

Vim macros enable you to record a sequence of keystrokes, which can be replayed as needed. Let's begin by looking at a straightforward task. Imagine you have a list of items separated by commas, and your goal is to place each item on a separate line. Here's the original list:

apple,banana,orange,grape,kiwi

And here's the desired result:

apple,
banana,
orange,
grape,
kiwi

In Vim, this can be accomplished by creating a macro with the following steps:

  1. Place the cursor at the start of the line
  2. Start recording a macro and store it in register a with qa
  3. Press f, to find the first comma
  4. Replace the comma with a newline: r<Enter>
  5. Stop recording by pressing q
  6. Finally, press 4@a to replay the macro 4 more times for the rest of the words.

Essentially, we created a macro for register a that finds the next comma and replaces it with a new line. It can be argued that this approach seems excessive since it can be easily accomplished in editors like VS Code.

When you encounter situations that are a bit more complex, vim marcos really start paying dividends. For instance, consider the following sample code:

func getUser() {
// ...
log.error("Failed to get user data", err)
// ...
}
func getProduct() {
// ...
log.error("Failed to get product data", error)
// ...
}
func deleteProduct() {
// ...
log.error("Failed to delete product data", parseError)
// ...
}

Note the different variable names used for errors -- err, error, and parseError. This is a fairly common occurence, especially in a complex codebase involving multiple developers. These irregularities are generally inconsequential, and in my opinion, it is better to have a descriptive name for what the error actually represents in the context of that method. Now, suppose you need to include a traceID as a third parameter in all the error logs. This task can become quite intricate when using conventional text editors, but this is exactly where Vim macros excel.

The basic idea is to find a combination of actions in Vim that will specifically target the text you want to edit and modify it in a way that does not affect any unintended lines.

  • Record the Macro

    • Position the cursor on the line containing log.error( in getUser().
    • Start recording a macro by pressing qa to record into register 'a'.
    • Move to the end of the line by pressing $.
    • Insert ', traceID' before the closing parenthesis by typing i, traceID and press ESC.
    • Stop recording the macro by pressing q.
  • Apply the Macro to the Rest of the File

    • Move the cursor to the next occurrence of log.error(. with n.
    • Run :.,$g/log.error(/norm @a and press Enter. This command applies the macro in register 'a' to all lines matching log.error( from the current line (.) to the end of the file ($).

Let's try another example that employs other features of Vim macros -- switching the order of parameters of a method. Consider the following sample code continuing with the log scenario:

function log(logLevel, message) {
// ... function definition ...
}
log("INFO", "Server started");
log("DEBUG", "User login event");
log("WARN", "Potential security issue detected");
log("ERROR", "Server crashed");

Imagine that we want to make the logLevel parameter optional and if it isn't provided in future usage, we will just assume it to be INFO. However, that doesn't quite work as an optional parameter cannot preceed a required one. This means we need to switch the positions of the parameters in the function definition, as well as update all the method invocations accordingly. That seems like an extremely tedious task. Let's try to do it with vim macros.

  • Recording the Macro

    • Open the file in Vim and navigate to the log function by searching \log(
    • Record the macro into register s (for 'switch'): - Press qs to start recording
    • Find the first opening parenthesis after log by typing f(
    • Move to the first character of the first parameter/argument with l
    • Enter visual mode by pressing v and select till the comma with f,
    • Unselect , by moving back one character with h
    • Now that we have the string we need visualized, we delete and store it to the clipboard with d
    • Go to the end of the function with f) and enter insert mode i
    • Add the comma and a space with , and once that is done, go back to normal mode by pressing <ESC>
    • p will paste the first parameter/argument from the clipboard
    • Go back to the start of the line 0
    • Navigate to the extra comma by pressing f,
    • Delete the comma and the extra whitespace with xx
    • Finally, Stop the recording with q

This certainly seems like a complex process, but now the rest of the job is straightforward. In any file where you need to make this change, simply run :g/log(/norm @s. This command essentially applies the recorded macro to every instance of log( in the file.

Hopefully, this post has provided an idea about the power of Vim macros. While I'm not sure how useful this skill will be in the future, considering that AI can handle these tasks with just a description in human language, it's still a pretty neat trick to have up your sleeve.