Skip to content

Commit e44026b

Browse files
authored
Support SnipMate-like syntax (#259)
* Allow to source snippets of snipmate format * Add utilities for .snippets * Add test * Add snipmate support into :VsnipOpen * Doc update for SnipMate
1 parent 6f87341 commit e44026b

File tree

10 files changed

+332
-15
lines changed

10 files changed

+332
-15
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ VSCode(LSP)'s snippet feature in vim/nvim.
2424
- [completion-nvim](https://github.com/nvim-lua/completion-nvim)
2525
- Vim script interpolation
2626
- You can use Vim script interpolation as `${VIM:...Vim script expression...}`.
27+
- SnipMate-like syntax support
28+
- Snippet files in SnipMate format with the extension `.snippets` can be load.
29+
- NOTE: Full compatibility is not guaranteed. It is intended to easily create user-defined snippets.
2730

2831
# Concept
2932

autoload/vsnip/source.vim

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
function! vsnip#source#refresh(path) abort
55
call vsnip#source#user_snippet#refresh(a:path)
66
call vsnip#source#vscode#refresh(a:path)
7+
call vsnip#source#snipmate#refresh(a:path)
78
endfunction
89

910
"
@@ -13,6 +14,7 @@ function! vsnip#source#find(bufnr) abort
1314
let l:sources = []
1415
let l:sources += vsnip#source#user_snippet#find(a:bufnr)
1516
let l:sources += vsnip#source#vscode#find(a:bufnr)
17+
let l:sources += vsnip#source#snipmate#find(a:bufnr)
1618
return l:sources
1719
endfunction
1820

@@ -65,7 +67,7 @@ endfunction
6567
" format_snippet
6668
"
6769
function! s:format_snippet(label, snippet) abort
68-
let [l:prefixes, l:prefixes_alias] = s:resolve_prefix(a:snippet.prefix)
70+
let [l:prefixes, l:prefixes_alias] = vsnip#source#resolve_prefix(a:snippet.prefix)
6971
let l:description = get(a:snippet, 'description', '')
7072

7173
return {
@@ -87,7 +89,7 @@ endfunction
8789
"
8890
" resolve_prefix.
8991
"
90-
function! s:resolve_prefix(prefix) abort
92+
function! vsnip#source#resolve_prefix(prefix) abort
9193
let l:prefixes = []
9294
let l:prefixes_alias = []
9395

autoload/vsnip/source/snipmate.vim

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
let s:cache = {}
2+
3+
function! vsnip#source#snipmate#refresh(path) abort
4+
if has_key(s:cache, a:path)
5+
unlet s:cache[a:path]
6+
endif
7+
endfunction
8+
9+
function! vsnip#source#snipmate#find(bufnr) abort
10+
let filetypes = vsnip#source#filetypes(a:bufnr)
11+
return s:find(filetypes, a:bufnr)
12+
endfunction
13+
14+
function! s:find(filetypes, bufnr) abort
15+
let sources = []
16+
for path in s:get_source_paths(a:filetypes, a:bufnr)
17+
if !has_key(s:cache, path)
18+
let s:cache[path] = s:create(path, a:bufnr)
19+
endif
20+
call add(sources, s:cache[path])
21+
endfor
22+
return sources
23+
endfunction
24+
25+
function! s:get_source_paths(filetypes, bufnr) abort
26+
let paths = []
27+
for dir in s:get_source_dirs(a:bufnr)
28+
for filetype in a:filetypes
29+
let path = resolve(expand(printf('%s/%s.snippets', dir, filetype)))
30+
if has_key(s:cache, path) || filereadable(path)
31+
call add(paths, path)
32+
endif
33+
endfor
34+
endfor
35+
return paths
36+
endfunction
37+
38+
function! s:get_source_dirs(bufnr) abort
39+
let dirs = []
40+
let buf_dir = getbufvar(a:bufnr, 'vsnip_snippet_dir', '')
41+
if buf_dir !=# ''
42+
let dirs += [buf_dir]
43+
endif
44+
let dirs += getbufvar(a:bufnr, 'vsnip_snippet_dirs', [])
45+
let dirs += [g:vsnip_snippet_dir]
46+
let dirs += g:vsnip_snippet_dirs
47+
return dirs
48+
endfunction
49+
50+
function! s:create(path, bufnr) abort
51+
let file = readfile(a:path)
52+
let file = type(file) == v:t_list ? file : [file]
53+
call map(file, { _, f -> iconv(f, 'utf-8', &encoding) })
54+
let source = []
55+
let i = -1
56+
while i + 1 < len(file)
57+
let [i, line] = [i + 1, file[i + 1]]
58+
if line =~# '^\(#\|\s*$\)'
59+
" Comment, or blank line before snippets
60+
elseif line =~# '^extends\s\+\S'
61+
let filetypes = map(split(line[7:], ','), 'trim(v:val)')
62+
let source += flatten(s:find(filetypes, a:bufnr))
63+
elseif line =~# '^snippet\s\+\S' && i + 1 < len(file)
64+
let matched = matchlist(line, '^snippet\s\+\(\S\+\)\s*\(.*\)')
65+
let [prefix, description] = [matched[1], matched[2]]
66+
let body = []
67+
let indent = matchstr(file[i + 1], '^\s\+')
68+
while i + 1 < len(file) && file[i + 1] =~# '^\(' . indent . '\|\s*$\)'
69+
let [i, line] = [i + 1, file[i + 1]]
70+
call add(body, line[strlen(indent):])
71+
endwhile
72+
let [prefixes, prefixes_alias] = vsnip#source#resolve_prefix(prefix)
73+
call add(source, {
74+
\ 'label': prefix,
75+
\ 'prefix': prefixes,
76+
\ 'prefix_alias': prefixes_alias,
77+
\ 'body': body,
78+
\ 'description': description
79+
\ })
80+
else
81+
echohl ErrorMsg
82+
echomsg printf('[vsnip] Parsing error occurred on: %s#L%s', a:path, i + 1)
83+
echohl None
84+
break
85+
endif
86+
endwhile
87+
return sort(source, { a, b -> strlen(b.prefix[0]) - strlen(a.prefix[0]) })
88+
endfunction

doc/vsnip.txt

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,15 @@ COMMAND *vsnip-command*
145145

146146
VsnipOpen~
147147

148-
>
149-
:VsnipOpen
150-
:VsnipOpenEdit
151-
:VsnipOpenSplit
152-
:VsnipOpenVsplit
153-
<
148+
149+
:VsnipOpen [-format {type}]
150+
:VsnipOpenEdit [-format {type}]
151+
:VsnipOpenSplit [-format {type}]
152+
:VsnipOpenVsplit [-format {type}]
153+
154154

155155
Open snippet source file under `g:vsnip_snippet_dir`.
156+
{type} is either 'snipmate' or 'vscode'. If omitted, it is 'vscode'.
156157

157158

158159
VsnipYank~
@@ -242,6 +243,44 @@ ${VIM:...Vim script expression...}~
242243

243244

244245

246+
==============================================================================
247+
SNIPMATE SUPPORT *vsnip-snipmate-support*
248+
249+
Files with the extension 'snippets' in directories `g:vsnip_snippet_dir` or
250+
`g:vsnip_snippet_dirs` are recognized as snippets with SnipMate-like syntax.
251+
252+
NOTE: This feature does not guarantee that SnipMate's snippet collection can
253+
be read in its entirety. It is intended to provide an easy way for users to
254+
write their own new snippet definitions.
255+
256+
The following two examples are equivalent.
257+
In SnipMate format. >
258+
snippet fn vim's function
259+
function! $1($2) abort
260+
$0
261+
endfunction
262+
<
263+
In VSCode format. >
264+
{
265+
"fn": {
266+
"prefix": "fn",
267+
"body": [
268+
"function! $1($2) abort",
269+
"\t$0",
270+
"endfunction"
271+
],
272+
"description": "vim's function"
273+
}
274+
}
275+
<
276+
You can also use the extends syntax. For example, the first line of
277+
cpp.snippets should have this. >
278+
extends c
279+
<
280+
281+
282+
283+
245284
==============================================================================
246285
LIMITATION *vsnip-limitation*
247286

ftplugin/snippets.vim

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
" MIT License
2+
"
3+
" Copyright 2009-2010 Michael Sanders. All rights reserved.
4+
5+
" Permission is hereby granted, free of charge, to any person obtaining a copy
6+
" of this software and associated documentation files (the "Software"), to deal
7+
" in the Software without restriction, including without limitation the rights
8+
" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
" copies of the Software, and to permit persons to whom the Software is
10+
" furnished to do so, subject to the following conditions:
11+
12+
" The above copyright notice and this permission notice shall be included in all
13+
" copies or substantial portions of the Software.
14+
15+
" The software is provided "as is", without warranty of any kind, express or
16+
" implied, including but not limited to the warranties of merchantability,
17+
" fitness for a particular purpose and noninfringement. In no event shall the
18+
" authors or copyright holders be liable for any claim, damages or other
19+
" liability, whether in an action of contract, tort or otherwise, arising from,
20+
" out of or in connection with the software or the use or other dealings in the
21+
" software." From https://github.com/garbas/vim-snipmate
22+
23+
24+
" Vim filetype plugin for SnipMate snippets (.snippets and .snippet files)
25+
26+
if exists("b:did_ftplugin")
27+
finish
28+
endif
29+
let b:did_ftplugin = 1
30+
31+
let b:undo_ftplugin = "setl et< sts< cms< fdm< fde<"
32+
33+
" Use hard tabs
34+
setlocal noexpandtab softtabstop=0
35+
36+
setlocal foldmethod=expr foldexpr=getline(v:lnum)!~'^\\t\\\\|^$'?'>1':1
37+
38+
setlocal commentstring=#\ %s
39+
setlocal nospell

indent/snippets.vim

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
" MIT License
2+
"
3+
" Copyright 2009-2010 Michael Sanders. All rights reserved.
4+
5+
" Permission is hereby granted, free of charge, to any person obtaining a copy
6+
" of this software and associated documentation files (the "Software"), to deal
7+
" in the Software without restriction, including without limitation the rights
8+
" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
" copies of the Software, and to permit persons to whom the Software is
10+
" furnished to do so, subject to the following conditions:
11+
12+
" The above copyright notice and this permission notice shall be included in all
13+
" copies or substantial portions of the Software.
14+
15+
" The software is provided "as is", without warranty of any kind, express or
16+
" implied, including but not limited to the warranties of merchantability,
17+
" fitness for a particular purpose and noninfringement. In no event shall the
18+
" authors or copyright holders be liable for any claim, damages or other
19+
" liability, whether in an action of contract, tort or otherwise, arising from,
20+
" out of or in connection with the software or the use or other dealings in the
21+
" software." From https://github.com/garbas/vim-snipmate
22+
23+
24+
" Simple indent support for SnipMate snippets files
25+
26+
if exists('b:did_indent')
27+
finish
28+
endif
29+
let b:did_indent = 1
30+
31+
setlocal nosmartindent
32+
setlocal indentkeys=!^F,o,O,=snippet,=extends
33+
setlocal indentexpr=GetSnippetIndent()
34+
35+
if exists("*GetSnippetIndent")
36+
finish
37+
endif
38+
39+
function! GetSnippetIndent()
40+
let line = getline(v:lnum)
41+
let prev_lnum = v:lnum - 1
42+
let prev_line = prev_lnum != 0 ? getline(prev_lnum) : ""
43+
44+
if line =~# '\v^(snippet|extends) '
45+
return 0
46+
elseif indent(v:lnum) > 0
47+
return indent(v:lnum)
48+
elseif prev_line =~# '^snippet '
49+
return &sw
50+
elseif indent(prev_lnum) > 0
51+
return indent(prev_lnum)
52+
endif
53+
54+
return 0
55+
endfunction

misc/snipmate.snippets

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
snippet arrow-function
2+
() =>
3+
snippet fn vim's function
4+
function! $1($2) abort
5+
$0
6+
endfunction

plugin/vsnip.vim

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ augroup END
2828
"
2929
" command
3030
"
31-
command! -bang VsnipOpen call s:open_command(<bang>0, 'vsplit')
32-
command! -bang VsnipOpenEdit call s:open_command(<bang>0, 'edit')
33-
command! -bang VsnipOpenVsplit call s:open_command(<bang>0, 'vsplit')
34-
command! -bang VsnipOpenSplit call s:open_command(<bang>0, 'split')
35-
function! s:open_command(bang, cmd)
31+
command! -nargs=* -bang VsnipOpen call s:open_command(<bang>0, 'vsplit', <q-args>)
32+
command! -nargs=* -bang VsnipOpenEdit call s:open_command(<bang>0, 'edit', <q-args>)
33+
command! -nargs=* -bang VsnipOpenVsplit call s:open_command(<bang>0, 'vsplit', <q-args>)
34+
command! -nargs=* -bang VsnipOpenSplit call s:open_command(<bang>0, 'split', <q-args>)
35+
function! s:open_command(bang, cmd, arg)
3636
let l:candidates = vsnip#source#filetypes(bufnr('%'))
3737
if a:bang
3838
let l:idx = 1
@@ -53,9 +53,12 @@ function! s:open_command(bang, cmd)
5353
endif
5454
endif
5555

56-
execute printf('%s %s', a:cmd, fnameescape(printf('%s/%s.json',
56+
let l:ext = a:arg =~# '-format\s\+snipmate' ? 'snippets' : 'json'
57+
58+
execute printf('%s %s', a:cmd, fnameescape(printf('%s/%s.%s',
5759
\ resolve(l:expanded_dir),
58-
\ l:candidates[l:idx - 1]
60+
\ l:candidates[l:idx - 1],
61+
\ l:ext
5962
\ )))
6063
endfunction
6164

@@ -190,6 +193,7 @@ augroup vsnip
190193
autocmd InsertLeave * call s:on_insert_leave()
191194
autocmd TextChanged,TextChangedI,TextChangedP * call s:on_text_changed()
192195
autocmd BufWritePost * call s:on_buf_write_post()
196+
autocmd BufRead,BufNewFile *.snippets setlocal filetype=snippets
193197
augroup END
194198

195199
"

spec/autoload/vsnip/source.vimspec

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,41 @@ Describe vsnip#source
5757

5858
End
5959

60+
Describe #snipmate
61+
62+
It should load snippets
63+
enew!
64+
set filetype=snipmate
65+
let l:snippets = vsnip#source#find(bufnr('%'))
66+
call s:expect(l:snippets[0]).to_have_length(2)
67+
End
68+
69+
It should format snippets
70+
enew!
71+
set filetype=snipmate
72+
let l:snippets = vsnip#source#find(bufnr('%'))
73+
call s:expect(sort(l:snippets[0])).to_equal(sort([{
74+
\ 'label': 'arrow-function',
75+
\ 'description': '',
76+
\ 'prefix': ['arrow-function'],
77+
\ 'prefix_alias': ['af'],
78+
\ 'body': [
79+
\ "() => "
80+
\ ]
81+
\ }, {
82+
\ 'label': 'fn',
83+
\ 'description': "vim's function",
84+
\ 'prefix': ['fn'],
85+
\ 'prefix_alias': [],
86+
\ 'body': [
87+
\ "function! $1($2) abort",
88+
\ "\t$0",
89+
\ "endfunction",
90+
\ ]
91+
\ }]))
92+
End
93+
94+
End
95+
6096
End
6197

0 commit comments

Comments
 (0)