使用Vim宏实现复杂的查找与替换

2022-12-14 ⏳3.8分钟(1.5千字) g

最近准备翻译一篇文章。原文是 HTML 格式,译文需要改成 Markdown 格式。两种格式使用不同的标题锚点,前者使用驼峰加下划线,后者使用小写字母加短杠。所以需要将原文中的锚点批量转换成小字字母加短杠的形式。想来想去,还是用Vim宏实现比较方便。今天就分享给大家作为学习宏的实践素材😄

先看一下原文锚点:

<a href="#Understanding_costs">GC model</a>:
<a href="#Optimization_guide">optimization guide</a>.

转成 Markdown 后需要替换为:

<a href="#understanding-costs">GC model</a>:
<a href="#optimization-guide">optimization guide</a>.

最简单的办法是处理两遍。第一遍先把锚点都转为小写字母。第二遍再把下划线替换成短杠。

我们先通过搜索找到要处理的文本,搜索指令为:

/\v"#\w+"

/表示搜索。\v比较特别,表示这下来使用 magic 模式,其实就是标准正则模式。在该模式下+表示的就是正则中至少出现一次的意思。如果不用\v,则需要写成\+'。然后我们用“#+”{regex}匹配原文中的锚点,其中等价于[a-zA-Z0-9_]`。

搜索后 Vim 会跳转到第一个匹配的位置。然后我们只需要执行gui"{viml}就可以把双引号内的字符全部改为小写。

我们分解一下刚才的指令。首先,gu表示将字符转小写,但输入后还需要指定修改范围。这个范围可以通过 text object 确定。这里我们用的是i",它表示双引号内部的所有内容,但不含引号本身。如果想包含双引号,则可以使用a"。有人可能会问为什么要区分i"和a"。在当前的场景下确实没有区别。但 text object 还可以跟其他指令配合。以删除为例,di"会删除双引号里面的内容,但保留引号;如果想连引号一块删除,则可以用da"。

接下来按n跳转到下一个锚点,再按.重复执行上一次修改(也就是转小写)。不断重复,直到改完为止。到这里我们就完成第一遍处理。

第二遍处理就稍微有点复杂了。我们知道,替换使用s指令。比如s/_/-/只会替换当前行出现的第一个下划线。如果要替换当前行所有的下划线,需要改成s/_/-/g,也就是在最后面追加g参数。但是这两种操作都不行,因为我们只想替换双引号内部所有的下划线😂

怎么办呢?我想到的是先用 visual 模式选中引号中的内容,然后再用'<和'>获取上次选中的范围,最后用s完成替换。完整指令为:

'<,'>s/_/-/g

这样就可以只替换双引号内部的的下划线了😄所以第二遍的处理过程是搜索、替换,然后按n跳到下一个位置,再按:&重复上一次替换。

注意,.无法重复执行替换指令⚠️

以上是传统的处理方案。考虑到锚点数量太多,分两遍处理有点傻,所以就引出本文的主要内容——使用宏将两种操作合并。

所谓宏,简单来说就是按键记录器。Vim录制宏的本质是把用户输入的字符记录下来。在回放宏的时候再替用户重复按一下键盘。就这么简单单。又因为Vim所有的操作都需要通过键盘控制,所以Vim的宏可以录制全部功能。

这里的思路是使用宏依次录制如下操作:

录制宏需要选一个寄存器,一般可以选a-z这26个。比如我用寄存器a。

在录制之前我们先跳转到一个锚点上,准备好。

开始录制指令为qa。此时 Vim 为显示recording @a。接下来所有的操作都会被保存到a中。

然后执行gui"将当前锚点转成小写字母。再执行vi"选中当前锚点,然后按Esc(上面的^[)退出 visual 模式。虽然退出了,但'<和'>还是会保存上次选中的范围。接着执行:'<,'>s/_/-/g完成替换。上面的^m表示回车。最后执行搜索/\v"#\w+"跳转到下一个锚点。

所有动作都完成后按q完成录制。我们可以执行reg a查看刚才录制的内容:

:reg a
Type Name Content
  c  "a   gui"vi"^[:'<,'>s/_/-/g^M/\v"#\w+"^M
Press ENTER or type command to continue

到这里,准备工作结束,可以摘果子了😄回放宏使用@a。因为我用了寄存器a,所以转入@a。Vim 一番操作后会跳转到下一个锚点。再次执行@a,会重复刚才的替换。我们还可以按@@再重复执行@a,加快速度。

最后提一下,录制宏一定要「谋定而后动」。宏一定是一系列可重复执行的操作。以本文为例,我是先完成替换再跳转到下一个锚点,这就为重复执行做好准备。如果没有最后的搜索,那在回放之后还得自行跳转,效果就会打折。

我之前写过一篇宏的入门文章,也欢迎阅读。