5.2 模板补全

开发时,我经常要输入相同的代码片断,比如 if-else、switch 语句,如果每个字符全由手工键入,我可吃不了这个苦,我想要简单的键入就能自动帮我完成代码模板的输入,并且光标停留在需要我编辑的位置,比如键入 if,vim 自动完成

  1. if (/* condition */) {
  2. TODO
  3. }

而且帮我选中 / condition / 部分,不会影响编码连续性 —— UltiSnips(https://github.com/SirVer/ultisnips ),我的选择。

在进行模板补全时,你是先键入模板名(如,if),接着键入补全快捷键(默认 <tab>),然后 UltiSnips 根据你键入的模板名在代码模板文件中搜索匹配的“模板名-模板”,找到对应模板后,将模板在光标当前位置展开。

UltiSnips 有一套自己的代码模板语法规则,类似:

  1. snippet if "if statement" i
  2. if (${1:/* condition */}) {
  3. ${2:TODO}
  4. }
  5. 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 内容如下:

  1. #=================================
  2. #预处理
  3. #=================================
  4. # #include "..."
  5. snippet INC
  6. #include "${1:TODO}"${2}
  7. endsnippet
  8. # #include <...>
  9. snippet inc
  10. #include <${1:TODO}>${2}
  11. endsnippet
  12. #=================================
  13. #结构语句
  14. #=================================
  15. # if
  16. snippet if
  17. if (${1:/* condition */}) {
  18. ${2:TODO}
  19. }
  20. endsnippet
  21. # else if
  22. snippet ei
  23. else if (${1:/* condition */}) {
  24. ${2:TODO}
  25. }
  26. endsnippet
  27. # else
  28. snippet el
  29. else {
  30. ${1:TODO}
  31. }
  32. endsnippet
  33. # return
  34. snippet re
  35. return(${1:/* condition */});
  36. endsnippet
  37. # Do While Loop
  38. snippet do
  39. do {
  40. ${2:TODO}
  41. } while (${1:/* condition */});
  42. endsnippet
  43. # While Loop
  44. snippet wh
  45. while (${1:/* condition */}) {
  46. ${2:TODO}
  47. }
  48. endsnippet
  49. # switch
  50. snippet sw
  51. switch (${1:/* condition */}) {
  52. case ${2:c}: {
  53. }
  54. break;
  55. default: {
  56. }
  57. break;
  58. }
  59. endsnippet
  60. # 通过迭代器遍历容器(可读写)
  61. snippet for
  62. for (auto ${2:iter} = ${1:c}.begin(); ${3:$2} != $1.end(); ${4:++iter}) {
  63. ${5:TODO}
  64. }
  65. endsnippet
  66. # 通过迭代器遍历容器(只读)
  67. snippet cfor
  68. for (auto ${2:citer} = ${1:c}.cbegin(); ${3:$2} != $1.cend(); ${4:++citer}) {
  69. ${5:TODO}
  70. }
  71. endsnippet
  72. # 通过下标遍历容器
  73. snippet For
  74. for (decltype($1.size()) ${2:i} = 0; $2 != ${1}.size(); ${3:++}$2) {
  75. ${4:TODO}
  76. }
  77. endsnippet
  78. # C++11风格for循环遍历(可读写)
  79. snippet F
  80. for (auto& e : ${1:c}) {
  81. }
  82. endsnippet
  83. # C++11风格for循环遍历(只读)
  84. snippet CF
  85. for (const auto& e : ${1:c}) {
  86. }
  87. endsnippet
  88. # For Loop
  89. snippet FOR
  90. for (unsigned ${2:i} = 0; $2 < ${1:count}; ${3:++}$2) {
  91. ${4:TODO}
  92. }
  93. endsnippet
  94. # try-catch
  95. snippet try
  96. try {
  97. } catch (${1:/* condition */}) {
  98. }
  99. endsnippet
  100. snippet ca
  101. catch (${1:/* condition */}) {
  102. }
  103. endsnippet
  104. snippet throw
  105. th (${1:/* condition */});
  106. endsnippet
  107. #=================================
  108. #容器
  109. #=================================
  110. # std::vector
  111. snippet vec
  112. vector<${1:char}> v${2};
  113. endsnippet
  114. # std::list
  115. snippet lst
  116. list<${1:char}> l${2};
  117. endsnippet
  118. # std::set
  119. snippet set
  120. set<${1:key}> s${2};
  121. endsnippet
  122. # std::map
  123. snippet map
  124. map<${1:key}, ${2:value}> m${3};
  125. endsnippet
  126. #=================================
  127. #语言扩展
  128. #=================================
  129. # Class
  130. snippet cl
  131. class ${1:`Filename('$1_t', 'name')`}
  132. {
  133. public:
  134. $1 ();
  135. virtual ~$1 ();
  136. private:
  137. };
  138. endsnippet
  139. #=================================
  140. #结对符
  141. #=================================
  142. # 括号 bracket
  143. snippet b "bracket" i
  144. (${1})${2}
  145. endsnippet
  146. # 方括号 square bracket,设定为 st 而非 sb,避免与 b 冲突
  147. snippet st "square bracket" i
  148. [${1}]${2}
  149. endsnippet
  150. # 大括号 brace
  151. snippet br "brace" i
  152. {
  153. ${1}
  154. }${2}
  155. endsnippet
  156. # 单引号 single quote,设定为 se 而非 sq,避免与 q 冲突
  157. snippet se "single quote" I
  158. '${1}'${2}
  159. endsnippet
  160. # 双引号 quote
  161. snippet q "quote" I
  162. "${1}"${2}
  163. endsnippet
  164. # 指针符号 arrow
  165. snippet ar "arrow" i
  166. ->${1}
  167. endsnippet
  168. # dot
  169. snippet d "dot" i
  170. .${1}
  171. endsnippet
  172. # 作用域 scope
  173. snippet s "scope" i
  174. ::${1}
  175. endsnippet

默认情况下,UltiSnips 模板补全快捷键是 <tab>,与后面介绍的 YCM 快捷键有冲突,所有须在 .vimrc 中重新设定:

  1. " UltiSnips 的 tab 键与 YCM 冲突,重新设定
  2. let g:UltiSnipsExpandTrigger="<leader><tab>"
  3. let g:UltiSnipsJumpForwardTrigger="<leader><tab>"
  4. let g:UltiSnipsJumpBackwardTrigger="<leader><s-tab>"

效果如下:

5.2 模板补全  - 图1(模板补全)