I broke up with neovim....vim is my best friend now

This commit is contained in:
LinlyBoi
2023-04-30 08:14:07 +03:00
parent 0d185449c5
commit 4a4a6b1e81
5245 changed files with 468325 additions and 25 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 hrsh7th
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.

View File

@@ -0,0 +1,199 @@
# vim-vsnip
VSCode(LSP)'s snippet feature in vim/nvim.
# Features
- Nested placeholders
- You can define snippet like `console.log($1${2:, $1})$0`
- Nested snippet expansion
- You can expand snippet even if you already activated other snippet (it will be merged as one snippet)
- Load snippet from VSCode extension
- If you install VSCode extension via `Plug 'golang/vscode-go'`, vsnip will load those snippets.
- Support many LSP-client & completion-engine by [vim-vsnip-integ](https://github.com/hrsh7th/vim-vsnip-integ)
- LSP-client
- [vim-lsp](https://github.com/prabirshrestha/vim-lsp)
- [vim-lsc](https://github.com/natebosch/vim-lsc)
- [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)
- Vim script interpolation
- You can use Vim script interpolation as `${VIM:...Vim script expression...}`.
- SnipMate-like syntax support
- Snippet files in SnipMate format with the extension `.snippets` can be load.
- NOTE: Full compatibility is not guaranteed. It is intended to easily create user-defined snippets.
# Concept
- Pure Vim script
- Well tested (neovim/0.4.4, vim/8.0.1567)
- Support VSCode snippet format
- Provide integration with many plugins
# Related repository
[friendly-snippets](https://github.com/rafamadriz/friendly-snippets) - Set of preconfigured snippets for all kind of programming languages that integrates really well with [vim-vsnip](https://github.com/hrsh7th/vim-vsnip), so all users can benefit from them and not to worry about setting up snippets on their own.
# Usage
### 1. Install
You can use your favorite plugin managers to install this plugin.
```viml
Plug 'hrsh7th/vim-vsnip'
Plug 'hrsh7th/vim-vsnip-integ'
call dein#add('hrsh7th/vim-vsnip')
call dein#add('hrsh7th/vim-vsnip-integ')
NeoBundle 'hrsh7th/vim-vsnip'
NeoBundle 'hrsh7th/vim-vsnip-integ'
```
### 2. Setting
```viml
" NOTE: You can use other key to expand snippet.
" Expand
imap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<C-j>'
smap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<C-j>'
" Expand or jump
imap <expr> <C-l> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
smap <expr> <C-l> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
" Jump forward or backward
imap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
smap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
imap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'
smap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'
" Select or cut text to use as $TM_SELECTED_TEXT in the next snippet.
" See https://github.com/hrsh7th/vim-vsnip/pull/50
nmap s <Plug>(vsnip-select-text)
xmap s <Plug>(vsnip-select-text)
nmap S <Plug>(vsnip-cut-text)
xmap S <Plug>(vsnip-cut-text)
" If you want to use snippet for multiple filetypes, you can `g:vsnip_filetypes` for it.
let g:vsnip_filetypes = {}
let g:vsnip_filetypes.javascriptreact = ['javascript']
let g:vsnip_filetypes.typescriptreact = ['typescript']
```
### 3. Create your own snippet
Snippet file will store to `g:vsnip_snippet_dir` per filetype.
1. Open some file (example: `Sample.js`)
2. Invoke `:VsnipOpen` command.
3. Edit snippet.
```json
{
"Class": {
"prefix": ["class"],
"body": [
"/**",
" * @author ${VIM:\\$USER}",
" */",
"class $1 ${2:extends ${3:Parent} }{",
"\tconstructor() {",
"\t\t$0",
"\t}",
"}"
],
"description": "Class definition template."
}
}
```
The snippet format was described in [here](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax) or [here](https://github.com/Microsoft/language-server-protocol/blob/master/snippetSyntax.md).
# Recipe
### $TM_FILENAME_BASE
You can insert the filename via `fname\<Plug>(vsnip-expand)`.
```json
{
"filename": {
"prefix": ["fname"],
"body": "$TM_FILENAME_BASE"
}
}
```
### Log $TM_SELECTED_TEXT
You can fill `$TM_SELECTED_TEXT` by `<Plug>(vsnip-select-text)` or `<Plug>(vsnip-cut-text)`.
```json
{
"log": {
"prefix": ["log"],
"body": "console.log(${1:$TM_SELECTED_TEXT});"
}
}
```
### Insert environment vars
You can insert value by Vim script expression.
```json
{
"user": {
"prefix": "username",
"body": "${VIM:\\$USER}"
}
}
```
### Insert UUID via python
You can insert UUID via python.
```json
{
"uuid": {
"prefix": "uuid",
"body": [
"${VIM:system('python -c \"import uuid, sys;sys.stdout.write(str(uuid.uuid4()))\"')}"
]
}
}
```
NOTE: `$VIM` is only in vsnip. So that makes to lost the snippet portability.
# DEMO
### LSP integration
<img src="https://user-images.githubusercontent.com/629908/90160819-3bd3ec80-ddcd-11ea-919b-577d7eb559a4.gif" width="480" alt="Nested snippet expansion" />
### `<Plug(vsnip-cut-text)` with `$TM_SELECTED_TEXT`
<img src="https://user-images.githubusercontent.com/629908/90157756-17761100-ddc9-11ea-843f-d8b0d529ac61.gif" width="480" alt="&lt;Plug&rt;(vsnip-cut-text) with $TM_SELECTED_TEXT" />
# Development
### How to run test it?
You can run `npm run test` after install [vim-themis](https://github.com/thinca/vim-themis).
### How sync same tabstop placeholders?
1. compute the `user-diff` ... `s:Session.flush_changes`
2. reflect the `user-diff` to snippet ast ... `s:Snippet.follow`
3. reflect the `sync-diff` to buffer content ... `s:Snippet.sync & s:Session.flush_changes`

View File

@@ -0,0 +1,9 @@
let s:_plugin_name = expand('<sfile>:t:r')
function! vital#{s:_plugin_name}#new() abort
return vital#{s:_plugin_name[1:]}#new()
endfunction
function! vital#{s:_plugin_name}#function(funcname) abort
silent! return function(a:funcname)
endfunction

View File

@@ -0,0 +1,164 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_vsnip#VS#LSP#Diff#import() abort', printf("return map({'try_enable_lua': '', 'compute': ''}, \"vital#_vsnip#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
"
" compute
"
function! s:compute(old, new) abort
let l:old = a:old
let l:new = a:new
let l:old_len = len(l:old)
let l:new_len = len(l:new)
let l:min_len = min([l:old_len, l:new_len])
" empty -> empty
if l:old_len == 0 && l:new_len == 0
return {
\ 'range': {
\ 'start': {
\ 'line': 0,
\ 'character': 0,
\ },
\ 'end': {
\ 'line': 0,
\ 'character': 0,
\ }
\ },
\ 'text': '',
\ 'rangeLength': 0
\ }
" not empty -> empty
elseif l:old_len != 0 && l:new_len == 0
return {
\ 'range': {
\ 'start': {
\ 'line': 0,
\ 'character': 0,
\ },
\ 'end': {
\ 'line': l:old_len - 1,
\ 'character': strchars(l:old[-1]),
\ }
\ },
\ 'text': '',
\ 'rangeLength': strchars(join(l:old, "\n"))
\ }
" empty -> not empty
elseif l:old_len == 0 && l:new_len != 0
return {
\ 'range': {
\ 'start': {
\ 'line': 0,
\ 'character': 0,
\ },
\ 'end': {
\ 'line': 0,
\ 'character': 0,
\ }
\ },
\ 'text': join(l:new, "\n"),
\ 'rangeLength': 0
\ }
endif
if s:is_lua_enabled
let [l:first_line, l:last_line] = luaeval('vital_vs_lsp_diff_search_line_region(_A[1], _A[2])', [l:old, l:new])
else
let l:first_line = 0
while l:first_line < l:min_len - 1
if l:old[l:first_line] !=# l:new[l:first_line]
break
endif
let l:first_line += 1
endwhile
let l:last_line = -1
while l:last_line > -l:min_len + l:first_line
if l:old[l:last_line] !=# l:new[l:last_line]
break
endif
let l:last_line -= 1
endwhile
endif
let l:old_lines = l:old[l:first_line : l:last_line]
let l:new_lines = l:new[l:first_line : l:last_line]
let l:old_text = join(l:old_lines, "\n") . "\n"
let l:new_text = join(l:new_lines, "\n") . "\n"
let l:old_text_len = strchars(l:old_text)
let l:new_text_len = strchars(l:new_text)
let l:min_text_len = min([l:old_text_len, l:new_text_len])
let l:first_char = 0
for l:first_char in range(0, l:min_text_len - 1)
if strgetchar(l:old_text, l:first_char) != strgetchar(l:new_text, l:first_char)
break
endif
endfor
let l:last_char = 0
for l:last_char in range(0, -l:min_text_len + l:first_char, -1)
if strgetchar(l:old_text, l:old_text_len + l:last_char - 1) != strgetchar(l:new_text, l:new_text_len + l:last_char - 1)
break
endif
endfor
return {
\ 'range': {
\ 'start': {
\ 'line': l:first_line,
\ 'character': l:first_char,
\ },
\ 'end': {
\ 'line': l:old_len + l:last_line,
\ 'character': strchars(l:old_lines[-1]) + l:last_char + 1,
\ }
\ },
\ 'text': strcharpart(l:new_text, l:first_char, l:new_text_len + l:last_char - l:first_char),
\ 'rangeLength': l:old_text_len + l:last_char - l:first_char
\ }
endfunction
function! s:try_enable_lua() abort
lua <<EOF
function vital_vs_lsp_diff_search_line_region(old, new)
local old_len = #old
local new_len = #new
local min_len = math.min(#old, #new)
local first_line = 0
while first_line < min_len - 1 do
if old[first_line + 1] ~= new[first_line + 1] then
break
end
first_line = first_line + 1
end
local last_line = -1
while last_line > -min_len + first_line do
if old[(old_len + last_line) + 1] ~= new[(new_len + last_line) + 1] then
break
end
last_line = last_line - 1
end
return { first_line, last_line }
end
EOF
endfunction
let s:is_lua_enabled = v:false
if has('nvim')
try
call s:try_enable_lua()
let s:is_lua_enabled = v:true
catch /.*/
endtry
endif

View File

@@ -0,0 +1,62 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_vsnip#VS#LSP#Position#import() abort', printf("return map({'cursor': '', 'vim_to_lsp': '', 'lsp_to_vim': ''}, \"vital#_vsnip#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
"
" cursor
"
function! s:cursor() abort
return s:vim_to_lsp('%', getpos('.')[1 : 3])
endfunction
"
" vim_to_lsp
"
function! s:vim_to_lsp(expr, pos) abort
let l:line = s:_get_buffer_line(a:expr, a:pos[0])
if l:line is v:null
return {
\ 'line': a:pos[0] - 1,
\ 'character': a:pos[1] - 1
\ }
endif
return {
\ 'line': a:pos[0] - 1,
\ 'character': strchars(strpart(l:line, 0, a:pos[1] - 1))
\ }
endfunction
"
" lsp_to_vim
"
function! s:lsp_to_vim(expr, position) abort
let l:line = s:_get_buffer_line(a:expr, a:position.line + 1)
if l:line is v:null
return [a:position.line + 1, a:position.character + 1]
endif
return [a:position.line + 1, byteidx(l:line, a:position.character) + 1]
endfunction
"
" _get_buffer_line
"
function! s:_get_buffer_line(expr, lnum) abort
try
let l:expr = bufnr(a:expr)
catch /.*/
let l:expr = a:expr
endtry
if bufloaded(l:expr)
return get(getbufline(l:expr, a:lnum), 0, v:null)
elseif filereadable(a:expr)
return get(readfile(a:expr, '', a:lnum), 0, v:null)
endif
return v:null
endfunction

View File

@@ -0,0 +1,23 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_vsnip#VS#LSP#Text#import() abort', printf("return map({'normalize_eol': '', 'split_by_eol': ''}, \"vital#_vsnip#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
"
" normalize_eol
"
function! s:normalize_eol(text) abort
return substitute(a:text, "\r\n\\|\r", "\n", 'g')
endfunction
"
" split_by_eol
"
function! s:split_by_eol(text) abort
return split(a:text, "\r\n\\|\r\\|\n", v:true)
endfunction

View File

@@ -0,0 +1,185 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_vsnip#VS#LSP#TextEdit#import() abort', printf("return map({'_vital_depends': '', 'apply': '', '_vital_loaded': ''}, \"vital#_vsnip#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
"
" _vital_loaded
"
function! s:_vital_loaded(V) abort
let s:Text = a:V.import('VS.LSP.Text')
let s:Position = a:V.import('VS.LSP.Position')
let s:Buffer = a:V.import('VS.Vim.Buffer')
let s:Option = a:V.import('VS.Vim.Option')
endfunction
"
" _vital_depends
"
function! s:_vital_depends() abort
return ['VS.LSP.Text', 'VS.LSP.Position', 'VS.Vim.Buffer', 'VS.Vim.Option']
endfunction
"
" apply
"
function! s:apply(path, text_edits) abort
let l:current_bufname = bufname('%')
let l:current_position = s:Position.cursor()
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)
if l:current_bufnr == l:target_bufnr
call cursor(s:Position.lsp_to_vim('%', l:current_position))
endif
endfunction
"
" _substitute
"
function! s:_substitute(bufnr, text_edits, current_position) abort
try
" Save state.
let l:Restore = s:Option.define({
\ 'foldenable': '0',
\ })
let l:view = winsaveview()
" Apply substitute.
let [l:fixeol, l:text_edits] = s:_normalize(a:bufnr, a:text_edits)
for l:text_edit in l:text_edits
let l:start = s:Position.lsp_to_vim(a:bufnr, l:text_edit.range.start)
let l:end = s:Position.lsp_to_vim(a:bufnr, l:text_edit.range.end)
let l:text = s:Text.normalize_eol(l:text_edit.newText)
execute printf('noautocmd keeppatterns keepjumps silent %ssubstitute/\%%%sl\%%%sc\_.\{-}\%%%sl\%%%sc/\=l:text/%se',
\ l:start[0],
\ l:start[0],
\ l:start[1],
\ l:end[0],
\ l:end[1],
\ &gdefault ? 'g' : ''
\ )
call s:_fix_cursor_position(a:current_position, l:text_edit, s:Text.split_by_eol(l:text))
endfor
" Remove last empty line if fixeol enabled.
if l:fixeol && getline('$') ==# ''
noautocmd keeppatterns keepjumps silent $delete _
endif
catch /.*/
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
finally
" Restore state.
call l:Restore()
call winrestview(l:view)
endtry
endfunction
"
" _fix_cursor_position
"
function! s:_fix_cursor_position(position, text_edit, lines) abort
let l:lines_len = len(a:lines)
let l:range_len = (a:text_edit.range.end.line - a:text_edit.range.start.line) + 1
if a:text_edit.range.end.line < a:position.line
let a:position.line += l:lines_len - l:range_len
elseif a:text_edit.range.end.line == a:position.line && a:text_edit.range.end.character <= a:position.character
let a:position.line += l:lines_len - l:range_len
let a:position.character = strchars(a:lines[-1]) + (a:position.character - a:text_edit.range.end.character)
if l:lines_len == 1
let a:position.character += a:text_edit.range.start.character
endif
endif
endfunction
"
" _normalize
"
function! s:_normalize(bufnr, text_edits) abort
let l:text_edits = type(a:text_edits) == type([]) ? a:text_edits : [a:text_edits]
let l:text_edits = s:_range(l:text_edits)
let l:text_edits = sort(l:text_edits, function('s:_compare'))
let l:text_edits = reverse(l:text_edits)
return s:_fix_text_edits(a:bufnr, l:text_edits)
endfunction
"
" _range
"
function! s:_range(text_edits) abort
let l:text_edits = []
for l:text_edit in a:text_edits
if type(l:text_edit) != type({})
continue
endif
if l:text_edit.range.start.line > l:text_edit.range.end.line || (
\ l:text_edit.range.start.line == l:text_edit.range.end.line &&
\ l:text_edit.range.start.character > l:text_edit.range.end.character
\ )
let l:text_edit.range = { 'start': l:text_edit.range.end, 'end': l:text_edit.range.start }
endif
let l:text_edits += [l:text_edit]
endfor
return l:text_edits
endfunction
"
" _compare
"
function! s:_compare(text_edit1, text_edit2) abort
let l:diff = a:text_edit1.range.start.line - a:text_edit2.range.start.line
if l:diff == 0
return a:text_edit1.range.start.character - a:text_edit2.range.start.character
endif
return l:diff
endfunction
"
" _fix_text_edits
"
function! s:_fix_text_edits(bufnr, text_edits) abort
let l:max = s:Buffer.get_line_count(a:bufnr)
let l:fixeol = v:false
let l:text_edits = []
for l:text_edit in a:text_edits
if l:max <= l:text_edit.range.start.line
let l:text_edit.range.start.line = l:max - 1
let l:text_edit.range.start.character = strchars(get(getbufline(a:bufnr, '$'), 0, ''))
let l:text_edit.newText = "\n" . l:text_edit.newText
let l:fixeol = &fixendofline && !&binary
endif
if l:max <= l:text_edit.range.end.line
let l:text_edit.range.end.line = l:max - 1
let l:text_edit.range.end.character = strchars(get(getbufline(a:bufnr, '$'), 0, ''))
let l:fixeol = &fixendofline && !&binary
endif
call add(l:text_edits, l:text_edit)
endfor
return [l:fixeol, l:text_edits]
endfunction
"
" _switch
"
function! s:_switch(path) abort
let l:curr = bufnr('%')
let l:next = bufnr(a:path)
if l:next >= 0
if l:curr != l:next
execute printf('noautocmd keepalt keepjumps %sbuffer!', bufnr(a:path))
endif
else
execute printf('noautocmd keepalt keepjumps edit! %s', fnameescape(a:path))
endif
return bufnr('%')
endfunction

View File

@@ -0,0 +1,126 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\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('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
let s:Do = { -> {} }
let g:___VS_Vim_Buffer_id = get(g:, '___VS_Vim_Buffer_id', 0)
"
" get_line_count
"
if exists('*nvim_buf_line_count')
function! s:get_line_count(bufnr) abort
return nvim_buf_line_count(a:bufnr)
endfunction
elseif has('patch-8.2.0019')
function! s:get_line_count(bufnr) abort
return getbufinfo(a:bufnr)[0].linecount
endfunction
else
function! s:get_line_count(bufnr) abort
if bufnr('%') == bufnr(a:bufnr)
return line('$')
endif
return len(getbufline(a:bufnr, '^', '$'))
endfunction
endif
"
" create
"
function! s:create(...) abort
let g:___VS_Vim_Buffer_id += 1
let l:bufname = printf('VS.Vim.Buffer: %s: %s',
\ g:___VS_Vim_Buffer_id,
\ get(a:000, 0, 'VS.Vim.Buffer.Default')
\ )
return s:load(l:bufname)
endfunction
"
" ensure
"
function! s:ensure(expr) abort
if !bufexists(a:expr)
if type(a:expr) == type(0)
throw printf('VS.Vim.Buffer: `%s` is not valid expr.', a:expr)
endif
badd `=a:expr`
endif
return bufnr(a:expr)
endfunction
"
" load
"
if exists('*bufload')
function! s:load(expr) abort
let l:bufnr = s:ensure(a:expr)
if !bufloaded(l:bufnr)
call bufload(l:bufnr)
endif
return l:bufnr
endfunction
else
function! s:load(expr) abort
let l:curr_bufnr = bufnr('%')
try
let l:bufnr = s:ensure(a:expr)
execute printf('keepalt keepjumps silent %sbuffer', l:bufnr)
catch /.*/
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
finally
execute printf('noautocmd keepalt keepjumps silent %sbuffer', l:curr_bufnr)
endtry
return l:bufnr
endfunction
endif
"
" do
"
function! s:do(bufnr, func) abort
let l:curr_bufnr = bufnr('%')
if l:curr_bufnr == a:bufnr
call a:func()
return
endif
try
execute printf('noautocmd keepalt keepjumps silent %sbuffer', a:bufnr)
call a:func()
catch /.*/
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
finally
execute printf('noautocmd keepalt keepjumps silent %sbuffer', l:curr_bufnr)
endtry
endfunction
"
" pseudo
"
function! s:pseudo(filepath) abort
if !filereadable(a:filepath)
throw printf('VS.Vim.Buffer: `%s` is not valid filepath.', a:filepath)
endif
" create pseudo buffer
let l:bufname = printf('VSVimBufferPseudo://%s', a:filepath)
if bufexists(l:bufname)
return s:ensure(l:bufname)
endif
let l:bufnr = s:ensure(l:bufname)
let l:group = printf('VS_Vim_Buffer_pseudo:%s', l:bufnr)
execute printf('augroup %s', l:group)
execute printf('autocmd BufReadCmd <buffer=%s> call setline(1, readfile(bufname("%")[20 : -1])) | try | filetype detect | catch /.*/ | endtry | augroup %s | autocmd! | augroup END', l:bufnr, l:group)
augroup END
return l:bufnr
endfunction

View File

@@ -0,0 +1,21 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_vsnip#VS#Vim#Option#import() abort', printf("return map({'define': ''}, \"vital#_vsnip#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
"
" define
"
function! s:define(map) abort
let l:old = {}
for [l:key, l:value] in items(a:map)
let l:old[l:key] = eval(printf('&%s', l:key))
execute printf('let &%s = "%s"', l:key, l:value)
endfor
return { -> s:define(l:old) }
endfunction

View File

@@ -0,0 +1,330 @@
let s:plugin_name = expand('<sfile>:t:r')
let s:vital_base_dir = expand('<sfile>:h')
let s:project_root = expand('<sfile>:h:h:h')
let s:is_vital_vim = s:plugin_name is# 'vital'
let s:loaded = {}
let s:cache_sid = {}
function! vital#{s:plugin_name}#new() abort
return s:new(s:plugin_name)
endfunction
function! vital#{s:plugin_name}#import(...) abort
if !exists('s:V')
let s:V = s:new(s:plugin_name)
endif
return call(s:V.import, a:000, s:V)
endfunction
let s:Vital = {}
function! s:new(plugin_name) abort
let base = deepcopy(s:Vital)
let base._plugin_name = a:plugin_name
return base
endfunction
function! s:vital_files() abort
if !exists('s:vital_files')
let s:vital_files = map(
\ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(),
\ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")')
endif
return copy(s:vital_files)
endfunction
let s:Vital.vital_files = function('s:vital_files')
function! s:import(name, ...) abort dict
let target = {}
let functions = []
for a in a:000
if type(a) == type({})
let target = a
elseif type(a) == type([])
let functions = a
endif
unlet a
endfor
let module = self._import(a:name)
if empty(functions)
call extend(target, module, 'keep')
else
for f in functions
if has_key(module, f) && !has_key(target, f)
let target[f] = module[f]
endif
endfor
endif
return target
endfunction
let s:Vital.import = function('s:import')
function! s:load(...) abort dict
for arg in a:000
let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg]
let target = split(join(as, ''), '\W\+')
let dict = self
let dict_type = type({})
while !empty(target)
let ns = remove(target, 0)
if !has_key(dict, ns)
let dict[ns] = {}
endif
if type(dict[ns]) == dict_type
let dict = dict[ns]
else
unlet dict
break
endif
endwhile
if exists('dict')
call extend(dict, self._import(name))
endif
unlet arg
endfor
return self
endfunction
let s:Vital.load = function('s:load')
function! s:unload() abort dict
let s:loaded = {}
let s:cache_sid = {}
unlet! s:vital_files
endfunction
let s:Vital.unload = function('s:unload')
function! s:exists(name) abort dict
if a:name !~# '\v^\u\w*%(\.\u\w*)*$'
throw 'vital: Invalid module name: ' . a:name
endif
return s:_module_path(a:name) isnot# ''
endfunction
let s:Vital.exists = function('s:exists')
function! s:search(pattern) abort dict
let paths = s:_extract_files(a:pattern, self.vital_files())
let modules = sort(map(paths, 's:_file2module(v:val)'))
return uniq(modules)
endfunction
let s:Vital.search = function('s:search')
function! s:plugin_name() abort dict
return self._plugin_name
endfunction
let s:Vital.plugin_name = function('s:plugin_name')
function! s:_self_vital_files() abort
let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name)
let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name)
let base = builtin . ',' . installed
return split(globpath(base, '**/*.vim', 1), "\n")
endfunction
function! s:_global_vital_files() abort
let pattern = 'autoload/vital/__*__/**/*.vim'
return split(globpath(&runtimepath, pattern, 1), "\n")
endfunction
function! s:_extract_files(pattern, files) abort
let tr = {'.': '/', '*': '[^/]*', '**': '.*'}
let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g')
let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target)
return filter(a:files, 'v:val =~# regexp')
endfunction
function! s:_file2module(file) abort
let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?')
let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$')
return join(split(tail, '[\\/]\+'), '.')
endfunction
" @param {string} name e.g. Data.List
function! s:_import(name) abort dict
if has_key(s:loaded, a:name)
return copy(s:loaded[a:name])
endif
let module = self._get_module(a:name)
if has_key(module, '_vital_created')
call module._vital_created(module)
endif
let export_module = filter(copy(module), 'v:key =~# "^\\a"')
" Cache module before calling module._vital_loaded() to avoid cyclic
" dependences but remove the cache if module._vital_loaded() fails.
" let s:loaded[a:name] = export_module
let s:loaded[a:name] = export_module
if has_key(module, '_vital_loaded')
try
call module._vital_loaded(vital#{s:plugin_name}#new())
catch
unlet s:loaded[a:name]
throw 'vital: fail to call ._vital_loaded(): ' . v:exception . " from:\n" . s:_format_throwpoint(v:throwpoint)
endtry
endif
return copy(s:loaded[a:name])
endfunction
let s:Vital._import = function('s:_import')
function! s:_format_throwpoint(throwpoint) abort
let funcs = []
let stack = matchstr(a:throwpoint, '^function \zs.*, .\{-} \d\+$')
for line in split(stack, '\.\.')
let m = matchlist(line, '^\(.\+\)\%(\[\(\d\+\)\]\|, .\{-} \(\d\+\)\)$')
if !empty(m)
let [name, lnum, lnum2] = m[1:3]
if empty(lnum)
let lnum = lnum2
endif
let info = s:_get_func_info(name)
if !empty(info)
let attrs = empty(info.attrs) ? '' : join([''] + info.attrs)
let flnum = info.lnum == 0 ? '' : printf(' Line:%d', info.lnum + lnum)
call add(funcs, printf('function %s(...)%s Line:%d (%s%s)',
\ info.funcname, attrs, lnum, info.filename, flnum))
continue
endif
endif
" fallback when function information cannot be detected
call add(funcs, line)
endfor
return join(funcs, "\n")
endfunction
function! s:_get_func_info(name) abort
let name = a:name
if a:name =~# '^\d\+$' " is anonymous-function
let name = printf('{%s}', a:name)
elseif a:name =~# '^<lambda>\d\+$' " is lambda-function
let name = printf("{'%s'}", a:name)
endif
if !exists('*' . name)
return {}
endif
let body = execute(printf('verbose function %s', name))
let lines = split(body, "\n")
let signature = matchstr(lines[0], '^\s*\zs.*')
let [_, file, lnum; __] = matchlist(lines[1],
\ '^\t\%(Last set from\|.\{-}:\)\s*\zs\(.\{-}\)\%( \S\+ \(\d\+\)\)\?$')
return {
\ 'filename': substitute(file, '[/\\]\+', '/', 'g'),
\ 'lnum': 0 + lnum,
\ 'funcname': a:name,
\ 'arguments': split(matchstr(signature, '(\zs.*\ze)'), '\s*,\s*'),
\ 'attrs': filter(['dict', 'abort', 'range', 'closure'], 'signature =~# (").*" . v:val)'),
\ }
endfunction
" s:_get_module() returns module object wihch has all script local functions.
function! s:_get_module(name) abort dict
let funcname = s:_import_func_name(self.plugin_name(), a:name)
try
return call(funcname, [])
catch /^Vim\%((\a\+)\)\?:E117:/
return s:_get_builtin_module(a:name)
endtry
endfunction
function! s:_get_builtin_module(name) abort
return s:sid2sfuncs(s:_module_sid(a:name))
endfunction
if s:is_vital_vim
" For vital.vim, we can use s:_get_builtin_module directly
let s:Vital._get_module = function('s:_get_builtin_module')
else
let s:Vital._get_module = function('s:_get_module')
endif
function! s:_import_func_name(plugin_name, module_name) abort
return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name))
endfunction
function! s:_module_sid(name) abort
let path = s:_module_path(a:name)
if !filereadable(path)
throw 'vital: module not found: ' . a:name
endif
let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name)
let base = join([vital_dir, ''], '[/\\]\+')
let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g')
let sid = s:_sid(path, p)
if !sid
call s:_source(path)
let sid = s:_sid(path, p)
if !sid
throw printf('vital: cannot get <SID> from path: %s', path)
endif
endif
return sid
endfunction
function! s:_module_path(name) abort
return get(s:_extract_files(a:name, s:vital_files()), 0, '')
endfunction
function! s:_module_sid_base_dir() abort
return s:is_vital_vim ? &rtp : s:project_root
endfunction
function! s:_dot_to_sharp(name) abort
return substitute(a:name, '\.', '#', 'g')
endfunction
function! s:_source(path) abort
execute 'source' fnameescape(a:path)
endfunction
" @vimlint(EVL102, 1, l:_)
" @vimlint(EVL102, 1, l:__)
function! s:_sid(path, filter_pattern) abort
let unified_path = s:_unify_path(a:path)
if has_key(s:cache_sid, unified_path)
return s:cache_sid[unified_path]
endif
for line in filter(split(execute(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern')
let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$')
if s:_unify_path(path) is# unified_path
let s:cache_sid[unified_path] = sid
return s:cache_sid[unified_path]
endif
endfor
return 0
endfunction
if filereadable(expand('<sfile>:r') . '.VIM') " is case-insensitive or not
let s:_unify_path_cache = {}
" resolve() is slow, so we cache results.
" Note: On windows, vim can't expand path names from 8.3 formats.
" So if getting full path via <sfile> and $HOME was set as 8.3 format,
" vital load duplicated scripts. Below's :~ avoid this issue.
function! s:_unify_path(path) abort
if has_key(s:_unify_path_cache, a:path)
return s:_unify_path_cache[a:path]
endif
let value = tolower(fnamemodify(resolve(fnamemodify(
\ a:path, ':p')), ':~:gs?[\\/]?/?'))
let s:_unify_path_cache[a:path] = value
return value
endfunction
else
function! s:_unify_path(path) abort
return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?'))
endfunction
endif
" copied and modified from Vim.ScriptLocal
let s:SNR = join(map(range(len("\<SNR>")), '"[\\x" . printf("%0x", char2nr("\<SNR>"[v:val])) . "]"'), '')
function! s:sid2sfuncs(sid) abort
let fs = split(execute(printf(':function /^%s%s_', s:SNR, a:sid)), "\n")
let r = {}
let pattern = printf('\m^function\s<SNR>%d_\zs\w\{-}\ze(', a:sid)
for fname in map(fs, 'matchstr(v:val, pattern)')
let r[fname] = function(s:_sfuncname(a:sid, fname))
endfor
return r
endfunction
"" Return funcname of script local functions with SID
function! s:_sfuncname(sid, funcname) abort
return printf('<SNR>%s_%s', a:sid, a:funcname)
endfunction

View File

@@ -0,0 +1,6 @@
vsnip
2755f0c8fbd3442bcb7f567832e4d1455b57f9a2
VS.LSP.TextEdit
VS.LSP.Diff
VS.LSP.Position

View File

@@ -0,0 +1,243 @@
let s:Session = vsnip#session#import()
let s:Snippet = vsnip#snippet#import()
let s:TextEdit = vital#vsnip#import('VS.LSP.TextEdit')
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.
"
function! vsnip#selected_text(...) abort
if len(a:000) == 1
let s:selected_text = a:000[0]
else
return s:selected_text
endif
endfunction
"
" vsnip#available.
"
function! vsnip#available(...) abort
let l:direction = get(a:000, 0, 1)
return vsnip#expandable() || vsnip#jumpable(l:direction)
endfunction
"
" vsnip#expandable.
"
function! vsnip#expandable() abort
return !empty(vsnip#get_context())
endfunction
"
" vsnip#jumpable.
"
function! vsnip#jumpable(...) abort
let l:direction = get(a:000, 0, 1)
return !empty(s:session) && s:session.jumpable(l:direction)
endfunction
"
" vsnip#expand
"
function! vsnip#expand() abort
let l:context = vsnip#get_context()
if !empty(l:context)
call s:TextEdit.apply(bufnr('%'), [{
\ 'range': l:context.range,
\ 'newText': ''
\ }])
call vsnip#anonymous(join(l:context.snippet.body, "\n"), {
\ 'position': l:context.range.start
\ })
endif
endfunction
"
" vsnip#anonymous.
"
function! vsnip#anonymous(text, ...) abort
let l:option = get(a:000, 0, {})
let l:prefix = get(l:option, 'prefix', v:null)
let l:position = get(l:option, 'position', s:Position.cursor())
if l:prefix isnot# v:null
let l:position.character -= strchars(l:prefix)
call s:TextEdit.apply(bufnr('%'), [{
\ 'range': {
\ 'start': l:position,
\ 'end': {
\ 'line': l:position.line,
\ 'character': l:position.character + strchars(l:prefix),
\ },
\ },
\ 'newText': ''
\ }])
endif
let l:session = s:Session.new(bufnr('%'), l:position, a:text)
call vsnip#selected_text('')
if !empty(s:session)
call s:session.flush_changes() " try to sync buffer content because vsnip#expand maybe remove prefix
endif
if empty(s:session)
let s:session = l:session
call s:session.expand()
else
call s:session.merge(l:session)
endif
doautocmd <nomodeline> User vsnip#expand
call s:session.refresh()
call s:session.jump(1)
endfunction
"
" vsnip#get_session
"
function! vsnip#get_session() abort
return s:session
endfunction
"
" vsnip#deactivate
"
function! vsnip#deactivate() abort
let s:session = {}
endfunction
"
" get_context.
"
function! vsnip#get_context() abort
let l:offset = mode()[0] ==# 'i' ? 2 : 1
let l:before_text = getline('.')[0 : col('.') - l:offset]
let l:before_text_len = strchars(l:before_text)
if l:before_text_len == 0
return {}
endif
let l:sources = vsnip#source#find(bufnr('%'))
" Search prefix
for l:source in l:sources
for l:snippet in l:source
for l:prefix in l:snippet.prefix
let l:prefix_len = strchars(l:prefix)
if strcharpart(l:before_text, l:before_text_len - l:prefix_len, l:prefix_len) !=# l:prefix
continue
endif
if l:prefix =~# '^\h' && l:before_text !~# '\<\V' . escape(l:prefix, '\/?') . '\m$'
continue
endif
return s:create_context(l:snippet, l:before_text_len, l:prefix_len)
endfor
endfor
endfor
" Search prefix-alias
for l:source in l:sources
for l:snippet in l:source
for l:prefix in l:snippet.prefix_alias
let l:prefix_len = strchars(l:prefix)
if strcharpart(l:before_text, l:before_text_len - l:prefix_len, l:prefix_len) !=# l:prefix
continue
endif
if l:prefix =~# '^\h' && l:before_text !~# '\<\V' . escape(l:prefix, '\/?') . '\m$'
continue
endif
return s:create_context(l:snippet, l:before_text_len, l:prefix_len)
endfor
endfor
endfor
return {}
endfunction
"
" vsnip#get_complete_items
"
function! vsnip#get_complete_items(bufnr) abort
let l:uniq = {}
let l:candidates = []
for l:source in vsnip#source#find(a:bufnr)
for l:snippet in l:source
for l:prefix in l:snippet.prefix
if has_key(l:uniq, l:prefix)
continue
endif
let l:uniq[l:prefix] = v:true
let l:menu = ''
let l:menu .= '[v]'
let l:menu .= ' '
let l:menu .= (strlen(l:snippet.description) > 0 ? l:snippet.description : l:snippet.label)
call add(l:candidates, {
\ 'word': l:prefix,
\ 'abbr': l:prefix,
\ 'kind': 'Snippet',
\ 'menu': l:menu,
\ 'dup': 1,
\ 'user_data': json_encode({
\ 'vsnip': {
\ 'snippet': l:snippet.body
\ }
\ })
\ })
endfor
endfor
endfor
return l:candidates
endfunction
"
" vsnip#decode
"
function! vsnip#to_string(text) abort
let l:text = type(a:text) == type([]) ? join(a:text, "\n") : a:text
return s:Snippet.new(s:Position.cursor(), l:text).text()
endfunction
"
" vsnip#debug
"
function! vsnip#debug() abort
if !empty(s:session)
call s:session.snippet.debug()
endif
endfunction
"
" create_context
"
function! s:create_context(snippet, before_text_len, prefix_len) abort
let l:line = line('.') - 1
return {
\ 'range': {
\ 'start': {
\ 'line': l:line,
\ 'character': a:before_text_len - a:prefix_len
\ },
\ 'end': {
\ 'line': l:line,
\ 'character': a:before_text_len
\ }
\ },
\ 'snippet': a:snippet
\ }
endfunction

View File

@@ -0,0 +1,61 @@
"
" vsnip#indent#get_one_indent
"
function! vsnip#indent#get_one_indent() abort
return !&expandtab ? "\t" : repeat(' ', &shiftwidth ? &shiftwidth : &tabstop)
endfunction
"
" vsnip#indent#get_base_indent
"
function! vsnip#indent#get_base_indent(text) abort
return matchstr(a:text, '^\s*')
endfunction
"
" vsnip#indent#adjust_snippet_body
"
function! vsnip#indent#adjust_snippet_body(line, text) abort
let l:one_indent = vsnip#indent#get_one_indent()
let l:base_indent = vsnip#indent#get_base_indent(a:line)
let l:text = a:text
if l:one_indent !=# "\t"
while match(l:text, "\\%(^\\|\n\\)\\s*\\zs\\t") != -1
let l:text = substitute(l:text, "\\%(^\\|\n\\)\\s*\\zs\\t", l:one_indent, 'g') " convert \t as one indent
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
return l:text
endfunction
"
" vsnip#indent#trim_base_indent
"
function! vsnip#indent#trim_base_indent(text) abort
let l:is_char_wise = match(a:text, "\n$") == -1
let l:text = substitute(a:text, "\n$", '', 'g')
let l:is_first_line = v:true
let l:base_indent = ''
for l:line in split(l:text, "\n", v:true)
" Ignore the first line when the text created as char-wise.
if l:is_char_wise && l:is_first_line
let l:is_first_line = v:false
continue
endif
" Ignore empty line.
if l:line ==# ''
continue
endif
" Detect most minimum base indent.
let l:indent = matchstr(l:line, '^\s*')
if l:base_indent ==# '' || strlen(l:indent) < strlen(l:base_indent)
let l:base_indent = l:indent
endif
endfor
return substitute(l:text, "\\%(^\\|\n\\)\\zs\\V" . l:base_indent, '', 'g')
endfunction

View File

@@ -0,0 +1,223 @@
function! vsnip#parser#combinator#import() abort
return {
\ 'skip': function('s:skip'),
\ 'token': function('s:token'),
\ 'many': function('s:many'),
\ 'or': function('s:or'),
\ 'seq': function('s:seq'),
\ 'pattern': function('s:pattern'),
\ 'lazy': function('s:lazy'),
\ 'option': function('s:option'),
\ 'map': function('s:map')
\ }
endfunction
"
" string.
"
function! s:skip(stop, escape) abort
let l:fn = {}
let l:fn.stop = a:stop
let l:fn.escape = a:escape
function! l:fn.parse(text, pos) abort
let l:pos = a:pos
let l:value = ''
let l:len = strchars(a:text)
while l:pos < l:len
let l:char = s:getchar(a:text, l:pos)
" check escaped stop chars.
if l:char ==# '\'
let l:pos += 1
let l:char = s:getchar(a:text, l:pos)
if index(self.stop + self.escape + ['\'], l:char) == -1
let l:value .= '\'
continue " ignore invalid escape char.
endif
let l:pos += 1
let l:value .= l:char
continue
endif
" check stop char.
if index(self.stop, l:char) >= 0
if a:pos != l:pos
return [v:true, [strcharpart(a:text, a:pos, l:pos - a:pos), l:value], l:pos]
else
return [v:false, v:null, l:pos]
endif
endif
let l:value .= l:char
let l:pos += 1
endwhile
" everything was string.
return [v:true, [strcharpart(a:text, a:pos), l:value], l:len]
endfunction
return l:fn
endfunction
"
" token.
"
function! s:token(token) abort
let l:fn = {}
let l:fn.token = a:token
function! l:fn.parse(text, pos) abort
let l:token_len = strchars(self.token)
let l:value = strcharpart(a:text, a:pos, l:token_len)
if l:value ==# self.token
return [v:true, self.token, a:pos + l:token_len]
endif
return [v:false, v:null, a:pos]
endfunction
return l:fn
endfunction
"
" many.
"
function! s:many(parser) abort
let l:fn = {}
let l:fn.parser = a:parser
function! l:fn.parse(text, pos) abort
let l:pos = a:pos
let l:values = []
let l:len = strchars(a:text)
while l:pos < l:len
let l:parsed = self.parser.parse(a:text, l:pos)
if l:parsed[0]
call add(l:values, l:parsed[1])
let l:pos = l:parsed[2]
else
break
endif
endwhile
if len(l:values) > 0
return [v:true, l:values, l:pos]
else
return [v:false, v:null, l:pos]
endif
endfunction
return l:fn
endfunction
"
" or.
"
function! s:or(...) abort
let l:fn = {}
let l:fn.parsers = a:000
function! l:fn.parse(text, pos) abort
for l:parser in self.parsers
let l:parsed = l:parser.parse(a:text, a:pos)
if l:parsed[0]
return l:parsed
endif
endfor
return [v:false, v:null, a:pos]
endfunction
return l:fn
endfunction
"
" seq.
"
function! s:seq(...) abort
let l:fn = {}
let l:fn.parsers = a:000
function! l:fn.parse(text, pos) abort
let l:pos = a:pos
let l:values = []
for l:parser in self.parsers
let l:parsed = l:parser.parse(a:text, l:pos)
if !l:parsed[0]
return [v:false, v:null, a:pos]
endif
call add(l:values, l:parsed[1])
let l:pos = l:parsed[2]
endfor
return [v:true, l:values, l:pos]
endfunction
return l:fn
endfunction
"
" lazy.
"
function! s:lazy(callback) abort
let l:fn = {}
let l:fn.callback = a:callback
function! l:fn.parse(text, pos) abort
if !has_key(self, 'parser')
let self.parser = self.callback()
endif
return self.parser.parse(a:text, a:pos)
endfunction
return l:fn
endfunction
"
" pattern.
"
function! s:pattern(pattern) abort
let l:fn = {}
let l:fn.pattern = a:pattern[0] ==# '^' ? a:pattern : '^' . a:pattern
function! l:fn.parse(text, pos) abort
let l:text = strcharpart(a:text, a:pos)
let l:matches = matchstrpos(l:text, self.pattern, 0, 1)
if l:matches[0] !=# ''
return [v:true, l:matches[0], a:pos + l:matches[2]]
endif
return [v:false, v:null, a:pos]
endfunction
return l:fn
endfunction
"
" map.
"
function! s:map(parser, callback) abort
let l:fn = {}
let l:fn.callback = a:callback
let l:fn.parser = a:parser
function! l:fn.parse(text, pos) abort
let l:parsed = self.parser.parse(a:text, a:pos)
if l:parsed[0]
return [v:true, self.callback(l:parsed[1]), l:parsed[2]]
endif
return l:parsed
endfunction
return l:fn
endfunction
"
" option.
"
function! s:option(parser) abort
let l:fn = {}
let l:fn.parser = a:parser
function! l:fn.parse(text, pos) abort
let l:parsed = self.parser.parse(a:text, a:pos)
if l:parsed[0]
return l:parsed
endif
return [v:true, v:null, a:pos]
endfunction
return l:fn
endfunction
"
" getchar.
"
function! s:getchar(text, pos) abort
let l:nr = strgetchar(a:text, a:pos)
if l:nr != -1
return nr2char(l:nr)
endif
return ''
endfunction

View File

@@ -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

View File

@@ -0,0 +1,260 @@
let s:Snippet = vsnip#snippet#import()
let s:TextEdit = vital#vsnip#import('VS.LSP.TextEdit')
let s:Position = vital#vsnip#import('VS.LSP.Position')
let s:Diff = vital#vsnip#import('VS.LSP.Diff')
"
" import.
"
function! vsnip#session#import() abort
return s:Session
endfunction
let s:Session = {}
"
" new.
"
function! s:Session.new(bufnr, position, text) abort
return extend(deepcopy(s:Session), {
\ 'bufnr': a:bufnr,
\ 'buffer': getbufline(a:bufnr, '^', '$'),
\ 'timer_id': -1,
\ 'changedtick': getbufvar(a:bufnr, 'changedtick', 0),
\ 'snippet': s:Snippet.new(a:position, vsnip#indent#adjust_snippet_body(getline('.'), a:text)),
\ 'tabstop': -1,
\ 'changenr': changenr(),
\ 'changenrs': {},
\ })
endfunction
"
" expand.
"
function! s:Session.expand() abort
" insert snippet.
call s:TextEdit.apply(self.bufnr, [{
\ 'range': {
\ 'start': self.snippet.position,
\ 'end': self.snippet.position
\ },
\ 'newText': self.snippet.text()
\ }])
call self.store(changenr())
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
"
" jumpable.
"
function! s:Session.jumpable(direction) abort
if a:direction == 1
let l:jumpable = !empty(self.snippet.get_next_jump_point(self.tabstop))
else
let l:jumpable = !empty(self.snippet.get_prev_jump_point(self.tabstop))
endif
return l:jumpable
endfunction
"
" jump.
"
function! s:Session.jump(direction) abort
call self.flush_changes()
if a:direction == 1
let l:jump_point = self.snippet.get_next_jump_point(self.tabstop)
else
let l:jump_point = self.snippet.get_prev_jump_point(self.tabstop)
endif
if empty(l:jump_point)
return
endif
let self.tabstop = l:jump_point.placeholder.id
" choice.
if len(l:jump_point.placeholder.choice) > 0
call self.choice(l:jump_point)
" select.
elseif l:jump_point.range.start.character != l:jump_point.range.end.character
call self.select(l:jump_point)
" move.
else
call self.move(l:jump_point)
endif
doautocmd <nomodeline> User vsnip#jump
endfunction
"
" choice.
"
function! s:Session.choice(jump_point) abort
call self.move(a:jump_point)
let l:fn = {}
let l:fn.jump_point = a:jump_point
function! l:fn.next_tick() abort
if mode()[0] ==# 'i'
let l:pos = s:Position.lsp_to_vim('%', self.jump_point.range.start)
call complete(l:pos[1], map(copy(self.jump_point.placeholder.choice), { k, v -> {
\ 'word': v.escaped,
\ 'abbr': v.escaped,
\ 'menu': '[vsnip]',
\ 'kind': 'Choice'
\ } }))
endif
endfunction
call timer_start(g:vsnip_choice_delay, { -> l:fn.next_tick() })
endfunction
"
" select.
"
" @NOTE: Must work even if virtualedit=all/onmore or not.
"
function! s:Session.select(jump_point) abort
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:cmd = ''
let l:cmd .= "\<Cmd>set virtualedit=onemore\<CR>"
let l:cmd .= mode()[0] ==# 'i' ? "\<Esc>" : ''
let l:cmd .= printf("\<Cmd>call cursor(%s, %s)\<CR>", l:start_pos[0], l:start_pos[1])
let l:cmd .= 'v'
let l:cmd .= printf("\<Cmd>call cursor(%s, %s)\<CR>%s", l:end_pos[0], l:end_pos[1], &selection ==# 'exclusive' ? '' : 'h')
if get(g:, 'vsnip_test_mode', v:false)
let l:cmd .= "\<Esc>gv"
endif
let l:cmd .= printf("\<Cmd>set virtualedit=%s\<CR>", &virtualedit)
let l:cmd .= "\<C-g>"
call feedkeys(l:cmd, 'ni')
endfunction
"
" move.
"
" @NOTE: Must work even if virtualedit=all/onmore or not.
"
function! s:Session.move(jump_point) abort
let l:pos = s:Position.lsp_to_vim('%', a:jump_point.range.end)
call cursor(l:pos)
if mode()[0] ==# 'n'
if l:pos[1] != getcurpos()[2]
call feedkeys('a', 'ni')
else
call feedkeys('i', 'ni')
endif
endif
endfunction
"
" refresh
"
function! s:Session.refresh() abort
let self.buffer = getbufline(self.bufnr, '^', '$')
let self.changedtick = getbufvar(self.bufnr, 'changedtick', 0)
endfunction
"
" on_insert_leave
"
function! s:Session.on_insert_leave() abort
call self.flush_changes()
endfunction
"
" on_text_changed
"
function! s:Session.on_text_changed() abort
if self.bufnr != bufnr('%')
return vsnip#deactivate()
endif
let l:changenr = changenr()
" save state.
if self.changenr != l:changenr
call self.store(self.changenr)
if has_key(self.changenrs, l:changenr)
let self.tabstop = self.changenrs[l:changenr].tabstop
let self.snippet = self.changenrs[l:changenr].snippet
let self.changenr = l:changenr
let self.buffer = getbufline(self.bufnr, '^', '$')
return
endif
endif
if g:vsnip_sync_delay == 0
call self.flush_changes()
elseif g:vsnip_sync_delay > 0
call timer_stop(self.timer_id)
let self.timer_id = timer_start(g:vsnip_sync_delay, { -> self.flush_changes() }, { 'repeat': 1 })
endif
endfunction
"
" flush_changes
"
function! s:Session.flush_changes() abort
let l:changedtick = getbufvar(self.bufnr, 'changedtick', 0)
if self.changedtick == l:changedtick
return
endif
let self.changedtick = l:changedtick
" compute diff.
let l:buffer = getbufline(self.bufnr, '^', '$')
let l:diff = s:Diff.compute(self.buffer, l:buffer)
let self.buffer = l:buffer
if l:diff.rangeLength == 0 && l:diff.text ==# ''
return
endif
" if follow succeeded, sync placeholders and write back to the buffer.
if self.snippet.follow(self.tabstop, l:diff)
try
let l:text_edits = self.snippet.sync()
if len(l:text_edits) > 0
undojoin | call s:TextEdit.apply(self.bufnr, l:text_edits)
endif
call self.refresh()
catch /.*/
" TODO: More strict changenrs mangement.
call vsnip#deactivate()
endtry
else
call vsnip#deactivate()
endif
endfunction
"
" save.
"
function! s:Session.store(changenr) abort
let self.changenrs[a:changenr] = {
\ 'tabstop': self.tabstop,
\ 'snippet': deepcopy(self.snippet)
\ }
let self.changenr = a:changenr
endfunction

View File

@@ -0,0 +1,557 @@
let s:max_tabstop = 1000000
let s:Position = vital#vsnip#import('VS.LSP.Position')
"
" import.
"
function! vsnip#snippet#import() abort
return s:Snippet
endfunction
let s:Snippet = {}
"
" new.
"
function! s:Snippet.new(position, text) abort
let l:pos = s:Position.lsp_to_vim('%', a:position)
let l:snippet = extend(deepcopy(s:Snippet), {
\ 'type': 'snippet',
\ 'position': a:position,
\ 'before_text': getline(l:pos[0])[0 : l:pos[1] - 2],
\ 'children': vsnip#snippet#node#create_from_ast(
\ vsnip#snippet#parser#parse(a:text)
\ )
\ })
call l:snippet.init()
call l:snippet.sync()
return l:snippet
endfunction
"
" init.
"
" NOTE: Must not use the node range in this method.
"
function! s:Snippet.init() abort
let l:fn = {}
let l:fn.self = self
let l:fn.group = {}
let l:fn.variable_placeholder = {}
let l:fn.has_final_tabstop = v:false
function! l:fn.traverse(context) abort
if a:context.node.type ==# 'placeholder'
" Mark as follower placeholder.
if !has_key(self.group, a:context.node.id)
let self.group[a:context.node.id] = a:context.node
else
let a:context.node.follower = v:true
endif
" Mark as having final tabstop
if a:context.node.is_final
let self.has_final_tabstop = v:true
endif
elseif a:context.node.type ==# 'variable'
" TODO refactor
" variable placeholder
if a:context.node.unknown
let a:context.node.type = 'placeholder'
let a:context.node.choice = []
if !has_key(self.variable_placeholder, a:context.node.name)
let self.variable_placeholder[a:context.node.name] = s:max_tabstop - (len(self.variable_placeholder) + 1)
let a:context.node.id = self.variable_placeholder[a:context.node.name]
let a:context.node.follower = v:false
let a:context.node.children = empty(a:context.node.children) ? [vsnip#snippet#node#create_text(a:context.node.name)] : a:context.node.children
let self.group[a:context.node.id] = a:context.node
else
let a:context.node.id = self.variable_placeholder[a:context.node.name]
let a:context.node.follower = v:true
let a:context.node.children = [vsnip#snippet#node#create_text(self.group[a:context.node.id].text())]
endif
else
let l:text = a:context.node.resolve(a:context)
let l:text = l:text is# v:null ? a:context.text : l:text
let l:index = index(a:context.parent.children, a:context.node)
call remove(a:context.parent.children, l:index)
call insert(a:context.parent.children, vsnip#snippet#node#create_text(l:text), l:index)
endif
endif
endfunction
call self.traverse(self, l:fn.traverse)
" Append ${MAX_TABSTOP} for the end of snippet.
if !l:fn.has_final_tabstop && g:vsnip_append_final_tabstop
let self.children += [vsnip#snippet#node#create_from_ast({
\ 'type': 'placeholder',
\ 'id': 0,
\ 'choice': [],
\ })]
endif
endfunction
"
" follow.
"
function! s:Snippet.follow(current_tabstop, diff) abort
if !self.is_followable(a:current_tabstop, a:diff)
return v:false
endif
let a:diff.range = [
\ self.position_to_offset(a:diff.range.start),
\ self.position_to_offset(a:diff.range.end),
\ ]
let l:fn = {}
let l:fn.current_tabstop = a:current_tabstop
let l:fn.diff = a:diff
let l:fn.is_target_context_fixed = v:false
let l:fn.target_context = v:null
let l:fn.contexts = []
function! l:fn.traverse(context) abort
if self.diff.range[1] < a:context.range[0]
return v:true
endif
if a:context.node.type !=# 'text'
return
endif
let l:included = v:false
let l:included = l:included || a:context.range[0] <= self.diff.range[0] && self.diff.range[0] < a:context.range[1] " right
let l:included = l:included || a:context.range[0] < self.diff.range[1] && self.diff.range[1] <= a:context.range[1] " left
let l:included = l:included || self.diff.range[0] <= a:context.range[0] && a:context.range[1] <= self.diff.range[1] " middle
if l:included
if !self.is_target_context_fixed && (empty(self.target_context) && a:context.parent.type ==# 'placeholder' || get(a:context.parent, 'id', -1) == self.current_tabstop)
let self.is_target_context_fixed = get(a:context.parent, 'id', -1) == self.current_tabstop
let self.target_context = a:context
endif
call add(self.contexts, a:context)
endif
endfunction
call self.traverse(self, l:fn.traverse)
if empty(l:fn.contexts)
return v:false
endif
let l:fn.target_context = empty(l:fn.target_context) ? l:fn.contexts[-1] : l:fn.target_context
let l:diff_text = a:diff.text
for l:context in l:fn.contexts
let l:diff_range = [max([a:diff.range[0], l:context.range[0]]), min([a:diff.range[1], l:context.range[1]])]
let l:start = l:diff_range[0] - l:context.range[0]
let l:end = l:diff_range[1] - l:context.range[0]
" Create patched new text.
let l:new_text = strcharpart(l:context.text, 0, l:start)
if l:fn.target_context is# l:context
let l:new_text .= l:diff_text
let l:followed = v:true
endif
let l:new_text .= strcharpart(l:context.text, l:end, l:context.length - l:end)
" Apply patched new text.
let l:context.node.value = l:new_text
endfor
" Squash nodes when the edit was unexpected
let l:squashed = []
for l:context in l:fn.contexts
let l:squash_targets = l:context.parents + [l:context.node]
for l:i in range(len(l:squash_targets) - 1, 1, -1)
let l:node = l:squash_targets[l:i]
let l:parent = l:squash_targets[l:i - 1]
let l:should_squash = v:false
let l:should_squash = l:should_squash || get(l:node, 'follower', v:false)
let l:should_squash = l:should_squash || get(l:parent, 'id', v:null) is# a:current_tabstop
let l:should_squash = l:should_squash || l:context isnot# l:fn.target_context && strlen(l:node.text()) == 0
if l:should_squash && index(l:squashed, l:node) == -1
let l:index = index(l:parent.children, l:node)
call remove(l:parent.children, l:index)
call insert(l:parent.children, vsnip#snippet#node#create_text(l:node.text()), l:index)
call add(l:squashed, l:node)
endif
endfor
endfor
return v:true
endfunction
"
" sync.
"
function! s:Snippet.sync() abort
let l:fn = {}
let l:fn.new_texts = {}
let l:fn.targets = []
function! l:fn.traverse(context) abort
if a:context.node.type ==# 'placeholder'
if !has_key(self.new_texts, a:context.node.id)
let self.new_texts[a:context.node.id] = a:context.text
else
if self.new_texts[a:context.node.id] !=# a:context.text
call add(self.targets, {
\ 'range': a:context.range,
\ 'node': a:context.node,
\ 'new_text': a:context.node.transform.text(self.new_texts[a:context.node.id]),
\ })
endif
endif
endif
endfunction
call self.traverse(self, l:fn.traverse)
" Create text_edits.
let l:text_edits = []
for l:target in l:fn.targets
call add(l:text_edits, {
\ 'node': l:target.node,
\ 'range': {
\ 'start': self.offset_to_position(l:target.range[0]),
\ 'end': self.offset_to_position(l:target.range[1]),
\ },
\ 'newText': l:target.new_text
\ })
endfor
" Sync placeholder text after created text_edits (the reason is to avoid using a modified range).
for l:text_edit in l:text_edits
let l:text_edit.node.children = [vsnip#snippet#node#create_text(l:text_edit.newText)]
endfor
return l:text_edits
endfunction
"
" range.
"
function! s:Snippet.range() abort
return {
\ 'start': self.offset_to_position(0),
\ 'end': self.offset_to_position(strchars(self.text()))
\ }
endfunction
"
" text.
"
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
"
function! s:Snippet.get_placeholder_nodes() abort
let l:fn = {}
let l:fn.nodes = []
function! l:fn.traverse(context) abort
if a:context.node.type ==# 'placeholder'
call add(self.nodes, a:context.node)
endif
endfunction
call self.traverse(self, l:fn.traverse)
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.
"
function! s:Snippet.get_next_jump_point(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' && self.current_tabstop < a:context.node.id
if !empty(self.context) && self.context.node.id <= a:context.node.id
return v:false
endif
let self.context = copy(a:context)
endif
endfunction
call self.traverse(self, l:fn.traverse)
let l:context = l:fn.context
if empty(l:context)
return {}
endif
return {
\ 'placeholder': l:context.node,
\ 'range': {
\ 'start': self.offset_to_position(l:context.range[0]),
\ 'end': self.offset_to_position(l:context.range[1])
\ }
\ }
endfunction
"
" get_prev_jump_point.
"
function! s:Snippet.get_prev_jump_point(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' && self.current_tabstop > a:context.node.id
if !empty(self.context) && self.context.node.id >= a:context.node.id
return v:false
endif
let self.context = copy(a:context)
endif
endfunction
call self.traverse(self, l:fn.traverse)
let l:context = l:fn.context
if empty(l:context)
return {}
endif
return {
\ 'placeholder': l:context.node,
\ 'range': {
\ 'start': self.offset_to_position(l:context.range[0]),
\ 'end': self.offset_to_position(l:context.range[1])
\ }
\ }
endfunction
"
" normalize
"
" - merge adjacent text-nodes
"
function! s:Snippet.normalize() abort
let l:fn = {}
let l:fn.prev_context = v:null
function! l:fn.traverse(context) abort
if !empty(self.prev_context)
if self.prev_context.node.type ==# 'text' && a:context.node.type ==# 'text' && self.prev_context.parent is# a:context.parent
let a:context.node.value = self.prev_context.node.value . a:context.node.value
call remove(self.prev_context.parent.children, index(self.prev_context.parent.children, self.prev_context.node))
endif
endif
let self.prev_context = copy(a:context)
endfunction
call self.traverse(self, l:fn.traverse)
endfunction
"
" merge
"
function! s:Snippet.merge(tabstop, snippet) abort
" increase new snippet's tabstop by current snippet's current tabstop
let l:offset = 1
let l:tabstop_map = {}
for l:node in a:snippet.get_placeholder_nodes()
if !has_key(l:tabstop_map, l:node.id)
let l:tabstop_map[l:node.id] = a:tabstop + l:offset
endif
let l:node.id = l:tabstop_map[l:node.id]
let l:offset += 1
endfor
if empty(l:tabstop_map)
return
endif
let l:tail = l:node
" re-assign current snippet's tabstop by new snippet's final tabstop
let l:offset = 1
let l:tabstop_map = {}
for l:node in self.get_placeholder_nodes()
if l:node.id > a:tabstop
if !has_key(l:tabstop_map, l:node.id)
let l:tabstop_map[l:node.id] = l:tail.id + l:offset
endif
let l:node.id = l:tabstop_map[l:node.id]
let l:offset += 1
endif
endfor
endfunction
"
" insert
"
function! s:Snippet.insert(position, nodes_to_insert) abort
let l:offset = self.position_to_offset(a:position)
" Search target node for inserting nodes.
let l:fn = {}
let l:fn.offset = l:offset
let l:fn.context = v:null
function! l:fn.traverse(context) abort
if a:context.range[0] <= self.offset && self.offset <= a:context.range[1] && a:context.node.type ==# 'text'
" prefer more deeper node.
if empty(self.context) || self.context.depth <= a:context.depth
let self.context = copy(a:context)
endif
endif
endfunction
call self.traverse(self, l:fn.traverse)
" This condition is unexpected normally
let l:context = l:fn.context
if empty(l:context)
return
endif
" Remove target text node
let l:index = index(l:context.parent.children, l:context.node)
call remove(l:context.parent.children, l:index)
" Should insert into existing text node when position is middle of node
let l:nodes_to_insert = reverse(a:nodes_to_insert)
if l:context.node.value !=# ''
let l:off = l:offset - l:context.range[0]
let l:before = vsnip#snippet#node#create_text(strcharpart(l:context.node.value, 0, l:off))
let l:after = vsnip#snippet#node#create_text(strcharpart(l:context.node.value, l:off, strchars(l:context.node.value) - l:off))
let l:nodes_to_insert = [l:after] + l:nodes_to_insert + [l:before]
endif
" Insert nodes.
for l:node in l:nodes_to_insert
call insert(l:context.parent.children, l:node, l:index)
endfor
call self.normalize()
endfunction
"
" offset_to_position.
"
" @param offset 0-based index for snippet text.
" @return position buffer position
"
function! s:Snippet.offset_to_position(offset) abort
let l:lines = split(strcharpart(self.text(), 0, a:offset), "\n", v:true)
return {
\ 'line': self.position.line + len(l:lines) - 1,
\ 'character': strchars(l:lines[-1]) + (len(l:lines) == 1 ? self.position.character : 0),
\ }
endfunction
"
" position_to_offset.
"
" @param position buffer position
" @return 0-based index for snippet text.
"
function! s:Snippet.position_to_offset(position) abort
let l:line = a:position.line - self.position.line
let l:char = a:position.character - (l:line == 0 ? self.position.character : 0)
let l:lines = split(self.text(), "\n", v:true)[0 : l:line]
let l:lines[-1] = strcharpart(l:lines[-1], 0, l:char)
return strchars(join(l:lines, "\n"))
endfunction
"
" traverse.
"
function! s:Snippet.traverse(node, callback) abort
let l:state = {
\ 'offset': 0,
\ 'before_text': self.before_text,
\ }
let l:context = {
\ 'depth': 0,
\ 'parent': v:null,
\ 'parents': [],
\ }
call s:traverse(a:node, a:callback, l:state, l:context)
endfunction
function! s:traverse(node, callback, state, context) abort
let l:text = ''
let l:length = 0
if a:node.type !=# 'snippet'
let l:text = a:node.text()
let l:length = strchars(l:text)
if a:callback({
\ 'node': a:node,
\ 'text': l:text,
\ 'length': l:length,
\ 'parent': a:context.parent,
\ 'parents': a:context.parents,
\ 'depth': a:context.depth,
\ 'offset': a:state.offset,
\ 'before_text': a:state.before_text,
\ 'range': [a:state.offset, a:state.offset + l:length],
\ })
return v:true
endif
endif
if len(a:node.children) > 0
let l:next_context = {
\ 'parent': a:node,
\ 'parents': a:context.parents + [a:node],
\ 'depth': len(a:context.parents) + 1,
\ }
for l:child in copy(a:node.children)
if s:traverse(l:child, a:callback, a:state, l:next_context)
return v:true
endif
endfor
else
let a:state.before_text .= l:text
let a:state.offset += l:length
endif
endfunction
"
" debug
"
function! s:Snippet.debug() abort
echomsg 'snippet.text()'
for l:line in split(self.text(), "\n", v:true)
echomsg string(l:line)
endfor
echomsg '-----'
let l:fn = {}
function! l:fn.traverse(context) abort
echomsg repeat(' ', a:context.depth - 1) . a:context.node.to_string()
endfunction
call self.traverse(self, l:fn.traverse)
echomsg ' '
endfunction

View File

@@ -0,0 +1,43 @@
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
"
function! vsnip#snippet#node#create_from_ast(ast) abort
if type(a:ast) == type([])
return map(a:ast, 'vsnip#snippet#node#create_from_ast(v:val)')
endif
if a:ast.type ==# 'placeholder'
return s:Placeholder.new(a:ast)
endif
if a:ast.type ==# 'variable'
return s:Variable.new(a:ast)
endif
if a:ast.type ==# 'text'
return s:Text.new(a:ast)
endif
throw 'vsnip: invalid node type'
endfunction
"
" vsnip#snippet#node#create_text
"
function! vsnip#snippet#node#create_text(text) abort
return s:Text.new({
\ 'type': 'text',
\ 'raw': a:text,
\ 'escaped': a:text
\ })
endfunction
"
" vsnip#snippet#node#create_transform
"
function! vsnip#snippet#node#create_transform(transform) abort
return s:Transform.new(a:transform)
endfunction

View File

@@ -0,0 +1,55 @@
let s:max_tabstop = 1000000
let s:uid = 0
function! vsnip#snippet#node#placeholder#import() abort
return s:Placeholder
endfunction
let s:Placeholder = {}
"
" new.
"
function! s:Placeholder.new(ast) abort
let s:uid += 1
let l:node = extend(deepcopy(s:Placeholder), {
\ 'uid': s:uid,
\ 'type': 'placeholder',
\ 'id': a:ast.id,
\ 'is_final': a:ast.id == 0,
\ '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
let l:node.id = s:max_tabstop
endif
if len(l:node.children) == 0
let l:node.children = [vsnip#snippet#node#create_text('')]
endif
return l:node
endfunction
"
" text.
"
function! s:Placeholder.text() abort
return join(map(copy(self.children), 'v:val.text()'), '')
endfunction
"
" to_string
"
function! s:Placeholder.to_string() abort
return printf('%s(id=%s, follower=%s, choise=%s)',
\ self.type,
\ self.id,
\ self.follower ? 'true' : 'false',
\ self.choice
\ )
endfunction

View File

@@ -0,0 +1,38 @@
let s:uid = 0
function! vsnip#snippet#node#text#import() abort
return s:Text
endfunction
let s:Text = {}
"
" new.
"
function! s:Text.new(ast) abort
let s:uid += 1
return extend(deepcopy(s:Text), {
\ 'uid': s:uid,
\ 'type': 'text',
\ 'value': a:ast.escaped,
\ 'children': [],
\ })
endfunction
"
" text.
"
function! s:Text.text() abort
return self.value
endfunction
"
" to_string
"
function! s:Text.to_string() abort
return printf('%s(%s)',
\ self.type,
\ self.value
\ )
endfunction

View File

@@ -0,0 +1,112 @@
function! vsnip#snippet#node#transform#import() abort
return s:Transform
endfunction
let s:Transform = {}
"
" 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
"
" 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

View File

@@ -0,0 +1,63 @@
let s:uid = 0
"
" vsnip#snippet#node#variable#import
"
function! vsnip#snippet#node#variable#import() abort
return s:Variable
endfunction
let s:Variable = {}
"
" new.
"
function! s:Variable.new(ast) abort
let s:uid += 1
let l:resolver = vsnip#variable#get(a:ast.name)
return extend(deepcopy(s:Variable), {
\ 'uid': s:uid,
\ 'type': 'variable',
\ 'name': a:ast.name,
\ '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
"
" text.
"
function! s:Variable.text() abort
return self.transform.text(join(map(copy(self.children), 'v:val.text()'), ''))
endfunction
"
" resolve.
"
function! s:Variable.resolve(context) abort
if !self.unknown
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])
return substitute(l:resolved, "\n\\zs", l:base_indent, 'g')
endif
endif
return v:null
endfunction
"
" to_string
"
function! s:Variable.to_string() abort
return printf('%s(name=%s, unknown=%s, text=%s)',
\ self.type,
\ self.name,
\ self.unknown ? 'true' : 'false',
\ self.text()
\ )
endfunction

View File

@@ -0,0 +1,212 @@
let s:Combinator = vsnip#parser#combinator#import()
"
" vsnip#snippet#parser#parse.
" @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
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
let s:token = s:Combinator.token
let s:many = s:Combinator.many
let s:or = s:Combinator.or
let s:seq = s:Combinator.seq
let s:lazy = s:Combinator.lazy
let s:option = s:Combinator.option
let s:pattern = s:Combinator.pattern
let s:map = s:Combinator.map
"
" primitives.
"
let s:dollar = s:token('$')
let s:open = s:token('{')
let s:close = s:token('}')
let s:colon = s:token(':')
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:text = { stop, escape -> s:map(
\ s:skip(stop, escape),
\ { value -> {
\ 'type': 'text',
\ 'raw': value[0],
\ 'escaped': value[1]
\ }
\ }) }
let s:regex = s:map(s:text(['/'], []), { value -> {
\ 'type': 'regex',
\ 'pattern': value.raw
\ } })
"
" any (without text).
"
let s:any = s:or(
\ s:lazy({ -> s:choice }),
\ s:lazy({ -> s:variable }),
\ s:lazy({ -> s:tabstop }),
\ s:lazy({ -> s:placeholder }),
\ )
"
" format.
"
let s:format1 = s:map(s:seq(s:dollar, s:int), { value -> {
\ 'type': 'format',
\ 'id': value[1]
\ } })
let s:format2 = s:map(s:seq(s:dollar, s:open, s:int, s:close), { value -> {
\ 'type': 'format',
\ 'id': value[2]
\ } })
let s:format3 = s:map(
\ s:seq(
\ s:dollar,
\ s:open,
\ s:int,
\ s:colon,
\ s:or(
\ 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'),
\ s:token('else')
\ ),
\ s:close
\ ), { value -> {
\ 'type': 'format',
\ 'id': value[2],
\ 'modifier': value[4]
\ } })
let s:format = s:or(s:format1, s:format2, s:format3)
"
" transform
"
let s:transform = s:map(s:seq(
\ s:slash,
\ s:regex,
\ s:slash,
\ s:many(s:or(s:format, s:text(['/', '$'], []))),
\ s:slash,
\ s:option(s:many(s:or(s:token('i'), s:token('g'))))
\ ), { value -> {
\ 'type': 'transform',
\ 'regex': value[1],
\ 'format': value[3],
\ 'option': value[5]
\ } })
"
" variable
"
let s:variable1 = s:map(s:seq(s:dollar, s:varname), { value -> {
\ 'type': 'variable',
\ 'name': value[1],
\ 'children': [],
\ } })
let s:variable2 = s:map(s:seq(s:dollar, s:open, s:varname, s:close), { value -> {
\ 'type': 'variable',
\ 'name': value[2],
\ 'children': [],
\ } })
let s:variable3 = s:map(s:seq(
\ s:dollar,
\ s:open,
\ s:varname,
\ s:colon,
\ s:many(s:or(s:any, s:text(['$', '}'], []))),
\ s:close
\ ), { value -> {
\ 'type': 'variable',
\ 'name': value[2],
\ 'children': value[4]
\ } })
let s:variable4 = s:map(s:seq(s:dollar, s:open, s:varname, s:transform, s:close), { value -> {
\ 'type': 'variable',
\ 'name': value[2],
\ 'transform': value[3],
\ 'children': [],
\ } })
let s:variable = s:or(s:variable1, s:variable2, s:variable3, s:variable4)
"
" placeholder.
"
let s:placeholder = s:map(s:seq(
\ s:dollar,
\ s:open,
\ s:int,
\ s:colon,
\ s:many(s:or(s:any, s:text(['$', '}'], []))),
\ s:close
\ ), { value -> {
\ 'type': 'placeholder',
\ 'id': value[2],
\ 'children': value[4]
\ } })
"
" tabstop
"
let s:tabstop1 = s:map(s:seq(s:dollar, s:int), { value -> {
\ 'type': 'placeholder',
\ 'id': value[1],
\ 'children': [],
\ } })
let s:tabstop2 = s:map(s:seq(s:dollar, s:open, s:int, s:option(s:colon), s:close), { value -> {
\ 'type': 'placeholder',
\ 'id': value[2],
\ 'children': [],
\ } })
let s:tabstop3 = s:map(s:seq(s:dollar, s:open, s:int, s:transform, s:close), { value -> {
\ 'type': 'placeholder',
\ 'id': value[2],
\ 'children': [],
\ 'transform': value[3]
\ } })
let s:tabstop = s:or(s:tabstop1, s:tabstop2, s:tabstop3)
"
" choice
"
let s:choice = s:map(s:seq(
\ s:dollar,
\ s:open,
\ s:int,
\ s:pipe,
\ s:many(
\ s:map(s:seq(s:text([',', '|'], []), s:option(s:comma)), { value -> value[0] }),
\ ),
\ s:pipe,
\ s:close
\ ), { value -> {
\ 'type': 'placeholder',
\ 'id': value[2],
\ 'choice': value[4],
\ 'children': [copy(value[4][0])],
\ } })
"
" parser.
"
let s:parser = s:many(s:or(s:any, s:text(['$'], ['}'])))

View File

@@ -0,0 +1,116 @@
"
" vsnip#source#refresh.
"
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
"
" vsnip#source#find.
"
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
"
" 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']
endfunction
"
" vsnip#source#create.
"
function! vsnip#source#create(path) abort
try
let l:file = readfile(a:path)
let l:file = type(l:file) == type([]) ? join(l:file, "\n") : l:file
let l:file = iconv(l:file, 'utf-8', &encoding)
let l:json = json_decode(l:file)
if type(l:json) != type({})
throw printf('%s is not valid json.', a:path)
endif
catch /.*/
let l:json = {}
echohl ErrorMsg
echomsg printf('[vsnip] Parsing error occurred on: %s', a:path)
echohl None
echomsg string({ 'exception': v:exception, 'throwpint': v:throwpoint })
endtry
" @see https://github.com/microsoft/vscode/blob/0ba9f6631daec96a2b71eeb337e29f50dd21c7e1/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts#L216
let l:source = []
for [l:key, l:value] in items(l:json)
if s:is_snippet(l:value)
call add(l:source, s:format_snippet(l:key, l:value))
else
for [l:key, l:value_] in items(l:value)
if s:is_snippet(l:value_)
call add(l:source, s:format_snippet(l:key, l:value_))
endif
endfor
endif
endfor
return sort(l:source, { a, b -> strlen(b.prefix[0]) - strlen(a.prefix[0]) })
endfunction
"
" format_snippet
"
function! s:format_snippet(label, snippet) abort
let [l:prefixes, l:prefixes_alias] = vsnip#source#resolve_prefix(a:snippet.prefix)
let l:description = get(a:snippet, 'description', '')
return {
\ 'label': a:label,
\ 'prefix': l:prefixes,
\ 'prefix_alias': l:prefixes_alias,
\ 'body': type(a:snippet.body) == type([]) ? a:snippet.body : [a:snippet.body],
\ 'description': type(l:description) == type([]) ? join(l:description, '') : l:description,
\ }
endfunction
"
" is_snippet
"
function! s:is_snippet(snippet_or_source) abort
return type(a:snippet_or_source) == type({}) && has_key(a:snippet_or_source, 'prefix') && has_key(a:snippet_or_source, 'body')
endfunction
"
" resolve_prefix.
"
function! vsnip#source#resolve_prefix(prefix) abort
let l:prefixes = []
let l:prefixes_alias = []
for l:prefix in type(a:prefix) == type([]) ? a:prefix : [a:prefix]
" namspace.
if strlen(g:vsnip_namespace) > 0
call add(l:prefixes, g:vsnip_namespace . l:prefix)
endif
" prefix.
call add(l:prefixes, l:prefix)
" alias.
if l:prefix =~# '^\h\w*\%(-\w\+\)\+$'
call add(l:prefixes_alias, join(map(split(l:prefix, '-'), { i, v -> v[0] }), ''))
endif
endfor
return [
\ sort(l:prefixes, { a, b -> strlen(b) - strlen(a) }),
\ sort(l:prefixes_alias, { a, b -> strlen(b) - strlen(a) })
\ ]
endfunction

View File

@@ -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

View File

@@ -0,0 +1,69 @@
let s:cache = {}
"
" vsnip#source#user_snippet#find.
"
function! vsnip#source#user_snippet#find(bufnr) abort
let l:sources = []
for l:path in s:get_source_paths(a:bufnr)
if !has_key(s:cache, l:path)
let s:cache[l:path] = vsnip#source#create(l:path)
endif
call add(l:sources, s:cache[l:path])
endfor
return l:sources
endfunction
"
" vsnip#source#user_snippet#refresh.
"
function! vsnip#source#user_snippet#refresh(path) abort
if has_key(s:cache, a:path)
unlet s:cache[a:path]
endif
endfunction
function! s:get_source_dirs(bufnr) abort
let l:dirs = []
let l:buf_dir = getbufvar(a:bufnr, 'vsnip_snippet_dir', v:null)
if l:buf_dir isnot v:null
let l:dirs += [l:buf_dir]
endif
let l:dirs += getbufvar(a:bufnr, 'vsnip_snippet_dirs', [])
let l:dirs += [g:vsnip_snippet_dir]
let l:dirs += g:vsnip_snippet_dirs
return l:dirs
endfunction
"
" get_source_paths.
"
function! s:get_source_paths(bufnr) abort
let l:filetypes = vsnip#source#filetypes(a:bufnr)
let l:paths = []
for l:dir in s:get_source_dirs(a:bufnr)
for l:filetype in l:filetypes
let l:path = resolve(expand(printf('%s/%s.json', l:dir, l:filetype)))
if has_key(s:cache, l:path) || filereadable(l:path)
call add(l:paths, l:path)
endif
endfor
endfor
return l:paths
endfunction
"
" vsnip#source#user_snippet#dirs
"
fun! vsnip#source#user_snippet#dirs(...) abort
return s:get_source_dirs(a:0 ? a:1 : bufnr(''))
endfun
"
" vsnip#source#user_snippet#paths
"
fun! vsnip#source#user_snippet#paths(...) abort
return s:get_source_paths(a:0 ? a:1 : bufnr(''))
endfun

View File

@@ -0,0 +1,104 @@
let s:snippets = {}
let s:runtimepaths = {}
"
" vsnip#source#vscode#refresh.
"
function! vsnip#source#vscode#refresh(path) abort
if has_key(s:snippets, a:path)
unlet s:snippets[a:path]
for [l:rtp, l:v] in items(s:runtimepaths)
if stridx(l:rtp, a:path) == 0
unlet s:runtimepaths[l:rtp]
endif
endfor
endif
endfunction
"
" vsnip#source#vscode#find.
"
function! vsnip#source#vscode#find(bufnr) abort
return s:find(map(vsnip#source#filetypes(a:bufnr), 's:get_language(v:val)'))
endfunction
"
" find.
"
function! s:find(languages) abort
" Load `package.json#contributes.snippets` if does not exists it's cache.
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
let s:runtimepaths[l:rtp] = v:true
try
let l:package_json = resolve(expand(l:rtp . '/package.json'))
if !filereadable(l:package_json)
continue
endif
let l:package_json = readfile(l:package_json)
let l:package_json = type(l:package_json) == type([]) ? join(l:package_json, "\n") : l:package_json
let l:package_json = iconv(l:package_json, 'utf-8', &encoding)
let l:package_json = json_decode(l:package_json)
" if package.json has not `contributes.snippets` fields, skip it.
if !has_key(l:package_json, 'contributes')
\ || !has_key(l:package_json.contributes, 'snippets')
continue
endif
" Create source if does not exists it's cache.
for l:snippet in l:package_json.contributes.snippets
let l:path = resolve(expand(l:rtp . '/' . l:snippet.path))
let l:languages = type(l:snippet.language) == type([]) ? l:snippet.language : [l:snippet.language]
" if already cached `snippets.json`, add new language.
if has_key(s:snippets, l:path)
for l:language in l:languages
if index(s:snippets[l:path].languages, l:language) == -1
call add(s:snippets[l:path].languages, l:language)
endif
endfor
continue
endif
" register new snippet.
let s:snippets[l:path] = {
\ 'languages': l:languages,
\ }
endfor
catch /.*/
endtry
endfor
" filter by language.
let l:sources = []
for l:language in a:languages
for [l:path, l:snippet] in items(s:snippets)
if index(l:snippet.languages, l:language) >= 0
if !has_key(l:snippet, 'source')
let l:snippet.source = vsnip#source#create(l:path)
end
call add(l:sources, l:snippet.source)
endif
endfor
endfor
return l:sources
endfunction
"
" get_language.
"
function! s:get_language(filetype) abort
return get({
\ 'javascript.jsx': 'javascriptreact',
\ 'typescript.tsx': 'typescriptreact',
\ 'sh': 'shellscript',
\ 'cs': 'csharp',
\ }, a:filetype, a:filetype)
endfunction

View File

@@ -0,0 +1,189 @@
let s:variables = {}
"
" vsnip#variable#register
"
function! vsnip#variable#register(name, func, ...) abort
let l:option = get(a:000, 0, {})
let s:variables[a:name] = {
\ 'func': a:func,
\ 'once': get(l:option, 'once', v:false)
\ }
endfunction
"
" vsnip#variable#get
"
function! vsnip#variable#get(name) abort
return get(s:variables, a:name, v:null)
endfunction
"
" Register built-in variables.
"
" @see https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables
"
function! s:TM_SELECTED_TEXT(context) abort
let l:selected_text = vsnip#selected_text()
if empty(l:selected_text)
return v:null
endif
return vsnip#indent#trim_base_indent(l:selected_text)
endfunction
call vsnip#variable#register('TM_SELECTED_TEXT', function('s:TM_SELECTED_TEXT'))
function! s:TM_CURRENT_LINE(context) abort
return getline('.')
endfunction
call vsnip#variable#register('TM_CURRENT_LINE', function('s:TM_CURRENT_LINE'))
function! s:TM_CURRENT_WORD(context) abort
return v:null
endfunction
call vsnip#variable#register('TM_CURRENT_WORD', function('s:TM_CURRENT_WORD'))
function! s:TM_LINE_INDEX(context) abort
return line('.') - 1
endfunction
call vsnip#variable#register('TM_LINE_INDEX', function('s:TM_LINE_INDEX'))
function! s:TM_LINE_NUMBER(context) abort
return line('.')
endfunction
call vsnip#variable#register('TM_LINE_NUMBER', function('s:TM_LINE_NUMBER'))
function! s:TM_FILENAME(context) abort
return expand('%:p:t')
endfunction
call vsnip#variable#register('TM_FILENAME', function('s:TM_FILENAME'))
function! s:TM_FILENAME_BASE(context) abort
return substitute(expand('%:p:t'), '^\@<!\..*$', '', '')
endfunction
call vsnip#variable#register('TM_FILENAME_BASE', function('s:TM_FILENAME_BASE'))
function! s:TM_DIRECTORY(context) abort
return expand('%:p:h:t')
endfunction
call vsnip#variable#register('TM_DIRECTORY', function('s:TM_DIRECTORY'))
function! s:TM_FILEPATH(context) abort
return expand('%:p')
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)
return v:null
endif
return vsnip#indent#trim_base_indent(l:clipboard)
endfunction
call vsnip#variable#register('CLIPBOARD', function('s:CLIPBOARD'))
function! s:WORKSPACE_NAME(context) abort
return v:null
endfunction
call vsnip#variable#register('WORKSPACE_NAME', function('s:WORKSPACE_NAME'))
function! s:CURRENT_YEAR(context) abort
return strftime('%Y')
endfunction
call vsnip#variable#register('CURRENT_YEAR', function('s:CURRENT_YEAR'))
function! s:CURRENT_YEAR_SHORT(context) abort
return strftime('%y')
endfunction
call vsnip#variable#register('CURRENT_YEAR_SHORT', function('s:CURRENT_YEAR_SHORT'))
function! s:CURRENT_MONTH(context) abort
return strftime('%m')
endfunction
call vsnip#variable#register('CURRENT_MONTH', function('s:CURRENT_MONTH'))
function! s:CURRENT_MONTH_NAME(context) abort
return strftime('%B')
endfunction
call vsnip#variable#register('CURRENT_MONTH_NAME', function('s:CURRENT_MONTH_NAME'))
function! s:CURRENT_MONTH_NAME_SHORT(context) abort
return strftime('%b')
endfunction
call vsnip#variable#register('CURRENT_MONTH_NAME_SHORT', function('s:CURRENT_MONTH_NAME_SHORT'))
function! s:CURRENT_DATE(context) abort
return strftime('%d')
endfunction
call vsnip#variable#register('CURRENT_DATE', function('s:CURRENT_DATE'))
function! s:CURRENT_DAY_NAME(context) abort
return strftime('%A')
endfunction
call vsnip#variable#register('CURRENT_DAY_NAME', function('s:CURRENT_DAY_NAME'))
function! s:CURRENT_DAY_NAME_SHORT(context) abort
return strftime('%a')
endfunction
call vsnip#variable#register('CURRENT_DAY_NAME_SHORT', function('s:CURRENT_DAY_NAME_SHORT'))
function! s:CURRENT_HOUR(context) abort
return strftime('%H')
endfunction
call vsnip#variable#register('CURRENT_HOUR', function('s:CURRENT_HOUR'))
function! s:CURRENT_MINUTE(context) abort
return strftime('%M')
endfunction
call vsnip#variable#register('CURRENT_MINUTE', function('s:CURRENT_MINUTE'))
function! s:CURRENT_SECOND(context) abort
return strftime('%S')
endfunction
call vsnip#variable#register('CURRENT_SECOND', function('s:CURRENT_SECOND'))
function! s:CURRENT_SECONDS_UNIX(context) abort
return localtime()
endfunction
call vsnip#variable#register('CURRENT_SECONDS_UNIX', function('s:CURRENT_SECONDS_UNIX'))
function! s:BLOCK_COMMENT_START(context) abort
return split(&commentstring, '%s')[0]
endfunction
call vsnip#variable#register('BLOCK_COMMENT_START', function('s:BLOCK_COMMENT_START'))
function! s:BLOCK_COMMENT_END(context) abort
let l:chars = split(&commentstring, '%s')
let l:comment = len(l:chars) > 1 ? l:chars[1] : l:chars[0]
return trim(l:comment)
endfunction
call vsnip#variable#register('BLOCK_COMMENT_END', function('s:BLOCK_COMMENT_END'))
function! s:LINE_COMMENT(context) abort
let l:chars = split(&commentstring, '%s')
let l:comment = &commentstring =~# '^/\*' ? '//' : substitute(&commentstring, '%s', '', 'g')
return trim(l:comment)
endfunction
call vsnip#variable#register('LINE_COMMENT', function('s:LINE_COMMENT'))
function! s:VIM(context) abort
let l:script = join(map(copy(a:context.node.children), 'v:val.text()'), '')
try
return eval(l:script)
catch /.*/
endtry
return v:null
endfunction
call vsnip#variable#register('VIM', function('s:VIM'))
function! s:VSNIP_CAMELCASE_FILENAME(context) abort
let l:basename = substitute(expand('%:p:t'), '^\@<!\..*$', '', '')
return substitute(l:basename, '\(\%(\<\l\+\)\%(_\)\@=\)\|_\(\l\)', '\u\1\2', 'g')
endfunction
call vsnip#variable#register('VSNIP_CAMELCASE_FILENAME', function('s:VSNIP_CAMELCASE_FILENAME'))

View File

@@ -0,0 +1,12 @@
vim-vsnip vsnip.txt /*vim-vsnip*
vsnip vsnip.txt /*vsnip*
vsnip-built-in-variable vsnip.txt /*vsnip-built-in-variable*
vsnip-changelog vsnip.txt /*vsnip-changelog*
vsnip-command vsnip.txt /*vsnip-command*
vsnip-contents vsnip.txt /*vsnip-contents*
vsnip-function vsnip.txt /*vsnip-function*
vsnip-install vsnip.txt /*vsnip-install*
vsnip-limitation vsnip.txt /*vsnip-limitation*
vsnip-mapping vsnip.txt /*vsnip-mapping*
vsnip-snipmate-support vsnip.txt /*vsnip-snipmate-support*
vsnip-variable vsnip.txt /*vsnip-variable*

View File

@@ -0,0 +1,323 @@
*vim-vsnip* *vsnip*
V(SCode) Snip(pet) like plugin.
==============================================================================
CONTENTS *vsnip-contents*
INSTALL |vsnip-install|
VARIABLE |vsnip-variable|
FUNCTION |vsnip-function|
MAPPING |vsnip-mapping|
COMMAND |vsnip-command|
BUILT-IN VARIABLE |vsnip-built-in-variable|
LIMITATION |vsnip-limitation|
CHANGELOG |vsnip-changelog|
==============================================================================
INSTALL *vsnip-install*
You can use your favorite plugin manager.
>
" dein.vim
call dein#add('hrsh7th/vim-vsnip')
" vim-plug
Plug 'hrsh7th/vim-vsnip'
" neobundle
NeoBundle 'hrsh7th/vim-vsnip'
<
If you use `deoplete.nvim` or other supported integration, you can use `vim-vsnip-integ`.
>
" dein.vim
call dein#add('hrsh7th/vim-vsnip-integ')
" vim-plug
Plug 'hrsh7th/vim-vsnip-integ'
" neobundle
NeoBundle 'hrsh7th/vim-vsnip-integ'
<
If you want to know supported plugins, you can see https://github.com/hrsh7th/vim-vsnip-integ
==============================================================================
VARIABLE *vsnip-variable*
let g:vsnip_extra_mapping = v:true~
Enable or disable extra mappings.
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 = []~
List of user snippet directories.
Also as buffer-local variable: `b:vsnip_snippet_dirs`
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.
`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~
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 = ''~
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.
==============================================================================
FUNCTION *vsnip-function*
vsnip#variable#register({VAR_NAME}, {FUNCREF}, [{OPTION}}])
Register your own custom variable resolver.
==============================================================================
MAPPING *vsnip-mapping*
You can use your favorite key to expand or jump snippet.
The below example uses '<Tab>' key.
>
" Expand
imap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<C-j>'
smap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<C-j>'
" Expand or jump
imap <expr> <C-l> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
smap <expr> <C-l> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
" Jump forward or backward
imap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
smap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
imap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'
smap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'
" Select or cut text to use as $TM_SELECTED_TEXT in the next snippet.
" See https://github.com/hrsh7th/vim-vsnip/pull/50
nmap s <Plug>(vsnip-select-text)
xmap s <Plug>(vsnip-select-text)
nmap S <Plug>(vsnip-cut-text)
xmap S <Plug>(vsnip-cut-text)
<
==============================================================================
COMMAND *vsnip-command*
VsnipOpen~
: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~
Copy the given range formatted as json into the clipboard.
Use this command to yank the current line as a snippet with the keyword 'key'
and open the snippets file.
>
:VsnipYank key | VsnipOpen
<
==============================================================================
BULT-IN VARIABLE *vsnip-built-in-variable*
Basically, vsnip provides some of built-in variables that defined in VSCode or LSP spec.
The following variables can be used in the same way they are in VSCode:
`TM_SELECTED_TEXT` The currently selected text or the empty string
`TM_CURRENT_LINE` The contents of the current line
`TM_CURRENT_WORD` The contents of the word under cursor or the empty string
`TM_LINE_INDEX` The zero-index based line number
`TM_LINE_NUMBER` The one-index based line number
`TM_FILENAME` The filename of the current document
`TM_FILENAME_BASE` The filename of the current document without its extensions
`TM_DIRECTORY` The directory of the current document
`TM_FILEPATH` The full file path of the current document
`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
For inserting the current date and time:
`CURRENT_YEAR` The current year
`CURRENT_YEAR_SHORT` The current year's last two digits
`CURRENT_MONTH` The month as two digits (example '02')
`CURRENT_MONTH_NAME` The full name of the month (example 'July')
`CURRENT_MONTH_NAME_SHORT` The short name of the month (example 'Jul')
`CURRENT_DATE` The day of the month
`CURRENT_DAY_NAME` The name of day (example 'Monday')
`CURRENT_DAY_NAME_SHORT` The short name of the day (example 'Mon')
`CURRENT_HOUR` The current hour in 24-hour clock format
`CURRENT_MINUTE` The current minute
`CURRENT_SECOND` The current second
`CURRENT_SECONDS_UNIX` The number of seconds since the Unix epoch
For inserting line or block comments, honoring the current language:
`BLOCK_COMMENT_START` Example output: in PHP /* or in HTML <!--
`BLOCK_COMMENT_END` Example output: in PHP */ or in HTML -->
`LINE_COMMENT` Example output: in PHP //
In addition, vsnip provides the below custom variables too.
`VSNIP_CAMELCASE_FILENAME` The filename of the current document without its extensions in CamelCase format
${VIM:...Vim script expression...}~
You can use this variable for `Vim script interpolation`.
For example, the below snippet will be current filetype.
>
{
"filetype": {
"prefix": "filetype",
"body": "${VIM:&filetype}"
}
}
<
You can also using any Vim script expression.
>
{
"sum": {
"prefix": "sum",
"body": "${VIM:1 + 2}"
}
}
<
Currently, vsnip only once resolves variable at the snippet initialization.
==============================================================================
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*
Currently vsnip has below limitations.
1. placeholder transform feature is not supported.~
I plan to support it later.
2. if text diff has multiple candidates, always use last one.~
below snippet is not work for expected.
>
class $1${2: extends ${3:SuperClass}} {
$0
}
<
below one is work as expected.
>
class $1 ${2:extends ${3:SuperClass} }{
$0
}
<
3. if edit the placeholder that does not the current_tabstop, vsnip try to follow correctly but sometimes it makes unexpected result.~
For example, you expand `console.log(${1:foo}${2:bar}, ${2})`, remove `foo` and change `bar` without jump.
In this case, vsnip will detects `$1 is edited.` so vsnip does not sync $2 placeholder.
==============================================================================
CHANGELOG *vsnip-changelog*
2019/12/01~
- publish v2.
==============================================================================
vim:tw=78:ts=4:et:ft=help:norl:

View File

@@ -0,0 +1 @@
ref: refs/heads/master

View File

@@ -0,0 +1,11 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://github.com/hrsh7th/vim-vsnip.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

View File

@@ -0,0 +1,174 @@
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
# An example hook script to integrate Watchman
# (https://facebook.github.io/watchman/) with git to speed up detecting
# new and modified files.
#
# The hook is passed a version (currently 2) and last update token
# formatted as a string and outputs to stdout a new update token and
# all files that have been modified since the update token. Paths must
# be relative to the root of the working tree and separated by a single NUL.
#
# To enable this hook, rename this file to "query-watchman" and set
# 'git config core.fsmonitor .git/hooks/query-watchman'
#
my ($version, $last_update_token) = @ARGV;
# Uncomment for debugging
# print STDERR "$0 $version $last_update_token\n";
# Check the hook interface version
if ($version ne 2) {
die "Unsupported query-fsmonitor hook version '$version'.\n" .
"Falling back to scanning...\n";
}
my $git_work_tree = get_working_dir();
my $retry = 1;
my $json_pkg;
eval {
require JSON::XS;
$json_pkg = "JSON::XS";
1;
} or do {
require JSON::PP;
$json_pkg = "JSON::PP";
};
launch_watchman();
sub launch_watchman {
my $o = watchman_query();
if (is_work_tree_watched($o)) {
output_result($o->{clock}, @{$o->{files}});
}
}
sub output_result {
my ($clockid, @files) = @_;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# binmode $fh, ":utf8";
# print $fh "$clockid\n@files\n";
# close $fh;
binmode STDOUT, ":utf8";
print $clockid;
print "\0";
local $, = "\0";
print @files;
}
sub watchman_clock {
my $response = qx/watchman clock "$git_work_tree"/;
die "Failed to get clock id on '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
return $json_pkg->new->utf8->decode($response);
}
sub watchman_query {
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
or die "open2() failed: $!\n" .
"Falling back to scanning...\n";
# In the query expression below we're asking for names of files that
# changed since $last_update_token but not from the .git folder.
#
# To accomplish this, we're using the "since" generator to use the
# recency index to select candidate nodes and "fields" to limit the
# output to file names only. Then we're using the "expression" term to
# further constrain the results.
my $last_update_line = "";
if (substr($last_update_token, 0, 1) eq "c") {
$last_update_token = "\"$last_update_token\"";
$last_update_line = qq[\n"since": $last_update_token,];
}
my $query = <<" END";
["query", "$git_work_tree", {$last_update_line
"fields": ["name"],
"expression": ["not", ["dirname", ".git"]]
}]
END
# Uncomment for debugging the watchman query
# open (my $fh, ">", ".git/watchman-query.json");
# print $fh $query;
# close $fh;
print CHLD_IN $query;
close CHLD_IN;
my $response = do {local $/; <CHLD_OUT>};
# Uncomment for debugging the watch response
# open ($fh, ">", ".git/watchman-response.json");
# print $fh $response;
# close $fh;
die "Watchman: command returned no output.\n" .
"Falling back to scanning...\n" if $response eq "";
die "Watchman: command returned invalid output: $response\n" .
"Falling back to scanning...\n" unless $response =~ /^\{/;
return $json_pkg->new->utf8->decode($response);
}
sub is_work_tree_watched {
my ($output) = @_;
my $error = $output->{error};
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
$retry--;
my $response = qx/watchman watch "$git_work_tree"/;
die "Failed to make watchman watch '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
$output = $json_pkg->new->utf8->decode($response);
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# close $fh;
# Watchman will always return all files on the first query so
# return the fast "everything is dirty" flag to git and do the
# Watchman query just to get it over with now so we won't pay
# the cost in git to look up each individual file.
my $o = watchman_clock();
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
output_result($o->{clock}, ("/"));
$last_update_token = $o->{clock};
eval { launch_watchman() };
return 0;
}
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
return 1;
}
sub get_working_dir {
my $working_dir;
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
$working_dir = Win32::GetCwd();
$working_dir =~ tr/\\/\//;
} else {
require Cwd;
$working_dir = Cwd::cwd();
}
return $working_dir;
}

View File

@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info

View File

@@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

View File

@@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

View File

@@ -0,0 +1,13 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
:

View File

@@ -0,0 +1,53 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
while read local_ref local_oid remote_ref remote_oid
do
if test "$local_oid" = "$zero"
then
# Handle delete
:
else
if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
range="$local_oid"
else
# Update to existing branch, examine new commits
range="$remote_oid..$local_oid"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0

View File

@@ -0,0 +1,169 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi

View File

@@ -0,0 +1,42 @@
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
# case "$COMMIT_SOURCE,$SHA1" in
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
# *) ;;
# esac
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi

View File

@@ -0,0 +1,78 @@
#!/bin/sh
# An example hook script to update a checked-out tree on a git push.
#
# This hook is invoked by git-receive-pack(1) when it reacts to git
# push and updates reference(s) in its repository, and when the push
# tries to update the branch that is currently checked out and the
# receive.denyCurrentBranch configuration variable is set to
# updateInstead.
#
# By default, such a push is refused if the working tree and the index
# of the remote repository has any difference from the currently
# checked out commit; when both the working tree and the index match
# the current commit, they are updated to match the newly pushed tip
# of the branch. This hook is to be used to override the default
# behaviour; however the code below reimplements the default behaviour
# as a starting point for convenient modification.
#
# The hook receives the commit with which the tip of the current
# branch is going to be updated:
commit=$1
# It can exit with a non-zero status to refuse the push (when it does
# so, it must not modify the index or the working tree).
die () {
echo >&2 "$*"
exit 1
}
# Or it can make any necessary changes to the working tree and to the
# index to bring them to the desired state when the tip of the current
# branch is updated to the new commit, and exit with a zero status.
#
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
# in order to emulate git fetch that is run in the reverse direction
# with git push, as the two-tree form of git read-tree -u -m is
# essentially the same as git switch or git checkout that switches
# branches while keeping the local changes in the working tree that do
# not interfere with the difference between the branches.
# The below is a more-or-less exact translation to shell of the C code
# for the default behaviour for git's push-to-checkout hook defined in
# the push_to_deploy() function in builtin/receive-pack.c.
#
# Note that the hook will be executed from the repository directory,
# not from the working tree, so if you want to perform operations on
# the working tree, you will have to adapt your code accordingly, e.g.
# by adding "cd .." or using relative paths.
if ! git update-index -q --ignore-submodules --refresh
then
die "Up-to-date check failed"
fi
if ! git diff-files --quiet --ignore-submodules --
then
die "Working directory has unstaged changes"
fi
# This is a rough translation of:
#
# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
if git cat-file -e HEAD 2>/dev/null
then
head=HEAD
else
head=$(git hash-object -t tree --stdin </dev/null)
fi
if ! git diff-index --quiet --cached --ignore-submodules $head --
then
die "Working directory has staged changes"
fi
if ! git read-tree -u -m "$commit"
then
die "Could not update working tree to new HEAD"
fi

View File

@@ -0,0 +1,128 @@
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --type=bool hooks.allowunannotated)
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0

Binary file not shown.

View File

@@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View File

@@ -0,0 +1,2 @@
0000000000000000000000000000000000000000 8dde8c0ef10bb1afdbb301e2bd7eb1c153dd558e LinlyBoi <libkyy@e.email> 1676062730 +0200 clone: from https://github.com/hrsh7th/vim-vsnip.git
8dde8c0ef10bb1afdbb301e2bd7eb1c153dd558e 8dde8c0ef10bb1afdbb301e2bd7eb1c153dd558e LinlyBoi <libkyy@e.email> 1676062731 +0200 checkout: moving from master to master

View File

@@ -0,0 +1 @@
0000000000000000000000000000000000000000 8dde8c0ef10bb1afdbb301e2bd7eb1c153dd558e LinlyBoi <libkyy@e.email> 1676062730 +0200 clone: from https://github.com/hrsh7th/vim-vsnip.git

View File

@@ -0,0 +1 @@
0000000000000000000000000000000000000000 8dde8c0ef10bb1afdbb301e2bd7eb1c153dd558e LinlyBoi <libkyy@e.email> 1676062730 +0200 clone: from https://github.com/hrsh7th/vim-vsnip.git

View File

@@ -0,0 +1,8 @@
# pack-refs with: peeled fully-peeled sorted
920cb43d5904726aa05efbae2e135bca6275ce4a refs/remotes/origin/custom-variable
c947871a69a614113617649bf7717308576fa602 refs/remotes/origin/editor
130b118ec45ecb9799cb0263baa76b191be27d10 refs/remotes/origin/fix-issue249
eee2a1b15b44ec73f96d19ae4667123d51ca15f7 refs/remotes/origin/improve-sync
8dde8c0ef10bb1afdbb301e2bd7eb1c153dd558e refs/remotes/origin/master
d75388bd7e395e1960bac2145b4699ab0c2ba4e7 refs/remotes/origin/prefer-depth
2c3e2b203d49802f1bac0b16ef4132d42c6b128e refs/remotes/origin/refactor-node

View File

@@ -0,0 +1 @@
8dde8c0ef10bb1afdbb301e2bd7eb1c153dd558e

View File

@@ -0,0 +1 @@
ref: refs/remotes/origin/master

View File

@@ -0,0 +1,7 @@
130b118ec45ecb9799cb0263baa76b191be27d10
2c3e2b203d49802f1bac0b16ef4132d42c6b128e
8dde8c0ef10bb1afdbb301e2bd7eb1c153dd558e
920cb43d5904726aa05efbae2e135bca6275ce4a
c947871a69a614113617649bf7717308576fa602
d75388bd7e395e1960bac2145b4699ab0c2ba4e7
eee2a1b15b44ec73f96d19ae4667123d51ca15f7

View File

@@ -0,0 +1,49 @@
name: linux_neovim
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
name: [neovim-v04-x64,neovim-nightly-x64]
include:
- name: neovim-v04-x64
os: ubuntu-latest
neovim_version: v0.4.4
- name: neovim-nightly-x64
os: ubuntu-latest
neovim_version: nightly
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v1
- name: Download neovim
shell: bash
run: |
mkdir -p ~/nvim/bin
curl -L https://github.com/neovim/neovim/releases/download/${{matrix.neovim_version}}/nvim.appimage -o ~/nvim/bin/nvim
chmod u+x ~/nvim/bin/nvim
- name: Download test runner
shell: bash
run: git clone --depth 1 --branch v1.5.5 --single-branch https://github.com/thinca/vim-themis ~/themis
- name: Run tests
shell: bash
run: |
export PATH=~/nvim/bin:$PATH
export PATH=~/themis/bin:$PATH
export THEMIS_VIM=nvim
nvim --version
ls -al
export VIRTUALEDIT=0
themis ./spec
export VIRTUALEDIT=1
themis ./spec

View File

@@ -0,0 +1,50 @@
name: linux_vim
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
name: [vim-v82-x64, vim-v81-x64]
include:
- name: vim-v82-x64
os: ubuntu-latest
vim_version: 8.2.0813
glibc_version: 2.15
- name: vim-v81-x64
os: ubuntu-latest
vim_version: 8.1.2414
glibc_version: 2.15
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v1
- name: Download vim
shell: bash
run: |
mkdir -p ~/vim/bin
curl -L https://github.com/vim/vim-appimage/releases/download/v${{matrix.vim_version}}/GVim-v${{matrix.vim_version}}.glibc${{matrix.glibc_version}}-x86_64.AppImage -o ~/vim/bin/vim
chmod u+x ~/vim/bin/vim
- name: Download test runner
shell: bash
run: git clone --depth 1 --branch v1.5.5 --single-branch https://github.com/thinca/vim-themis ~/themis
- name: Run tests
shell: bash
run: |
export PATH=~/vim/bin:$PATH
export PATH=~/themis/bin:$PATH
export THEMIS_VIM=vim
vim --version
ls -al
export VIRTUALEDIT=0
themis ./spec
export VIRTUALEDIT=1
themis ./spec

View File

@@ -0,0 +1,49 @@
name: mac_neovim
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [macos-latest]
name: [neovim-v04-x64,neovim-nightly-x64]
include:
- name: neovim-v04-x64
os: macos-latest
neovim_version: v0.4.4
- name: neovim-nightly-x64
os: macos-latest
neovim_version: nightly
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v1
- name: Download neovim
shell: bash
run: curl -L https://github.com/neovim/neovim/releases/download/${{matrix.neovim_version}}/nvim-macos.tar.gz -o ~/nvim.tar.gz
- name: Extract neovim
shell: bash
run: tar xzf ~/nvim.tar.gz -C ~/
- name: Download test runner
shell: bash
run: git clone --depth 1 --branch v1.5.5 --single-branch https://github.com/thinca/vim-themis ~/themis
- name: Run tests
shell: bash
run: |
export PATH=~/nvim-osx64/bin:$PATH
export PATH=~/langservers:$PATH
export PATH=~/themis/bin:$PATH
export THEMIS_VIM=nvim
nvim --version
ls -al
export VIRTUALEDIT=0
themis ./spec
export VIRTUALEDIT=1
themis ./spec

View File

@@ -0,0 +1,50 @@
name: windows_neovim
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [windows-latest]
name: [neovim-v04-x64,neovim-nightly-x64]
include:
- name: neovim-v04-x64
os: windows-latest
neovim_version: v0.4.4
neovim_arch: win64
- name: neovim-nightly-x64
os: windows-latest
neovim_version: nightly
neovim_arch: win64
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v1
- name: Download neovim
shell: PowerShell
run: Invoke-WebRequest -Uri https://github.com/neovim/neovim/releases/download/${{matrix.neovim_version}}/nvim-${{matrix.neovim_arch}}.zip -OutFile neovim.zip
- name: Extract neovim
shell: PowerShell
run: Expand-Archive -Path neovim.zip -DestinationPath $env:USERPROFILE
- name: Download test runner
shell: PowerShell
run: git clone --depth 1 --branch v1.5.5 --single-branch https://github.com/thinca/vim-themis $env:USERPROFILE\themis
- name: Run tests
shell: cmd
run: |
SET PATH=%USERPROFILE%\Neovim\bin;%PATH%;
SET PATH=%USERPROFILE%\themis\bin;%PATH%;
SET THEMIS_VIM=nvim
nvim --version
ls -al
export VIRTUALEDIT=0
themis ./spec
export VIRTUALEDIT=1
themis ./spec

View File

@@ -0,0 +1,56 @@
name: windows_vim
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [windows-latest]
name: [vim-v82-x64, vim-v81-x64, vim-v80-x64]
include:
- name: vim-v82-x64
os: windows-latest
vim_version: 8.2.0813
vim_arch: x64
vim_ver_path: vim82
- name: vim-v81-x64
os: windows-latest
vim_version: 8.1.2414
vim_arch: x64
vim_ver_path: vim81
- name: vim-v80-x64
os: windows-latest
vim_version: 8.0.1567
vim_arch: x64
vim_ver_path: vim80
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v1
- name: Download vim
shell: PowerShell
run: Invoke-WebRequest -Uri https://github.com/vim/vim-win32-installer/releases/download/v${{matrix.vim_version}}/gvim_${{matrix.vim_version}}_${{matrix.vim_arch}}.zip -OutFile vim.zip
- name: Extract vim
shell: PowerShell
run: Expand-Archive -Path vim.zip -DestinationPath $env:USERPROFILE
- name: Download test runner
shell: PowerShell
run: git clone --depth 1 --branch v1.5.5 --single-branch https://github.com/thinca/vim-themis $env:USERPROFILE\themis
- name: Run tests
shell: cmd
run: |
SET PATH=%USERPROFILE%\vim\${{matrix.vim_ver_path}};%PATH%;
SET PATH=%USERPROFILE%\themis\bin;%PATH%;
vim --version
ls -al
export VIRTUALEDIT=0
themis ./spec
export VIRTUALEDIT=1
themis ./spec

View File

@@ -0,0 +1,2 @@
node_modules
/doc/tags

View File

@@ -0,0 +1 @@
package-lock=false

View File

@@ -0,0 +1,58 @@
call themis#option('recursive', 1)
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('<sfile>'), ':h') . '/misc'
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'
inoremap <Plug>(vsnip-C-j) <C-j>
inoremap <Plug>(vsnip-Tab) <Tab>
inoremap <Plug>(vsnip-S-Tab) <S-Tab>
imap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<Plug>(vsnip-C-j)'
smap <expr> <C-j> vsnip#expandable() ? '<Plug>(vsnip-expand)' : '<Plug>(vsnip-C-j)'
imap <expr> <Tab> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<Plug>(vsnip-Tab)'
smap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Plug>(vsnip-Tab)'
imap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<Plug>(vsnip-S-Tab)'
smap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<Plug>(vsnip-S-Tab)'
imap <Plug>(vsnip-assert) <C-r>=<SID>assert()<CR>
nmap <expr><Plug>(vsnip-assert) <SID>assert()
smap <expr><Plug>(vsnip-assert) <SID>assert()
function! s:assert() abort
let l:keys = sort(keys(g:vsnip_assert))
if len(l:keys) > 0
let l:session = vsnip#get_session()
if !empty(l:session)
call l:session.on_text_changed()
endif
let l:key = l:keys[0]
let l:Fn = g:vsnip_assert[l:key]
unlet! g:vsnip_assert[l:key]
call l:Fn()
endif
return ''
endfunction

View File

@@ -0,0 +1,47 @@
if has('vim_starting')
set encoding=utf-8
endif
scriptencoding utf-8
if &compatible
" vint: -ProhibitSetNoCompatible
set nocompatible
endif
if !isdirectory(expand('~/.vim/plugged/vim-plug'))
silent !curl -fLo ~/.vim/plugged/vim-plug/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
end
execute printf('source %s', expand('~/.vim/plugged/vim-plug/plug.vim'))
call plug#begin('~/.vim/plugged')
Plug 'gruvbox-community/gruvbox'
Plug expand('<sfile>:p:h:h') . '/vim-vsnip'
Plug expand('<sfile>:p:h:h') . '/vim-vsnip-integ'
call plug#end()
PlugInstall
colorscheme gruvbox
let g:mapleader = ' '
"
" required options.
"
set hidden
set ambiwidth=double
set completeopt=menu,menuone,noselect
let g:vsnip_snippet_dirs = [dein#get('vim-vsnip').rtp . '/misc']
"
" vim-vsnip mapping.
"
imap <expr><C-j> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-j>'
smap <expr><C-j> vsnip#available(1) ? '<Plug>(vsnip-expand-or-jump)' : '<C-j>'
imap <expr><Tab> vsnip#available(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
smap <expr><Tab> vsnip#available(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>'
imap <expr><S-Tab> vsnip#available(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'
smap <expr><S-Tab> vsnip#available(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>'

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,16 @@
{
"if": {
"prefix": "if",
"body": [
"if ${1:condition}",
"\t$0",
"endif"
]
},
"inline-fn": {
"prefix": ["inline-fn"],
"body": [
"{ -> $1 }$0"
]
}
}

View File

@@ -0,0 +1,185 @@
{
"spec1": {
"description": "simple snippet",
"prefix": ["spec1"],
"body": [
"snippet"
]
},
"spec2": {
"description": "jump at first of snippet",
"prefix": ["spec2"],
"body": [
"$1$2snippet"
]
},
"spec3": {
"description": "jump at middle of snippet",
"prefix": ["spec3"],
"body": [
"$1sni$2ppet"
]
},
"spec4": {
"description": "jump at last of snippet",
"prefix": ["spec4"],
"body": [
"$1snippet"
]
},
"spec5": {
"description": "select 1 length first of snippet text",
"prefix": ["spec5"],
"body": [
"$1${2:s}nippet"
]
},
"spec6": {
"description": "select 1 length middle of snippet text",
"prefix": ["spec6"],
"body": [
"$1sn${2:i}ppet"
]
},
"spec7": {
"description": "select 1 length last of snippet text",
"prefix": ["spec7"],
"body": [
"$1snippe${2:t}"
]
},
"spec8": {
"description": "select 3 length first of snippet text",
"prefix": ["spec8"],
"body": [
"$1${2:sni}ppet"
]
},
"spec9": {
"description": "select 3 length middle of snippet text",
"prefix": ["spec9"],
"body": [
"$1sn${2:ipp}et"
]
},
"spec10": {
"description": "select 3 length last of snippet text",
"prefix": ["spec10"],
"body": [
"$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か}う"
]
},
"deactivate1": {
"prefix": "deactivate",
"body": [
"function! $1() abort",
"\t$0",
"endfunction"
]
},
"realworld1": {
"description": "Complex example",
"prefix": ["realworld1"],
"body": [
"/** @class ${1:ClassName} */",
"class ${1} ${2:extends ${3:ParentClassName} }{",
"\tpublic constructor() {",
"\t\t$0",
"\t}",
"}"
]
},
"realworld2": {
"description": "$VIM variable",
"prefix": ["realworld2"],
"body": [
"${VIM:\\$USER}"
]
},
"realworld3": {
"description": "indented $TM_SELECTED_TEXT",
"prefix": ["realworld3"],
"body": [
"<div>",
"\t$TM_SELECTED_TEXT",
"</div>"
]
},
"realworld4": {
"description": "no indented $TM_SELECTED_TEXT",
"prefix": ["realworld4"],
"body": [
"<div>$TM_SELECTED_TEXT</div>"
]
},
"realworld5": {
"description": "modify follower placeholder manually",
"prefix": ["realworld5"],
"body": [
"$1, ${2:_${3:___$1___}_}"
]
},
"issue82": {
"description": "issue82",
"prefix": ["'"],
"body": [
"'$0'"
]
},
"issue85": {
"description": "issue85",
"prefix": ["issue85"],
"body": [
"for ${1:i}=${2:1},${3:10}",
"\t${0:print(i)}"
]
},
"issue106": {
"description": "issue106",
"prefix": ["issue106"],
"body": [
"$1"
]
},
"issue122": {
"description": "issue122",
"prefix": ["issue122>"],
"body": [
"$1"
]
},
"issue129": {
"description": "issue129",
"prefix": ["issue129"],
"body": [
"console.log('$1', $2);"
]
},
"issue139": {
"description": "issue139",
"prefix": "issue139",
"body": [
"for (${1:size_t }${2:i}=0; ${2} < ${3:count}; ${4:${2}++}) {",
"\t$TM_SELECTED_TEXT$5",
"}$0"
]
},
"issue249": {
"prefix": "issue249",
"body": ["${1:FOO}\n"]
}
}

View File

@@ -0,0 +1,6 @@
snippet arrow-function
() =>
snippet fn vim's function
function! $1($2) abort
$0
endfunction

View File

@@ -0,0 +1,13 @@
{
"no-prefix-alias": {
"prefix": ["---"],
"body": ["${VIM:repeat('-', &tw)}"]
},
"prefix-alias": {
"prefix": ["arrow-function"],
"body": "() => "
},
"has-no-prefix": {
"body": "() => "
}
}

View File

@@ -0,0 +1,10 @@
{
"contributes": {
"snippets": [
{
"path": "./source_spec_vscode.json",
"language": "source_spec_vscode"
}
]
}
}

View File

@@ -0,0 +1,10 @@
{
"func": {
"prefix": "func",
"body": [
"function! ${1:name}() abort",
"\t$0",
"endfunction"
]
}
}

View File

@@ -0,0 +1,38 @@
{
"name": "vim-vsnip",
"version": "1.0.0",
"description": "This aims to plugin like Visual Studio Code's Snippet feature.",
"scripts": {
"open": "nvim -u .vimrc",
"test": "run-s test:*",
"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": {
"hooks": {
"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",
"npm-run-all": "^4.1.5",
"watch": "^1.0.2"
}
}

View File

@@ -0,0 +1,225 @@
if exists('g:loaded_vsnip')
finish
endif
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)
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'])
let g:vsnip_filetypes.javascriptreact = get(g:vsnip_filetypes, 'javascriptreact', ['javascript'])
let g:vsnip_filetypes.vimspec = get(g:vsnip_filetypes, 'vimspec', ['vim'])
augroup vsnip#silent
autocmd!
autocmd User vsnip#expand silent
autocmd User vsnip#jump silent
augroup END
"
" command
"
command! -nargs=* -bang VsnipOpen call s:open_command(<bang>0, 'vsplit', <q-args>)
command! -nargs=* -bang VsnipOpenEdit call s:open_command(<bang>0, 'edit', <q-args>)
command! -nargs=* -bang VsnipOpenVsplit call s:open_command(<bang>0, 'vsplit', <q-args>)
command! -nargs=* -bang VsnipOpenSplit call s:open_command(<bang>0, 'split', <q-args>)
function! s:open_command(bang, cmd, arg)
let l:candidates = vsnip#source#filetypes(bufnr('%'))
if a:bang
let l:idx = 1
else
let l:idx = inputlist(['Select type: '] + map(copy(l:candidates), { k, v -> printf('%s: %s', k + 1, v) }))
if l:idx == 0
return
endif
endif
let l:expanded_dir = expand(g:vsnip_snippet_dir)
if !isdirectory(l:expanded_dir)
let l:prompt = printf('`%s` does not exists, create? y(es)/n(o): ', g:vsnip_snippet_dir)
if index(['y', 'ye', 'yes'], input(l:prompt)) >= 0
call mkdir(l:expanded_dir, 'p')
else
return
endif
endif
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:ext
\ )))
endfunction
command! -range -nargs=? -bar VsnipYank call s:add_command(<line1>, <line2>, <q-args>)
function! s:add_command(start, end, name) abort
let lines = map(getbufline('%', a:start, a:end), { key, val -> json_encode(substitute(val, '\$', '\\$', 'ge')) })
let format = " \"%s\": {\n \"prefix\": [\"%s\"],\n \"body\": [\n %s\n ]\n }"
let name = empty(a:name) ? 'new' : a:name
let reg = &clipboard =~# 'unnamed' ? '*' : '"'
let reg = &clipboard =~# 'unnamedplus' ? '+' : reg
call setreg(reg, printf(format, name, name, join(lines, ",\n ")), 'l')
endfunction
"
" extra mapping
"
if g:vsnip_extra_mapping
snoremap <expr> <BS> ("\<BS>" . (&virtualedit ==# '' && getcurpos()[2] >= col('$') - 1 ? 'a' : 'i'))
endif
"
" <Plug>(vsnip-expand-or-jump)
"
inoremap <silent> <Plug>(vsnip-expand-or-jump) <Esc>:<C-u>call <SID>expand_or_jump()<CR>
snoremap <silent> <Plug>(vsnip-expand-or-jump) <Esc>:<C-u>call <SID>expand_or_jump()<CR>
function! s:expand_or_jump()
let l:ctx = {}
function! l:ctx.callback() abort
let l:context = vsnip#get_context()
let l:session = vsnip#get_session()
if !empty(l:context)
call vsnip#expand()
elseif !empty(l:session) && l:session.jumpable(1)
call l:session.jump(1)
endif
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) && 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
call l:ctx.callback()
endif
endfunction
"
" <Plug>(vsnip-expand)
"
inoremap <silent> <Plug>(vsnip-expand) <Esc>:<C-u>call <SID>expand()<CR>
snoremap <silent> <Plug>(vsnip-expand) <C-g><Esc>:<C-u>call <SID>expand()<CR>
function! s:expand() abort
let l:ctx = {}
function! l:ctx.callback() abort
call vsnip#expand()
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) && 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
call l:ctx.callback()
endif
endfunction
"
" <Plug>(vsnip-jump-next)
" <Plug>(vsnip-jump-prev)
"
inoremap <silent> <Plug>(vsnip-jump-next) <Esc>:<C-u>call <SID>jump(1)<CR>
snoremap <silent> <Plug>(vsnip-jump-next) <Esc>:<C-u>call <SID>jump(1)<CR>
inoremap <silent> <Plug>(vsnip-jump-prev) <Esc>:<C-u>call <SID>jump(-1)<CR>
snoremap <silent> <Plug>(vsnip-jump-prev) <Esc>:<C-u>call <SID>jump(-1)<CR>
function! s:jump(direction) abort
let l:session = vsnip#get_session()
if !empty(l:session) && l:session.jumpable(a:direction)
call l:session.jump(a:direction)
endif
endfunction
"
" <Plug>(vsnip-select-text)
"
nnoremap <silent> <Plug>(vsnip-select-text) :set operatorfunc=<SID>vsnip_select_text_normal<CR>g@
snoremap <silent> <Plug>(vsnip-select-text) <C-g>:<C-u>call <SID>vsnip_visual_text(visualmode())<CR>gv<C-g>
xnoremap <silent> <Plug>(vsnip-select-text) :<C-u>call <SID>vsnip_visual_text(visualmode())<CR>gv
function! s:vsnip_select_text_normal(type) abort
call s:vsnip_set_text(a:type)
endfunction
"
" <Plug>(vsnip-cut-text)
"
nnoremap <silent> <Plug>(vsnip-cut-text) :set operatorfunc=<SID>vsnip_cut_text_normal<CR>g@
snoremap <silent> <Plug>(vsnip-cut-text) <C-g>:<C-u>call <SID>vsnip_visual_text(visualmode())<CR>gv"_c
xnoremap <silent> <Plug>(vsnip-cut-text) :<C-u>call <SID>vsnip_visual_text(visualmode())<CR>gv"_c
function! s:vsnip_cut_text_normal(type) abort
call feedkeys(s:vsnip_set_text(a:type) . '"_c', 'n')
endfunction
function! s:vsnip_visual_text(type) abort
call s:vsnip_set_text(a:type)
endfunction
function! s:vsnip_set_text(type) abort
let oldreg = [getreg('"'), getregtype('"')]
if a:type ==# 'v'
let select = '`<v`>'
elseif a:type ==# 'V'
let select = "'<V'>"
elseif a:type ==? "\<C-V>"
let select = "`<\<C-V>`>"
elseif a:type ==# 'char'
let select = '`[v`]'
elseif a:type ==# 'line'
let select = "'[V']"
else
return
endif
execute 'normal! ' . select . 'y'
call vsnip#selected_text(@")
call setreg('"', oldreg[0], oldreg[1])
return select
endfunction
"
" augroup.
"
augroup vsnip
autocmd!
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
"
" on_insert_leave
"
function! s:on_insert_leave() abort
let l:session = vsnip#get_session()
if !empty(l:session)
call l:session.on_insert_leave()
endif
endfunction
"
" on_text_changed
"
function! s:on_text_changed() abort
let l:session = vsnip#get_session()
if !empty(l:session)
call l:session.on_text_changed()
endif
endfunction
"
" on_buf_write_post
"
function! s:on_buf_write_post() abort
call vsnip#source#refresh(resolve(fnamemodify(bufname('%'), ':p')))
endfunction

View File

@@ -0,0 +1,149 @@
let s:expect = themis#helper('expect')
Describe vsnip
Describe #get_context
It should return context information in insert-mode
enew!
set filetype=basic_spec
call setline(1, 'if')
call cursor([1, 2])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(mode(1)).to_equal('i')
call s:expect(vsnip#get_context()).to_equal({
\ 'range': {
\ 'start': {
\ 'line': 0,
\ 'character': 0,
\ },
\ 'end': {
\ 'line': 0,
\ 'character': 2,
\ },
\ },
\ 'snippet': {
\ 'label': 'if',
\ 'description': '',
\ 'prefix': ['if'],
\ 'prefix_alias': [],
\ 'body': [
\ "if ${1:condition}",
\ "\t$0",
\ "endif",
\ ],
\ }
\ })
endfunction
call feedkeys("a\<Plug>(vsnip-assert)", 'x')
End
It should return context information in normal-mode
enew!
set filetype=basic_spec
call setline(1, 'if')
call cursor([1, 2])
call s:expect(mode(1)).to_equal('n')
call s:expect(vsnip#get_context()).to_equal({
\ 'range': {
\ 'start': {
\ 'line': 0,
\ 'character': 0,
\ },
\ 'end': {
\ 'line': 0,
\ 'character': 2,
\ },
\ },
\ 'snippet': {
\ 'label': 'if',
\ 'description': '',
\ 'prefix': ['if'],
\ 'prefix_alias': [],
\ 'body': [
\ "if ${1:condition}",
\ "\t$0",
\ "endif",
\ ],
\ }
\ })
End
It should return context information in select-mode
enew!
set filetype=basic_spec
call setline(1, 'if')
call feedkeys("v\<C-g>", 'x')
call cursor([1, 2])
call s:expect(mode(1)).to_equal('s')
call s:expect(vsnip#get_context()).to_equal({
\ 'range': {
\ 'start': {
\ 'line': 0,
\ 'character': 0,
\ },
\ 'end': {
\ 'line': 0,
\ 'character': 2,
\ },
\ },
\ 'snippet': {
\ 'label': 'if',
\ 'description': '',
\ 'prefix': ['if'],
\ 'prefix_alias': [],
\ 'body': [
\ "if ${1:condition}",
\ "\t$0",
\ "endif",
\ ],
\ }
\ })
End
End
Describe #get_complete_items
It should return complete items
enew!
set filetype=basic_spec
call s:expect(vsnip#get_complete_items(bufnr('%'))[-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

View File

@@ -0,0 +1,129 @@
let s:expect = themis#helper('expect')
Describe vsnip#indent
After each
set expandtab shiftwidth=2
End
Describe #get_one_indent
It should return one indent
enew!
for l:execute in [
\ 'set expandtab shiftwidth=4 tabstop=2',
\ 'set expandtab shiftwidth=2 tabstop=4',
\ 'set expandtab shiftwidth=0 tabstop=2',
\ 'set noexpandtab shiftwidth=4 tabstop=4',
\ ]
execute l:execute
%delete _
call setline(1, '<')
normal! >>
call s:expect(vsnip#indent#get_one_indent()).to_equal(getline(1)[0 : -2])
endfor
End
End
Describe #get_base_indent
It should return base indent
enew!
call setline(1, ['foo'])
call s:expect(vsnip#indent#get_base_indent(getline(1))).to_equal('')
call setline(1, [' foo'])
call s:expect(vsnip#indent#get_base_indent(getline(1))).to_equal(' ')
call setline(1, ["\tfoo"])
call s:expect(vsnip#indent#get_base_indent(getline(1))).to_equal("\t")
End
End
Describe #adjust_snippet_body
It should return adjusted snippet body for expandtab
set expandtab shiftwidth=2
call s:expect(vsnip#indent#adjust_snippet_body(' foo', join([
\ "class $1 {",
\ "\tpublic constructor() {",
\ "\t\t$0",
\ "\t}",
\ "}"
\ ], "\n"))).to_equal(join([
\ "class $1 {",
\ " public constructor() {",
\ " $0",
\ " }",
\ " }"
\ ], "\n"))
End
It should return adjusted snippet body for noexpandtab
set noexpandtab shiftwidth=2
call s:expect(vsnip#indent#adjust_snippet_body("\tfoo", join([
\ "class $1 {",
\ "\tpublic constructor() {",
\ "\t\t$0",
\ "\t}",
\ "}"
\ ], "\n"))).to_equal(join([
\ "class $1 {",
\ "\t\tpublic constructor() {",
\ "\t\t\t$0",
\ "\t\t}",
\ "\t}"
\ ], "\n"))
End
End
Describe #trim_base_indent
It should trim base indent when target is line-wise multiline text
call s:expect(vsnip#indent#trim_base_indent(join([
\ " function! s:foo()",
\ " return 'foo'",
\ " endfunction"
\ ], "\n") . "\n")).to_equal(join([
\ "function! s:foo()",
\ " return 'foo'",
\ "endfunction"
\ ], "\n"))
End
It should trim base indent when target is char-wise multiline text
call s:expect(vsnip#indent#trim_base_indent(join([
\ "function! s:foo()",
\ " return 'foo'",
\ " endfunction"
\ ], "\n"))).to_equal(join([
\ "function! s:foo()",
\ " return 'foo'",
\ "endfunction"
\ ], "\n"))
End
It should trim base indent when target is line-wise singleline selection
call s:expect(vsnip#indent#trim_base_indent(join([
\ " function! s:foo()",
\ ], "\n") . "\n")).to_equal(join([
\ "function! s:foo()",
\ ], "\n"))
End
It should trim base indent when target is char-wise singleline selection
call s:expect(vsnip#indent#trim_base_indent(join([
\ " function! s:foo()",
\ ], "\n"))).to_equal(join([
\ " function! s:foo()",
\ ], "\n"))
End
End
End

View File

@@ -0,0 +1,573 @@
let s:expect = themis#helper('expect')
let s:Snippet = vsnip#snippet#import()
let s:start_position = {
\ 'line': 1,
\ 'character': 1
\ }
Describe vsnip#snippet
Describe #init
It should mark follower placeholders
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1:default}, $1, $1)')
call s:expect(l:snippet.children[3].follower).to_equal(v:true)
call s:expect(l:snippet.children[5].follower).to_equal(v:true)
End
It should add final tabstop
let l:snippet = s:Snippet.new(s:start_position, 'console.log($1)')
call s:expect(l:snippet.children[3].is_final).to_equal(v:true)
End
It should convert variable to placeholder
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${variable:default}, $variable)')
call s:expect(l:snippet.text()).to_equal('console.log(default, default)')
call s:expect(l:snippet.children[3].follower).to_equal(v:true)
End
It should resolve known variables
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
It should sync placeholder text in same tabstop groups
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1:default}, $1)')
call l:snippet.follow(0, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 13
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 17
\ }
\ },
\ 'text': '___'
\ })
call l:snippet.sync()
call s:expect(l:snippet.text()).to_equal('console.log(___ult, ___ult)')
End
End
Describe #follow
It should not affect following empty diff
let l:snippet = s:Snippet.new(s:start_position, "class $1 {\n\tpublic ${2:default}() {\n\t\t$0\n\t}\n}")
let l:followed = l:snippet.follow(0, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 1
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 1
\ }
\ },
\ 'text': ''
\ })
call s:expect(l:followed).to_equal(v:true)
call s:expect(l:snippet.text()).to_equal("class {\n\tpublic default() {\n\t\t\n\t}\n}")
End
It should follow when diff range covers whole of snippet
let l:snippet = s:Snippet.new(s:start_position, "class $1 {\n\tpublic ${2:default}() {\n\t\t$0\n\t}\n}")
let l:followed = l:snippet.follow(0, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 1
\ },
\ 'end': {
\ 'line': 5,
\ 'character': 1
\ }
\ },
\ 'text': ''
\ })
call s:expect(l:followed).to_equal(v:true)
call s:expect(l:snippet.text()).to_equal("")
End
It should squash placeholder when diff range covers multiple placeholders
let l:snippet = s:Snippet.new(s:start_position, "console.log(${1:first}, ${2:second})")
call s:expect(l:snippet.get_placeholder_nodes()).to_have_length(3)
let l:followed = l:snippet.follow(0, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 13
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 26
\ }
\ },
\ 'text': ''
\ })
call s:expect(l:followed).to_equal(v:true)
call s:expect(l:snippet.get_placeholder_nodes()).to_have_length(2)
call s:expect(l:snippet.text()).to_equal('console.log()')
End
It should not squash placeholder when diff range includes multiple placeholders but last one does not covered
let l:snippet = s:Snippet.new(s:start_position, "console.log(${1:first}, ${2:second})")
call s:expect(l:snippet.get_placeholder_nodes()).to_have_length(3)
let l:followed = l:snippet.follow(0, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 13
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 25
\ }
\ },
\ 'text': ''
\ })
call s:expect(l:followed).to_equal(v:true)
call s:expect(l:snippet.get_placeholder_nodes()).to_have_length(3)
call s:expect(l:snippet.text()).to_equal('console.log(d)')
End
It should prefer current placeholder
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1}, ${2:, ${3:default}})')
let l:followed = l:snippet.follow(3, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 17
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 17
\ }
\ },
\ 'text': '___'
\ })
call s:expect(l:followed).to_equal(v:true)
call s:expect(l:snippet.get_placeholder_nodes()[2].text()).to_equal('___default')
End
It should follow when diff range is within one node range
let l:snippet = s:Snippet.new(s:start_position, "class $1 {\n\tpublic ${2:default}() {\n\t\t$0\n\t}\n}")
call l:snippet.follow(0, {
\ 'range': {
\ 'start': {
\ 'line': 2,
\ 'character': 9
\ },
\ 'end': {
\ 'line': 2,
\ 'character': 12
\ }
\ },
\ 'text': '___'
\ })
call s:expect(l:snippet.text()).to_equal("class {\n\tpublic d___ult() {\n\t\t\n\t}\n}")
call s:expect(l:snippet.get_next_jump_point(1).placeholder.text()).to_equal('d___ult')
End
It should follow when diff range included only text node
let l:snippet = s:Snippet.new(s:start_position, "class $1 {\n\tpublic ${2:default}() {\n\t\t$0\n\t}\n}")
call l:snippet.follow(1, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 1
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 6
\ }
\ },
\ 'text': 'modified'
\ })
call s:expect(l:snippet.text()).to_equal("modified {\n\tpublic default() {\n\t\t\n\t}\n}")
End
It should prefer placeholder node than text node when both followable (left)
let l:snippet = s:Snippet.new(s:start_position, '[${1:text1}][${2:text2}][${3:text3}]')
call l:snippet.follow(1, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 9
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 9
\ }
\ },
\ 'text': '___'
\ })
call s:expect(l:snippet.text()).to_equal('[text1][___text2][text3]')
call s:expect(l:snippet.children[3].text()).to_equal('___text2')
End
It should prefer placeholder node than text node when both followable (right)
let l:snippet = s:Snippet.new(s:start_position, '[${1:text1}][${2:text2}][${3:text3}]')
call l:snippet.follow(1, {
\ 'range': {
\ 'start': {
\ 'line': 1,
\ 'character': 14
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 14
\ }
\ },
\ 'text': '___'
\ })
call s:expect(l:snippet.text()).to_equal('[text1][text2___][text3]')
call s:expect(l:snippet.children[3].text()).to_equal('text2___')
End
End
Describe #text
It should return text1
let l:snippet = s:Snippet.new(s:start_position, 'console.log($0${1:default})')
call s:expect(l:snippet.text()).to_equal('console.log(default)')
End
It should return text2
call vsnip#selected_text('THIS_IS_SELECTED_TEXT')
let l:snippet = s:Snippet.new(s:start_position, '$TM_SELECTED_TEXT')
call s:expect(l:snippet.text()).to_equal('THIS_IS_SELECTED_TEXT')
End
It should return text2
call vsnip#selected_text('THIS_IS_SELECTED_TEXT')
let l:snippet = s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT}')
call s:expect(l:snippet.text()).to_equal('THIS_IS_SELECTED_TEXT')
End
It should return text3
call vsnip#selected_text('')
let l:snippet = s:Snippet.new(s:start_position, '${TM_SELECTED_TEXT:default}')
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
It should return range1
let l:snippet = s:Snippet.new(s:start_position, "01234\n56789")
call s:expect(l:snippet.range()).to_equal({
\ 'start': {
\ 'line': s:start_position.line,
\ 'character': s:start_position.character
\ },
\ 'end': {
\ 'line': s:start_position.line + 1,
\ 'character': 5
\ }
\ })
End
It should return range2
let l:snippet = s:Snippet.new(s:start_position, "012345")
call s:expect(l:snippet.range()).to_equal({
\ 'start': {
\ 'line': s:start_position.line,
\ 'character': s:start_position.character
\ },
\ 'end': {
\ 'line': s:start_position.line,
\ 'character': 7
\ }
\ })
End
End
Describe #offset_to_position
It should return position from offset
let l:snippet = s:Snippet.new(s:start_position, "class クラス {\n\tpublic constructor() {\n\t\t$0\n\t}\n}")
call s:expect(l:snippet.offset_to_position(13)).to_equal({
\ 'line': 2,
\ 'character': 1
\ })
End
End
Describe #position_to_offset
It should return offset from position
let l:snippet = s:Snippet.new(s:start_position, "class クラス {\n\tpublic constructor() {\n\t\t$0\n\t}\n}")
call s:expect(l:snippet.position_to_offset({
\ 'line': 2,
\ 'character': 1
\ })).to_equal(13)
End
End
Describe #normalize
It should not normalize when does not exists adjacent text nodes
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1:i}${2:++})')
let l:text = l:snippet.text()
call s:expect(len(l:snippet.children)).to_equal(5)
call l:snippet.normalize()
call s:expect(len(l:snippet.children)).to_equal(5)
call s:expect(l:text).to_equal(l:snippet.text())
End
It should normalize adjacent text nodes
let l:snippet = s:Snippet.new(s:start_position, 'console.log')
call insert(l:snippet.children, vsnip#snippet#node#create_text('___'), 1)
let l:text = l:snippet.text()
call s:expect(len(l:snippet.children)).to_equal(3)
call l:snippet.normalize()
call s:expect(len(l:snippet.children)).to_equal(2)
call s:expect(l:text).to_equal(l:snippet.text())
End
It should normalize correctly when the node has the same structure children
let l:snippet = s:Snippet.new(s:start_position, '')
let l:snippet.children = vsnip#snippet#node#create_from_ast([{
\ 'type': 'text',
\ 'value': '*',
\ 'escaped': '*',
\ }, {
\ 'type': 'placeholder',
\ 'id': 1,
\ 'children': [{
\ 'type': 'text',
\ 'value': '',
\ 'escaped': '',
\ }]
\ }, {
\ 'type': 'text',
\ 'value': '*',
\ 'escaped': '*',
\ }, {
\ 'type': 'text',
\ 'value': '_',
\ 'escaped': '_',
\ }, {
\ 'type': 'placeholder',
\ 'id': 1,
\ 'children': [{
\ 'type': 'text',
\ 'value': '',
\ 'escaped': '',
\ }]
\ }, {
\ 'type': 'text',
\ 'value': '*',
\ 'escaped': '*',
\ }, {
\ 'type': 'text',
\ 'value': '__',
\ 'escaped': '__',
\ }])
let l:text = l:snippet.text()
call l:snippet.normalize()
call s:expect(l:text).to_equal(l:snippet.text())
End
End
Describe #insert
It should insert node 1
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1}, ${2:${1}})')
call l:snippet.insert({ 'line': 1, 'character': 13 }, s:Snippet.new(s:start_position, 'console.log(${3}, ${4:${3}})').children)
call s:expect(l:snippet.text()).to_equal('console.log(console.log(, ), )')
End
It should insert node 2
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1}, ${2:${1}})')
call l:snippet.insert({ 'line': 1, 'character': 15 }, s:Snippet.new(s:start_position, 'console.log()').children)
call s:expect(l:snippet.text()).to_equal('console.log(, console.log())')
call s:expect(len(l:snippet.get_placeholder_nodes())).to_equal(5)
End
It should insert node 3
let l:snippet = s:Snippet.new(s:start_position, 'console.log(aiueo)')
call l:snippet.insert({ 'line': 1, 'character': 13 }, s:Snippet.new(s:start_position, '___').children)
call s:expect(l:snippet.text()).to_equal('console.log(___aiueo)')
End
It should insert node 4
let l:snippet = s:Snippet.new(s:start_position, 'console.log(aiueo)')
call l:snippet.insert({ 'line': 1, 'character': 15 }, s:Snippet.new(s:start_position, '___').children)
call s:expect(l:snippet.text()).to_equal('console.log(ai___ueo)')
End
It should insert node 5
let l:snippet = s:Snippet.new(s:start_position, 'console.log(aiueo)')
call l:snippet.insert({ 'line': 1, 'character': 18 }, [vsnip#snippet#node#create_text('___')])
call s:expect(l:snippet.text()).to_equal('console.log(aiueo___)')
End
End
Describe #get_next_jump_point
It should return next jump point 1
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1:012345})')
call s:expect(l:snippet.get_next_jump_point(0).range).to_equal({
\ 'start': {
\ 'line': 1,
\ 'character': 13
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 19
\ }
\ })
End
It should return next jump point 2
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1:0123${2:456}7890})')
call s:expect(l:snippet.get_next_jump_point(0).range).to_equal({
\ 'start': {
\ 'line': 1,
\ 'character': 13
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 24
\ }
\ })
call s:expect(l:snippet.get_next_jump_point(1).range).to_equal({
\ 'start': {
\ 'line': 1,
\ 'character': 17,
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 20
\ }
\ })
End
It should return next jump point 3
let l:snippet = s:Snippet.new(s:start_position, 'console.log(${1:0${3:12}3${2:456}7890})')
call s:expect(l:snippet.get_next_jump_point(0).range).to_equal({
\ 'start': {
\ 'line': 1,
\ 'character': 13
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 24
\ }
\ })
call s:expect(l:snippet.get_next_jump_point(1).range).to_equal({
\ 'start': {
\ 'line': 1,
\ 'character': 17,
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 20
\ }
\ })
call s:expect(l:snippet.get_next_jump_point(2).range).to_equal({
\ 'start': {
\ 'line': 1,
\ 'character': 14,
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 16
\ }
\ })
End
It should return next jump point 4
let l:snippet = s:Snippet.new(s:start_position, 'console.log(0${1}123456789${1}0)')
call s:expect(l:snippet.get_next_jump_point(0).range).to_equal({
\ 'start': {
\ 'line': 1,
\ 'character': 14
\ },
\ 'end': {
\ 'line': 1,
\ 'character': 14
\ }
\ })
End
End
End

View File

@@ -0,0 +1,231 @@
let s:expect = themis#helper('expect')
Describe vsnip#snippet#parser
Describe #parse
It should parse text
let l:parsed = vsnip#snippet#parser#parse('console.log()')
call s:expect(len(l:parsed)).to_equal(1)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'text',
\ 'raw': 'console.log()',
\ 'escaped': 'console.log()'
\ })
End
It should parse tabstop
let l:parsed = vsnip#snippet#parser#parse('console.log($0$1${1/(.*)/${1:/capitalize}/})')
call s:expect(len(l:parsed)).to_equal(5)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'text',
\ 'raw': 'console.log(',
\ 'escaped': 'console.log('
\ })
call s:expect(l:parsed[1]).to_equal({
\ 'type': 'placeholder',
\ 'id': 0,
\ 'children': []
\ })
call s:expect(l:parsed[2]).to_equal({
\ 'type': 'placeholder',
\ 'id': 1,
\ 'children': []
\ })
call s:expect(l:parsed[3]).to_equal({
\ 'type': 'placeholder',
\ 'id': 1,
\ 'children': [],
\ 'transform': {
\ 'type': 'transform',
\ 'regex': {
\ 'type': 'regex',
\ 'pattern': '(.*)',
\ },
\ 'format': [{
\ 'type': 'format',
\ 'id': 1,
\ 'modifier': '/capitalize'
\ }],
\ 'option': v:null
\ }
\ })
call s:expect(l:parsed[4]).to_equal({
\ 'type': 'text',
\ 'raw': ')',
\ 'escaped': ')'
\ })
End
It should parse choice
let l:parsed = vsnip#snippet#parser#parse("${1|log,warn,error|}")
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'placeholder',
\ 'id': 1,
\ 'choice': [{
\ 'type': 'text',
\ 'raw': 'log',
\ 'escaped': 'log'
\ }, {
\ 'type': 'text',
\ 'raw': 'warn',
\ 'escaped': 'warn'
\ }, {
\ 'type': 'text',
\ 'raw': 'error',
\ 'escaped': 'error'
\ }],
\ 'children': [{
\ 'type': 'text',
\ 'raw': 'log',
\ 'escaped': 'log'
\ }]
\ })
End
It should parse complex escaped chars
let l:parsed = vsnip#snippet#parser#parse('\\\$variable\}')
call s:expect(len(l:parsed)).to_equal(1)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'text',
\ 'raw': '\\\$variable\}',
\ 'escaped': '\$variable}'
\ })
End
It should parse escapable char
let l:parsed = vsnip#snippet#parser#parse('{\}')
call s:expect(len(l:parsed)).to_equal(1)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'text',
\ 'raw': '{\}',
\ 'escaped': '{}'
\ })
End
It should not remove unmeaningful escape
let l:parsed = vsnip#snippet#parser#parse('\{}')
call s:expect(len(l:parsed)).to_equal(1)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'text',
\ 'raw': '\{}',
\ 'escaped': '\{}'
\ })
End
It should parse omitted backslash for the escapable non-stop char
let l:parsed = vsnip#snippet#parser#parse('{}')
call s:expect(len(l:parsed)).to_equal(1)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'text',
\ 'raw': '{}',
\ 'escaped': '{}'
\ })
End
It should parse nested placeholder
let l:parsed = vsnip#snippet#parser#parse('class $1${2: extends ${3:SuperClass}} { $0 }')
call s:expect(len(l:parsed)).to_equal(6)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'text',
\ 'raw': 'class ',
\ 'escaped': 'class '
\ })
call s:expect(l:parsed[1]).to_equal({
\ 'type': 'placeholder',
\ 'id': 1,
\ 'children': []
\ })
call s:expect(l:parsed[2]).to_equal({
\ 'type': 'placeholder',
\ 'id': 2,
\ 'children': [{
\ 'type': 'text',
\ 'raw': ' extends ',
\ 'escaped': ' extends '
\ }, {
\ 'type': 'placeholder',
\ 'id': 3,
\ 'children': [{
\ 'type': 'text',
\ 'raw': 'SuperClass',
\ 'escaped': 'SuperClass'
\ }]
\ }]
\ })
call s:expect(l:parsed[3]).to_equal({
\ 'type': 'text',
\ 'raw': ' { ',
\ 'escaped': ' { '
\ })
call s:expect(l:parsed[4]).to_equal({
\ 'type': 'placeholder',
\ 'id': 0,
\ 'children': []
\ })
call s:expect(l:parsed[5]).to_equal({
\ 'type': 'text',
\ 'raw': ' }',
\ 'escaped': ' }'
\ })
End
It should parse simple variable
let l:parsed = vsnip#snippet#parser#parse('${variable:default}')
call s:expect(len(l:parsed)).to_equal(1)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'variable',
\ 'name': 'variable',
\ 'children': [{
\ 'type': 'text',
\ 'raw': 'default',
\ 'escaped': 'default',
\ }]
\ })
End
It should parse variable with multiple children
let l:parsed = vsnip#snippet#parser#parse('${variable: $1}')
call s:expect(len(l:parsed)).to_equal(1)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'variable',
\ 'name': 'variable',
\ 'children': [{
\ 'type': 'text',
\ 'raw': ' ',
\ 'escaped': ' ',
\ }, {
\ 'type': 'placeholder',
\ 'id': 1,
\ 'children': []
\ }],
\ })
End
It should parse variable with regex
let l:parsed = vsnip#snippet#parser#parse('${variable/(.*)/${1:/capitalize}/}')
call s:expect(len(l:parsed)).to_equal(1)
call s:expect(l:parsed[0]).to_equal({
\ 'type': 'variable',
\ 'name': 'variable',
\ 'children': [],
\ 'transform': {
\ 'type': 'transform',
\ 'regex': {
\ 'type': 'regex',
\ 'pattern': '(.*)',
\ },
\ 'format': [{
\ 'type': 'format',
\ 'id': 1,
\ 'modifier': '/capitalize'
\ }],
\ 'option': v:null
\ }
\ })
End
End
End

View File

@@ -0,0 +1,97 @@
let s:expect = themis#helper('expect')
Describe vsnip#source
Describe #filetypes
It should return all filetype
enew!
set filetype=javascript.jsx
call s:expect(vsnip#source#filetypes(bufnr('%'))).to_equal([
\ 'javascript',
\ 'jsx',
\ 'global'
\ ])
End
End
Describe #find
It should load snippet for extended filetypes
enew!
set filetype=source_spec_enhanced
let l:snippets = vsnip#source#find(bufnr('%'))
call s:expect(l:snippets[0]).to_have_length(2)
End
It should load snippet from vscode extension
enew!
set filetype=source_spec_vscode
let l:snippets = vsnip#source#find(bufnr('%'))
call s:expect(l:snippets[0]).to_have_length(1)
End
It should format snippets
enew!
set filetype=source_spec
let l:snippets = vsnip#source#find(bufnr('%'))
call s:expect(sort(l:snippets[0])).to_equal(sort([{
\ 'label': 'no-prefix-alias',
\ 'description': '',
\ 'prefix': ['---'],
\ 'prefix_alias': [],
\ 'body': [
\ "${VIM:repeat('-', &tw)}"
\ ]
\ }, {
\ 'label': 'prefix-alias',
\ 'description': '',
\ 'prefix': ['arrow-function'],
\ 'prefix_alias': ['af'],
\ 'body': [
\ "() => "
\ ]
\ }]))
End
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

View File

@@ -0,0 +1,709 @@
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
call vsnip#deactivate()
End
Context expand
It should expand when prefix start col is 1
enew!
set filetype=integration
call setline(1, 'spec1')
call cursor([1, 5])
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\<C-j>\<Plug>(vsnip-assert)", 'x')
End
It should expand when prefix is in middle of line
enew!
set filetype=integration
call setline(1, '(spec1)')
call cursor([1, 6])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal(['(snippet)'])
endfunction
call feedkeys("a\<C-j>\<Plug>(vsnip-assert)", 'x')
End
It should expand when prefix was selected in select-mode
enew!
set filetype=integration
call setline(1, 'spec1')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("v$\<C-g>\<C-j>\<Plug>(vsnip-assert)", 'x')
End
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])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal(['(aspec1', ')'])
endfunction
call feedkeys("a\<C-j>\<Plug>(vsnip-assert)", 'x')
End
It should jump to first placeholder when expanded snippet
enew!
set filetype=integration
call setline(1, 'spec2')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getcurpos()[1 : 2]).to_equal([1, 1])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Plug>(vsnip-assert)", 'x')
End
End
Context jump
It should jump to first of snippet
enew!
set filetype=integration
call setline(1, 'spec2')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getcurpos()[1 : 2]).to_equal([1, 1])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
It should jump to middle of snippet
enew!
set filetype=integration
call setline(1, 'spec3')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getcurpos()[1 : 2]).to_equal([1, 4])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
It should jump to last of snippet
enew!
set filetype=integration
call setline(1, 'spec4')
call cursor([1, 5])
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\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
It should select 1 length first of snippet text
enew!
set filetype=integration
call setline(1, 'spec5')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect_selection([1, 1], [1, 1])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
It should select 1 length middle of snippet text
enew!
set filetype=integration
call setline(1, 'spec6')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect_selection([1, 3], [1, 3])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
It should select 1 length last of snippet text
enew!
set filetype=integration
call setline(1, 'spec7')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect_selection([1, 7], [1, 7])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
It should select 3 length first of snippet text
enew!
set filetype=integration
call setline(1, 'spec8')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect_selection([1, 1], [1, 3])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
It should select 3 length middle of snippet text
enew!
set filetype=integration
call setline(1, 'spec9')
call cursor([1, 5])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect_selection([1, 3], [1, 5])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
It should select 3 length last of snippet text
enew!
set filetype=integration
call setline(1, 'spec10')
call cursor([1, 6])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect_selection([1, 5], [1, 7])
call s:expect(getbufline('%', '^', '$')).to_equal(['snippet'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
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\<C-j>\<Tab>\<Plug>(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_selection([1, 7], [1, 12])
call s:expect(getbufline('%', '^', '$')).to_equal(['あいかkaかう'])
endfunction
call feedkeys("a\<C-j>\<Tab>\<Plug>(vsnip-assert)", 'x')
End
End
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',
\ 'deactivate',
\ 'outside',
\ ])
call cursor([2, 11])
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 .= "a\<C-j>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step2()
call s:expect(vsnip#get_session()).not.to_be_empty()
endfunction
let l:sequence .= "funcname\<Plug>(vsnip-assert)"
function g:vsnip_assert.step3()
call s:expect(vsnip#get_session()).not.to_be_empty()
endfunction
let l:sequence .= "\<Tab>return 1\<Plug>(vsnip-assert)"
function g:vsnip_assert.step4()
call s:expect(vsnip#get_session()).to_be_empty()
endfunction
let l:sequence .= "\<Cmd>call cursor([1, 1])\<CR>modified\<Esc>"
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!
set filetype=integration
let g:vsnip_deactivate_on = g:vsnip#DeactivateOn.OutsideOfCurrentTabstop
call setline(1, [
\ 'outside',
\ 'deactivate',
\ 'outside',
\ ])
call cursor([2, 11])
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 .= "a\<C-j>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step2()
call s:expect(vsnip#get_session()).not.to_be_empty()
endfunction
let l:sequence .= "funcname\<Plug>(vsnip-assert)"
function g:vsnip_assert.step3()
call s:expect(vsnip#get_session()).to_be_empty()
endfunction
let l:sequence .= "\<Right>arguments\<Plug>(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)
enew!
set filetype=integration
call setline(1, 'realworld1')
call cursor([1, 10])
let g:vsnip_assert = {}
let l:sequence = ''
" expand and jump
let l:sequence .= "a\<C-j>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ '/** @class ClassName */',
\ 'class ClassName extends ParentClassName {',
\ ' public constructor() {',
\ ' ',
\ ' }',
\ '}',
\ ])
endfunction
" sync placeholder
let l:sequence .= "ModifiedClassName\<Plug>(vsnip-assert)"
function g:vsnip_assert.step2()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ '/** @class ModifiedClassName */',
\ 'class ModifiedClassName extends ParentClassName {',
\ ' public constructor() {',
\ ' ',
\ ' }',
\ '}',
\ ])
endfunction
" edit nested placeholder
let l:sequence .= "\<Tab>\<Tab>ModifiedParentClassName\<Plug>(vsnip-assert)"
function g:vsnip_assert.step3()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ '/** @class ModifiedClassName */',
\ 'class ModifiedClassName extends ModifiedParentClassName {',
\ ' public constructor() {',
\ ' ',
\ ' }',
\ '}',
\ ])
endfunction
" remove nested placeholder by <BS>
let l:sequence .= "\<S-Tab>\<BS>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step4()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ '/** @class ModifiedClassName */',
\ 'class ModifiedClassName {',
\ ' public constructor() {',
\ ' ',
\ ' }',
\ '}',
\ ])
endfunction
" jump to final tabstop
let l:sequence .= "\<Tab>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step5()
call s:expect(getcurpos()[1 : 2]).to_equal([4, 5])
call s:expect(getbufline('%', '^', '$')).to_equal([
\ '/** @class ModifiedClassName */',
\ 'class ModifiedClassName {',
\ ' public constructor() {',
\ ' ',
\ ' }',
\ '}',
\ ])
endfunction
call feedkeys(l:sequence, 'x')
End
It should work ($VIM variable)
enew!
set filetype=integration
call setline(1, 'realworld2')
call cursor([1, 10])
let g:vsnip_assert = {}
function! g:vsnip_assert.step1() abort
call s:expect(getbufline('%', '^', '$')).to_equal([
\ $USER
\ ])
endfunction
call feedkeys("a\<C-j>\<Plug>(vsnip-assert)", 'x')
End
It should work (indented $TM_SELECTED_TEXT char-wise)
enew!
set filetype=integration
call setline(1, [
\ ' <div>',
\ ' <span />',
\ ' </div>'
\ ])
let g:vsnip_assert = {}
function! g:vsnip_assert.step1() abort
call s:expect(getbufline('%', '^', '$')).to_equal([
\ ' <div>',
\ ' <div>',
\ ' <span />',
\ ' </div>',
\ ' </div>'
\ ])
endfunction
call feedkeys("1G3|v3G7|\<Plug>(vsnip-cut-text)realworld3\<C-j>", 'x')
End
It should work (indented $TM_SELECTED_TEXT line-wise)
enew!
set filetype=integration
call setline(1, [
\ ' <div>',
\ ' <span />',
\ ' </div>'
\ ])
let g:vsnip_assert = {}
function! g:vsnip_assert.step1() abort
call s:expect(getbufline('%', '^', '$')).to_equal([
\ ' <div>',
\ ' <div>',
\ ' <span />',
\ ' </div>',
\ ' </div>'
\ ])
endfunction
call feedkeys("1GV3G\<Plug>(vsnip-cut-text)realworld3\<C-j>", 'x')
End
It should work (no indented $TM_SELECTED_TEXT char-wise)
enew!
set filetype=integration
call setline(1, [
\ ' <div>',
\ ' <span />',
\ ' </div>'
\ ])
let g:vsnip_assert = {}
function! g:vsnip_assert.step1() abort
call s:expect(getbufline('%', '^', '$')).to_equal([
\ ' <div><div>',
\ ' <span />',
\ ' </div></div>'
\ ])
endfunction
call feedkeys("1G3|v3G7|\<Plug>(vsnip-cut-text)realworld4\<C-j>", 'x')
End
It should work (no indented $TM_SELECTED_TEXT line-wise)
enew!
set filetype=integration
call setline(1, [
\ ' <div>',
\ ' <span />',
\ ' </div>'
\ ])
let g:vsnip_assert = {}
function! g:vsnip_assert.step1() abort
call s:expect(getbufline('%', '^', '$')).to_equal([
\ ' <div><div>',
\ ' <span />',
\ ' </div></div>'
\ ])
endfunction
call feedkeys("1GV3G\<Plug>(vsnip-cut-text)realworld4\<C-j>", 'x')
End
It should work (modify follower placeholder manually)
enew!
set filetype=integration
call setline(1, 'realworld5')
call cursor([1, 10])
let g:vsnip_assert = {}
let l:sequence = ''
" expand and jump
let l:sequence .= "a\<C-j>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ ', ________'
\ ])
endfunction
" edit $1
let l:sequence .= "foobar\<Plug>(vsnip-assert)"
function g:vsnip_assert.step2()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ 'foobar, ____foobar____'
\ ])
endfunction
" edit $1's follower
let l:sequence .= "\<Esc>1G16|dt_ibaz\<Plug>(vsnip-assert)"
function g:vsnip_assert.step3()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ 'foobar, ____foobaz____'
\ ])
endfunction
" edit $1 again
let l:sequence .= "\<Esc>1G1|dt,imodified\<Plug>(vsnip-assert)"
function g:vsnip_assert.step4()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ 'modified, ____foobaz____'
\ ])
endfunction
" can jump to $2
let l:sequence .= "\<Tab>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step5()
call s:expect_selection([1, 11], [1, 24])
endfunction
" can jump to $3
let l:sequence .= "\<Tab>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step6()
call s:expect_selection([1, 12], [1, 23])
endfunction
call feedkeys(l:sequence, 'x')
End
End
Context issue
It should work issue82
enew!
set filetype=integration
call setline(1, "'")
call cursor([1, 1])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getcurpos()[1 : 2]).to_equal([1, 2])
call s:expect(getbufline('%', '^', '$')).to_equal(["''"])
endfunction
call feedkeys("a\<C-j>\<Plug>(vsnip-assert)", 'x')
End
It should work issue85
enew!
set filetype=integration
call setline(1, 'issue85')
call cursor([1, 7])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal(['for i=1,10', ' print(i)'])
call s:expect_selection([1, 5], [1, 5])
endfunction
call feedkeys("a\<C-j>\<Plug>(vsnip-assert)", 'x')
End
It should work issue106
enew!
set filetype=integration
call setline(1, ['foo', 'issue106'])
call cursor([2, 8])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(vsnip#get_session()).to_be_empty()
endfunction
call feedkeys("a\<C-j>\<Esc>gg0i_\<Plug>(vsnip-assert)", 'x')
End
It should work issue122
enew!
set filetype=integration
call setline(1, ['foo', 'issue122>'])
call cursor([2, 9])
let g:vsnip_assert = {}
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal(['foo', ''])
endfunction
call feedkeys("a\<C-j>\<Plug>(vsnip-assert)", 'x')
End
It should work issue129
enew!
set filetype=integration
call setline(1, ['issue129'])
call cursor([1, 8])
let g:vsnip_assert = {}
let l:sequence = ''
" expand
let l:sequence .= "a\<C-j>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal(["console.log('', );"])
endfunction
" modify
let l:sequence .= "\<Esc>1G13|dt,iMODIFY\<Plug>(vsnip-assert)"
function g:vsnip_assert.step2()
call s:expect(getbufline('%', '^', '$')).to_equal(["console.log(MODIFY, );"])
endfunction
" jump
let l:sequence .= "\<Tab>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step3()
call s:expect(getcurpos()[1 : 2]).to_equal([1, 21])
endfunction
call feedkeys(l:sequence, 'x')
End
It should work issue139
enew!
set filetype=integration
call setline(1, ['issue139'])
call cursor([1, 8])
let g:vsnip_assert = {}
let l:sequence = ''
" expand
let l:sequence .= "a\<C-j>\<Plug>(vsnip-assert)"
function g:vsnip_assert.step1()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ 'for (size_t i=0; i < count; i++) {',
\ ' ',
\ '}'
\ ])
endfunction
" modify
let l:sequence .= "\<BS>\<Tab>variable\<Plug>(vsnip-assert)"
function g:vsnip_assert.step2()
call s:expect(getbufline('%', '^', '$')).to_equal([
\ 'for (variable=0; variable < count; variable++) {',
\ ' ',
\ '}'
\ ])
endfunction
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\<C-j>\<Plug>(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

View File

@@ -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 '\%(\\\@<!\%(\\\\\)*\)\@<=`.\{-}\%(\\\@<!\%(\\\\\)*\)\@<=`'
syn match snippet '^snippet.*' contains=multiSnipText,snipKeyword
syn match snippet '^extends.*' contains=snipKeyword
syn match multiSnipText '\S\+ \zs.*' contained
syn match snipKeyword '^(snippet|extends)'me=s+8 contained
syn match snipError "^[^#vse\t].*$"
hi link snippet Identifier
hi link snipComment Comment
hi link multiSnipText String
hi link snipKeyword Keyword
hi link snipEscape SpecialChar
hi link placeHolder Special
hi link tabStop Special
hi link snipCommand String
hi link snipError Error