随着云服务的发展,越来越多的开发工作在Linux下进行,Vim的使用已成为一项必备技能。本文针对有一些Vim使用经验,但使用不多的开发人员,通过一些实例介绍如何在Vim中进行重复操作。如果你已经能在工作学习中熟练使用Vim,对Vim宏的使用已经有一定的了解,请直接跳转至第4部分开始阅读。
1. 重复输入命令Vim中的 . 命令用于重复操作。我们先看一下Vim帮助手册中对 . 命令的描述:
. Repeat last change, with count replaced with [count]. Also repeat a yank command, when the 'y' flag is included in 'cpoptions'. Does not repeat a command-line command.

可以看到 . 命令的作用就是一个,“重复上次修改”。我们先需要了解一下什么可以称为一次修改。例如,当使用 x 命令删除一个字符后,“删除一个字符” 就是一次修改,再使用 . 命令就会重复这个删除操作。再如,使用 p 命令粘贴一段文本,那个这个粘贴就是一次修改,再使用 . 就会重复进行粘贴。特别要注意的是,每次从普通模式到插入模式,输入文字后再回到普通模式,算是一次修改,不论中间输入了多少文字。
实例1
删除多个单词
One Two unwanted wrong words Three Four
首先把光标移动到unwanted的词首。我们可以输入 3dw 删除后面的3个单词,也可以用 dw.. 删除一个单词,然后重复两次。区别在于,第一个命令是一次修改,而后面的操作是三次修改。如果使用 u 命令来撤销操作的话,后面的命令可以分次撤销,而且不用数单词数,即使多按了一两次 . 按键,也不是什么问题,只要按 u 命令撤销就是了。
实例 2
在每一行代码后面加一个逗号
ONE = 1TWO = 2THREE = 3FOUR = 4
在第一行输入 A 在行尾插入,输入需要的逗号,然后按<esc>退回到普通模式。在其他行使用 . 命令就可以重复在行尾插入逗号的操作。这里 A 相当于输入 $a ,移动到行尾并进入到插入模式,不同之处在于,如果使用 $a ,就变成了一次移动加一次修改,就不能直接使用 . 命令来重复操作了,因为 . 命令只会“重复上一次修改”。Vim还有其它一些和 A 类似的“移动并进入插入模式”的命令。
2. Vim宏当需要执行的操作比较复杂时,我们把操作录制成一个宏来,然后通过播放这个宏完成重复。在普通模式下,按 q 命令加宏名称开始录制宏,再次按 q 键结束录制,然后就可以通过 @ 键播放录制的宏来进行重复操作。
实例1
曾经我们有类似这样的代码:
char string1 = "Not enough energy.";char string2 = "We are under attack!";...char string9 = "Nuclear launch detected.";
从C++11开始这些代码就会产生一个警告,提示不能从字面量转换到char 指针。于是我们考虑把它们替换成更加现代化的C++风格:
std::string string1 {"Not enough energy."};std::string string2 {"We are under attack!"};...std::string string9 {"Nuclear launch detected."};
由于字符串数量较多,我们希望只对 string1 做一次修改,然后在其他行通过重复动作自动完成所有的更改。这可以通过录制下面这样一个宏来完成。
在 string1 所在行输入 qa 开始录制宏到寄存器 a 。输入 0dwx 删除 char 。输入 i 命令进入编辑模式,输入 std::string␣ ,输入 <ESC> 返回普通模式。输入 f= ,输入 caw{ 把 = 替换为 { 。输入 $i} 在分号前插入 } 。输入 j 切换到下一行。输入 q 结束宏的录制。
现在我们处于 string2 这一行。输入 @a 即可完成对改行的修改,在输入多次 . 命令完成所有行的修改。或者输入 8@a 直接完成所有修改。
这里由于我们在宏的结尾处输入了 j 命令跳到下一行,所以直接重复操作就可以完成所有行的修改。如果一个宏只有针对一行的操作而没有切换到下一行,我们可以在选择模式下选中需要修改的行,然后执行普通模式下的 @a 命令,那么就会对每一行执行操作,也可以直接完成对所有行的修改。
:'<,'>norm! @a
实例 4
我们使用GTest编写单元测试代码,经常需要对一些成员函数做mock。也就是把如下一些函数:
virtual int GetValue(int proj, int user);virtual bool IsGood(bool lie);virtual bool IsBad(void);HRESULT DoSomething(int proj, int user, bool flag);HRESULT DoSomethingElse(int user, bool flag);
变成下面这样的代码:
MOCK_METHOD2(GetValue, int (int proj, int user));MOCK_METHOD1(IsGood, bool (bool lie));MOCK_METHOD0(IsBad, bool (void));MOCK_METHOD3(DoSomething, HRESULT (int proj, int user, bool flag));MOCK_METHOD2(DoSomethingElse, HRESULT (int user, bool flag));
我们可以录制一个宏来完成操作。和前一个例子相比,这个例子要复杂一些,原因在于 MOCK_METHOD 一系列名称需要知道参数个数。
简单起见,我们通过检查函数定义中的 , 的个数来确定参数数量,比如有一个,则说明有两个参数。这里要注意的是,没有 , 可能有两种情况,一种是函数有一个参数,还有可能函数没有参数,这个需要通过检查参数表是不是 void 来确定。基于这样的想法,可以通过下面的代码获取函数参数:
if getline(".") =~ "\([ \t][:a-zA-Z_].\)" \ && getline(".") !~ "\([ \t]void[ \t]\)" echo count(getline("."), ",") + 1else echo count(getline("."), ",")endif
我们可以把结果保存到寄存器里,在后面需要的时候插入寄存器里的内容。这里选择寄存器 o 。
:let @o=execute('if getline(".") =~ "\([ \t][:a-zA-Z_].\)" && getline(".") !~ "\([ \t]void[ \t]\)" | echo count(getline("."), ",") + 1 | else | echo count(getline("."), ",") | endif')
查看一下寄存器 o 的内容:
:reg oType Name Content c "o ^J2
这不是我们想要的内容,多了一个 ^J (LineFeed)。需要用下面的命令把这个多余的符号删掉。
:let @o=substitute(strtrans(@o),'\^@','','g')
再查看一下,现在OK了:
:reg oType Name Content c "o 2
知道怎么获取参数之后就可以开始录制我们需要的宏了。
在第一行输入 qm 开始录制宏到寄存器 m 。输入 :s/^[ \t]\(virtual\)[ \t]//g 删除行首的 virtual ,如果有的话。输入前面说的 :let @o=execute(...) 和 :let @o=substitute(...) 两个命令,把当前函数的参数个数保存到寄存器 o 。输入 I 进入插入模式,光标停留在行首,输入 MOCK_METHOD 。输入 =@o 在插入模式下插入参数数量。输入 (<esc> ,并输入 ldw 剪切返回类型到无名寄存器。输入 f( ,按 i 进入插入模式输入 ,<space><esc> 返回,然后输入 p 把返回类型粘贴在参数表前面。输入 $i)<esc> 在分号前增加一个 ) 。输入 j 切换到下一行。输入 q 结束宏的录制。
各个步骤的结果如下:
序号
输入
结果
1
qm
开始录制宏
2
:s/^[ \t]\(virtual\)[ \t]//g
int GetValue(int proj, int user);
3
:let @o=execute(...):let @o=substitute(...)
@o == 2
4
IMOCK_METHOD
MOCK_METHODint GetValue(int proj, int user);
5
=@o
MOCK_METHOD2int GetValue(int proj, int user);
6
(ldw
MOCK_METHOD2(GetValue(int proj, int user);
7
f(i,p
MOCK_METHOD2(GetValue, int (int proj, int user);
8
$i)
MOCK_METHOD2(GetValue, int (int proj, int user));
9
jq
移动光标到下一行,结束录制
录制结束后,输入 4@m 即可完成对下面4行的修改。
3. Tips
在普通模式停留
避免长时间停留在插入模式,在任何需要暂停输入的情况下,就回到普通模式。进出一次插入模式就形成一次修改,经常返回普通模式,也减小了每一次修改的粒度,而使用 u 命令撤销的操作就是一次修改,所以当长时间在插入模式下输入后如果输入错误想撤销时,会一次性把进入插入模式后的所有输入都撤销,一大片都没了,这可能并不是用户想要的结果。
配置
当重复操作应用在大量的行上时,可能会遇到性能问题,这主要是因为对每一行操作之后Vim会进行重绘。通过下面的配置可以提高性能。
set lazyredraw
4. 感谢
感谢阅读本文。
Happy Vimming!
点个关注可以了解更多微策略最新动态,行业资讯,计算机技术以及程序员日常