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,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'))