dev (#1)
* dev * Improve sync design * Support buffer local mapping * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * stylua * tmp * tmp * tmp * tmp * tmp * tmp * tmp * integration * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * update * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp * tmp
This commit is contained in:
178
autoload/vital/_cmp/VS/LSP/CompletionItem.vim
Normal file
178
autoload/vital/_cmp/VS/LSP/CompletionItem.vim
Normal file
@@ -0,0 +1,178 @@
|
||||
" ___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#_cmp#VS#LSP#CompletionItem#import() abort', printf("return map({'_vital_depends': '', 'confirm': '', '_vital_loaded': ''}, \"vital#_cmp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
"
|
||||
" _vital_loaded
|
||||
"
|
||||
function! s:_vital_loaded(V) abort
|
||||
let s:Position = a:V.import('VS.LSP.Position')
|
||||
let s:TextEdit = a:V.import('VS.LSP.TextEdit')
|
||||
let s:Text = a:V.import('VS.LSP.Text')
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _vital_depends
|
||||
"
|
||||
function! s:_vital_depends() abort
|
||||
return ['VS.LSP.Position', 'VS.LSP.TextEdit', 'VS.LSP.Text']
|
||||
endfunction
|
||||
|
||||
"
|
||||
" confirm
|
||||
"
|
||||
" @param {LSP.Position} args.suggest_position
|
||||
" @param {LSP.Position} args.request_position
|
||||
" @param {LSP.Position} args.current_position
|
||||
" @param {string} args.current_line
|
||||
" @param {LSP.CompletionItem} args.completion_item
|
||||
" @param {(args: { body: string; insert_text_mode: number; }) => void?} args.expand_snippet
|
||||
"
|
||||
" # Pre-condition
|
||||
"
|
||||
" - You must pass `current_position` that represents the position when `CompleteDone` was fired.
|
||||
" - You must pass `current_line` that represents the line when `CompleteDone` was fired.
|
||||
" - You must call this function after the commit characters has been inserted.
|
||||
"
|
||||
" # The positoins
|
||||
"
|
||||
" 0. The example case
|
||||
"
|
||||
" call getbufl|<C-x><C-o><C-n><C-y> -> call getbufline|
|
||||
"
|
||||
" 1. suggest_position
|
||||
"
|
||||
" call |getbufline
|
||||
"
|
||||
" 2. request_position
|
||||
"
|
||||
" call getbufl|ine
|
||||
"
|
||||
" 3. current_position
|
||||
"
|
||||
" call getbufline|
|
||||
"
|
||||
"
|
||||
function! s:confirm(args) abort
|
||||
let l:suggest_position = a:args.suggest_position
|
||||
let l:request_position = a:args.request_position
|
||||
let l:current_position = a:args.current_position
|
||||
let l:current_line = a:args.current_line
|
||||
let l:completion_item = a:args.completion_item
|
||||
let l:ExpandSnippet = get(a:args, 'expand_snippet', v:null)
|
||||
|
||||
" 1. Prepare for alignment to VSCode behavior.
|
||||
let l:expansion = s:_get_expansion({
|
||||
\ 'suggest_position': l:suggest_position,
|
||||
\ 'request_position': l:request_position,
|
||||
\ 'current_position': l:current_position,
|
||||
\ 'current_line': l:current_line,
|
||||
\ 'completion_item': l:completion_item,
|
||||
\ })
|
||||
if !empty(l:expansion)
|
||||
" Remove commit characters if expansion is needed.
|
||||
if getline('.') !=# l:current_line
|
||||
call setline(l:current_position.line + 1, l:current_line)
|
||||
call cursor(s:Position.lsp_to_vim('%', l:current_position))
|
||||
endif
|
||||
|
||||
" Restore state of the timing when `textDocument/completion` was sent.
|
||||
call s:TextEdit.apply('%', [{
|
||||
\ 'range': { 'start': l:request_position, 'end': l:current_position },
|
||||
\ 'newText': ''
|
||||
\ }])
|
||||
endif
|
||||
|
||||
" 2. Apply additionalTextEdits
|
||||
if type(get(l:completion_item, 'additionalTextEdits', v:null)) == type([])
|
||||
call s:TextEdit.apply('%', l:completion_item.additionalTextEdits)
|
||||
endif
|
||||
|
||||
" 3. Apply expansion
|
||||
if !empty(l:expansion)
|
||||
let l:current_position = s:Position.cursor() " Update current_position to after additionalTextEdits.
|
||||
let l:range = {
|
||||
\ 'start': extend({
|
||||
\ 'character': l:current_position.character - l:expansion.overflow_before,
|
||||
\ }, l:current_position, 'keep'),
|
||||
\ 'end': extend({
|
||||
\ 'character': l:current_position.character + l:expansion.overflow_after,
|
||||
\ }, l:current_position, 'keep')
|
||||
\ }
|
||||
|
||||
" Snippet.
|
||||
if l:expansion.is_snippet && !empty(l:ExpandSnippet)
|
||||
call s:TextEdit.apply('%', [{ 'range': l:range, 'newText': '' }])
|
||||
call cursor(s:Position.lsp_to_vim('%', l:range.start))
|
||||
call l:ExpandSnippet({ 'body': l:expansion.new_text, 'insert_text_mode': get(l:completion_item, 'insertTextMode', 2) })
|
||||
|
||||
" TextEdit.
|
||||
else
|
||||
call s:TextEdit.apply('%', [{ 'range': l:range, 'newText': l:expansion.new_text }])
|
||||
|
||||
" Move cursor position to end of new_text like as snippet.
|
||||
let l:lines = s:Text.split_by_eol(l:expansion.new_text)
|
||||
let l:cursor = copy(l:range.start)
|
||||
let l:cursor.line += len(l:lines) - 1
|
||||
let l:cursor.character = strchars(l:lines[-1]) + (len(l:lines) == 1 ? l:cursor.character : 0)
|
||||
call cursor(s:Position.lsp_to_vim('%', l:cursor))
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _get_expansion
|
||||
"
|
||||
function! s:_get_expansion(args) abort
|
||||
let l:current_line = a:args.current_line
|
||||
let l:suggest_position = a:args.suggest_position
|
||||
let l:request_position = a:args.request_position
|
||||
let l:current_position = a:args.current_position
|
||||
let l:completion_item = a:args.completion_item
|
||||
|
||||
let l:is_snippet = get(l:completion_item, 'insertTextFormat', 1) == 2
|
||||
if type(get(l:completion_item, 'textEdit', v:null)) == type({})
|
||||
let l:inserted_text = strcharpart(l:current_line, l:request_position.character, l:current_position.character - l:request_position.character)
|
||||
let l:overflow_before = l:request_position.character - l:completion_item.textEdit.range.start.character
|
||||
let l:overflow_after = l:completion_item.textEdit.range.end.character - l:request_position.character
|
||||
let l:inserted = ''
|
||||
\ . strcharpart(l:current_line, l:request_position.character - l:overflow_before, l:overflow_before)
|
||||
\ . strcharpart(l:current_line, l:request_position.character, strchars(l:inserted_text) + l:overflow_after)
|
||||
let l:new_text = l:completion_item.textEdit.newText
|
||||
if s:_trim_tabstop(l:new_text) !=# l:inserted
|
||||
" The LSP spec says `textEdit range must contain the request position.`
|
||||
return {
|
||||
\ 'overflow_before': max([0, l:overflow_before]),
|
||||
\ 'overflow_after': max([0, l:overflow_after]),
|
||||
\ 'new_text': l:new_text,
|
||||
\ 'is_snippet': l:is_snippet,
|
||||
\ }
|
||||
endif
|
||||
else
|
||||
let l:inserted = strcharpart(l:current_line, l:suggest_position.character, l:current_position.character - l:suggest_position.character)
|
||||
let l:new_text = get(l:completion_item, 'insertText', v:null)
|
||||
let l:new_text = !empty(l:new_text) ? l:new_text : l:completion_item.label
|
||||
if s:_trim_tabstop(l:new_text) !=# l:inserted
|
||||
return {
|
||||
\ 'overflow_before': l:request_position.character - l:suggest_position.character,
|
||||
\ 'overflow_after': 0,
|
||||
\ 'new_text': l:new_text,
|
||||
\ 'is_snippet': l:is_snippet,
|
||||
\ }
|
||||
endif
|
||||
endif
|
||||
return {}
|
||||
endfunction
|
||||
|
||||
"
|
||||
" _trim_tabstop
|
||||
"
|
||||
function! s:_trim_tabstop(text) abort
|
||||
return substitute(a:text, '\%(\$0\|\${0}\)$', '', 'g')
|
||||
endfunction
|
||||
|
||||
62
autoload/vital/_cmp/VS/LSP/Position.vim
Normal file
62
autoload/vital/_cmp/VS/LSP/Position.vim
Normal 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#_cmp#VS#LSP#Position#import() abort', printf("return map({'cursor': '', 'vim_to_lsp': '', 'lsp_to_vim': ''}, \"vital#_cmp#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
|
||||
|
||||
23
autoload/vital/_cmp/VS/LSP/Text.vim
Normal file
23
autoload/vital/_cmp/VS/LSP/Text.vim
Normal 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#_cmp#VS#LSP#Text#import() abort', printf("return map({'normalize_eol': '', 'split_by_eol': ''}, \"vital#_cmp#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
|
||||
|
||||
185
autoload/vital/_cmp/VS/LSP/TextEdit.vim
Normal file
185
autoload/vital/_cmp/VS/LSP/TextEdit.vim
Normal 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#_cmp#VS#LSP#TextEdit#import() abort', printf("return map({'_vital_depends': '', 'apply': '', '_vital_loaded': ''}, \"vital#_cmp#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
|
||||
|
||||
Reference in New Issue
Block a user