vim-vsnip编写片段

2023年10月23日星期一晴北京市北京师范大学, 在使用vsnip的自定义片段时,每次都会出现一个换行符,这明显不符合我的预期,当配置好nvimvim之后,编写适合自己的vsnip片段就是一个非常重要的工作。然后,时间一长忘记了具体规则,于是今天开此博客,记录下vim-vsnip官方的使用说明文档。

参考文章:VSCode User Snippets(用户代码片段):用快捷键快速生成代码,提高你的开发效率!

vim-vsnip

VSCode(LSP)'s snippet feature in vim/nvim.

Features

  • Nested placeholders
    • You can define snippet like console.log($1${2:, $1})$0
  • Nested snippet expansion
    • You can expand snippet even if you already activated other snippet (it will be merged as one snippet)
  • Load snippet from VSCode extension
    • If you install VSCode extension via Plug 'golang/vscode-go', vsnip will load those snippets.
  • Support many LSP-client & completion-engine by vim-vsnip-integ
  • Vim script interpolation
    • You can use Vim script interpolation as ${VIM:...Vim script expression...}.
  • SnipMate-like syntax support
    • Snippet files in SnipMate format with the extension .snippets can be load.
    • NOTE: Full compatibility is not guaranteed. It is intended to easily create user-defined snippets.

Concept

  • Pure Vim script
  • Well tested (neovim/0.4.4, vim/8.0.1567)
  • Support VSCode snippet format
  • Provide integration with many plugins

Related repository

friendly-snippets - Set of preconfigured snippets for all kind of programming languages that integrates really well with vim-vsnip, so all users can benefit from them and not to worry about setting up snippets on their own.

Usage

1. Install

You can use your favorite plugin managers to install this plugin.

1
2
3
4
5
6
7
8
Plug 'hrsh7th/vim-vsnip'
Plug 'hrsh7th/vim-vsnip-integ'

call dein#add('hrsh7th/vim-vsnip')
call dein#add('hrsh7th/vim-vsnip-integ')

NeoBundle 'hrsh7th/vim-vsnip'
NeoBundle 'hrsh7th/vim-vsnip-integ'

2. Setting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
" NOTE: You can use other key to expand snippet.

" Expand
imap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<C-j>'
smap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<C-j>'

" Expand or jump
imap <expr> <C-l> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
smap <expr> <C-l> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'

" Jump forward or backward
imap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
smap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
imap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'
smap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'

" Select or cut text to use as $TM_SELECTED_TEXT in the next snippet.
" See https://github.com/hrsh7th/vim-vsnip/pull/50
nmap s <Plug>(vsnip-select-text)
xmap s <Plug>(vsnip-select-text)
nmap S <Plug>(vsnip-cut-text)
xmap S <Plug>(vsnip-cut-text)

" If you want to use snippet for multiple filetypes, you can `g:vsnip_filetypes` for it.
let g:vsnip_filetypes = {}
let g:vsnip_filetypes.javascriptreact = ['javascript']
let g:vsnip_filetypes.typescriptreact = ['typescript']

3. Create your own snippet

Snippet file will store to g:vsnip_snippet_dir per filetype.

  1. Open some file (example: Sample.js)
  2. Invoke :VsnipOpen command.
  3. Edit snippet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Class": {
"prefix": ["class"],
"body": [
"/**",
" * @author ${VIM:\\$USER}",
" */",
"class $1 ${2:extends ${3:Parent} }{",
"\tconstructor() {",
"\t\t$0",
"\t}",
"}"
],
"description": "Class definition template."
}
}

The snippet format was described in here or here.

Recipe

注意:这一部分是重点,需要仔细研究!!

$TM_FILENAME_BASE

You can insert the filename via fname\<Plug>(vsnip-expand).

1
2
3
4
5
6
{
"filename": {
"prefix": ["fname"],
"body": "$TM_FILENAME_BASE"
}
}

Log $TM_SELECTED_TEXT

You can fill $TM_SELECTED_TEXT by <Plug>(vsnip-select-text) or <Plug>(vsnip-cut-text).

1
2
3
4
5
6
{
"log": {
"prefix": ["log"],
"body": "console.log(${1:$TM_SELECTED_TEXT});"
}
}

Insert environment vars

You can insert value by Vim script expression.

1
2
3
4
5
6
{
"user": {
"prefix": "username",
"body": "${VIM:\\$USER}"
}
}

Insert UUID via python

You can insert UUID via python.

1
2
3
4
5
6
7
8
{
"uuid": {
"prefix": "uuid",
"body": [
"${VIM:system('python -c \"import uuid, sys;sys.stdout.write(str(uuid.uuid4()))\"')}"
]
}
}

NOTE: $VIM is only in vsnip. So that makes to lost the snippet portability.

Variables

With $name or ${name:default} you can insert the value of a variable. When a variable isn’t set, its default or the empty string is inserted. When a variable is unknown (that is, its name isn’t defined) the name of the variable is inserted and it is transformed into a placeholder.

The following variables can be used:

  • TM_SELECTED_TEXT The currently selected text or the empty string
  • TM_CURRENT_LINE The contents of the current line
  • TM_CURRENT_WORD The contents of the word under cursor or the empty string
  • TM_LINE_INDEX The zero-index based line number
  • TM_LINE_NUMBER The one-index based line number
  • TM_FILENAME The filename of the current document
  • TM_FILENAME_BASE The filename of the current document without its extensions
  • TM_DIRECTORY The directory of the current document
  • TM_FILEPATH The full file path of the current document

Snippets in Visual Studio Code

DEMO

LSP integration

Nested snippet expansion

<Plug(vsnip-cut-text) with $TM_SELECTED_TEXT

<Plug&rt;(vsnip-cut-text) with $TM_SELECTED_TEXT

Development

How to run test it?

You can run npm run test after install vim-themis.

How sync same tabstop placeholders?

  1. compute the user-diff ... s:Session.flush_changes
  2. reflect the user-diff to snippet ast ... s:Snippet.follow
  3. reflect the sync-diff to buffer content ... s:Snippet.sync & s:Session.flush_changes

Snippet Syntax

The body of a snippet can use special constructs to control cursors and the text being inserted. The following are supported features and their syntaxes:

Tabstops

With tabstops, you can make the editor cursor move inside a snippet. Use $1, $2 to specify cursor locations. The number is the order in which tabstops will be visited, whereas $0 denotes the final cursor position. Multiple tabstops are linked and updated in sync.

Placeholders

Placeholders are tabstops with values, like ${1:foo}. The placeholder text will be inserted and selected such that it can be easily changed. Placeholders can be nested, like ${1:another ${2:placeholder}}.

Choice

Placeholders can have choices as values. The syntax is a comma separated enumeration of values, enclosed with the pipe-character, for example ${1|one,two,three|}. When the snippet is inserted and the placeholder selected, choices will prompt the user to pick one of the values.

Variables

With $name or ${name:default} you can insert the value of a variable. When a variable isn’t set, its default or the empty string is inserted. When a variable is unknown (that is, its name isn’t defined) the name of the variable is inserted and it is transformed into a placeholder.

The following variables can be used:

  • TM_SELECTED_TEXT The currently selected text or the empty string
  • TM_CURRENT_LINE The contents of the current line
  • TM_CURRENT_WORD The contents of the word under cursor or the empty string
  • TM_LINE_INDEX The zero-index based line number
  • TM_LINE_NUMBER The one-index based line number
  • TM_FILENAME The filename of the current document
  • TM_FILENAME_BASE The filename of the current document without its extensions
  • TM_DIRECTORY The directory of the current document
  • TM_FILEPATH The full file path of the current document

Variable Transforms

Transformations allow you to modify the value of a variable before it is inserted. The definition of a transformation consists of three parts:

  1. A regular expression that is matched against the value of a variable, or the empty string when the variable cannot be resolved.
  2. A "format string" that allows to reference matching groups from the regular expression. The format string allows for conditional inserts and simple modifications.
  3. Options that are passed to the regular expression.

The following example inserts the name of the current file without its ending, so from foo.txt it makes foo.

1
2
3
4
5
6
7
8
9
10
11
${TM_FILENAME/(.*)\..+$/$1/}
| | | |
| | | |-> no options
| | |
| | |-> references the contents of the first
| | capture group
| |
| |-> regex to capture everything before
| the final `.suffix`
|
|-> resolves to the filename

Grammar

Below is the EBNF (extended Backus-Naur form) for snippets. With \ (backslash), you can escape $, } and \. Within choice elements, the backslash also escapes comma and pipe characters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
any         ::= tabstop | placeholder | choice | variable | text
tabstop ::= '$' int | '${' int '}'
placeholder ::= '${' int ':' any '}'
choice ::= '${' int '|' text (',' text)* '|}'
variable ::= '$' var | '${' var }'
| '${' var ':' any '}'
| '${' var '/' regex '/' (format | text)+ '/' options '}'
format ::= '$' int | '${' int '}'
| '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
| '${' int ':+' if '}'
| '${' int ':?' if ':' else '}'
| '${' int ':-' else '}' | '${' int ':' else '}'
regex ::= JavaScript Regular Expression value (ctor-string)
options ::= JavaScript Regular Expression option (ctor-options)
var ::= [_a-zA-Z] [_a-zA-Z0-9]*
int ::= [0-9]+
text ::= .*