Vim Macro 101

在我学习 Vim 的过程中,最具有启发意义的一句话是:

Vim 其实是一门“编程语言”

很早之前我就接触过 Vim,但是当时 Vim 的按键组合对我来说很难记,再加上 Vim 的界面实在太过于复古,于是我就转向了比较现代的文本编辑器。但当我学完 Missing semester 这门课程的时候,我对 Vim 的看法有了改观:它远远不止是一个文本编辑器,各种 Vim 的命令的排列组合更像是在写代码,背后都是有逻辑的!

这似乎和这篇博客要讨论的主题没有关系。但其实不是这样的,认识到 Vim 是一门编程语言是一件重要的事情。程序员经常通过编程来避免重复操作,不同人对编程语言的偏好各不相同。如果把 Vim 看成一门编程语言,那么它应该也可以做到类似的事情,今天要研究的就是——如何使用 Vim 来避免重复操作

下面假定你对 Vim 有一定的了解 : )

在 Vim 里面,我们可以用 . 重复上一次的“更改”。但如果我们想要重复一系列地更改呢?答案是:用 Vim 的宏就可以。通过使用宏,我们可以录制一系列的操作,并把他们存放在指定的寄存器里面,后续就可以重复执行。如果你检查寄存器里面的内容的话,你会发现:宏不过就是一连串的字符序列。

步骤

  1. 在 normal 模式下,按 q<register>.
    1. 这里的意思是:先按 q 然后选择一个寄存器 register 来存放待会录制的宏
    2. 可以选用的寄存器 regitser 包括: 0-9a-z"
  2. 开始录制宏,如果上一步没有问题的话,此时你应该会看到左下角显示 recording @<register>。从此刻起,你所有的操作都会被记录下来
  3. 再次按下 q 退出录制

前面提到,Vim 会把我们录制的宏存放在指定的寄存器里面。查看宏的内容很简单,用一个简单的 :reg 命令就行:

sh

:reg <register>

里面你会看到一些奇怪的字符,比如

  • ^[<ESC>
  • <80>kbBAKESPACE
  • @<register>: 执行存放在 <register> 这个寄存器里面的宏
  • @@:执行我们上一次调用的宏
  • 如果想要多次执行的话,在命令的前面先输入 [COUNT] 表示要执行多少次。
    • e.g. 100@@ 的意思是:执行上一次调用的宏 100 次

自己计算 [COUNT] 的值应该是多大太枯燥了,好在当我们设置一个很大的值的时候,超过的部分会被 Vim 忽略 :)

当然,如果只能在一个文件里面重复执行宏的话,能解决的问题还比较局限。一个很经典的场景是我们想要对一堆文件执行一样的操作,我们总不可能一个个地处理文件。这绝对违反了 DRY(Don’t Repeat Yourself)原则

Vim 允许我们同时打开多个文件然后一个个编辑。不仅如此,它还支持我们在所有打开的文件上执行指定的宏。

比如,我们想要编辑当前目录下的所有 txt 文件,只要在命令行输入 vim *.txt 就可以打开所有文件(每个文件对应一个 buffer,整体可以看成是一个 buffer 列表)。在这个模式下我们可以一个个文件地编辑,编辑完毕之后按 :wn 保存当前文件的更改并跳转到下一个文件。但我们也能一次性对这个文件列表里的所有文件进行更改:

  1. 先用第一个文件录制宏,录制完成之后,在 normal 模式下输入 :edit! 撤销对第一个文件的更改(不然后续批处理文件的时候第一个文件会被修改两次)
  2. 在 normal 模式下,输入命令 :bufdo execute "normal @<register>" | update.
    1. 关于 bufdo 的说明可以在 Vim 里面输入 :h bufdo 查看。可以理解为对所有的 buffer 执行指定的操作
    2. 这里的 normal @<register> 的意思就是在 normal 模式下执行 @<register> 里面的宏

更多的细节可以看 这里

设想你在录制一个很长的宏,突然录到一半不小心按下了 q 键结束了录制。重新录制肯定很繁琐,Vim 允许我们直接在某个录制好了的宏后面继续追加操作。办法就是用 q<REGISTER>,这里的 <REGISTER> 表示 A-Z 中的一个字母。比如之前录制到一半的宏在 a,那么追加的时候就要用 A

如果要编辑的位置是在宏的中间呢?从头开始录制宏肯定不是一个好的选择。前面提到过,宏的本质上只是存储在寄存器里面的字符序列。我们只需要修改寄存器里的内容就行,具体操作如下:

  1. 在 normal 模式下,按下 G 跳转到当前文件的最后一行(其实别的行也可以,只是跳转到最后一行我们编辑宏的时候比较不会受到干扰),然后输入 :put <register> 命令,对应寄存器里面的内容就会被粘贴在下一行
  2. 接下来直接修改这个内容就行
  3. 编辑完成之后(确保光标还在这一行),按下 :d <register> 就可以把当前行的内容存回原来的寄存器

下面是一个简单的例子,我录制了一个宏放在寄存器 y 里面,功能是:在当前行的末尾追加 world,现在我想要修改这个宏,改成追加 hello world

这只是对 Vim 的宏的一个简单介绍,它只是 Vim 强大功能的冰山一角,当你掌握更多 Vim 的其他操作的时候(比如搜索与替换,快速定位到某个位置),把他们结合起来 Vim 将会变得很强大。如果这篇博客可以让你对 Vim 产生兴趣那就再好不过。希望本篇博客对你有所帮助😉