From 32ddc125ec6f5a07fe59b3ab6b4b5f50093102ee Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sat, 16 Oct 2021 11:15:35 +0100 Subject: [PATCH 01/32] Nvim 0.6: fix snippet detection in start packages (#224) Nvim 0.6 no longer adds start packages to &rtp. This means that, for example, simply installing friendly-snippets as a start package no longer works. It'll instead need to be installed as an opt package and packadded so it appears in &rtp for vim-vsnip to detect it (some package managers may do this indirectly). The recommended approach is to use nvim_list_runtime_paths instead; use it if it's available. --- autoload/vsnip/source/vscode.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autoload/vsnip/source/vscode.vim b/autoload/vsnip/source/vscode.vim index 87b38a2..e673e41 100644 --- a/autoload/vsnip/source/vscode.vim +++ b/autoload/vsnip/source/vscode.vim @@ -28,7 +28,8 @@ endfunction " function! s:find(languages) abort " Load `package.json#contributes.snippets` if does not exists it's cache. - for l:rtp in split(&runtimepath, ',') + let l:rtp_list = exists('*nvim_list_runtime_paths') ? nvim_list_runtime_paths() : split(&runtimepath, ',') + for l:rtp in l:rtp_list if has_key(s:runtimepaths, l:rtp) continue endif From 4d57a1f4efce38e0d05196f9beba4bcc6e9d1ed5 Mon Sep 17 00:00:00 2001 From: Nikita Ivanchenko <36507839+Nivanchenko@users.noreply.github.com> Date: Tue, 2 Nov 2021 17:37:44 +0300 Subject: [PATCH 02/32] Fix #226 (#228) Fix https://github.com/hrsh7th/vim-vsnip/issues/226 explanation https://renenyffenegger.ch/notes/development/vim/script/vimscript/functions/strlen strlen returns the number of bytes in a string, not the length of the string in characters Use strchars to get the number character --- autoload/vsnip/session.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index 2ba939b..c74442c 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -130,7 +130,7 @@ function! s:Session.select(jump_point) abort let l:pos = s:Position.lsp_to_vim('%', a:jump_point.range.end) call cursor([l:pos[0], l:pos[1] - 1]) " Use `a:jump_point.range.end as inclusive position - let l:select_length = strlen(a:jump_point.placeholder.text()) - 1 + let l:select_length = strchars(a:jump_point.placeholder.text()) - 1 let l:cmd = '' let l:cmd .= mode()[0] ==# 'i' ? "\l" : '' let l:cmd .= printf('v%s', l:select_length > 0 ? l:select_length . 'h' : '') @@ -153,7 +153,7 @@ function! s:Session.move(jump_point) abort call cursor(l:pos) - if l:pos[1] > strlen(getline(l:pos[0])) + if l:pos[1] > strchars(getline(l:pos[0])) startinsert! else startinsert From 60ee20318550f4a5b6f7a5a8b827540c2c386898 Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Mon, 15 Nov 2021 12:58:30 +0900 Subject: [PATCH 03/32] Does not remove the final new line's indent --- autoload/vsnip/indent.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/vsnip/indent.vim b/autoload/vsnip/indent.vim index cf2162a..6222dd8 100644 --- a/autoload/vsnip/indent.vim +++ b/autoload/vsnip/indent.vim @@ -25,7 +25,7 @@ function! vsnip#indent#adjust_snippet_body(line, text) abort endwhile endif let l:text = substitute(l:text, "\n\\zs", l:base_indent, 'g') " add base_indent for all lines - let l:text = substitute(l:text, "\n\\s*\\ze\\(\n\\|$\\)", "\n", 'g') " remove empty line's indent + let l:text = substitute(l:text, "\n\\s*\\ze\n", "\n", 'g') " remove empty line's indent return l:text endfunction From 30449e9c19c73f64f4c87c29c426318f5519c314 Mon Sep 17 00:00:00 2001 From: Munif Tanjim Date: Mon, 22 Nov 2021 14:51:19 +0600 Subject: [PATCH 04/32] Support whole word transform on tabstop and variable (#232) * Support whole word transform: capitalize, downcase, upcase * Support additional text with whole word transform * Update parser to support format: camelcase, pascalcase * Refactor node/transform formatters * Support whole word transform: camelcase, pascalcase --- autoload/vsnip/snippet.vim | 2 +- autoload/vsnip/snippet/node.vim | 8 ++ autoload/vsnip/snippet/node/placeholder.vim | 1 + autoload/vsnip/snippet/node/transform.vim | 100 ++++++++++++++++++++ autoload/vsnip/snippet/node/variable.vim | 5 +- autoload/vsnip/snippet/parser.vim | 2 + spec/autoload/vsnip/snippet.vimspec | 61 ++++++++++++ 7 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 autoload/vsnip/snippet/node/transform.vim diff --git a/autoload/vsnip/snippet.vim b/autoload/vsnip/snippet.vim index 5f38ffd..fc8a0f5 100644 --- a/autoload/vsnip/snippet.vim +++ b/autoload/vsnip/snippet.vim @@ -200,7 +200,7 @@ function! s:Snippet.sync() abort call add(self.targets, { \ 'range': a:context.range, \ 'node': a:context.node, - \ 'new_text': self.new_texts[a:context.node.id], + \ 'new_text': a:context.node.transform.text(self.new_texts[a:context.node.id]), \ }) endif endif diff --git a/autoload/vsnip/snippet/node.vim b/autoload/vsnip/snippet/node.vim index 57f5049..83f9c14 100644 --- a/autoload/vsnip/snippet/node.vim +++ b/autoload/vsnip/snippet/node.vim @@ -1,6 +1,7 @@ let s:Placeholder = vsnip#snippet#node#placeholder#import() let s:Variable = vsnip#snippet#node#variable#import() let s:Text = vsnip#snippet#node#text#import() +let s:Transform = vsnip#snippet#node#transform#import() " " vsnip#snippet#node#create_from_ast @@ -33,3 +34,10 @@ function! vsnip#snippet#node#create_text(text) abort \ 'escaped': a:text \ }) endfunction + +" +" vsnip#snippet#node#create_transform +" +function! vsnip#snippet#node#create_transform(transform) abort + return s:Transform.new(a:transform) +endfunction diff --git a/autoload/vsnip/snippet/node/placeholder.vim b/autoload/vsnip/snippet/node/placeholder.vim index 2cc1c0d..db05209 100644 --- a/autoload/vsnip/snippet/node/placeholder.vim +++ b/autoload/vsnip/snippet/node/placeholder.vim @@ -21,6 +21,7 @@ function! s:Placeholder.new(ast) abort \ 'follower': v:false, \ 'choice': get(a:ast, 'choice', []), \ 'children': vsnip#snippet#node#create_from_ast(get(a:ast, 'children', [])), + \ 'transform': vsnip#snippet#node#create_transform(get(a:ast, 'transform')), \ }) if l:node.is_final diff --git a/autoload/vsnip/snippet/node/transform.vim b/autoload/vsnip/snippet/node/transform.vim new file mode 100644 index 0000000..ae53cf7 --- /dev/null +++ b/autoload/vsnip/snippet/node/transform.vim @@ -0,0 +1,100 @@ +function! vsnip#snippet#node#transform#import() abort + return s:Transform +endfunction + +let s:Transform = {} + +function! s:upcase(word) + let word = toupper(a:word) + return word +endfunction + +function! s:downcase(word) + let word = tolower(a:word) + return word +endfunction + +function! s:capitalize(word) + let word = s:upcase(strpart(a:word, 0, 1)) . strpart(a:word, 1) + return word +endfunction + +" https://github.com/tpope/vim-abolish/blob/3f0c8faa/plugin/abolish.vim#L111-L118 +function! s:camelcase(word) + let word = substitute(a:word, '-', '_', 'g') + if word !~# '_' && word =~# '\l' + return substitute(word,'^.','\l&','') + else + return substitute(word,'\C\(_\)\=\(.\)','\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g') + endif +endfunction + +" +" new. +" +function! s:Transform.new(ast) abort + let l:transform = empty(a:ast) ? {} : a:ast + + let l:node = extend(deepcopy(s:Transform), { + \ 'type': 'transform', + \ 'regex': get(l:transform, 'regex', v:null), + \ 'replacements': get(l:transform, 'format', []), + \ 'options': get(l:transform, 'option', []), + \ }) + + let l:node.is_noop = l:node.regex is v:null + + return l:node +endfunction + +" +" text. +" +function! s:Transform.text(input_text) abort + if empty(a:input_text) || self.is_noop + return a:input_text + endif + + if self.regex.pattern !=# '(.*)' + " TODO: fully support regex + return a:input_text + endif + + let l:text = '' + + for l:replacement in self.replacements + if l:replacement.type ==# 'format' + if l:replacement.modifier ==# '/capitalize' + let l:text .= s:capitalize(a:input_text) + elseif l:replacement.modifier ==# '/downcase' + let l:text .= s:downcase(a:input_text) + elseif l:replacement.modifier ==# '/upcase' + let l:text .= s:upcase(a:input_text) + elseif l:replacement.modifier ==# '/camelcase' + let l:text .= s:camelcase(a:input_text) + elseif l:replacement.modifier ==# '/pascalcase' + let l:text .= s:capitalize(s:camelcase(a:input_text)) + endif + elseif l:replacement.type ==# 'text' + let l:text .= l:replacement.escaped + endif + endfor + + return l:text +endfunction + +" +" to_string +" +function! s:Transform.to_string() abort + if self.is_noop + return + end + + return printf('%s(regex=%s, total_replacements=%s, options=%s)', + \ self.type, + \ get(self.regex, 'pattern', ''), + \ len(self.replacements), + \ join(self.options, ''), + \ ) +endfunction diff --git a/autoload/vsnip/snippet/node/variable.vim b/autoload/vsnip/snippet/node/variable.vim index 9b2ba21..4de1e48 100644 --- a/autoload/vsnip/snippet/node/variable.vim +++ b/autoload/vsnip/snippet/node/variable.vim @@ -23,6 +23,7 @@ function! s:Variable.new(ast) abort \ 'unknown': empty(l:resolver), \ 'resolver': l:resolver, \ 'children': vsnip#snippet#node#create_from_ast(get(a:ast, 'children', [])), + \ 'transform': vsnip#snippet#node#create_transform(get(a:ast, 'transform')), \ }) endfunction @@ -30,7 +31,7 @@ endfunction " text. " function! s:Variable.text() abort - return join(map(copy(self.children), 'v:val.text()'), '') + return self.transform.text(join(map(copy(self.children), 'v:val.text()'), '')) endfunction " @@ -38,7 +39,7 @@ endfunction " function! s:Variable.resolve(context) abort if !self.unknown - let l:resolved = self.resolver.func({ 'node': self }) + let l:resolved = self.transform.text(self.resolver.func({ 'node': self })) if l:resolved isnot v:null " Fix indent when one variable returns multiple lines let l:base_indent = vsnip#indent#get_base_indent(split(a:context.before_text, "\n", v:true)[-1]) diff --git a/autoload/vsnip/snippet/parser.vim b/autoload/vsnip/snippet/parser.vim index 4808a39..ddc6010 100644 --- a/autoload/vsnip/snippet/parser.vim +++ b/autoload/vsnip/snippet/parser.vim @@ -82,6 +82,8 @@ let s:format3 = s:map( \ s:token('/upcase'), \ s:token('/downcase'), \ s:token('/capitalize'), +\ s:token('/camelcase'), +\ s:token('/pascalcase'), \ s:token('+if'), \ s:token('?if:else'), \ s:token('-else'), diff --git a/spec/autoload/vsnip/snippet.vimspec b/spec/autoload/vsnip/snippet.vimspec index 31126a3..fa5df1d 100644 --- a/spec/autoload/vsnip/snippet.vimspec +++ b/spec/autoload/vsnip/snippet.vimspec @@ -31,6 +31,11 @@ Describe vsnip#snippet let l:snippet = s:Snippet.new(s:start_position, 'console.log(${CURRENT_YEAR})') call s:expect(l:snippet.text()).to_equal('console.log(' . strftime('%Y') . ')') End + + It should support whole word transform + let l:snippet = s:Snippet.new(s:start_position, '${1:state}, set${1/(.*)/${1:/capitalize}/}') + call s:expect(l:snippet.text()).to_equal('state, setState') + End End Describe #sync @@ -259,6 +264,62 @@ Describe vsnip#snippet call s:expect(l:snippet.text()).to_equal('default') End + It should support whole word transform (upcase) on tabstop + let l:snippet = s:Snippet.new(s:start_position, '${1:varName}, ${1/(.*)/${1:/upcase}/}') + call s:expect(l:snippet.text()).to_equal('varName, VARNAME') + End + + It should support whole word transform (downcase) on variable + call vsnip#selected_text('varName') + let l:snippet = s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/${1:/downcase}/}') + call s:expect(l:snippet.text()).to_equal('varname') + End + + It should support whole word transform (capitalize) + call vsnip#selected_text('varName') + let l:snippet = s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/${1:/capitalize}/}') + call s:expect(l:snippet.text()).to_equal('VarName') + End + + It should support whole word transform (camelcase) + call vsnip#selected_text('var_name') + call s:expect( + \ s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/${1:/camelcase}/}').text() + \ ).to_equal('varName') + + call vsnip#selected_text('VAR_NAME') + call s:expect( + \ s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/${1:/camelcase}/}').text() + \ ).to_equal('varName') + + call vsnip#selected_text('VarName') + call s:expect( + \ s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/${1:/camelcase}/}').text() + \ ).to_equal('varName') + End + + It should support whole word transform (pascalcase) + call vsnip#selected_text('var_name') + call s:expect( + \ s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/${1:/pascalcase}/}').text() + \ ).to_equal('VarName') + + call vsnip#selected_text('VAR_NAME') + call s:expect( + \ s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/${1:/pascalcase}/}').text() + \ ).to_equal('VarName') + + call vsnip#selected_text('varName') + call s:expect( + \ s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/${1:/pascalcase}/}').text() + \ ).to_equal('VarName') + End + + It should support whole word transform with additional text + call vsnip#selected_text('varName') + let l:snippet = s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT/(.*)/start-lowercase-${1:/downcase}/}-end') + call s:expect(l:snippet.text()).to_equal('start-lowercase-varname-end') + End End Describe #range From 39e41dca42c802ae655855dc816001ead8c0267b Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Thu, 16 Dec 2021 17:26:59 +0900 Subject: [PATCH 05/32] Update undo history when nested expansion --- autoload/vsnip/session.vim | 6 ++- autoload/vsnip/snippet/node/transform.vim | 62 ++++++++++++++--------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index c74442c..b5c700d 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -47,10 +47,14 @@ endfunction " merge. " function! s:Session.merge(session) abort + call s:TextEdit.apply(self.bufnr, self.snippet.sync()) + call self.store(self.changenr) + call a:session.expand() call self.snippet.merge(self.tabstop, a:session.snippet) call self.snippet.insert(deepcopy(a:session.snippet.position), a:session.snippet.children) call s:TextEdit.apply(self.bufnr, self.snippet.sync()) + call self.store(changenr()) endfunction " @@ -188,7 +192,6 @@ function! s:Session.on_text_changed() abort " save state. if self.changenr != l:changenr call self.store(self.changenr) - let self.changenr = l:changenr if has_key(self.changenrs, l:changenr) let self.tabstop = self.changenrs[l:changenr].tabstop let self.snippet = self.changenrs[l:changenr].snippet @@ -249,5 +252,6 @@ function! s:Session.store(changenr) abort \ 'tabstop': self.tabstop, \ 'snippet': deepcopy(self.snippet) \ } + let self.changenr = a:changenr endfunction diff --git a/autoload/vsnip/snippet/node/transform.vim b/autoload/vsnip/snippet/node/transform.vim index ae53cf7..f241fc8 100644 --- a/autoload/vsnip/snippet/node/transform.vim +++ b/autoload/vsnip/snippet/node/transform.vim @@ -4,31 +4,6 @@ endfunction let s:Transform = {} -function! s:upcase(word) - let word = toupper(a:word) - return word -endfunction - -function! s:downcase(word) - let word = tolower(a:word) - return word -endfunction - -function! s:capitalize(word) - let word = s:upcase(strpart(a:word, 0, 1)) . strpart(a:word, 1) - return word -endfunction - -" https://github.com/tpope/vim-abolish/blob/3f0c8faa/plugin/abolish.vim#L111-L118 -function! s:camelcase(word) - let word = substitute(a:word, '-', '_', 'g') - if word !~# '_' && word =~# '\l' - return substitute(word,'^.','\l&','') - else - return substitute(word,'\C\(_\)\=\(.\)','\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g') - endif -endfunction - " " new. " @@ -98,3 +73,40 @@ function! s:Transform.to_string() abort \ join(self.options, ''), \ ) endfunction + +" +" upcase +" +function! s:upcase(word) abort + let word = toupper(a:word) + return word +endfunction + +" +" downcase +" +function! s:downcase(word) abort + let word = tolower(a:word) + return word +endfunction + +" +" capitalize +" +function! s:capitalize(word) abort + let word = s:upcase(strpart(a:word, 0, 1)) . strpart(a:word, 1) + return word +endfunction + +" +" camelcase +" @see https://github.com/tpope/vim-abolish/blob/3f0c8faa/plugin/abolish.vim#L111-L118 +" +function! s:camelcase(word) abort + let word = substitute(a:word, '-', '_', 'g') + if word !~# '_' && word =~# '\l' + return substitute(word,'^.','\l&','') + else + return substitute(word,'\C\(_\)\=\(.\)','\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g') + endif +endfunction From 805a39af7035217942a9479886a29cd86c66cd42 Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Thu, 16 Dec 2021 18:36:41 +0900 Subject: [PATCH 06/32] Strict deactivation --- autoload/vsnip/session.vim | 20 ++++++++++++++++++-- plugin/vsnip.vim | 11 +++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index b5c700d..c719850 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -173,14 +173,30 @@ function! s:Session.refresh() abort endfunction " -" on_insert_leave. +" on_insert_leave " function! s:Session.on_insert_leave() abort call self.flush_changes() endfunction " -" on_text_changed. +" on_insert_char_pre +" +function! s:Session.on_insert_char_pre(char) abort + if a:char =~# '^[:print:]\+$' + let l:range = self.snippet.range() + let l:position = s:Position.cursor() + let l:is_out_of_range = v:false + let l:is_out_of_range = l:is_out_of_range || l:position.line < l:range.start.line || (l:position.line == l:range.start.line && l:position.character < l:range.start.character) + let l:is_out_of_range = l:is_out_of_range || l:position.line > l:range.end.line || (l:position.line == l:range.end.line && l:position.character > l:range.end.character) + if l:is_out_of_range + call vsnip#deactivate() + endif + endif +endfunction + +" +" on_text_changed " function! s:Session.on_text_changed() abort if self.bufnr != bufnr('%') diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index 5b1f5be..b6de71e 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -186,6 +186,7 @@ endfunction augroup vsnip autocmd! autocmd InsertLeave * call s:on_insert_leave() + autocmd InsertCharPre * call s:on_insert_char_pre() autocmd TextChanged,TextChangedI,TextChangedP * call s:on_text_changed() autocmd BufWritePost * call s:on_buf_write_post() augroup END @@ -200,6 +201,16 @@ function! s:on_insert_leave() abort endif endfunction +" +" on_insert_char_pre +" +function! s:on_insert_char_pre() abort + let l:session = vsnip#get_session() + if !empty(l:session) + call l:session.on_insert_char_pre(v:char) + endif +endfunction + " " on_text_changed " From b7e48ef0a4a1b927919795c15131631b8bd58d55 Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Wed, 5 Jan 2022 23:34:14 +0900 Subject: [PATCH 07/32] Fix #239 --- autoload/vsnip/session.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index c719850..a81c265 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -132,9 +132,9 @@ endfunction " function! s:Session.select(jump_point) abort let l:pos = s:Position.lsp_to_vim('%', a:jump_point.range.end) - call cursor([l:pos[0], l:pos[1] - 1]) " Use `a:jump_point.range.end as inclusive position + call cursor([l:pos[0], l:pos[1] - (&selection !~# 'exclusive')]) - let l:select_length = strchars(a:jump_point.placeholder.text()) - 1 + let l:select_length = strchars(a:jump_point.placeholder.text()) - (&selection !~# 'exclusive') let l:cmd = '' let l:cmd .= mode()[0] ==# 'i' ? "\l" : '' let l:cmd .= printf('v%s', l:select_length > 0 ? l:select_length . 'h' : '') From 7fde9c0b6878a62bcc6d2d29f9a85a6616032f02 Mon Sep 17 00:00:00 2001 From: Haruki Matsui <63794197+matsui54@users.noreply.github.com> Date: Thu, 6 Jan 2022 23:01:27 +0900 Subject: [PATCH 08/32] Resend #237 (#240) * Use strlen() instead of strchars() to avoid multibyte bug * Add multibyte tests --- autoload/vsnip/session.vim | 2 +- misc/integration.json | 14 ++++++++++++++ spec/plugin/vsnip.vimspec | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index a81c265..b82cd1e 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -157,7 +157,7 @@ function! s:Session.move(jump_point) abort call cursor(l:pos) - if l:pos[1] > strchars(getline(l:pos[0])) + if l:pos[1] > strlen(getline(l:pos[0])) startinsert! else startinsert diff --git a/misc/integration.json b/misc/integration.json index 75e454d..c01865d 100644 --- a/misc/integration.json +++ b/misc/integration.json @@ -69,6 +69,20 @@ "$1snip${2:pet}" ] }, + "multi1": { + "description": "jump at middle of snippet", + "prefix": ["マルチ1"], + "body": [ + "あ$1い$2う" + ] + }, + "multi2": { + "description": "select 4 length middle of snippet text", + "prefix": ["マルチ2"], + "body": [ + "あ$1い${2:かkaか}う" + ] + }, "realworld1": { "description": "Complex example", "prefix": ["realworld1"], diff --git a/spec/plugin/vsnip.vimspec b/spec/plugin/vsnip.vimspec index dd212ae..b3eef7e 100644 --- a/spec/plugin/vsnip.vimspec +++ b/spec/plugin/vsnip.vimspec @@ -218,6 +218,40 @@ Describe vsnip End + Context multiByte + + It should jump to middle of snippet + enew! + set filetype=integration + call setline(1, 'マルチ1') + call cursor([1, 10]) + + let g:vsnip_assert = {} + function g:vsnip_assert.step1() + call s:expect(getcurpos()[1 : 2]).to_equal([1, 7]) + call s:expect(getbufline('%', '^', '$')).to_equal(['あいう']) + endfunction + call feedkeys("a\\\(vsnip-assert)", 'x') + End + + It should select 4 length middle of snippet text + enew! + set filetype=integration + call setline(1, 'マルチ2') + call cursor([1, 10]) + + let g:vsnip_assert = {} + function g:vsnip_assert.step1() + call s:expect(mode(1)).to_equal('s') + call s:expect(getpos("'<")[1 : 2]).to_equal([1, 7]) + call s:expect(getpos("'>")[1 : 2]).to_equal([1, 12]) + call s:expect(getbufline('%', '^', '$')).to_equal(['あいかkaかう']) + endfunction + call feedkeys("a\\\(vsnip-assert)", 'x') + End + + End + Context realworld It should work (complex example) From 70a1131d64d75150ece513b983b0f42939bcb03c Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Sat, 26 Feb 2022 15:50:17 +0900 Subject: [PATCH 09/32] Add g:vsnip_deactivate_on --- .themisrc | 4 +- autoload/vsnip.vim | 4 ++ autoload/vsnip/range.vim | 10 +++++ autoload/vsnip/snippet.vim | 41 +++++++++++++++++--- doc/vsnip.txt | 40 +++++++------------ plugin/vsnip.vim | 1 + spec/plugin/vsnip.vimspec | 79 +++++++++++++++++++++++++++++++++++++- 7 files changed, 146 insertions(+), 33 deletions(-) create mode 100644 autoload/vsnip/range.vim diff --git a/.themisrc b/.themisrc index 66f6134..9d12204 100644 --- a/.themisrc +++ b/.themisrc @@ -11,10 +11,12 @@ endif let g:vsnip_test_mode = v:true let g:vsnip_snippet_dir = fnamemodify(expand(''), ':h') . '/misc' -let &runtimepath .= ',' . g:vsnip_snippet_dir . '/source_spec_vscode' +let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfSnippet let g:vsnip_filetypes = {} let g:vsnip_filetypes.source_spec_enhanced = ['source_spec'] +let &runtimepath .= ',' . g:vsnip_snippet_dir . '/source_spec_vscode' + imap vsnip#expandable() ? '(vsnip-expand)' : '' smap vsnip#expandable() ? '(vsnip-expand)' : '' diff --git a/autoload/vsnip.vim b/autoload/vsnip.vim index afdaed9..8069eee 100644 --- a/autoload/vsnip.vim +++ b/autoload/vsnip.vim @@ -6,6 +6,10 @@ let s:Position = vital#vsnip#import('VS.LSP.Position') let s:session = v:null let s:selected_text = '' +let g:vsnip#DeactivateOn = {} +let g:vsnip#DeactivateOn.OutsideOfSnippet = 1 +let g:vsnip#DeactivateOn.OutsideOfCurrentTabstop = 2 + " " vsnip#selected_text. " diff --git a/autoload/vsnip/range.vim b/autoload/vsnip/range.vim new file mode 100644 index 0000000..8ba5321 --- /dev/null +++ b/autoload/vsnip/range.vim @@ -0,0 +1,10 @@ +" +" vsnip#range#cover +" +function! vsnip#range#cover(whole_range, target_range) abort + let l:cover = v:true + let l:cover = l:cover && (a:whole_range.start.line < a:target_range.start.line || a:whole_range.start.line == a:target_range.start.line && a:whole_range.start.character <= a:target_range.start.character) + let l:cover = l:cover && (a:target_range.end.line < a:whole_range.end.line || a:target_range.end.line == a:whole_range.end.line && a:target_range.end.character <= a:whole_range.end.character) + return l:cover +endfunction + diff --git a/autoload/vsnip/snippet.vim b/autoload/vsnip/snippet.vim index fc8a0f5..292d473 100644 --- a/autoload/vsnip/snippet.vim +++ b/autoload/vsnip/snippet.vim @@ -95,11 +95,7 @@ endfunction " follow. " function! s:Snippet.follow(current_tabstop, diff) abort - let l:range = self.range() - let l:in_range = v:true - let l:in_range = l:in_range && (l:range.start.line < a:diff.range.start.line || l:range.start.line == a:diff.range.start.line && l:range.start.character <= a:diff.range.start.character) - let l:in_range = l:in_range && (a:diff.range.end.line < l:range.end.line || a:diff.range.end.line == l:range.end.line && a:diff.range.end.character <= l:range.end.character) - if !l:in_range + if !self.is_followable(a:current_tabstop, a:diff) return v:false endif @@ -246,6 +242,24 @@ function! s:Snippet.text() abort return join(map(copy(self.children), 'v:val.text()'), '') endfunction +" +" is_followable. +" +function! s:Snippet.is_followable(current_tabstop, diff) abort + if g:vsnip#DeactivateOn.OutsideOfSnippet == g:vsnip_deactivate_on + return vsnip#range#cover(self.range(), a:diff.range) + elseif g:vsnip#DeactivateOn.OutsideOfCurrentTabstop == g:vsnip_deactivate_on + let l:context = self.get_placeholder_context_by_tabstop(a:current_tabstop) + if empty(l:context) + return v:false + endif + return vsnip#range#cover({ + \ 'start': self.offset_to_position(l:context.range[0]), + \ 'end': self.offset_to_position(l:context.range[1]), + \ }, a:diff.range) + endif +endfunction + " " get_placeholder_nodes " @@ -262,6 +276,23 @@ function! s:Snippet.get_placeholder_nodes() abort return sort(l:fn.nodes, { a, b -> a.id - b.id }) endfunction +" +" get_placeholder_context_by_tabstop +" +function! s:Snippet.get_placeholder_context_by_tabstop(current_tabstop) abort + let l:fn = {} + let l:fn.current_tabstop = a:current_tabstop + let l:fn.context = v:null + function! l:fn.traverse(context) abort + if a:context.node.type ==# 'placeholder' && a:context.node.id == self.current_tabstop + let self.context = a:context + return v:true + endif + endfunction + call self.traverse(self, l:fn.traverse) + return l:fn.context +endfunction + " " get_next_jump_point. " diff --git a/doc/vsnip.txt b/doc/vsnip.txt index b2ed41e..392382a 100644 --- a/doc/vsnip.txt +++ b/doc/vsnip.txt @@ -53,57 +53,45 @@ If you want to know supported plugins, you can see https://github.com/hrsh7th/vi ============================================================================== VARIABLE *vsnip-variable* -> - let g:vsnip_extra_mapping = v:true -< + let g:vsnip_extra_mapping = v:true~ Enable or disable extra mappings. - -> - let g:vsnip_snippet_dir = expand('~/.vsnip') -< + let g:vsnip_snippet_dir = expand('~/.vsnip')~ Specify user snippet directory. Also as buffer-local variable: `b:vsnip_snippet_dir` - -> - let g:vsnip_snippet_dirs = [] -< + let g:vsnip_snippet_dirs = []~ List of user snippet directories. Also as buffer-local variable: `b:vsnip_snippet_dirs` - -> - let g:vsnip_filetypes = {} -< + let g:vsnip_filetypes = {}~ Specify extended filetypes. For example, you can extend `javascript` filetype with `javascriptreact` filetype. > let g:vsnip_filetypes = {} let g:vsnip_filetypes.javascriptreact = ['javascript'] < + let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfSnippet~ + Specify when to deactivate the current snippet. -> - let g:vsnip_sync_delay = 0 -< + `g:vsnip#DeactivateOn.OutsideOfSnippet`: + Deactivate on edit the outside of snippet. + `g:vsnip#DeactivateOn.OutsideOfCurrentTabstop`: + Deactivate on edit the outside of current tabstop. + + let g:vsnip_sync_delay = 0~ Specify delay time to sync same tabstop placeholder. -1: No sync 0: Always sync N: Debounce N milliseconds - -> - let g:vsnip_choice_delay = 500 -< + let g:vsnip_choice_delay = 500~ Specify delay time to show choice candidates. Sometimes choice completion menu is closed by auto-completion engine. You can use this variable to solve this conflict. - -> - let g:vsnip_namespace = '' -< + let g:vsnip_namespace = ''~ Specify all snippet prefix's prefix. It useful when you use auto-completion. diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index b6de71e..d491580 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -7,6 +7,7 @@ let g:loaded_vsnip = 1 " variable " let g:vsnip_extra_mapping = get(g:, 'vsnip_extra_mapping', v:true) +let g:vsnip_deactivate_on = get(g:, 'vsnip_deactivate_on', g:vsnip#DeactivateOn.OutsideOfCurrentTabstop) let g:vsnip_snippet_dir = get(g:, 'vsnip_snippet_dir', expand('~/.vsnip')) let g:vsnip_snippet_dirs = get(g:, 'vsnip_snippet_dirs', []) let g:vsnip_sync_delay = get(g:, 'vsnip_sync_delay', 0) diff --git a/spec/plugin/vsnip.vimspec b/spec/plugin/vsnip.vimspec index b3eef7e..be0e209 100644 --- a/spec/plugin/vsnip.vimspec +++ b/spec/plugin/vsnip.vimspec @@ -218,7 +218,7 @@ Describe vsnip End - Context multiByte + Context multibyte It should jump to middle of snippet enew! @@ -252,6 +252,83 @@ Describe vsnip End + Context g:vsnip_deactivate_on + It should deactivate snippet on edit the outside of snippet + enew! + let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfSnippet + + call setline(1, [ + \ 'outside', + \ 'prefix', + \ 'outside', + \ ]) + call cursor([2, 7]) + call vsnip#anonymous(join([ + \ "function! $1() abort", + \ "\t$0", + \ "endfunction", + \ ], "\n")) + call s:expect(vsnip#get_session()).not.to_be_empty() + + let g:vsnip_assert = {} + let l:sequence = '' + + function g:vsnip_assert.step1() + call s:expect(vsnip#get_session()).not.to_be_empty() + endfunction + let l:sequence .= "ifuncname\(vsnip-assert)" + + function g:vsnip_assert.step2() + call s:expect(vsnip#get_session()).not.to_be_empty() + endfunction + let l:sequence .= "\arg\(vsnip-assert)" + + function g:vsnip_assert.step3() + call s:expect(vsnip#get_session()).to_be_empty() + endfunction + let l:sequence .= "\call cursor(1, 1)\modiied\(vsnip-assert)" + + call feedkeys(l:sequence, 'x') + + let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfSnippet + End + + It should deactivate snippet on edit the outside of current tabstop + enew! + let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfCurrentTabstop + + call setline(1, [ + \ 'outside', + \ 'prefix', + \ 'outside', + \ ]) + call cursor([2, 7]) + call vsnip#anonymous(join([ + \ "function! $1() abort", + \ "\t$0", + \ "endfunction", + \ ], "\n")) + call s:expect(vsnip#get_session()).not.to_be_empty() + + let g:vsnip_assert = {} + let l:sequence = '' + + function g:vsnip_assert.step1() + call s:expect(vsnip#get_session()).not.to_be_empty() + endfunction + let l:sequence .= "ifuncname\(vsnip-assert)" + + function g:vsnip_assert.step2() + call s:expect(vsnip#get_session()).to_be_empty() + endfunction + let l:sequence .= "\arg\(vsnip-assert)" + + call feedkeys(l:sequence, 'x') + + let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfSnippet + End + End + Context realworld It should work (complex example) From 8f199ef690ed26dcbb8973d9a6760d1332449ac9 Mon Sep 17 00:00:00 2001 From: Tim Macfarlane Date: Fri, 22 Apr 2022 14:47:02 +0200 Subject: [PATCH 10/32] RELATIVE_FILEPATH (#248) --- autoload/vsnip/variable.vim | 5 +++++ doc/vsnip.txt | 1 + 2 files changed, 6 insertions(+) diff --git a/autoload/vsnip/variable.vim b/autoload/vsnip/variable.vim index bb7d860..54efd83 100644 --- a/autoload/vsnip/variable.vim +++ b/autoload/vsnip/variable.vim @@ -73,6 +73,11 @@ function! s:TM_FILEPATH(context) abort endfunction call vsnip#variable#register('TM_FILEPATH', function('s:TM_FILEPATH')) +function! s:RELATIVE_FILEPATH(context) abort + return expand('%') +endfunction +call vsnip#variable#register('RELATIVE_FILEPATH', function('s:RELATIVE_FILEPATH')) + function! s:CLIPBOARD(context) abort let l:clipboard = getreg(v:register) if empty(l:clipboard) diff --git a/doc/vsnip.txt b/doc/vsnip.txt index 392382a..0ffc689 100644 --- a/doc/vsnip.txt +++ b/doc/vsnip.txt @@ -179,6 +179,7 @@ The following variables can be used in the same way they are in VSCode: `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 + `RELATIVE_FILEPATH` The relative (to the current working directory) file path of the current document `CLIPBOARD` The contents of your clipboard `WORKSPACE_NAME` The name of the opened workspace or folder From cbfb3a636b8f61c503e39a5c6d4f0e2139b27fc0 Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Fri, 7 Oct 2022 20:11:31 +0900 Subject: [PATCH 11/32] Fix #253 --- autoload/vsnip/snippet.vim | 2 +- doc/vsnip.txt | 3 +++ plugin/vsnip.vim | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/autoload/vsnip/snippet.vim b/autoload/vsnip/snippet.vim index 292d473..114a102 100644 --- a/autoload/vsnip/snippet.vim +++ b/autoload/vsnip/snippet.vim @@ -82,7 +82,7 @@ function! s:Snippet.init() abort call self.traverse(self, l:fn.traverse) " Append ${MAX_TABSTOP} for the end of snippet. - if !l:fn.has_final_tabstop + if !l:fn.has_final_tabstop && g:vsnip_append_final_tabstop let self.children += [vsnip#snippet#node#create_from_ast({ \ 'type': 'placeholder', \ 'id': 0, diff --git a/doc/vsnip.txt b/doc/vsnip.txt index 0ffc689..defe50b 100644 --- a/doc/vsnip.txt +++ b/doc/vsnip.txt @@ -95,6 +95,9 @@ VARIABLE *vsnip-variable* Specify all snippet prefix's prefix. It useful when you use auto-completion. + let g:vsnip_append_final_tabstop = v:true~ + Specify whether to add a final tabstop. + ============================================================================== diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index d491580..00e24c4 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -12,6 +12,7 @@ let g:vsnip_snippet_dir = get(g:, 'vsnip_snippet_dir', expand('~/.vsnip')) let g:vsnip_snippet_dirs = get(g:, 'vsnip_snippet_dirs', []) let g:vsnip_sync_delay = get(g:, 'vsnip_sync_delay', 0) let g:vsnip_choice_delay = get(g:, 'vsnip_choice_delay', 500) +let g:vsnip_append_final_tabstop = get(g:, 'vsnip_append_final_tabstop', v:true) let g:vsnip_namespace = get(g:, 'vsnip_namespace', '') let g:vsnip_filetypes = get(g:, 'vsnip_filetypes', {}) let g:vsnip_filetypes.typescriptreact = get(g:vsnip_filetypes, 'typescriptreact', ['typescript']) From 7a817cd9dd457b4bedb0dc0e9dc57d084ce48bab Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Sat, 8 Oct 2022 00:43:42 +0900 Subject: [PATCH 12/32] issue249 (#250) * issue249 * fix * Remove test case Co-authored-by: hrsh7th <> --- .themisrc | 22 ++++++++--- autoload/vsnip/session.vim | 21 ++++++----- misc/integration.json | 4 ++ package.json | 12 ++++-- spec/plugin/vsnip.vimspec | 76 +++++++++++++++++++++++--------------- 5 files changed, 86 insertions(+), 49 deletions(-) diff --git a/.themisrc b/.themisrc index 9d12204..91e60db 100644 --- a/.themisrc +++ b/.themisrc @@ -3,11 +3,17 @@ call themis#option('exclude', '\.vim$') set shiftwidth=2 set expandtab + if get(environ(), 'VIRTUALEDIT', '0') == '1' set virtualedit=all else set virtualedit= endif +if get(environ(), 'EXCLUSIVE', '0') == '1' + set selection=exclusive +else + set selection=inclusive +endif let g:vsnip_test_mode = v:true let g:vsnip_snippet_dir = fnamemodify(expand(''), ':h') . '/misc' @@ -17,13 +23,17 @@ let g:vsnip_filetypes.source_spec_enhanced = ['source_spec'] let &runtimepath .= ',' . g:vsnip_snippet_dir . '/source_spec_vscode' -imap vsnip#expandable() ? '(vsnip-expand)' : '' -smap vsnip#expandable() ? '(vsnip-expand)' : '' +inoremap (vsnip-C-j) +inoremap (vsnip-Tab) +inoremap (vsnip-S-Tab) + +imap vsnip#expandable() ? '(vsnip-expand)' : '(vsnip-C-j)' +smap vsnip#expandable() ? '(vsnip-expand)' : '(vsnip-C-j)' -imap vsnip#available(1) ? '(vsnip-expand-or-jump)' : '' -smap vsnip#jumpable(1) ? '(vsnip-jump-next)' : '' -imap vsnip#jumpable(-1) ? '(vsnip-jump-prev)' : '' -smap vsnip#jumpable(-1) ? '(vsnip-jump-prev)' : '' +imap vsnip#available(1) ? '(vsnip-expand-or-jump)' : '(vsnip-Tab)' +smap vsnip#jumpable(1) ? '(vsnip-jump-next)' : '(vsnip-Tab)' +imap vsnip#jumpable(-1) ? '(vsnip-jump-prev)' : '(vsnip-S-Tab)' +smap vsnip#jumpable(-1) ? '(vsnip-jump-prev)' : '(vsnip-S-Tab)' imap (vsnip-assert) =assert() nmap (vsnip-assert) assert() diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index b82cd1e..23f627d 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -131,20 +131,21 @@ endfunction " @NOTE: Must work even if virtualedit=all/onmore or not. " function! s:Session.select(jump_point) abort - let l:pos = s:Position.lsp_to_vim('%', a:jump_point.range.end) - call cursor([l:pos[0], l:pos[1] - (&selection !~# 'exclusive')]) + let l:start_pos = s:Position.lsp_to_vim('%', a:jump_point.range.start) + let l:end_pos = s:Position.lsp_to_vim('%', a:jump_point.range.end) - let l:select_length = strchars(a:jump_point.placeholder.text()) - (&selection !~# 'exclusive') let l:cmd = '' - let l:cmd .= mode()[0] ==# 'i' ? "\l" : '' - let l:cmd .= printf('v%s', l:select_length > 0 ? l:select_length . 'h' : '') + let l:cmd .= "\set virtualedit=onemore\" + let l:cmd .= mode()[0] ==# 'i' ? "\" : '' + let l:cmd .= printf("\call cursor(%s, %s)\", l:start_pos[0], l:start_pos[1]) + let l:cmd .= 'v' + let l:cmd .= printf("\call cursor(%s, %s)\%s", l:end_pos[0], l:end_pos[1], &selection ==# 'exclusive' ? '' : 'h') if get(g:, 'vsnip_test_mode', v:false) - let l:cmd .= "\gvo\" " Update `last visual selection` for getting it in test. - execute printf('normal! %s', l:cmd) - else - let l:cmd .= "o\" - call feedkeys(l:cmd, 'n') + let l:cmd .= "\gv" endif + let l:cmd .= printf("\set virtualedit=%s\", &virtualedit) + let l:cmd .= "\" + call feedkeys(l:cmd, 'ni') endfunction " diff --git a/misc/integration.json b/misc/integration.json index c01865d..49ec0a2 100644 --- a/misc/integration.json +++ b/misc/integration.json @@ -169,5 +169,9 @@ "\t$TM_SELECTED_TEXT$5", "}$0" ] + }, + "issue249": { + "prefix": "issue249", + "body": ["${1:FOO}\n"] } } diff --git a/package.json b/package.json index da10091..f454311 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,14 @@ "scripts": { "open": "nvim -u .vimrc", "test": "run-s test:*", - "test:vim-virtualedit-off": "THEMIS_VIM=vim VIRTUALEDIT=0 themis ./spec", - "test:vim-virtualedit-on": "THEMIS_VIM=vim VIRTUALEDIT=1 themis ./spec", - "test:nvim-virtualedit-off": "THEMIS_VIM=nvim VIRTUALEDIT=0 themis ./spec", - "test:nvim-virtualedit-on": "THEMIS_VIM=nvim VIRTUALEDIT=1 themis ./spec", + "test:01": "THEMIS_VIM=vim EXCLUSIVE=0 VIRTUALEDIT=0 themis ./spec", + "test:02": "THEMIS_VIM=vim EXCLUSIVE=0 VIRTUALEDIT=1 themis ./spec", + "test:03": "THEMIS_VIM=vim EXCLUSIVE=1 VIRTUALEDIT=0 themis ./spec", + "test:04": "THEMIS_VIM=vim EXCLUSIVE=1 VIRTUALEDIT=1 themis ./spec", + "test:05": "THEMIS_VIM=nvim EXCLUSIVE=0 VIRTUALEDIT=0 themis ./spec", + "test:06": "THEMIS_VIM=nvim EXCLUSIVE=0 VIRTUALEDIT=1 themis ./spec", + "test:07": "THEMIS_VIM=nvim EXCLUSIVE=1 VIRTUALEDIT=0 themis ./spec", + "test:08": "THEMIS_VIM=nvim EXCLUSIVE=1 VIRTUALEDIT=1 themis ./spec", "lint": "vint ." }, "husky": { diff --git a/spec/plugin/vsnip.vimspec b/spec/plugin/vsnip.vimspec index be0e209..be682a9 100644 --- a/spec/plugin/vsnip.vimspec +++ b/spec/plugin/vsnip.vimspec @@ -1,5 +1,19 @@ let s:expect = themis#helper('expect') +function! s:expect_selection(s, e) abort + if &selection ==# 'exclusive' && &virtualedit ==# '' + return + endif + + call s:expect(getpos("'<")[1 : 2]).to_equal([a:s[0], a:s[1]]) + if &selection ==# 'exclusive' + let l:text = getline('.') + let l:char = strcharpart(l:text, charidx(l:text, a:e[1] - 1), 1) + let a:e[1] = a:e[1] + strlen(l:char) + endif + call s:expect(getpos("'>")[1 : 2]).to_equal([a:e[0], a:e[1]]) +endfunction + Describe vsnip After each @@ -128,9 +142,7 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() - call s:expect(mode(1)).to_equal('s') - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 1]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 1]) + call s:expect_selection([1, 1], [1, 1]) call s:expect(getbufline('%', '^', '$')).to_equal(['snippet']) endfunction call feedkeys("a\\\(vsnip-assert)", 'x') @@ -144,15 +156,13 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() - call s:expect(mode(1)).to_equal('s') - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 3]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 3]) + call s:expect_selection([1, 3], [1, 3]) call s:expect(getbufline('%', '^', '$')).to_equal(['snippet']) endfunction call feedkeys("a\\\(vsnip-assert)", 'x') End - It should select 1 length last of snippet text + It should select 1 length last of snippet text enew! set filetype=integration call setline(1, 'spec7') @@ -160,9 +170,7 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() - call s:expect(mode(1)).to_equal('s') - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 7]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 7]) + call s:expect_selection([1, 7], [1, 7]) call s:expect(getbufline('%', '^', '$')).to_equal(['snippet']) endfunction call feedkeys("a\\\(vsnip-assert)", 'x') @@ -176,9 +184,7 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() - call s:expect(mode(1)).to_equal('s') - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 1]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 3]) + call s:expect_selection([1, 1], [1, 3]) call s:expect(getbufline('%', '^', '$')).to_equal(['snippet']) endfunction call feedkeys("a\\\(vsnip-assert)", 'x') @@ -192,9 +198,7 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() - call s:expect(mode(1)).to_equal('s') - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 3]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 5]) + call s:expect_selection([1, 3], [1, 5]) call s:expect(getbufline('%', '^', '$')).to_equal(['snippet']) endfunction call feedkeys("a\\\(vsnip-assert)", 'x') @@ -208,9 +212,7 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() - call s:expect(mode(1)).to_equal('s') - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 5]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 7]) + call s:expect_selection([1, 5], [1, 7]) call s:expect(getbufline('%', '^', '$')).to_equal(['snippet']) endfunction call feedkeys("a\\\(vsnip-assert)", 'x') @@ -242,9 +244,7 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() - call s:expect(mode(1)).to_equal('s') - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 7]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 12]) + call s:expect_selection([1, 7], [1, 12]) call s:expect(getbufline('%', '^', '$')).to_equal(['あいかkaかう']) endfunction call feedkeys("a\\\(vsnip-assert)", 'x') @@ -547,15 +547,13 @@ Describe vsnip " can jump to $2 let l:sequence .= "\\(vsnip-assert)" function g:vsnip_assert.step5() - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 11]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 24]) + call s:expect_selection([1, 11], [1, 24]) endfunction " can jump to $3 let l:sequence .= "\\(vsnip-assert)" function g:vsnip_assert.step6() - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 12]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 23]) + call s:expect_selection([1, 12], [1, 23]) endfunction call feedkeys(l:sequence, 'x') @@ -588,9 +586,7 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() call s:expect(getbufline('%', '^', '$')).to_equal(['for i=1,10', ' print(i)']) - call s:expect(mode(1)).to_equal('s') - call s:expect(getpos("'<")[1 : 2]).to_equal([1, 5]) - call s:expect(getpos("'>")[1 : 2]).to_equal([1, 5]) + call s:expect_selection([1, 5], [1, 5]) endfunction call feedkeys("a\\(vsnip-assert)", 'x') End @@ -683,6 +679,28 @@ Describe vsnip call feedkeys(l:sequence, 'x') End + It should work issue249 + enew! + set filetype=integration + call setline(1, ['issue249']) + call cursor([1, 8]) + + let g:vsnip_assert = {} + let l:sequence = '' + + " expand + let l:sequence .= "a\\(vsnip-assert)" + function g:vsnip_assert.step1() + call s:expect_selection([1, 1], [1, 3]) + call s:expect(getbufline('%', '^', '$')).to_equal([ + \ 'FOO', + \ '', + \ ]) + endfunction + + call feedkeys(l:sequence, 'x') + End + End End From b2caf50a6e3c021c92b236abff70bbb467bce24f Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Sat, 8 Oct 2022 00:54:42 +0900 Subject: [PATCH 13/32] Remove unneeded event handling --- autoload/vsnip/session.vim | 16 ---------------- plugin/vsnip.vim | 11 ----------- spec/plugin/vsnip.vimspec | 1 + 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index 23f627d..40d1272 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -180,22 +180,6 @@ function! s:Session.on_insert_leave() abort call self.flush_changes() endfunction -" -" on_insert_char_pre -" -function! s:Session.on_insert_char_pre(char) abort - if a:char =~# '^[:print:]\+$' - let l:range = self.snippet.range() - let l:position = s:Position.cursor() - let l:is_out_of_range = v:false - let l:is_out_of_range = l:is_out_of_range || l:position.line < l:range.start.line || (l:position.line == l:range.start.line && l:position.character < l:range.start.character) - let l:is_out_of_range = l:is_out_of_range || l:position.line > l:range.end.line || (l:position.line == l:range.end.line && l:position.character > l:range.end.character) - if l:is_out_of_range - call vsnip#deactivate() - endif - endif -endfunction - " " on_text_changed " diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index 00e24c4..2f76836 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -188,7 +188,6 @@ endfunction augroup vsnip autocmd! autocmd InsertLeave * call s:on_insert_leave() - autocmd InsertCharPre * call s:on_insert_char_pre() autocmd TextChanged,TextChangedI,TextChangedP * call s:on_text_changed() autocmd BufWritePost * call s:on_buf_write_post() augroup END @@ -203,16 +202,6 @@ function! s:on_insert_leave() abort endif endfunction -" -" on_insert_char_pre -" -function! s:on_insert_char_pre() abort - let l:session = vsnip#get_session() - if !empty(l:session) - call l:session.on_insert_char_pre(v:char) - endif -endfunction - " " on_text_changed " diff --git a/spec/plugin/vsnip.vimspec b/spec/plugin/vsnip.vimspec index be682a9..57b731f 100644 --- a/spec/plugin/vsnip.vimspec +++ b/spec/plugin/vsnip.vimspec @@ -64,6 +64,7 @@ Describe vsnip It should not expand when prefix is word and it does not separate by word boundary enew! set filetype=integration + set iskeyword=@,48-57,_,192-255 call setline(1, '(aspec1)') call cursor([1, 7]) From abfe8411a0d1c0dfea56b7221e49cbb6cf7cf4fe Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Sat, 15 Oct 2022 19:56:49 +0900 Subject: [PATCH 14/32] Avoid startinsert and use feedkeys --- README.md | 2 +- autoload/vsnip/session.vim | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5e646f6..8c84022 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # vim-vsnip -VSCode(LSP)'s snippet feature in vim. +VSCode(LSP)'s snippet feature in vim/nvim. # Features diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index 40d1272..36b9045 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -158,10 +158,12 @@ function! s:Session.move(jump_point) abort call cursor(l:pos) - if l:pos[1] > strlen(getline(l:pos[0])) - startinsert! - else - startinsert + if mode()[0] !~# 'i' + if l:pos[1] > strlen(getline(l:pos[0])) + call feedkeys('a', 'ni') + else + call feedkeys('i', 'ni') + endif endif endfunction From 5ed732af0cf791466513657653bd03fd555b8ed7 Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Sat, 15 Oct 2022 20:01:14 +0900 Subject: [PATCH 15/32] Add doautocmd for test --- spec/plugin/vsnip.vimspec | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/plugin/vsnip.vimspec b/spec/plugin/vsnip.vimspec index 57b731f..37e08ad 100644 --- a/spec/plugin/vsnip.vimspec +++ b/spec/plugin/vsnip.vimspec @@ -285,6 +285,7 @@ Describe vsnip let l:sequence .= "\arg\(vsnip-assert)" function g:vsnip_assert.step3() + doautocmd TextChangedI call s:expect(vsnip#get_session()).to_be_empty() endfunction let l:sequence .= "\call cursor(1, 1)\modiied\(vsnip-assert)" From 7de8a71e5d6e39a05e259f9ad4dba5e0aee8bbba Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Sat, 15 Oct 2022 20:25:27 +0900 Subject: [PATCH 16/32] Fix deactivate tests --- misc/integration.json | 8 ++++++++ spec/plugin/vsnip.vimspec | 43 +++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/misc/integration.json b/misc/integration.json index 49ec0a2..d2fdf0b 100644 --- a/misc/integration.json +++ b/misc/integration.json @@ -83,6 +83,14 @@ "あ$1い${2:かkaか}う" ] }, + "deactivate1": { + "prefix": "deactivate", + "body": [ + "function! $1() abort", + "\t$0", + "endfunction" + ] + }, "realworld1": { "description": "Complex example", "prefix": ["realworld1"], diff --git a/spec/plugin/vsnip.vimspec b/spec/plugin/vsnip.vimspec index 37e08ad..9e36873 100644 --- a/spec/plugin/vsnip.vimspec +++ b/spec/plugin/vsnip.vimspec @@ -256,20 +256,15 @@ Describe vsnip Context g:vsnip_deactivate_on It should deactivate snippet on edit the outside of snippet enew! + set filetype=integration let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfSnippet call setline(1, [ \ 'outside', - \ 'prefix', + \ 'deactivate', \ 'outside', \ ]) - call cursor([2, 7]) - call vsnip#anonymous(join([ - \ "function! $1() abort", - \ "\t$0", - \ "endfunction", - \ ], "\n")) - call s:expect(vsnip#get_session()).not.to_be_empty() + call cursor([2, 11]) let g:vsnip_assert = {} let l:sequence = '' @@ -277,18 +272,22 @@ Describe vsnip function g:vsnip_assert.step1() call s:expect(vsnip#get_session()).not.to_be_empty() endfunction - let l:sequence .= "ifuncname\(vsnip-assert)" + let l:sequence .= "a\\(vsnip-assert)" function g:vsnip_assert.step2() call s:expect(vsnip#get_session()).not.to_be_empty() endfunction - let l:sequence .= "\arg\(vsnip-assert)" + let l:sequence .= "funcname\(vsnip-assert)" function g:vsnip_assert.step3() - doautocmd TextChangedI + call s:expect(vsnip#get_session()).not.to_be_empty() + endfunction + let l:sequence .= "\return 1\(vsnip-assert)" + + function g:vsnip_assert.step4() call s:expect(vsnip#get_session()).to_be_empty() endfunction - let l:sequence .= "\call cursor(1, 1)\modiied\(vsnip-assert)" + let l:sequence .= "\call cursor([1, 1])\modified\" call feedkeys(l:sequence, 'x') @@ -297,20 +296,15 @@ Describe vsnip It should deactivate snippet on edit the outside of current tabstop enew! + set filetype=integration let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfCurrentTabstop call setline(1, [ \ 'outside', - \ 'prefix', + \ 'deactivate', \ 'outside', \ ]) - call cursor([2, 7]) - call vsnip#anonymous(join([ - \ "function! $1() abort", - \ "\t$0", - \ "endfunction", - \ ], "\n")) - call s:expect(vsnip#get_session()).not.to_be_empty() + call cursor([2, 11]) let g:vsnip_assert = {} let l:sequence = '' @@ -318,12 +312,17 @@ Describe vsnip function g:vsnip_assert.step1() call s:expect(vsnip#get_session()).not.to_be_empty() endfunction - let l:sequence .= "ifuncname\(vsnip-assert)" + let l:sequence .= "a\\(vsnip-assert)" function g:vsnip_assert.step2() + call s:expect(vsnip#get_session()).not.to_be_empty() + endfunction + let l:sequence .= "funcname\(vsnip-assert)" + + function g:vsnip_assert.step3() call s:expect(vsnip#get_session()).to_be_empty() endfunction - let l:sequence .= "\arg\(vsnip-assert)" + let l:sequence .= "\arguments\(vsnip-assert)" call feedkeys(l:sequence, 'x') From 03010115eb8bdda00ce5f845cc2f7025700e33bb Mon Sep 17 00:00:00 2001 From: hokorobi Date: Sun, 23 Oct 2022 16:14:44 +0900 Subject: [PATCH 17/32] Add check for existence of 'user_data' in v:completed_item (#257) Because "E716: Key not present in Dictionary" may appear. --- plugin/vsnip.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index 2f76836..70ecb42 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -95,7 +95,7 @@ function! s:expand_or_jump() endfunction " This is needed to keep normal-mode during 0ms to prevent CompleteDone handling by LSP Client. - let l:maybe_complete_done = !empty(v:completed_item) && !empty(v:completed_item.user_data) + let l:maybe_complete_done = !empty(v:completed_item) && has_key(v:completed_item, 'user_data') && !empty(v:completed_item.user_data) if l:maybe_complete_done call timer_start(0, { -> l:ctx.callback() }) else From 3662e8a1bb547afb5298830c97c376e834e61161 Mon Sep 17 00:00:00 2001 From: hokorobi Date: Sun, 30 Oct 2022 15:06:32 +0900 Subject: [PATCH 18/32] Add check for existence of 'user_data' in v:completed_item (#258) The same fix was needed for s:expand(). --- plugin/vsnip.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index 70ecb42..8f4fa64 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -115,7 +115,7 @@ function! s:expand() abort endfunction " This is needed to keep normal-mode during 0ms to prevent CompleteDone handling by LSP Client. - let l:maybe_complete_done = !empty(v:completed_item) && !empty(v:completed_item.user_data) + let l:maybe_complete_done = !empty(v:completed_item) && has_key(v:completed_item, 'user_data') && !empty(v:completed_item.user_data) if l:maybe_complete_done call timer_start(0, { -> l:ctx.callback() }) else From 7b567e1ff14b7fb0d30d62e9d30ac97e9a45335f Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Sun, 30 Oct 2022 21:13:25 +0900 Subject: [PATCH 19/32] Fix virtualedit=all --- autoload/vsnip/session.vim | 2 +- spec/plugin/vsnip.vimspec | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index 36b9045..c111bd1 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -159,7 +159,7 @@ function! s:Session.move(jump_point) abort call cursor(l:pos) if mode()[0] !~# 'i' - if l:pos[1] > strlen(getline(l:pos[0])) + if (getcurpos()[2] - 1) > strlen(getline(getcurpos()[1])) call feedkeys('a', 'ni') else call feedkeys('i', 'ni') diff --git a/spec/plugin/vsnip.vimspec b/spec/plugin/vsnip.vimspec index 9e36873..aaf4477 100644 --- a/spec/plugin/vsnip.vimspec +++ b/spec/plugin/vsnip.vimspec @@ -30,6 +30,7 @@ Describe vsnip let g:vsnip_assert = {} function g:vsnip_assert.step1() + call s:expect(getcurpos()[1 : 2]).to_equal([1, 8]) call s:expect(getbufline('%', '^', '$')).to_equal(['snippet']) endfunction call feedkeys("a\\(vsnip-assert)", 'x') From d7d384530213986b98ab31d0049ce005aaa919ce Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Mon, 31 Oct 2022 13:48:35 +0900 Subject: [PATCH 20/32] Fix virtualedit=1 again --- autoload/vsnip/session.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index c111bd1..7653316 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -159,7 +159,7 @@ function! s:Session.move(jump_point) abort call cursor(l:pos) if mode()[0] !~# 'i' - if (getcurpos()[2] - 1) > strlen(getline(getcurpos()[1])) + if (getcurpos()[2] - 1) >= strlen(getline(getcurpos()[1])) call feedkeys('a', 'ni') else call feedkeys('i', 'ni') From ceeee48145d27f0b3986ab6f75f52a2449974603 Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Mon, 31 Oct 2022 13:55:40 +0900 Subject: [PATCH 21/32] Fix virtualedit=1 again --- autoload/vsnip/session.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index 7653316..58c6fc3 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -158,8 +158,8 @@ function! s:Session.move(jump_point) abort call cursor(l:pos) - if mode()[0] !~# 'i' - if (getcurpos()[2] - 1) >= strlen(getline(getcurpos()[1])) + if mode()[0] ==# 'n' + if l:pos[1] != getcurpos()[2] call feedkeys('a', 'ni') else call feedkeys('i', 'ni') From 6f873418c4dc601d8ad019a5906eddff5088de9b Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Mon, 14 Nov 2022 13:31:01 +0900 Subject: [PATCH 22/32] Fix backspace with virtualedit --- plugin/vsnip.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index 8f4fa64..090ec8f 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -74,7 +74,7 @@ endfunction " extra mapping " if g:vsnip_extra_mapping - snoremap ("\" . (getcurpos()[2] == col('$') - 1 ? 'a' : 'i')) + snoremap ("\" . (&virtualedit ==# '' && getcurpos()[2] >= col('$') - 1 ? 'a' : 'i')) endif " From e44026b5394fd79aa0f2118aaf41627ef9c354ee Mon Sep 17 00:00:00 2001 From: NAKAI Tsuyoshi <82267684+uga-rosa@users.noreply.github.com> Date: Wed, 30 Nov 2022 13:28:59 +0900 Subject: [PATCH 23/32] 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 --- README.md | 3 + autoload/vsnip/source.vim | 6 +- autoload/vsnip/source/snipmate.vim | 88 ++++++++++++++++++++++++++++++ doc/vsnip.txt | 51 +++++++++++++++-- ftplugin/snippets.vim | 39 +++++++++++++ indent/snippets.vim | 55 +++++++++++++++++++ misc/snipmate.snippets | 6 ++ plugin/vsnip.vim | 18 +++--- spec/autoload/vsnip/source.vimspec | 36 ++++++++++++ syntax/snippets.vim | 45 +++++++++++++++ 10 files changed, 332 insertions(+), 15 deletions(-) create mode 100644 autoload/vsnip/source/snipmate.vim create mode 100644 ftplugin/snippets.vim create mode 100644 indent/snippets.vim create mode 100644 misc/snipmate.snippets create mode 100644 syntax/snippets.vim diff --git a/README.md b/README.md index 8c84022..9bf1229 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ VSCode(LSP)'s snippet feature in vim/nvim. - [completion-nvim](https://github.com/nvim-lua/completion-nvim) - 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 diff --git a/autoload/vsnip/source.vim b/autoload/vsnip/source.vim index d7a833b..a6dc972 100644 --- a/autoload/vsnip/source.vim +++ b/autoload/vsnip/source.vim @@ -4,6 +4,7 @@ function! vsnip#source#refresh(path) abort call vsnip#source#user_snippet#refresh(a:path) call vsnip#source#vscode#refresh(a:path) + call vsnip#source#snipmate#refresh(a:path) endfunction " @@ -13,6 +14,7 @@ function! vsnip#source#find(bufnr) abort let l:sources = [] let l:sources += vsnip#source#user_snippet#find(a:bufnr) let l:sources += vsnip#source#vscode#find(a:bufnr) + let l:sources += vsnip#source#snipmate#find(a:bufnr) return l:sources endfunction @@ -65,7 +67,7 @@ endfunction " format_snippet " function! s:format_snippet(label, snippet) abort - let [l:prefixes, l:prefixes_alias] = s:resolve_prefix(a:snippet.prefix) + let [l:prefixes, l:prefixes_alias] = vsnip#source#resolve_prefix(a:snippet.prefix) let l:description = get(a:snippet, 'description', '') return { @@ -87,7 +89,7 @@ endfunction " " resolve_prefix. " -function! s:resolve_prefix(prefix) abort +function! vsnip#source#resolve_prefix(prefix) abort let l:prefixes = [] let l:prefixes_alias = [] diff --git a/autoload/vsnip/source/snipmate.vim b/autoload/vsnip/source/snipmate.vim new file mode 100644 index 0000000..c500d1a --- /dev/null +++ b/autoload/vsnip/source/snipmate.vim @@ -0,0 +1,88 @@ +let s:cache = {} + +function! vsnip#source#snipmate#refresh(path) abort + if has_key(s:cache, a:path) + unlet s:cache[a:path] + endif +endfunction + +function! vsnip#source#snipmate#find(bufnr) abort + let filetypes = vsnip#source#filetypes(a:bufnr) + return s:find(filetypes, a:bufnr) +endfunction + +function! s:find(filetypes, bufnr) abort + let sources = [] + for path in s:get_source_paths(a:filetypes, a:bufnr) + if !has_key(s:cache, path) + let s:cache[path] = s:create(path, a:bufnr) + endif + call add(sources, s:cache[path]) + endfor + return sources +endfunction + +function! s:get_source_paths(filetypes, bufnr) abort + let paths = [] + for dir in s:get_source_dirs(a:bufnr) + for filetype in a:filetypes + let path = resolve(expand(printf('%s/%s.snippets', dir, filetype))) + if has_key(s:cache, path) || filereadable(path) + call add(paths, path) + endif + endfor + endfor + return paths +endfunction + +function! s:get_source_dirs(bufnr) abort + let dirs = [] + let buf_dir = getbufvar(a:bufnr, 'vsnip_snippet_dir', '') + if buf_dir !=# '' + let dirs += [buf_dir] + endif + let dirs += getbufvar(a:bufnr, 'vsnip_snippet_dirs', []) + let dirs += [g:vsnip_snippet_dir] + let dirs += g:vsnip_snippet_dirs + return dirs +endfunction + +function! s:create(path, bufnr) abort + let file = readfile(a:path) + let file = type(file) == v:t_list ? file : [file] + call map(file, { _, f -> iconv(f, 'utf-8', &encoding) }) + let source = [] + let i = -1 + while i + 1 < len(file) + let [i, line] = [i + 1, file[i + 1]] + if line =~# '^\(#\|\s*$\)' + " Comment, or blank line before snippets + elseif line =~# '^extends\s\+\S' + let filetypes = map(split(line[7:], ','), 'trim(v:val)') + let source += flatten(s:find(filetypes, a:bufnr)) + elseif line =~# '^snippet\s\+\S' && i + 1 < len(file) + let matched = matchlist(line, '^snippet\s\+\(\S\+\)\s*\(.*\)') + let [prefix, description] = [matched[1], matched[2]] + let body = [] + let indent = matchstr(file[i + 1], '^\s\+') + while i + 1 < len(file) && file[i + 1] =~# '^\(' . indent . '\|\s*$\)' + let [i, line] = [i + 1, file[i + 1]] + call add(body, line[strlen(indent):]) + endwhile + let [prefixes, prefixes_alias] = vsnip#source#resolve_prefix(prefix) + call add(source, { + \ 'label': prefix, + \ 'prefix': prefixes, + \ 'prefix_alias': prefixes_alias, + \ 'body': body, + \ 'description': description + \ }) + else + echohl ErrorMsg + echomsg printf('[vsnip] Parsing error occurred on: %s#L%s', a:path, i + 1) + echohl None + break + endif + endwhile + return sort(source, { a, b -> strlen(b.prefix[0]) - strlen(a.prefix[0]) }) +endfunction diff --git a/doc/vsnip.txt b/doc/vsnip.txt index defe50b..2a65afa 100644 --- a/doc/vsnip.txt +++ b/doc/vsnip.txt @@ -145,14 +145,15 @@ COMMAND *vsnip-command* VsnipOpen~ -> - :VsnipOpen - :VsnipOpenEdit - :VsnipOpenSplit - :VsnipOpenVsplit -< + + :VsnipOpen [-format {type}] + :VsnipOpenEdit [-format {type}] + :VsnipOpenSplit [-format {type}] + :VsnipOpenVsplit [-format {type}] + Open snippet source file under `g:vsnip_snippet_dir`. +{type} is either 'snipmate' or 'vscode'. If omitted, it is 'vscode'. VsnipYank~ @@ -242,6 +243,44 @@ ${VIM:...Vim script expression...}~ +============================================================================== +SNIPMATE SUPPORT *vsnip-snipmate-support* + +Files with the extension 'snippets' in directories `g:vsnip_snippet_dir` or +`g:vsnip_snippet_dirs` are recognized as snippets with SnipMate-like syntax. + +NOTE: This feature does not guarantee that SnipMate's snippet collection can +be read in its entirety. It is intended to provide an easy way for users to +write their own new snippet definitions. + +The following two examples are equivalent. +In SnipMate format. > + snippet fn vim's function + function! $1($2) abort + $0 + endfunction +< +In VSCode format. > + { + "fn": { + "prefix": "fn", + "body": [ + "function! $1($2) abort", + "\t$0", + "endfunction" + ], + "description": "vim's function" + } + } +< +You can also use the extends syntax. For example, the first line of +cpp.snippets should have this. > + extends c +< + + + + ============================================================================== LIMITATION *vsnip-limitation* diff --git a/ftplugin/snippets.vim b/ftplugin/snippets.vim new file mode 100644 index 0000000..2ef49c8 --- /dev/null +++ b/ftplugin/snippets.vim @@ -0,0 +1,39 @@ +" MIT License +" +" Copyright 2009-2010 Michael Sanders. All rights reserved. + +" Permission is hereby granted, free of charge, to any person obtaining a copy +" of this software and associated documentation files (the "Software"), to deal +" in the Software without restriction, including without limitation the rights +" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +" copies of the Software, and to permit persons to whom the Software is +" furnished to do so, subject to the following conditions: + +" The above copyright notice and this permission notice shall be included in all +" copies or substantial portions of the Software. + +" The software is provided "as is", without warranty of any kind, express or +" implied, including but not limited to the warranties of merchantability, +" fitness for a particular purpose and noninfringement. In no event shall the +" authors or copyright holders be liable for any claim, damages or other +" liability, whether in an action of contract, tort or otherwise, arising from, +" out of or in connection with the software or the use or other dealings in the +" software." From https://github.com/garbas/vim-snipmate + + +" Vim filetype plugin for SnipMate snippets (.snippets and .snippet files) + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +let b:undo_ftplugin = "setl et< sts< cms< fdm< fde<" + +" Use hard tabs +setlocal noexpandtab softtabstop=0 + +setlocal foldmethod=expr foldexpr=getline(v:lnum)!~'^\\t\\\\|^$'?'>1':1 + +setlocal commentstring=#\ %s +setlocal nospell diff --git a/indent/snippets.vim b/indent/snippets.vim new file mode 100644 index 0000000..824f056 --- /dev/null +++ b/indent/snippets.vim @@ -0,0 +1,55 @@ +" MIT License +" +" Copyright 2009-2010 Michael Sanders. All rights reserved. + +" Permission is hereby granted, free of charge, to any person obtaining a copy +" of this software and associated documentation files (the "Software"), to deal +" in the Software without restriction, including without limitation the rights +" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +" copies of the Software, and to permit persons to whom the Software is +" furnished to do so, subject to the following conditions: + +" The above copyright notice and this permission notice shall be included in all +" copies or substantial portions of the Software. + +" The software is provided "as is", without warranty of any kind, express or +" implied, including but not limited to the warranties of merchantability, +" fitness for a particular purpose and noninfringement. In no event shall the +" authors or copyright holders be liable for any claim, damages or other +" liability, whether in an action of contract, tort or otherwise, arising from, +" out of or in connection with the software or the use or other dealings in the +" software." From https://github.com/garbas/vim-snipmate + + +" Simple indent support for SnipMate snippets files + +if exists('b:did_indent') + finish +endif +let b:did_indent = 1 + +setlocal nosmartindent +setlocal indentkeys=!^F,o,O,=snippet,=extends +setlocal indentexpr=GetSnippetIndent() + +if exists("*GetSnippetIndent") + finish +endif + +function! GetSnippetIndent() + let line = getline(v:lnum) + let prev_lnum = v:lnum - 1 + let prev_line = prev_lnum != 0 ? getline(prev_lnum) : "" + + if line =~# '\v^(snippet|extends) ' + return 0 + elseif indent(v:lnum) > 0 + return indent(v:lnum) + elseif prev_line =~# '^snippet ' + return &sw + elseif indent(prev_lnum) > 0 + return indent(prev_lnum) + endif + + return 0 +endfunction diff --git a/misc/snipmate.snippets b/misc/snipmate.snippets new file mode 100644 index 0000000..8433e63 --- /dev/null +++ b/misc/snipmate.snippets @@ -0,0 +1,6 @@ +snippet arrow-function + () => +snippet fn vim's function + function! $1($2) abort + $0 + endfunction diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index 090ec8f..2f4f0a7 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -28,11 +28,11 @@ augroup END " " command " -command! -bang VsnipOpen call s:open_command(0, 'vsplit') -command! -bang VsnipOpenEdit call s:open_command(0, 'edit') -command! -bang VsnipOpenVsplit call s:open_command(0, 'vsplit') -command! -bang VsnipOpenSplit call s:open_command(0, 'split') -function! s:open_command(bang, cmd) +command! -nargs=* -bang VsnipOpen call s:open_command(0, 'vsplit', ) +command! -nargs=* -bang VsnipOpenEdit call s:open_command(0, 'edit', ) +command! -nargs=* -bang VsnipOpenVsplit call s:open_command(0, 'vsplit', ) +command! -nargs=* -bang VsnipOpenSplit call s:open_command(0, 'split', ) +function! s:open_command(bang, cmd, arg) let l:candidates = vsnip#source#filetypes(bufnr('%')) if a:bang let l:idx = 1 @@ -53,9 +53,12 @@ function! s:open_command(bang, cmd) endif endif - execute printf('%s %s', a:cmd, fnameescape(printf('%s/%s.json', + let l:ext = a:arg =~# '-format\s\+snipmate' ? 'snippets' : 'json' + + execute printf('%s %s', a:cmd, fnameescape(printf('%s/%s.%s', \ resolve(l:expanded_dir), - \ l:candidates[l:idx - 1] + \ l:candidates[l:idx - 1], + \ l:ext \ ))) endfunction @@ -190,6 +193,7 @@ augroup vsnip autocmd InsertLeave * call s:on_insert_leave() autocmd TextChanged,TextChangedI,TextChangedP * call s:on_text_changed() autocmd BufWritePost * call s:on_buf_write_post() + autocmd BufRead,BufNewFile *.snippets setlocal filetype=snippets augroup END " diff --git a/spec/autoload/vsnip/source.vimspec b/spec/autoload/vsnip/source.vimspec index 79cc2bd..767ca3b 100644 --- a/spec/autoload/vsnip/source.vimspec +++ b/spec/autoload/vsnip/source.vimspec @@ -57,5 +57,41 @@ Describe vsnip#source End + Describe #snipmate + + It should load snippets + enew! + set filetype=snipmate + let l:snippets = vsnip#source#find(bufnr('%')) + call s:expect(l:snippets[0]).to_have_length(2) + End + + It should format snippets + enew! + set filetype=snipmate + let l:snippets = vsnip#source#find(bufnr('%')) + call s:expect(sort(l:snippets[0])).to_equal(sort([{ + \ 'label': 'arrow-function', + \ 'description': '', + \ 'prefix': ['arrow-function'], + \ 'prefix_alias': ['af'], + \ 'body': [ + \ "() => " + \ ] + \ }, { + \ 'label': 'fn', + \ 'description': "vim's function", + \ 'prefix': ['fn'], + \ 'prefix_alias': [], + \ 'body': [ + \ "function! $1($2) abort", + \ "\t$0", + \ "endfunction", + \ ] + \ }])) + End + + End + End diff --git a/syntax/snippets.vim b/syntax/snippets.vim new file mode 100644 index 0000000..02d8c20 --- /dev/null +++ b/syntax/snippets.vim @@ -0,0 +1,45 @@ +" MIT License +" +" Copyright 2009-2010 Michael Sanders. All rights reserved. + +" Permission is hereby granted, free of charge, to any person obtaining a copy +" of this software and associated documentation files (the "Software"), to deal +" in the Software without restriction, including without limitation the rights +" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +" copies of the Software, and to permit persons to whom the Software is +" furnished to do so, subject to the following conditions: + +" The above copyright notice and this permission notice shall be included in all +" copies or substantial portions of the Software. + +" The software is provided "as is", without warranty of any kind, express or +" implied, including but not limited to the warranties of merchantability, +" fitness for a particular purpose and noninfringement. In no event shall the +" authors or copyright holders be liable for any claim, damages or other +" liability, whether in an action of contract, tort or otherwise, arising from, +" out of or in connection with the software or the use or other dealings in the +" software." From https://github.com/garbas/vim-snipmate + + +" Syntax highlighting for .snippets files (used for snipMate.vim) +" Hopefully this should make snippets a bit nicer to write! +syn match snipComment '^#.*' +syn match placeHolder '\${\d\+\(:.\{-}\)\=}' contains=snipCommand +syn match tabStop '\$\d\+' +syn match snipEscape '\\\\\|\\`' +syn match snipCommand '\%(\\\@ Date: Tue, 20 Dec 2022 23:27:54 +0900 Subject: [PATCH 24/32] Delete FUNDING.yml --- .github/FUNDING.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index ccdeccc..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: [hrsh7th] From 7753ba9c10429c29d25abfd11b4c60b76718c438 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Sun, 12 Mar 2023 15:18:46 +0700 Subject: [PATCH 25/32] Update REAMDE for vim-easycomplete and remove obsoluted plugins (#265) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9bf1229..e815848 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@ VSCode(LSP)'s snippet feature in vim/nvim. - LSP-client - [vim-lsp](https://github.com/prabirshrestha/vim-lsp) - [vim-lsc](https://github.com/natebosch/vim-lsc) + - [yegappan-lsp](https://github.com/yegappan/lsp) - [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim) - [neovim built-in lsp](https://github.com/neovim/neovim) - - [vim-lamp](https://github.com/hrsh7th/vim-lamp) - completion-engine - - [deoplete.nvim](https://github.com/Shougo/deoplete.nvim) - [asyncomplete.vim](https://github.com/prabirshrestha/asyncomplete.vim) - [vim-mucomplete](https://github.com/lifepillar/vim-mucomplete) - - [completion-nvim](https://github.com/nvim-lua/completion-nvim) + - [ddc.vim](https://github.com/Shougo/ddc.vim) + - [vim-easycompletion](https://github.com/jayli/vim-easycomplete) - Vim script interpolation - You can use Vim script interpolation as `${VIM:...Vim script expression...}`. - SnipMate-like syntax support From be277461265f1e5c7db470aa479f30956597ea9e Mon Sep 17 00:00:00 2001 From: NAKAI Tsuyoshi <82267684+uga-rosa@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:25:11 +0900 Subject: [PATCH 26/32] Fix bug in s:int() that only recognizes the first character (#273) --- autoload/vsnip/snippet/parser.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/vsnip/snippet/parser.vim b/autoload/vsnip/snippet/parser.vim index ddc6010..be0d1dc 100644 --- a/autoload/vsnip/snippet/parser.vim +++ b/autoload/vsnip/snippet/parser.vim @@ -37,7 +37,7 @@ let s:slash = s:token('/') let s:comma = s:token(',') let s:pipe = s:token('|') let s:varname = s:pattern('[_[:alpha:]]\w*') -let s:int = s:map(s:pattern('\d\+'), { value -> str2nr(value[0]) }) +let s:int = s:map(s:pattern('\d\+'), { value -> str2nr(value) }) let s:text = { stop, escape -> s:map( \ s:skip(stop, escape), \ { value -> { From 8eebdf6ab4a880d845893f210fd20516d2e2384f Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Sun, 12 Nov 2023 17:05:03 +0900 Subject: [PATCH 27/32] Fix for Next.js file pattern --- autoload/vital/_vsnip/VS/LSP/TextEdit.vim | 4 ++-- autoload/vital/_vsnip/VS/Vim/Buffer.vim | 18 ++++++++++++++++-- autoload/vital/vsnip.vim | 6 +++++- autoload/vital/vsnip.vital | 2 +- ftplugin/snippets.vim | 4 ++-- indent/snippets.vim | 4 ++-- 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/autoload/vital/_vsnip/VS/LSP/TextEdit.vim b/autoload/vital/_vsnip/VS/LSP/TextEdit.vim index 7d6504f..968f8d5 100644 --- a/autoload/vital/_vsnip/VS/LSP/TextEdit.vim +++ b/autoload/vital/_vsnip/VS/LSP/TextEdit.vim @@ -172,10 +172,10 @@ endfunction " function! s:_switch(path) abort let l:curr = bufnr('%') - let l:next = bufnr(a:path) + let l:next = filereadable(a:path) ? bufnr(fnameescape(a:path)) : bufnr(a:path) if l:next >= 0 if l:curr != l:next - execute printf('noautocmd keepalt keepjumps %sbuffer!', bufnr(a:path)) + execute printf('noautocmd keepalt keepjumps %sbuffer!', l:next) endif else execute printf('noautocmd keepalt keepjumps edit! %s', fnameescape(a:path)) diff --git a/autoload/vital/_vsnip/VS/Vim/Buffer.vim b/autoload/vital/_vsnip/VS/Vim/Buffer.vim index b5c10f3..f1275a7 100644 --- a/autoload/vital/_vsnip/VS/Vim/Buffer.vim +++ b/autoload/vital/_vsnip/VS/Vim/Buffer.vim @@ -4,7 +4,7 @@ function! s:_SID() abort return matchstr(expand(''), '\zs\d\+\ze__SID$') endfunction -execute join(['function! vital#_vsnip#VS#Vim#Buffer#import() abort', printf("return map({'get_line_count': '', 'do': '', 'create': '', 'pseudo': '', 'ensure': '', 'load': ''}, \"vital#_vsnip#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") +execute join(['function! vital#_vsnip#VS#Vim#Buffer#import() abort', printf("return map({'add': '', 'do': '', 'create': '', 'get_line_count': '', 'pseudo': '', 'ensure': '', 'load': ''}, \"vital#_vsnip#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") delfunction s:_SID " ___vital___ let s:Do = { -> {} } @@ -51,11 +51,25 @@ function! s:ensure(expr) abort if type(a:expr) == type(0) throw printf('VS.Vim.Buffer: `%s` is not valid expr.', a:expr) endif - badd `=a:expr` + call s:add(a:expr) endif return bufnr(a:expr) endfunction +" +" add +" +if exists('*bufadd') + function! s:add(name) abort + let l:bufnr = bufadd(a:name) + call setbufvar(l:bufnr, '&buflisted', 1) + endfunction +else + function! s:add(name) abort + badd `=a:name` + endfunction +endif + " " load " diff --git a/autoload/vital/vsnip.vim b/autoload/vital/vsnip.vim index 6730f4e..5b7b102 100644 --- a/autoload/vital/vsnip.vim +++ b/autoload/vital/vsnip.vim @@ -190,6 +190,8 @@ function! s:_format_throwpoint(throwpoint) abort return join(funcs, "\n") endfunction +" @vimlint(EVL102, 1, l:_) +" @vimlint(EVL102, 1, l:__) function! s:_get_func_info(name) abort let name = a:name if a:name =~# '^\d\+$' " is anonymous-function @@ -213,8 +215,10 @@ function! s:_get_func_info(name) abort \ 'attrs': filter(['dict', 'abort', 'range', 'closure'], 'signature =~# (").*" . v:val)'), \ } endfunction +" @vimlint(EVL102, 0, l:__) +" @vimlint(EVL102, 0, l:_) -" s:_get_module() returns module object wihch has all script local functions. +" s:_get_module() returns module object which has all script local functions. function! s:_get_module(name) abort dict let funcname = s:_import_func_name(self.plugin_name(), a:name) try diff --git a/autoload/vital/vsnip.vital b/autoload/vital/vsnip.vital index 8bc4646..8f293cd 100644 --- a/autoload/vital/vsnip.vital +++ b/autoload/vital/vsnip.vital @@ -1,5 +1,5 @@ vsnip -2755f0c8fbd3442bcb7f567832e4d1455b57f9a2 +f28b0d147b702686817da56eef47e0736f425233 VS.LSP.TextEdit VS.LSP.Diff diff --git a/ftplugin/snippets.vim b/ftplugin/snippets.vim index 2ef49c8..3ecefa5 100644 --- a/ftplugin/snippets.vim +++ b/ftplugin/snippets.vim @@ -23,12 +23,12 @@ " Vim filetype plugin for SnipMate snippets (.snippets and .snippet files) -if exists("b:did_ftplugin") +if exists('b:did_ftplugin') finish endif let b:did_ftplugin = 1 -let b:undo_ftplugin = "setl et< sts< cms< fdm< fde<" +let b:undo_ftplugin = 'setl et< sts< cms< fdm< fde<' " Use hard tabs setlocal noexpandtab softtabstop=0 diff --git a/indent/snippets.vim b/indent/snippets.vim index 824f056..7deb397 100644 --- a/indent/snippets.vim +++ b/indent/snippets.vim @@ -32,14 +32,14 @@ setlocal nosmartindent setlocal indentkeys=!^F,o,O,=snippet,=extends setlocal indentexpr=GetSnippetIndent() -if exists("*GetSnippetIndent") +if exists('*GetSnippetIndent') finish endif function! GetSnippetIndent() let line = getline(v:lnum) let prev_lnum = v:lnum - 1 - let prev_line = prev_lnum != 0 ? getline(prev_lnum) : "" + let prev_line = prev_lnum != 0 ? getline(prev_lnum) : '' if line =~# '\v^(snippet|extends) ' return 0 From 02a8e79295c9733434aab4e0e2b8c4b7cea9f3a9 Mon Sep 17 00:00:00 2001 From: giri Date: Thu, 11 Jan 2024 02:41:39 +0100 Subject: [PATCH 28/32] Add another completion engine that supports vim-vsnip (#275) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e815848..dc7923c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ VSCode(LSP)'s snippet feature in vim/nvim. - completion-engine - [asyncomplete.vim](https://github.com/prabirshrestha/asyncomplete.vim) - [vim-mucomplete](https://github.com/lifepillar/vim-mucomplete) + - [vimcomplete](https://github.com/girishji/vimcomplete) - [ddc.vim](https://github.com/Shougo/ddc.vim) - [vim-easycompletion](https://github.com/jayli/vim-easycomplete) - Vim script interpolation From b7445b3c43acb08c0b74350d046e0088ece88033 Mon Sep 17 00:00:00 2001 From: "Dmytro Z." Date: Fri, 28 Mar 2025 05:05:53 +0200 Subject: [PATCH 29/32] Add `treesitter` integration (#280) * feat: add treesitter integration * feat: add treesitter integration * chore: lint code * chore: bump dependencies * chore: bump dependencies * chore: lint code * chore: update lua call --- .editorconfig | 13 +++++++++ .github/workflows/linux_neovim.yml | 1 - README.md | 5 ++-- autoload/vital/_vsnip/VS/LSP/Diff.vim | 1 - autoload/vital/_vsnip/VS/LSP/Position.vim | 1 - autoload/vital/_vsnip/VS/LSP/Text.vim | 1 - autoload/vital/_vsnip/VS/LSP/TextEdit.vim | 1 - autoload/vital/_vsnip/VS/Vim/Buffer.vim | 1 - autoload/vital/_vsnip/VS/Vim/Option.vim | 1 - autoload/vsnip/indent.vim | 1 - autoload/vsnip/parser/combinator.vim | 1 - autoload/vsnip/range.vim | 1 - autoload/vsnip/session.vim | 1 - autoload/vsnip/snippet/node/transform.vim | 2 +- autoload/vsnip/snippet/node/variable.vim | 1 - autoload/vsnip/snippet/parser.vim | 17 ++++++------ autoload/vsnip/source.vim | 12 +++++--- autoload/vsnip/source/user_snippet.vim | 1 - autoload/vsnip/source/vscode.vim | 1 - autoload/vsnip/variable.vim | 1 - lua/vsnip/treesitter.lua | 34 +++++++++++++++++++++++ package.json | 22 +++++++-------- plugin/vsnip.vim | 1 - 23 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 .editorconfig create mode 100644 lua/vsnip/treesitter.lua diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b5ba17a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = off + +[*.{yaml,yml}] +indent_size = 2 diff --git a/.github/workflows/linux_neovim.yml b/.github/workflows/linux_neovim.yml index 18231a8..3c5ca68 100644 --- a/.github/workflows/linux_neovim.yml +++ b/.github/workflows/linux_neovim.yml @@ -46,4 +46,3 @@ jobs: themis ./spec export VIRTUALEDIT=1 themis ./spec - diff --git a/README.md b/README.md index dc7923c..89f2676 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ The snippet format was described in [here](https://code.visualstudio.com/docs/ed # Recipe -### $TM_FILENAME_BASE +### $TM\_FILENAME\_BASE You can insert the filename via `fname\(vsnip-expand)`. @@ -133,7 +133,7 @@ You can insert the filename via `fname\(vsnip-expand)`. } ``` -### Log $TM_SELECTED_TEXT +### Log $TM\_SELECTED\_TEXT You can fill `$TM_SELECTED_TEXT` by `(vsnip-select-text)` or `(vsnip-cut-text)`. @@ -197,4 +197,3 @@ You can run `npm run test` after install [vim-themis](https://github.com/thinca/ 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` - diff --git a/autoload/vital/_vsnip/VS/LSP/Diff.vim b/autoload/vital/_vsnip/VS/LSP/Diff.vim index ae7cda3..9e4c02e 100644 --- a/autoload/vital/_vsnip/VS/LSP/Diff.vim +++ b/autoload/vital/_vsnip/VS/LSP/Diff.vim @@ -161,4 +161,3 @@ if has('nvim') catch /.*/ endtry endif - diff --git a/autoload/vital/_vsnip/VS/LSP/Position.vim b/autoload/vital/_vsnip/VS/LSP/Position.vim index ff38e69..81f5690 100644 --- a/autoload/vital/_vsnip/VS/LSP/Position.vim +++ b/autoload/vital/_vsnip/VS/LSP/Position.vim @@ -59,4 +59,3 @@ function! s:_get_buffer_line(expr, lnum) abort endif return v:null endfunction - diff --git a/autoload/vital/_vsnip/VS/LSP/Text.vim b/autoload/vital/_vsnip/VS/LSP/Text.vim index 5062a62..3bef78c 100644 --- a/autoload/vital/_vsnip/VS/LSP/Text.vim +++ b/autoload/vital/_vsnip/VS/LSP/Text.vim @@ -20,4 +20,3 @@ endfunction function! s:split_by_eol(text) abort return split(a:text, "\r\n\\|\r\\|\n", v:true) endfunction - diff --git a/autoload/vital/_vsnip/VS/LSP/TextEdit.vim b/autoload/vital/_vsnip/VS/LSP/TextEdit.vim index 968f8d5..3acc669 100644 --- a/autoload/vital/_vsnip/VS/LSP/TextEdit.vim +++ b/autoload/vital/_vsnip/VS/LSP/TextEdit.vim @@ -182,4 +182,3 @@ function! s:_switch(path) abort endif return bufnr('%') endfunction - diff --git a/autoload/vital/_vsnip/VS/Vim/Buffer.vim b/autoload/vital/_vsnip/VS/Vim/Buffer.vim index f1275a7..87dd47b 100644 --- a/autoload/vital/_vsnip/VS/Vim/Buffer.vim +++ b/autoload/vital/_vsnip/VS/Vim/Buffer.vim @@ -137,4 +137,3 @@ function! s:pseudo(filepath) abort augroup END return l:bufnr endfunction - diff --git a/autoload/vital/_vsnip/VS/Vim/Option.vim b/autoload/vital/_vsnip/VS/Vim/Option.vim index 9399977..05f42a8 100644 --- a/autoload/vital/_vsnip/VS/Vim/Option.vim +++ b/autoload/vital/_vsnip/VS/Vim/Option.vim @@ -18,4 +18,3 @@ function! s:define(map) abort endfor return { -> s:define(l:old) } endfunction - diff --git a/autoload/vsnip/indent.vim b/autoload/vsnip/indent.vim index 6222dd8..98c0bf2 100644 --- a/autoload/vsnip/indent.vim +++ b/autoload/vsnip/indent.vim @@ -58,4 +58,3 @@ function! vsnip#indent#trim_base_indent(text) abort endfor return substitute(l:text, "\\%(^\\|\n\\)\\zs\\V" . l:base_indent, '', 'g') endfunction - diff --git a/autoload/vsnip/parser/combinator.vim b/autoload/vsnip/parser/combinator.vim index 59bfd83..7d4b06c 100644 --- a/autoload/vsnip/parser/combinator.vim +++ b/autoload/vsnip/parser/combinator.vim @@ -220,4 +220,3 @@ function! s:getchar(text, pos) abort endif return '' endfunction - diff --git a/autoload/vsnip/range.vim b/autoload/vsnip/range.vim index 8ba5321..868ba8f 100644 --- a/autoload/vsnip/range.vim +++ b/autoload/vsnip/range.vim @@ -7,4 +7,3 @@ function! vsnip#range#cover(whole_range, target_range) abort let l:cover = l:cover && (a:target_range.end.line < a:whole_range.end.line || a:target_range.end.line == a:whole_range.end.line && a:target_range.end.character <= a:whole_range.end.character) return l:cover endfunction - diff --git a/autoload/vsnip/session.vim b/autoload/vsnip/session.vim index 58c6fc3..e716e08 100644 --- a/autoload/vsnip/session.vim +++ b/autoload/vsnip/session.vim @@ -257,4 +257,3 @@ function! s:Session.store(changenr) abort \ } let self.changenr = a:changenr endfunction - diff --git a/autoload/vsnip/snippet/node/transform.vim b/autoload/vsnip/snippet/node/transform.vim index f241fc8..64c6280 100644 --- a/autoload/vsnip/snippet/node/transform.vim +++ b/autoload/vsnip/snippet/node/transform.vim @@ -36,7 +36,7 @@ function! s:Transform.text(input_text) abort endif let l:text = '' - + for l:replacement in self.replacements if l:replacement.type ==# 'format' if l:replacement.modifier ==# '/capitalize' diff --git a/autoload/vsnip/snippet/node/variable.vim b/autoload/vsnip/snippet/node/variable.vim index 4de1e48..4442d75 100644 --- a/autoload/vsnip/snippet/node/variable.vim +++ b/autoload/vsnip/snippet/node/variable.vim @@ -60,4 +60,3 @@ function! s:Variable.to_string() abort \ self.text() \ ) endfunction - diff --git a/autoload/vsnip/snippet/parser.vim b/autoload/vsnip/snippet/parser.vim index be0d1dc..44873ee 100644 --- a/autoload/vsnip/snippet/parser.vim +++ b/autoload/vsnip/snippet/parser.vim @@ -5,15 +5,15 @@ let s:Combinator = vsnip#parser#combinator#import() " @see https://github.com/Microsoft/language-server-protocol/blob/master/snippetSyntax.md " function! vsnip#snippet#parser#parse(text) abort - if strlen(a:text) == 0 - return [] - endif + if strlen(a:text) == 0 + return [] + endif - let l:parsed = s:parser.parse(a:text, 0) - if !l:parsed[0] - throw json_encode({ 'text': a:text, 'result': l:parsed }) - endif - return l:parsed[1] + let l:parsed = s:parser.parse(a:text, 0) + if !l:parsed[0] + throw json_encode({ 'text': a:text, 'result': l:parsed }) + endif + return l:parsed[1] endfunction let s:skip = s:Combinator.skip @@ -209,4 +209,3 @@ let s:choice = s:map(s:seq( " parser. " let s:parser = s:many(s:or(s:any, s:text(['$'], ['}']))) - diff --git a/autoload/vsnip/source.vim b/autoload/vsnip/source.vim index a6dc972..6ac3460 100644 --- a/autoload/vsnip/source.vim +++ b/autoload/vsnip/source.vim @@ -21,9 +21,14 @@ endfunction " " vsnip#source#filetypes " -function! vsnip#source#filetypes(bufnr) abort - let l:filetype = getbufvar(a:bufnr, '&filetype', '') - return split(l:filetype, '\.') + get(g:vsnip_filetypes, l:filetype, []) + ['global'] +function! vsnip#source#filetypes( bufnr ) abort + if has( "nvim" ) + let l:filetype = v:lua.require'vsnip.treesitter'.get_ft_at_cursor( a:bufnr ) + else + let l:filetype = getbufvar( a:bufnr, "&filetype", "" ) + endif + + return split( l:filetype, '\.' ) + get( g:vsnip_filetypes, l:filetype, [] ) + [ "global" ] endfunction " @@ -113,4 +118,3 @@ function! vsnip#source#resolve_prefix(prefix) abort \ sort(l:prefixes_alias, { a, b -> strlen(b) - strlen(a) }) \ ] endfunction - diff --git a/autoload/vsnip/source/user_snippet.vim b/autoload/vsnip/source/user_snippet.vim index b7e959f..5d6cc64 100644 --- a/autoload/vsnip/source/user_snippet.vim +++ b/autoload/vsnip/source/user_snippet.vim @@ -66,4 +66,3 @@ endfun fun! vsnip#source#user_snippet#paths(...) abort return s:get_source_paths(a:0 ? a:1 : bufnr('')) endfun - diff --git a/autoload/vsnip/source/vscode.vim b/autoload/vsnip/source/vscode.vim index e673e41..18e2d53 100644 --- a/autoload/vsnip/source/vscode.vim +++ b/autoload/vsnip/source/vscode.vim @@ -101,4 +101,3 @@ function! s:get_language(filetype) abort \ 'cs': 'csharp', \ }, a:filetype, a:filetype) endfunction - diff --git a/autoload/vsnip/variable.vim b/autoload/vsnip/variable.vim index 54efd83..8cae2d4 100644 --- a/autoload/vsnip/variable.vim +++ b/autoload/vsnip/variable.vim @@ -186,4 +186,3 @@ function! s:VSNIP_CAMELCASE_FILENAME(context) abort return substitute(l:basename, '\(\%(\<\l\+\)\%(_\)\@=\)\|_\(\l\)', '\u\1\2', 'g') endfunction call vsnip#variable#register('VSNIP_CAMELCASE_FILENAME', function('s:VSNIP_CAMELCASE_FILENAME')) - diff --git a/lua/vsnip/treesitter.lua b/lua/vsnip/treesitter.lua new file mode 100644 index 0000000..6a9c033 --- /dev/null +++ b/lua/vsnip/treesitter.lua @@ -0,0 +1,34 @@ +local M = {} + +local ok_parsers, ts_parsers = pcall( require, "nvim-treesitter.parsers" ) +if not ok_parsers then + ts_parsers = nil +end + +local ok_utils, ts_utils = pcall( require, "nvim-treesitter.ts_utils" ) +if not ok_utils then + ts_utils = nil +end + +function M.is_available () + return ok_parsers and ok_utils +end + +function M.get_ft_at_cursor ( bufnr ) + if M.is_available() then + local cur_node = ts_utils.get_node_at_cursor( vim.fn.bufwinid( bufnr ) ) + + if cur_node then + local parser = ts_parsers.get_parser( bufnr ) + local lang = parser:language_for_range( { cur_node:range() } ):lang() + + if ts_parsers.list[ lang ] ~= nil then + return ts_parsers.list[ lang ].filetype or lang + end + end + end + + return vim.bo[ bufnr ].filetype or "" +end + +return M diff --git a/package.json b/package.json index f454311..f83d652 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,16 @@ "name": "vim-vsnip", "version": "1.0.0", "description": "This aims to plugin like Visual Studio Code's Snippet feature.", + "homepage": "https://github.com/hrsh7th/vim-test-snips#readme", + "bugs": { + "url": "https://github.com/hrsh7th/vim-test-snips/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hrsh7th/vim-test-snips.git" + }, + "license": "MIT", + "author": "hrsh7th", "scripts": { "open": "nvim -u .vimrc", "test": "run-s test:*", @@ -20,18 +30,8 @@ "pre-commit": "npm run lint && npm run test" } }, - "repository": { - "type": "git", - "url": "git+https://github.com/hrsh7th/vim-test-snips.git" - }, - "author": "hrsh7th", - "license": "MIT", - "bugs": { - "url": "https://github.com/hrsh7th/vim-test-snips/issues" - }, - "homepage": "https://github.com/hrsh7th/vim-test-snips#readme", "devDependencies": { - "husky": "^3.0.5", + "husky": "^9.0.0", "npm-run-all": "^4.1.5", "watch": "^1.0.2" } diff --git a/plugin/vsnip.vim b/plugin/vsnip.vim index 2f4f0a7..19e42e6 100644 --- a/plugin/vsnip.vim +++ b/plugin/vsnip.vim @@ -222,4 +222,3 @@ endfunction function! s:on_buf_write_post() abort call vsnip#source#refresh(resolve(fnamemodify(bufname('%'), ':p'))) endfunction - From 0a4b8419e44f47c57eec4c90df17567ad4b1b36e Mon Sep 17 00:00:00 2001 From: "Dmytro Z." Date: Tue, 8 Apr 2025 17:31:55 +0300 Subject: [PATCH 30/32] Refactor treesitter filetype detection (#281) * feat: refactor treesitter filetype detection * feat: refactor treesitter filetype detection * feat: refactor treesitter filetype detection * feat: refactor treesitter filetype detection * feat: refactor treesitter filetype detection * feat: refactor treesitter filetype detection * chore: update docs * chore: check filetype separator to '.' * chore: revert filetype separator to '/' * chore: revert filetype separator to '/' --- autoload/vsnip/source.vim | 20 +++++++++++++++--- doc/vsnip.txt | 10 +++++++++ lua/vsnip/treesitter.lua | 43 +++++++++++++++++++++++++++++++++------ 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/autoload/vsnip/source.vim b/autoload/vsnip/source.vim index 6ac3460..28df252 100644 --- a/autoload/vsnip/source.vim +++ b/autoload/vsnip/source.vim @@ -23,12 +23,26 @@ endfunction " function! vsnip#source#filetypes( bufnr ) abort if has( "nvim" ) - let l:filetype = v:lua.require'vsnip.treesitter'.get_ft_at_cursor( a:bufnr ) + let l:filetypes = v:lua.require'vsnip.treesitter'.get_ft_at_cursor( a:bufnr ) + + " buffer has no filetype defined + if l:filetypes.filetype == "" + return [ "global" ] + + " buffer has filetype + else + return + \ get( g:vsnip_filetypes, l:filetypes.injected_filetype, + \ get( g:vsnip_filetypes, l:filetypes.filetype, + \ [ l:filetypes.filetype ] + \ ) ) + \ + [ "global" ] + endif else let l:filetype = getbufvar( a:bufnr, "&filetype", "" ) - endif - return split( l:filetype, '\.' ) + get( g:vsnip_filetypes, l:filetype, [] ) + [ "global" ] + return split( l:filetype, '\.' ) + get( g:vsnip_filetypes, l:filetype, [] ) + [ "global" ] + endif endfunction " diff --git a/doc/vsnip.txt b/doc/vsnip.txt index 2a65afa..c26c09f 100644 --- a/doc/vsnip.txt +++ b/doc/vsnip.txt @@ -71,6 +71,16 @@ VARIABLE *vsnip-variable* let g:vsnip_filetypes = {} let g:vsnip_filetypes.javascriptreact = ['javascript'] < + + If you are using `treesitter` you can define snippets for injected + languages like this: + +> + let g:vsnip_filetypes['vim/lua'] = ['lua', 'vim/lua'] + let g:vsnip_filetypes['vue'] = ['html'] + let g:vsnip_filetypes['vue/javascript'] = ['javascript', 'vue/javascript'] +< + let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfSnippet~ Specify when to deactivate the current snippet. diff --git a/lua/vsnip/treesitter.lua b/lua/vsnip/treesitter.lua index 6a9c033..311be5a 100644 --- a/lua/vsnip/treesitter.lua +++ b/lua/vsnip/treesitter.lua @@ -10,25 +10,56 @@ if not ok_utils then ts_utils = nil end -function M.is_available () +local function get_parser_filetype ( lang ) + if lang and ts_parsers.list[ lang ] then + return ts_parsers.list[ lang ].filetype or lang + else + return "" + end +end + +local function is_available () return ok_parsers and ok_utils end function M.get_ft_at_cursor ( bufnr ) - if M.is_available() then + local filetypes = { + filetype = "", + injected_filetype = "", + } + + if is_available() then local cur_node = ts_utils.get_node_at_cursor( vim.fn.bufwinid( bufnr ) ) if cur_node then local parser = ts_parsers.get_parser( bufnr ) - local lang = parser:language_for_range( { cur_node:range() } ):lang() + local language_tree_at_cursor = parser:language_for_range( { cur_node:range() } ) + local language_at_cursor = language_tree_at_cursor:lang() + + local filetype = get_parser_filetype( language_at_cursor ) - if ts_parsers.list[ lang ] ~= nil then - return ts_parsers.list[ lang ].filetype or lang + if filetype ~= "" then + filetypes.filetype = filetype + + local parent_language_tree = language_tree_at_cursor:parent() + + if parent_language_tree then + local parent_language = parent_language_tree:lang() + local parent_filetype = get_parser_filetype( parent_language ) + + if parent_filetype ~= "" then + filetypes.injected_filetype = parent_filetype .. "/" .. filetype + end + end + + return filetypes end end end - return vim.bo[ bufnr ].filetype or "" + filetypes.filetype = vim.bo[ bufnr ].filetype or "" + + return filetypes end return M From a8039976c7ca4393f30076b9fe5a4ade4c20514b Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Fri, 26 Sep 2025 17:41:56 +0900 Subject: [PATCH 31/32] chore: update vim-vital-vs --- autoload/vital/_vsnip/VS/LSP/Diff.vim | 1 + autoload/vital/_vsnip/VS/LSP/Position.vim | 1 + autoload/vital/_vsnip/VS/LSP/Text.vim | 1 + autoload/vital/_vsnip/VS/LSP/TextEdit.vim | 36 ++++++++--------------- autoload/vital/_vsnip/VS/Vim/Buffer.vim | 1 + autoload/vital/_vsnip/VS/Vim/Option.vim | 1 + autoload/vital/vsnip.vital | 2 +- 7 files changed, 19 insertions(+), 24 deletions(-) diff --git a/autoload/vital/_vsnip/VS/LSP/Diff.vim b/autoload/vital/_vsnip/VS/LSP/Diff.vim index 9e4c02e..ae7cda3 100644 --- a/autoload/vital/_vsnip/VS/LSP/Diff.vim +++ b/autoload/vital/_vsnip/VS/LSP/Diff.vim @@ -161,3 +161,4 @@ if has('nvim') catch /.*/ endtry endif + diff --git a/autoload/vital/_vsnip/VS/LSP/Position.vim b/autoload/vital/_vsnip/VS/LSP/Position.vim index 81f5690..ff38e69 100644 --- a/autoload/vital/_vsnip/VS/LSP/Position.vim +++ b/autoload/vital/_vsnip/VS/LSP/Position.vim @@ -59,3 +59,4 @@ function! s:_get_buffer_line(expr, lnum) abort endif return v:null endfunction + diff --git a/autoload/vital/_vsnip/VS/LSP/Text.vim b/autoload/vital/_vsnip/VS/LSP/Text.vim index 3bef78c..5062a62 100644 --- a/autoload/vital/_vsnip/VS/LSP/Text.vim +++ b/autoload/vital/_vsnip/VS/LSP/Text.vim @@ -20,3 +20,4 @@ endfunction function! s:split_by_eol(text) abort return split(a:text, "\r\n\\|\r\\|\n", v:true) endfunction + diff --git a/autoload/vital/_vsnip/VS/LSP/TextEdit.vim b/autoload/vital/_vsnip/VS/LSP/TextEdit.vim index 3acc669..d4a3f36 100644 --- a/autoload/vital/_vsnip/VS/LSP/TextEdit.vim +++ b/autoload/vital/_vsnip/VS/LSP/TextEdit.vim @@ -28,16 +28,21 @@ endfunction " apply " function! s:apply(path, text_edits) abort - let l:current_bufname = bufname('%') - let l:current_position = s:Position.cursor() + let l:current_bufnr = bufnr('%') + for l:bufnr in range(1, bufnr('$')) + if index([fnamemodify(a:path, ':p'),fnameescape(fnamemodify(a:path, ':p'))], bufname(l:bufnr)) != -1 + let l:target_bufnr = l:bufnr + break + endif + endfor + if !exists('l:target_bufnr') + let l:target_bufnr = s:Buffer.ensure(a:path) + endif - let l:target_bufnr = s:_switch(a:path) - call s:_substitute(l:target_bufnr, a:text_edits, l:current_position) - let l:current_bufnr = s:_switch(l:current_bufname) + let l:current_position = s:Position.cursor() + call s:Buffer.do(l:target_bufnr, { -> s:_substitute(l:target_bufnr, a:text_edits, l:current_position) }) - if l:current_bufnr == l:target_bufnr - call cursor(s:Position.lsp_to_vim('%', l:current_position)) - endif + call cursor(s:Position.lsp_to_vim('%', l:current_position)) endfunction " @@ -167,18 +172,3 @@ function! s:_fix_text_edits(bufnr, text_edits) abort return [l:fixeol, l:text_edits] endfunction -" -" _switch -" -function! s:_switch(path) abort - let l:curr = bufnr('%') - let l:next = filereadable(a:path) ? bufnr(fnameescape(a:path)) : bufnr(a:path) - if l:next >= 0 - if l:curr != l:next - execute printf('noautocmd keepalt keepjumps %sbuffer!', l:next) - endif - else - execute printf('noautocmd keepalt keepjumps edit! %s', fnameescape(a:path)) - endif - return bufnr('%') -endfunction diff --git a/autoload/vital/_vsnip/VS/Vim/Buffer.vim b/autoload/vital/_vsnip/VS/Vim/Buffer.vim index 87dd47b..f1275a7 100644 --- a/autoload/vital/_vsnip/VS/Vim/Buffer.vim +++ b/autoload/vital/_vsnip/VS/Vim/Buffer.vim @@ -137,3 +137,4 @@ function! s:pseudo(filepath) abort augroup END return l:bufnr endfunction + diff --git a/autoload/vital/_vsnip/VS/Vim/Option.vim b/autoload/vital/_vsnip/VS/Vim/Option.vim index 05f42a8..9399977 100644 --- a/autoload/vital/_vsnip/VS/Vim/Option.vim +++ b/autoload/vital/_vsnip/VS/Vim/Option.vim @@ -18,3 +18,4 @@ function! s:define(map) abort endfor return { -> s:define(l:old) } endfunction + diff --git a/autoload/vital/vsnip.vital b/autoload/vital/vsnip.vital index 8f293cd..f1d4801 100644 --- a/autoload/vital/vsnip.vital +++ b/autoload/vital/vsnip.vital @@ -1,5 +1,5 @@ vsnip -f28b0d147b702686817da56eef47e0736f425233 +4d4bb61bb1f9b3ac6cd679e3551de88ad3682cc3 VS.LSP.TextEdit VS.LSP.Diff From 9bcfabea653abdcdac584283b5097c3f8760abaa Mon Sep 17 00:00:00 2001 From: hokorobi Date: Sun, 5 Oct 2025 22:45:10 +0900 Subject: [PATCH 32/32] Add completefunc (#283) * Add vsnip#completefunc() * Add test for vsnip#completefunc() * Add vsnip#completefunc to document --- README.md | 3 +++ autoload/vsnip.vim | 19 ++++++++++++++++ doc/vsnip.txt | 5 +++++ spec/autoload/vsnip.vimspec | 45 +++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/README.md b/README.md index 89f2676..bcec69b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ VSCode(LSP)'s snippet feature in vim/nvim. - [vimcomplete](https://github.com/girishji/vimcomplete) - [ddc.vim](https://github.com/Shougo/ddc.vim) - [vim-easycompletion](https://github.com/jayli/vim-easycomplete) +- Support built-in completion + - A function can be registered for finding completions. + `set complete+=Fvsnip#completefunc` - Vim script interpolation - You can use Vim script interpolation as `${VIM:...Vim script expression...}`. - SnipMate-like syntax support diff --git a/autoload/vsnip.vim b/autoload/vsnip.vim index 8069eee..6ccf4b0 100644 --- a/autoload/vsnip.vim +++ b/autoload/vsnip.vim @@ -166,6 +166,25 @@ function! vsnip#get_context() abort return {} endfunction +" +" vsnip#completefunc +" +function! vsnip#completefunc(findstart, base) abort + if !a:findstart + if a:base ==# '' + return [] + endif + return vsnip#get_complete_items(bufnr('%')) + endif + + let line = getline('.') + let start = col('.') - 2 + while start >= 0 && line[start] =~# '\k' + let start -= 1 + endwhile + return start + 1 +endfunction + " " vsnip#get_complete_items " diff --git a/doc/vsnip.txt b/doc/vsnip.txt index c26c09f..040a405 100644 --- a/doc/vsnip.txt +++ b/doc/vsnip.txt @@ -117,7 +117,12 @@ FUNCTION *vsnip-function* Register your own custom variable resolver. + vsnip#completefunc() + Register for finding completions. +> + set complete+=Fvsnip#completefunc +< ============================================================================== MAPPING *vsnip-mapping* diff --git a/spec/autoload/vsnip.vimspec b/spec/autoload/vsnip.vimspec index 3d8bc2e..f2da6a4 100644 --- a/spec/autoload/vsnip.vimspec +++ b/spec/autoload/vsnip.vimspec @@ -145,5 +145,50 @@ Describe vsnip End + Describe #completefunc + + It should return start position + enew! + set filetype=basic_spec + call setline(1, ' if') + call cursor([1, 3]) + call s:expect(vsnip#completefunc(1, '')).to_equal(1) + End + + It should return complete items + enew! + set filetype=basic_spec + call s:expect(vsnip#completefunc(0, 'if')[-1]).to_equal({ + \ 'word': 'if', + \ 'abbr': 'if', + \ 'kind': 'Snippet', + \ 'menu': '[v] if', + \ 'dup': 1, + \ 'user_data': json_encode({ + \ 'vsnip': { + \ 'snippet': [ + \ "if ${1:condition}", + \ "\t$0", + \ "endif", + \ ] + \ } + \ }) + \ }, { + \ 'word': 'inline-fn', + \ 'abbr': 'inline-fn', + \ 'kind': 'Snippet', + \ 'menu': '[v] inline-fn', + \ 'dup': 1, + \ 'user_data': json_encode({ + \ 'vsnip': { + \ 'snippet': [ + \ "{ -> $1 }$0" + \ ] + \ } + \ }) + \ }) + End + End + End