Vim provides a macro recording feature that allows you to record a series of actions and then repeat them. This feature is relatively cold, but also very powerful. Just because the operation is complicated, few people learn and rarely use. But I think the idea of macros is very simple, the reason why I think it is complex is not particularly good real-world cases to reflect its value. I came across a more suitable scenario for the use of macros when I was modifying the Sniper framework today. Now I’m going to share it with my friends who are learning and using Vim. In addition to macros, this article also shows you the comprehensive use of functions such as copy and paste, in-line lookup, temporary insertion mode, registers, etc. It is especially suitable for beginners and intermediate Vim enthusiasts.

The Sniper framework entails wrapping the viper library and providing a set of tool functions. These functions are simple calls to the corresponding functions of the viper object, with exactly the same function signatures. I used the tagbar in Vim to view the code structure and copied the viper to be wrapped from the tagbar at once as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 +GetBool(key string) : bool
   +GetDuration(key string) : time.Duration
   +GetFloat64(key string) : float64
   +GetInt(key string) : int
   +GetInt32(key string) : int32
   +GetInt64(key string) : int64
   +GetIntSlice(key string) : []int
   +GetSizeInBytes(key string) : uint
   +GetString(key string) : string
   +GetStringMap(key string) : map[string]interface{}
   +GetStringMapString(key string) : map[string]string
   +GetStringMapStringSlice(key string) : map[string][]string
   +GetStringSlice(key string) : []string
   +GetTime(key string) : time.Time
   +GetUint(key string) : uint
   +GetUint32(key string) : uint32
   +GetUint64(key string) : uint64

Now I need to modify +GetBool(key string) : bool to look like the following.

1
func GetBool(key string) : bool { return File(defaultName).GetBool(key) }

The key thing here is that the content of the body corresponds to the name of the previous function (e.g. GetBool here). The whole replacement logic is exactly the same, but each line has a different content to deal with. This situation is particularly suitable for handling with macros. I’ll analyze how to do it below.

First, start recording and press qa. Here a means the register number, which can be taken as a-z and will be used when the macro is repeated. After pressing qa, all the next operations will be recorded.

First press 0 to jump to the start of the current line (^ can only jump to the first non-blank character) This step is very critical and will be revealed in the later part 😄.

Then press cf+, through the line to find f+ selected from the beginning to + this area, c means clear the selected area and enter the insertion mode. After pressing +, you will see that + and the preceding blank are deleted, at this point type func and go back to normal mode. By now, we have changed the beginning part. At this point the cursor stops at the space after func.

Next we add the function body. Since we need to use the current function name in the function body, we will copy it to the clipboard first. Press w to jump to the beginning of the function name, and then press ciw to copy the entire word, with the i indicating that it does not contain the word. The boundary of the word can be a space or some other punctuation mark, like ( here. After you press it, GetBool will be saved to the clipboard.

Before appending the function body, we also need to remove the colon in the middle. First move the cursor to the colon, press f:, and then press "_dw. Note that you press "_ first and then dw to remove the colon. By default, Vim will save the current deletion to the clipboard, so if you press it directly, it will overwrite the function name you just saved. And here "_ means black hole register, which means pressing "_ tells Vim not to save the deleted content (colon) this time.

Finally, we press A directly for $a, which actually jumps to the end of the line first and enters insert mode (note the difference between a and i). At this point you can type { return Func(defaultName). . At this point it takes a bit of hacking. Next we need to enter the current function name. The easiest way to do this is to go back to normal mode, press p to paste, and then press A to continue typing. But that’s a bit too verbose.

The essential need here is to paste content from the clipboard in insert mode . There are two ways of thinking about this.

The first is to insert the contents of the specified register directly. By default, Vim will store the copied content in the register numbered " (you read that right, it’s in quotes). So to paste the copied content in insert mode you can press CTRL-r Shift". This way can be used for any register, not limited to copy and paste, so you should learn to use it. However, considering that the registers themselves are not commonly used, this method is not recommended.

Another way is to use the so-called Insert Normal mode, which we may call temporary normal mode. Pressing CTRL-o in insert mode allows normal mode, and you press any shortcut key in normal mode. But you can only press it once, and Vim will automatically switch back to insert mode after performing the corresponding operation. If you need to copy, you can press CTRL-op and then continue typing } to finish all the changes.

The Insert Normal mode works very well here. For example, if you want to quickly move to the beginning of a line when inserting code, you can press CTRL-o^, and if you find that you have written the wrong word halfway through and want to retype it, you can press CTRL-ocw, etc. We recommend you to master it.

Finally, return to normal mode and press q to end the macro recording. Here, the preparation work is finished. Now repeat the operation we recorded.

Press j to move to the next line. Since the cursor is at the end of the previous line, it is still at the current end after moving. That’s why I need to press 0 first to jump to the beginning of the line while the macro is recording. Then press @a to execute the macro record saved in register a. You’ll see that +GetDuration(key string) : time.Duration is instantly changed to.

1
func GetDuration(key string) : time.Duration { return File(defaultName).GetDuration(key) }

Pressing j@a repeatedly will modify each line in turn. If you find pressing @a a bit cumbersome, you can also press @@ after the first run, where the second @ indicates the last register used, which is a.

Finally, we can see what is stored in register a by executing :echo @a and the result is as follows.

1
0cf+func ^[wyiwf:"_dwA { return File(defaultName).^Op(key) }^[

Here ^[ means esc, and you can check these contents against the previous article, which are actually all the keys we have pressed between pressing qa and q!

Executing a macro once is an atomic operation, no matter how much content is modified. That means you can press u to undo the changes, or you can press CTRL-r again to undo them in reverse.

In fact, Vim also automatically records macros that record the most recent modification operation. For example, you can delete a line by pressing dd and this will be automatically recorded by Vim. You don’t need to specify a register to repeat this macro, you can just press . . You can try it.

That’s all there is to this article. Although macros are a bit complicated and not very common, they are very powerful and simple to implement, so we recommend that all Vim users master them.