终于到了我第二喜欢的vim功能了(当然了,最喜欢的是.命令)。我原本计划在介绍完.命令之后介绍宏,以便让各位小伙伴们能了解到vim对于重复操作进行的强大的优化。但是由于宏本身跟寄存器息息相关,所以还是忍痛割爱,将它放到寄存器之后。废话不多说让我们开始吧。

宏的基本使用

我们还是以一个例子进入相应的内容。我要将下面这段代码

foot = 'foot' ball = 'ball' football = foot ball

变为这样

var foot = 'foot'; var ball = 'ball'; var football = foot ball;

通过分析它这三行其实做的都是同样的功能,即在每行的行首添加 var 关键字,然后在每行的行尾添加分号。我们可以利用之前介绍的.命令或者针对列的可视模式来处理,只是不管用哪种方法都需要至少两次操作。

问题先放在这里,让我们先了解下什么是宏,以及怎么用宏。

宏是存储在寄存器中的连续的操作指令,以便后续可以对这些指令进行回放。可以使用 q 进行录制,后面跟寄存器名称,表示将接下来的操作记录保存到这个寄存器中。例如使用 qa 表示将接下来的操作保存到 a 这个寄存器中。退出宏的录制可以直接输入 q

针对上面的例子,我们可以执行 qa 进行宏的录制,然后使用 A 在行尾进入插入模式,接着输入 ; 完成行尾的操作。然后使用 I 进入行首,然后在行首输入 var 完成这部分的工作。最后使用 q 退出宏的录制。这样就将这个宏保存在了a 寄存器.

一张图牢记vim命令(vim从嫌弃到依赖16)(1)

我们可以使用 :reg a 来查看寄存器的内容。

一张图牢记vim命令(vim从嫌弃到依赖16)(2)

这个内容完全是我们之前通过键盘输入到vim中的内容,只是返回到普通模式输入的是<Esc> 而这个保存的是^[ 因为宏有自己的键盘编码方式,这个方式我觉得不需要特别去查去记,自己就可以从寄存器中查到。

宏录制完成之后,可以使用 @ 寄存器 来回放寄存器中保存的宏。在回放宏之后可以使用 @@ 来快速回放上一次回放的宏。

一张图牢记vim命令(vim从嫌弃到依赖16)(3)

到现在各位小伙伴可能已经发现了,它与.命令比较类似,只能机械地执行之前执行过的内容,它无法做到智能化,例如我在录制宏的过程中使用了诸如 2w之类的命令,后面在重复的时候很有可能发生错误。这就要求我们在使用宏的过程中,尽量规范化光标移动,不要搞这种特例的形式。就像写代码不要写死一样。

这里我们还是手动执行了好多次同样的宏,宏与普通的operator 一行支持前面加数字表示重复,例如2@a 表示重复执行两次这个宏。上面的例子我们可以稍微做一下修改,即在最后添加一步将光标移动到下一行的操作——j。然后使用这个特性进行重复。

一张图牢记vim命令(vim从嫌弃到依赖16)(4)

仔细点可以发现,我们执行了3次这个宏,也就是要执行3次j 操作,但是我们是在第二行执行的宏,也就是剩下的行只允许我们执行一次j 。这里虽然有问题,但是宏还是正确的对文本进行了修改。

这是因为 vim 宏在 motion 执行失败之后会终止执行,这个并不是一个 bug,而是一个特性,也就是说利用这个特性我们可以更好地使用宏。例如上述例子中,宏只执行了一次 j ,第二次执行到j 的时候出错了,于是就停下来了。这就告诉我们不用关心剩下的操作需要重复多少次,只需要给出一个足够大的数,保证已有行能正常进行修改就可以了。

我们再来看一个例子将

1. one 2. two 3. three 4. four 5. five 6. six 7. seven 8. eight 9. nine 10. ten

改为

1) One 2) Two 3) Three 4) Four 5) Five 6) Six 7) Seven 8) Eight 9) Nine 10) Ten

我们可以这么归纳这个操作,从行首开始找第一个 .,然后执行替换操作将其替换为 )然后找到下一个单词,将首字母改为大写。我们可以在宏中执行 0f.r)w~j最后退出。读一下这段内容, ~之前没见过吧。之前介绍过,gU和 gu后面可以跟 motion表示将对应范围的字符转化为大写和小写。g~可以进行大小写反转。而这里的~直接将当前光标所在字符进行反转。

一张图牢记vim命令(vim从嫌弃到依赖16)(5)

上述命令我们首先使用 0将光标至于行首,这样就规范了每行的查找操作。另外这里由于 10 有两个字符,所以这里使用 f 来查找而不仅仅使用 l往后移动一个字符,最后我们不确定. 和单词之间会不会有空格。所以这里最好是使用 w 而不是 l 。这些细节体现了我们之前说的要更加规范的移动光标。

一张图牢记vim命令(vim从嫌弃到依赖16)(6)

宏录制完了之后,我们可以利用之前介绍的 motion失败会终止执行的特性,不用数需要处理多少行,直接 10@a(因为第一行已经处理了,所以这里只有9行待处理)

以并行的方式执行宏

我们将上述例子进行变更

1. one 2. two 3. three 4. four // do something 5. five 6. six 7. seven 8. eight 9. nine 10. ten

执行上次录制的宏,发现它在第5行的位置停止了,因为在第5行中未找到 .,所以它终止了,为了继续运行,需要手动跳过,然后继续执行。假设我们有多处有注释,每次遇到问题就停下来,再手工执行,会显得比较麻烦。为了解决这个问题,我们使用vim提供的另外一种执行宏的方式——以并行的方式执行。

重新录制宏,与之前相比,只需要将j这个操作给去除掉。然后使用针对行的可视模式,选中待处理行,然后针对这些选中行来执行宏。我们在这里来审视一下这两种方式,并行方式需要提供重复次数,它是第一次执行完了接着执行下一次,下一次的执行依赖于上一次成功的执行。并行则不然,并行是针对选中部分,同时执行一个宏操作。即使中间有错也不影响其他行的运行。

一张图牢记vim命令(vim从嫌弃到依赖16)(7)

一张图牢记vim命令(vim从嫌弃到依赖16)(8)

给宏追加命令

还是上面的例子,假设在录制好了宏之后发现我们少了一个j,使用串行话的方式无法顺利执行。这种情况下不需要重新录制宏,只需要在对应寄存器中添加一条指令。

这里补充一下寄存器相关知识。在上一篇介绍寄存器的时候我们只演示了使用小写字母的寄存器,没有提到大写字母的寄存器。根据之前的惯例,大写字母与小写字母都可以使用,大写字母的功能比小写字母要强,例如大写的标签标示全局,小写的只能用于单个文件。这里大写的寄存器与小写的寄存器是同一个寄存器,使用大写时我们可以对寄存器内容进行追加操作。

宏是保存在寄存器中的,q 后面加字母表示宏的内容保存在哪个寄存器中,说到这里,聪明的你已经反应过来该如何将命令追加到寄存器中了。那就是使用 q 大写字母。

针对并行操作的例子,假设已经录好其他操作只差一个j 了,我们可以使用 qA 进行追加,然后添加 j 操作即可追加前宏的内容如下:

一张图牢记vim命令(vim从嫌弃到依赖16)(9)

一张图牢记vim命令(vim从嫌弃到依赖16)(10)

添加完成之后,宏变成了如下内容

一张图牢记vim命令(vim从嫌弃到依赖16)(11)

后面就可以以串行的方式执行这个宏了

配合文件参数列表使用宏

之前介绍过文件参数列表,即使用 :args 可以对文件进行分组,各位小伙伴可能只知道这个,但是没找到它的使用场景。也不知道vim提供这个功能有什么用处。在这里我们就来看看它的一个使用场景。

我们还是以之前的 neovim 配置文件的工程为例,我要在每个lua文件中添加一行注释 --this is add by vim macro 。打开一个 lua 文件之后,使用 :args **/*.lua 来将每个 lua 文件加入到参数列表中。然后随意打开一个 lua 文件,在录制宏的时候执行 ggO<ESC>S--this is add by vim macro 然后退出。这里还是贯彻了前面说的要是移动更加规范,我们先用 gg 移动到第一行,以便能准确的在首行插入内容。由于在 lua 文件中有注释的话使用 O 添加一行的时候它会自动添加一个注释。但是不能确保所有的 lua 文件在行首都有注释,所以我们先使用 S 删除一行并进入插入模式。当然通过配置也可以取消这个特性,等介绍到文件类型的时候再来讨论这个。

此时文件已经发生了变化,如果我们直接执行宏的话,之前录制时修改的文件将会两次执行相同的命令,所以这里不能保存,可以执行 :edit! 放弃本次修改,或者如果已经修改了的,可以执行u进行回退。

结合之前介绍的在命令模式中执行普通模式的命令,可以使用 :argdo normal @a 。argdo 表示循环对参数列表中的每个文件执行相同的操作。

录制宏:

一张图牢记vim命令(vim从嫌弃到依赖16)(12)

添加参数列表:

一张图牢记vim命令(vim从嫌弃到依赖16)(13)

执行宏

一张图牢记vim命令(vim从嫌弃到依赖16)(14)

上述的操作方式采用的是并行的执行宏,我们可以对其进行一些修改,让其支持串行的方式。

还记得之前介绍的怎么遍历参数列表吗,不记得也没关系。我们可以使用 :next 来访问下一个,:prev 来访问上一个。配合之前的命令可以使用 ggO<Esc>S--this is add by vim macro:next。我们无法知道参数列表中到底有多少个文件,但是可以利用失败即终止这个特性输入一个足够大的数字即可,例如 100@a即可。这样就省去了执行命令模式中命令的相关操作。

对比两个宏发现我只需要在之前的宏后面添加一个 :next 指令即可,所以这里就直接执行了 qA:nextq

一张图牢记vim命令(vim从嫌弃到依赖16)(15)

对比上面两种方式发现,并行执行的时候中间某个缓冲区如果出错并不影响其他缓冲区的执行,这就给我们排查造成了一定的问题,一旦出错我们不得不打开每一个缓冲区查看执行的结果来找到出错的位置。而串行则会停在出错的位置,我们只要针对出错的部分做一定的调整,然后继续执行就好了。而且这个例子中列表参数并不会循环遍历,也就不用担心之前修改过的内容又被修改。

编辑宏内容

上面我们说到宏是保存在寄存器中的一组操作指令,既然可以利用往寄存器中追加内容的方式往宏中追加指令,那么是不是我只要更新了寄存器中的内容,在执行宏的时候命令就会改变呢?如果你能这么想,那么恭喜你都会抢答了,而且答对了!

还是以上面那个添加注释的例子为例,假设我之前忘记了删除新添加的 --,也就是我录入的宏变成了 ggOthis is add by vim macro 我们会发现在第一行是注释的文本中它的表现是正常的,但是第一行不是注释,添加的就是有问题的,例如 nvim-config/lua/config/auto-session.lua。我们发现了这个问题需要对这个宏进行修改。

首先我们需要将 宏从寄存器中放到编辑器中,这就要使用 :put a取出寄存器中的内容,你可能会疑惑为什么不用 "ap 呢,这是因为 p 命令默认会将寄存器中的内容放到光标所在位置的后面,而 :put 则会直接放到下一行,所以这里还是放入到当前命令之后要好。接着修改一下这个宏。在对应位置加上 S 这个操作,最后使用 0d$ 从行首粘贴到行尾,注意这里尽量不要使用 dd,它会连带着换行符一块进行粘贴,可能会破坏宏的指令。最后我们可以先删除之前粘贴的一行,再重新执行这个宏

一张图牢记vim命令(vim从嫌弃到依赖16)(16)

最后的叨叨

宏是vim提供的很有用的功能,希望我通过本文让各位小伙伴对它有一个初步的认识,想要用好宏这个强大的工具还是需要花大量的时间去学习研究的。vim这个工具也是常用常学常新的,时不时你就能发现自己当初不知道的内容,就像有小伙伴给我留言给我介绍了一些我之前不知道的命令,在这里对所有给我留言的小伙伴表示感谢。vim的指令实在太多了,指望我把所有好用的一一介绍,文章的篇幅就显的太长了,这里我就不加了,各位小伙伴有什么好用的方式也可以留言给其他不会的小伙伴一个学习的机会。大家一起共同进步。谢谢大家

,