5.2 模板补全
开发时,我经常要输入相同的代码片断,比如 if-else、switch 语句,如果每个字符全由手工键入,我可吃不了这个苦,我想要简单的键入就能自动帮我完成代码模板的输入,并且光标停留在需要我编辑的位置,比如键入 if,vim 自动完成
if (/* condition */) {TODO}
而且帮我选中 / condition / 部分,不会影响编码连续性 —— UltiSnips(https://github.com/SirVer/ultisnips ),我的选择。
在进行模板补全时,你是先键入模板名(如,if),接着键入补全快捷键(默认 <tab>),然后 UltiSnips 根据你键入的模板名在代码模板文件中搜索匹配的“模板名-模板”,找到对应模板后,将模板在光标当前位置展开。
UltiSnips 有一套自己的代码模板语法规则,类似:
snippet if "if statement" iif (${1:/* condition */}) {${2:TODO}}endsnippet
其中,snippet 和 endsnippet 用于表示模板的开始和结束;if 是模板名;"if statement" 是模板描述,你可以把多个模板的模板名定义成一样(如,if () {} 和 if () {} else {} 两模板都定义成相同模板名 if),在模板描述中加以区分(如,分别对应 "if statement" 和 "if-else statement"),这样,在 YCM(重量级智能补全插件) 的补全列表中可以根据模板描述区分选项不同模板;i 是模板控制参数,用于控制模板补全行为,具体参见“快速编辑结对符”一节;${1}、${2} 是 <tab> 跳转的先后顺序。
新版 UltiSnips 并未自带预定义的代码模板,你可以从 https://github.com/honza/vim-snippets 获取各类语言丰富的代码模板,也可以重新写一套符合自己编码风格的模板。无论哪种方式,你需要在 .vimrc 中设定该模板所在目录名,以便 UltiSnips 寻找到。比如,我自定义的代码模板文件 cpp.snippets,路径为 ~/.vim/bundle/ultisnips/mysnippets/cpp.snippets,对应设置如下:let g:UltiSnipsSnippetDirectories=["mysnippets"]其中,目录名切勿取为 snippets,这是 UltiSnips 内部保留关键字;另外,目录一定要是 ~/.vim/bundle/ 下的子目录,也就是 vim 的运行时目录。
完整 cpp.snippets 内容如下:
#=================================#预处理#=================================# #include "..."snippet INC#include "${1:TODO}"${2}endsnippet# #include <...>snippet inc#include <${1:TODO}>${2}endsnippet#=================================#结构语句#=================================# ifsnippet ifif (${1:/* condition */}) {${2:TODO}}endsnippet# else ifsnippet eielse if (${1:/* condition */}) {${2:TODO}}endsnippet# elsesnippet elelse {${1:TODO}}endsnippet# returnsnippet rereturn(${1:/* condition */});endsnippet# Do While Loopsnippet dodo {${2:TODO}} while (${1:/* condition */});endsnippet# While Loopsnippet whwhile (${1:/* condition */}) {${2:TODO}}endsnippet# switchsnippet swswitch (${1:/* condition */}) {case ${2:c}: {}break;default: {}break;}endsnippet# 通过迭代器遍历容器(可读写)snippet forfor (auto ${2:iter} = ${1:c}.begin(); ${3:$2} != $1.end(); ${4:++iter}) {${5:TODO}}endsnippet# 通过迭代器遍历容器(只读)snippet cforfor (auto ${2:citer} = ${1:c}.cbegin(); ${3:$2} != $1.cend(); ${4:++citer}) {${5:TODO}}endsnippet# 通过下标遍历容器snippet Forfor (decltype($1.size()) ${2:i} = 0; $2 != ${1}.size(); ${3:++}$2) {${4:TODO}}endsnippet# C++11风格for循环遍历(可读写)snippet Ffor (auto& e : ${1:c}) {}endsnippet# C++11风格for循环遍历(只读)snippet CFfor (const auto& e : ${1:c}) {}endsnippet# For Loopsnippet FORfor (unsigned ${2:i} = 0; $2 < ${1:count}; ${3:++}$2) {${4:TODO}}endsnippet# try-catchsnippet trytry {} catch (${1:/* condition */}) {}endsnippetsnippet cacatch (${1:/* condition */}) {}endsnippetsnippet throwth (${1:/* condition */});endsnippet#=================================#容器#=================================# std::vectorsnippet vecvector<${1:char}> v${2};endsnippet# std::listsnippet lstlist<${1:char}> l${2};endsnippet# std::setsnippet setset<${1:key}> s${2};endsnippet# std::mapsnippet mapmap<${1:key}, ${2:value}> m${3};endsnippet#=================================#语言扩展#=================================# Classsnippet clclass ${1:`Filename('$1_t', 'name')`}{public:$1 ();virtual ~$1 ();private:};endsnippet#=================================#结对符#=================================# 括号 bracketsnippet b "bracket" i(${1})${2}endsnippet# 方括号 square bracket,设定为 st 而非 sb,避免与 b 冲突snippet st "square bracket" i[${1}]${2}endsnippet# 大括号 bracesnippet br "brace" i{${1}}${2}endsnippet# 单引号 single quote,设定为 se 而非 sq,避免与 q 冲突snippet se "single quote" I'${1}'${2}endsnippet# 双引号 quotesnippet q "quote" I"${1}"${2}endsnippet# 指针符号 arrowsnippet ar "arrow" i->${1}endsnippet# dotsnippet d "dot" i.${1}endsnippet# 作用域 scopesnippet s "scope" i::${1}endsnippet
默认情况下,UltiSnips 模板补全快捷键是 <tab>,与后面介绍的 YCM 快捷键有冲突,所有须在 .vimrc 中重新设定:
" UltiSnips 的 tab 键与 YCM 冲突,重新设定let g:UltiSnipsExpandTrigger="<leader><tab>"let g:UltiSnipsJumpForwardTrigger="<leader><tab>"let g:UltiSnipsJumpBackwardTrigger="<leader><s-tab>"
效果如下:
(模板补全)
