UltiSnips 不愧为 vim (或乃至所有编辑器)生态下的最佳代码片段解决方案。支持多种常见语法、snippet 添加和管理符合直觉(此处应 cue 一下隔壁对多行文本支持令人垂泪的 vscode)、还有眼花缭乱的 python 脚本插值供 高级玩家 使用。

UltiSnips 项目已有十年的历史,功能为其模仿和致敬的 SnipMate 的超集,SnipMate 模仿了 TextMate 的语法。

vscode 也模仿了 TextMate snippet 的语法,从而使得在 ultisnips 和 vscode snippet 语法之间 转换 变得相对容易。

仔细阅读文档以后,发现了一些以前遗漏了的功能。

snippets 文件组织方式

引自官方文档:

Using a strategy similar to how Vim detects |ftplugins|, UltiSnips iterates
over the snippet definition directories looking for files with names of the
following patterns: ft.snippets, ft_*.snippets, or ft/*, where "ft" is the
'filetype' of the current document and "*" is a shell-like wildcard matching
any string including the empty string. The following table shows some typical
snippet filenames and their associated filetype.

snippet filename filetype ~
ruby.snippets ruby
perl.snippets perl
c.snippets c
c_my.snippets c
c/a c
c/b.snippets c
all.snippets all
all/a.snippets all

同一语言的 snippet 不用都塞到一个文件里,可以按照主题组织。

-- javascript
|-- jest.snippets
|-- vue.snippets
|-- class.snippets

trigger 不用费脑记

列出当前可展开的 snippets

在 Normal 模式下,可以使用 :call UltiSnips#ListSnippets(),该函数会列出当前 cursor 处所有支持的 snippet (如果当前 cursor 处没有字符,列出的是此文件所有支持的 snippet),且可以通过数字选择直接展开 snippet 。

在 Insert 模式下,可设置 g:UltiSnipsListSnippets 绑定按键触发以上函数,默认是 <c-tab>,在一些 terminal 软件里可能会有按键冲突,可以改成别的键,例如:

g:UltiSnipsListSnippets='<c-u><c-l>'

效果如下:

list snippets usage

不过这个函数调用的是 vim 的 inputlist 接口,稍显原始,同时列出的数据太过详尽,有点干扰正常输入流。如果不介意多安装一些东西,可以看下一节。

与 vim 的自动补全插件配合

文档的 UltiSnips and Other Plugins 一节列出了一些支持的自动补全插件。

YouCompleteMe - comes with out of the box completion support for UltiSnips. It offers a really nice completion dialogue for snippets.

neocomplete - UltiSnips ships with a source for neocomplete and therefore offers out of the box completion dialogue support for it too.

deoplete - The successor of neocomplete is also supported.

unite - UltiSnips has a source for unite.

我试过 deoplete ,开箱即用,体验良好。不过由于其他的一些原因,同类型插件我只使用了 coc.nvim ,它其实也有对 ultisnips 很好的支持(翻源码才发现它甚至还 port 了一个 vscode 的 snippet 引擎)。

ultisnips with coc.nvim

以前端项目为例子,可以使用的进阶功能

Vim script code + Transformation

形式为 ${<tab_stop_no/regular_expression/replacement/options} 的 Transformation 需要依赖于一个 tabstop。是在强大的 python script 之外,稍微简单和通用一点的转换形式。

例如在 test-component.vue 文件里,快速起草一个 Vue SFC 文件结构。

snippet sfc_component "setup sfc component"
<template>
<div class="${1:`!v expand("%:r")`}">
</div>
</template>

<script>
export default {
name: '${1/[-_]?([a-z]+)/\u$1/g}',
}
</script>
endsnippet

展开后内容为:

<template>
<div class="test-component">
</div>
</template>

<script>
export default {
name: 'TestComponent',
}
</script>