平时,最让我头痛的字符莫过于 {}、""、[] 等这类结对符,输入它们之所以麻烦,主要因为A)盲打很难找准它们位置,B)还得同时按住shift键。两者再一叠加,非常影响我的思维。要高效输入结对符,应该是输入少量几个字母(对,字母,不是字符)后 vim 自动为你输入完整结对符,而非是我输入一半 vim 输入另一半(不用 delimitMate 的原因)。刚好,这在 UltiSnips 能力范围内,只要定义好模板,可完美地解决这类问题,具体模板见上例中最后的结对符部分。
在定义结对符模板时,你应该考虑加上模板控制参数 i。默认情况下,UltiSnips 只会当模板名前是空白字符或行首时才进行模板补全,比如,定义 () 的模板如下:
snippet b "bracket"
(${1})${2}
endsnippet
我要调用函数 printf(),在输入完 printf 后应该接着输入括号模板名 b,然后输入模板展开快捷键 ,你会发现 UltiSnips 无法帮你补全模板,因为它看到的不是 b 而是 printfb,这在模板文件中根本未定义。有一种间接解决方式是在 printf 后加个空格,再输入 b 进行补全,这就成了 printf (),不喜欢这种编码风格。其实,UltiSnips 的作者也注意到这个问题了,他让你可以通过前面提过的模板控制参数 i 进行解决。重新定义 () 的模板如下:
snippet b "bracket" i
(${1})${2}
endsnippet
这样,UltiSnips 只管光标前 1 个字符是否是 b,若是则补全 (),不论 b 前是否有其他字符。类似,其他结对符模板都按此加上 i 控制参数。结对符模板完整定义参见上一节 cpp.snippets 示例。如下是几个快速输入结对符的演示:
快速输入结对符
另外,要想高效编辑结对符,你得了解 VIM 自身的某些快捷键。比如,有如下字符串且光标在该字符串的任意字符上,这时在命令模式下键入 va) 后将选中包括括号在内的整个字符串:
快速选中结对符
其中,v 是动作、a 是范围、) 是结对符。结对符命令的动作包括:选中 v、复制 y、删除 d、删除后插入 c;结对符命令的范围包括:含结对符 a、不含结对符 i。针对不同结对符,组合不同动作和范围就有 4*2 种方式。比如,di{ 删除不含结对符 {} 的字符串,va[ 将选中含结对符 [] 内的所有字符。选中结对符内的文本是我较为频繁的操作之一,通过诸如 vi[ 的命令可以选中当前结对符 [] 内的所有文本,这虽谈不上麻烦,但每次都得去看下是 ]、)、> 还是 },总是有点别扭。有款叫 wildfire.vim(https://github.com/gcmt/wildfire.vim )的插件,让我能更自然地选中结对符内的文本,有了它,我只需按下空格(你也可以设置成其他快捷键),自动选中光标所在区域最近的一层结对符内的文本,如果没有结对符,则选择最近的一个段落。简单设置:
" 快捷键
map <SPACE> <Plug>(wildfire-fuel)
vmap <S-SPACE> <Plug>(wildfire-water)
" 适用于哪些结对符
let g:wildfire_objects = ["i'", 'i"', "i)", "i]", "i}", "i>", "ip"]
这样,在 VIM 的命令模式下,一次空格选中最近一层结对符内的文本,两次则选中近两层内的文本,三次三层,依此类推;或者键入 3space,直接选中三层内的文本;若要取消,键入 shift-space 即可。另外,结对符类型也可以在 wildfire_objects 变量中指定。如下图所示:
快速选中结对符文本
下一节:undo,编辑器世界中的后悔药,让你有机会撤销最近一步或多步操作,这是任何编辑器都具备的基础功能。比如,第一步输入 A,第二步输入 B,第三步输入 C,当前文本为 ABC,一次 undo 后变成 AB,再次 undo 后变成 A,显然,每次 undo 撤销的均是最后的一步操作,通常采用栈这种数据结构来实现 undo 功能,由于栈具有后进先出的特点,所以,功能实现起来非常自然且便捷,但同时,也引入了致命伤,无法支持分支上的 undo 操作。