Vim宏快速入门

2021-10-15 创作不易,请勿屏蔽广告🙏 g

Vim提供宏录制功能,可以记录一系列操作然后重复执行。这个功能比较冷门,却也非常强大。只是因为操作复杂,很少有人学,也很少用。但我认为宏的思想很简单,之所以觉得复杂是没有特别好的实战案例来体现它的价值。我今天在修改Sniper框架的时候就遇到了一个比较适合使用宏的场景。现在整理出来分享给学习和使用 Vim 的朋友。除了宏之外,本文还向大家展示了复制粘贴、行内查找、临时插入模式、寄存器等功能综合运用,特别适合初级和中级 Vim 爱好者。

Sniper框架需要封装 viper 库并提供一系列工具函数。这些函数都是简单的调用 viper 对象对应的函数,函数签名完全一样。我在 Vim 中用 tagbar 查看代码结构,一次性从 tagbar 中把需要封装的 viper 复制出来,内容如下:

   +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

现在我需要把 +GetBool(key string) : bool 修改成下面的样子:

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

对比一下就会发现,需要把前面的空白和+替换成func,删除中间的冒号,最后插入一段函数 body。这里最关键的是 body 的内容跟前面函数名是对应的(比如这里的GetBool)。整个替换的逻辑完全相同,但每一行要处理的内容又不一样。这种情况就特别适合用宏来处理。下面我就分析一下如何操作。

首先开始录制,按qa。这里的a表示寄存器编号,可以取a-z,等重复执行宏的时候要用到。按了qa之后,接下来所有的操作都会被记录下来。

先按0,跳到当前行的开始位置(^只能跳到第一非空白字符)这一步非常关键,后面部分会揭秘😄。

然后按cf+,通过行内查找f+选中从开头到+这片区域,c表示清空选足的区域并进入插入模式。按完就会发现+和前面的空白都删除了,这个时候输入func ,然后回到普通模式。到现在,我们就把开头部分改好了。此时光标停在func后面的空格上。

下面我们添加函数体。因为在函数体中需要用到当前的函数名,所以我们先复制到剪贴板。按w跳到函数名开头,再按ciw复制整个单词,i表示不包含单词。单词的边界可以是空格,也可以是其他一些标点符号,比如这里的(。按完之后,GetBool就会保存在剪贴板。

在追加函数体之前,我们还需要删除中间的冒号。先把光标移动到冒号,按f:,然后按"_dw。注意,这里先按了"_,再按的dw删除冒号。为什么不直接按dw呢?Vim默认会把当前删除的内容保存的剪贴板,如果直接按,会覆盖刚才保存的函数名。而这里的"_表示黑洞寄存器,也就是说按了"_告诉 Vim 这次不要保存删除的内容(冒号)。

最后,我们直接按A,表示$a,其实就是先跳到行尾,进入插入模式(注意a与i的区别)。这个时候就可以输入{ return Func(defaultName).。到这个时候需要一点黑科技了。接下来我们需要输入当前了函数名。最简单的办法是回到普通模式,按p执行粘贴操作,再按A继续输入。但有点太啰嗦了。

这里的本质需求是要在插入模式下从剪贴板粘贴内容。有两种思路。

第一种是直接插入指定寄存器的内容。这个需要先按CTRL-r再按对应的寄存器编号。Vim默认会把复制的内容存入编号为"的寄存器(你没看错,是英文引号)。所以要在插入模式下粘贴复制的内容可以按CTRL-r Shift"。这种方式可以用于任意寄存器,不局限于复制粘贴,大家要活学活用。但考虑到寄存器本身不常用,所以这种方法不太推荐。

另一种是使用所谓的Insert Normal模式,我们不妨称之为临时普通模式。在插入模式中按CTRL-o可以普通模式,你按任意普通模式下的快捷键。但只能按一次,Vim执行完对应的操作之后会自动切换回插入模式。如果需要复制,可以按CTRL-op,然后继续输入 }完成全部改动。

这里的Insert Normal模式非常好用。比如我们在插入代码的时候想快速移到到一行的开始,可以按CTRL-o^,如果单词写到一半发现写错了,想重新输入,可以按CTRL-ocw等等。建议大家熟练掌握。

最后,返回普通模式,按q,结束宏录制。到这里,准备工作就结束了。现在重复我们录制的操作。

按j移到到下一行。因为光标在上一行的结尾,所以移动后还是在当前的结尾。这就是我需要在宏录制的时候先按0跳到行首的原因。然后按@a,执行寄存器a中保存的宏记录。你会发现 +GetDuration(key string) : time.Duration瞬间被改成了:

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

重复按j@a就会依次修改每一行。如果觉得按@a有点麻烦,还可以在首次运行后按@@,这里第二个@表示上一次使用的寄存器,也就是a。

最后我们可以看看寄存器a中到底保存了什么内容,可以执行:echo @a,结果如下:

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

这里的^[表示esc,大家可以对照前文看看这些内容,其实就是我们在按qa和q之间按过的所有键!

执行一次宏,不管修改了多少内容,都是原子操作。也就是说你可以按u来撤销修改,也可以再按CTRL-r反向撤销。

其实 Vim 还会自动录制宏,记录最近的一次修改操作。比如删除一行可以按dd,这个会被 Vim 自动记录。重复执行这个宏不需要指定寄存器,可以直接按.。大家可以试试。

以上就是就本文的全部内容。宏虽然有点复杂,也不是很常用,但宏的功能很强大,实现方式也很简单,建议所有 Vim 同学熟练掌握。