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..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 @@ -120,7 +123,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 +136,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 +200,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/TextEdit.vim b/autoload/vital/_vsnip/VS/LSP/TextEdit.vim index 968f8d5..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,19 +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.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 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/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..28df252 100644 --- a/autoload/vsnip/source.vim +++ b/autoload/vsnip/source.vim @@ -21,9 +21,28 @@ 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: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", "" ) + + return split( l:filetype, '\.' ) + get( g:vsnip_filetypes, l:filetype, [] ) + [ "global" ] + endif endfunction " @@ -113,4 +132,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/doc/vsnip.txt b/doc/vsnip.txt index 2a65afa..040a405 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. @@ -107,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/lua/vsnip/treesitter.lua b/lua/vsnip/treesitter.lua new file mode 100644 index 0000000..311be5a --- /dev/null +++ b/lua/vsnip/treesitter.lua @@ -0,0 +1,65 @@ +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 + +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 ) + 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 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 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 + + filetypes.filetype = vim.bo[ bufnr ].filetype or "" + + return filetypes +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 - 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