]> git.madduck.net Git - etc/vim.git/blobdiff - .vim/bundle/vim-lsp/autoload/lsp/ui/vim/completion.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Merge commit 'a39f715c13be3352193ffd9c5b7536b8786eff64' as '.vim/bundle/vim-lsp'
[etc/vim.git] / .vim / bundle / vim-lsp / autoload / lsp / ui / vim / completion.vim
diff --git a/.vim/bundle/vim-lsp/autoload/lsp/ui/vim/completion.vim b/.vim/bundle/vim-lsp/autoload/lsp/ui/vim/completion.vim
new file mode 100644 (file)
index 0000000..5989ba0
--- /dev/null
@@ -0,0 +1,299 @@
+" vint: -ProhibitUnusedVariable
+"
+let s:context = {}
+
+function! lsp#ui#vim#completion#_setup() abort
+  augroup lsp_ui_vim_completion
+    autocmd!
+    autocmd CompleteDone * call s:on_complete_done()
+  augroup END
+endfunction
+
+function! lsp#ui#vim#completion#_disable() abort
+  augroup lsp_ui_vim_completion
+    autocmd!
+  augroup END
+endfunction
+
+"
+" After CompleteDone, v:complete_item's word has been inserted into the line.
+" Yet not inserted commit characters.
+"
+" below example uses | as cursor position.
+"
+" 1. `call getbuf|`<C-x><C-o>
+" 2. select `getbufline` item.
+" 3. Insert commit characters. e.g. `(`
+" 4. fire CompleteDone, then the line is `call getbufline|`
+" 5. call feedkeys to call `s:on_complete_done_after`
+" 6. then the line is `call getbufline(|` in `s:on_complete_done_after`
+"
+function! s:on_complete_done() abort
+  " Sometimes, vim occurs `CompleteDone` unexpectedly.
+  " We try to detect it by checking empty completed_item.
+  if empty(v:completed_item) || get(v:completed_item, 'word', '') ==# '' && get(v:completed_item, 'abbr', '') ==# ''
+    doautocmd <nomodeline> User lsp_complete_done
+    return
+  endif
+
+  " Try to get managed user_data.
+  let l:managed_user_data = lsp#omni#get_managed_user_data_from_completed_item(v:completed_item)
+
+  " Clear managed user_data.
+  call lsp#omni#_clear_managed_user_data_map()
+
+  " If managed user_data does not exists, skip it.
+  if empty(l:managed_user_data)
+    doautocmd <nomodeline> User lsp_complete_done
+    return
+  endif
+
+  let s:context['done_line'] = getline('.')
+  let s:context['done_line_nr'] = line('.')
+  let s:context['done_position'] = lsp#utils#position#vim_to_lsp('%', getpos('.')[1 : 2])
+  let s:context['complete_position'] = l:managed_user_data['complete_position']
+  let s:context['server_name'] = l:managed_user_data['server_name']
+  let s:context['completion_item'] = l:managed_user_data['completion_item']
+  let s:context['start_character'] = l:managed_user_data['start_character']
+  let s:context['complete_word'] = l:managed_user_data['complete_word']
+  call feedkeys(printf("\<C-r>=<SNR>%d_on_complete_done_after()\<CR>", s:SID()), 'n')
+endfunction
+
+"
+" Apply textEdit or insertText(snippet) and additionalTextEdits.
+"
+function! s:on_complete_done_after() abort
+  " Clear message line. feedkeys above leave garbage on message line.
+  echo ''
+
+  " Ignore process if the mode() is not insert-mode after feedkeys.
+  if mode(1)[0] !=# 'i'
+    return ''
+  endif
+
+  let l:done_line = s:context['done_line']
+  let l:done_line_nr = s:context['done_line_nr']
+  let l:done_position = s:context['done_position']
+  let l:complete_position = s:context['complete_position']
+  let l:server_name = s:context['server_name']
+  let l:completion_item = s:context['completion_item']
+  let l:start_character = s:context['start_character']
+  let l:complete_word = s:context['complete_word']
+
+  " check the commit characters are <BS> or <C-w>.
+  if line('.') ==# l:done_line_nr && strlen(getline('.')) < strlen(l:done_line)
+    doautocmd <nomodeline> User lsp_complete_done
+    return ''
+  endif
+
+  " Do nothing if text_edit is disabled.
+  if !g:lsp_text_edit_enabled
+    doautocmd <nomodeline> User lsp_complete_done
+    return ''
+  endif
+
+  let l:completion_item = s:resolve_completion_item(l:completion_item, l:server_name)
+
+  " clear completed string if need.
+  let l:is_expandable = s:is_expandable(l:done_line, l:done_position, l:complete_position, l:completion_item, l:complete_word)
+  if l:is_expandable
+    call s:clear_auto_inserted_text(l:done_line, l:done_position, l:complete_position)
+  endif
+
+  " apply additionalTextEdits.
+  if has_key(l:completion_item, 'additionalTextEdits') && !empty(l:completion_item['additionalTextEdits'])
+    call lsp#utils#text_edit#apply_text_edits(lsp#utils#get_buffer_uri(bufnr('%')), l:completion_item['additionalTextEdits'])
+  endif
+
+  " snippet or textEdit.
+  if l:is_expandable
+    " At this timing, the cursor may have been moved by additionalTextEdit, so we use overflow information instead of textEdit itself.
+    if type(get(l:completion_item, 'textEdit', v:null)) == type({})
+      let l:range = lsp#utils#text_edit#get_range(l:completion_item['textEdit'])
+      let l:overflow_before = max([0, l:start_character - l:range['start']['character']])
+      let l:overflow_after = max([0, l:range['end']['character'] - l:complete_position['character']])
+      let l:text = l:completion_item['textEdit']['newText']
+    else
+      let l:overflow_before = 0
+      let l:overflow_after = 0
+      let l:text = s:get_completion_text(l:completion_item)
+    endif
+
+    " apply snipept or text_edit
+    let l:position = lsp#utils#position#vim_to_lsp('%', getpos('.')[1 : 2])
+    let l:range = {
+    \   'start': {
+    \     'line': l:position['line'],
+    \     'character': l:position['character'] - (l:complete_position['character'] - l:start_character) - l:overflow_before,
+    \   },
+    \   'end': {
+    \     'line': l:position['line'],
+    \     'character': l:position['character'] + l:overflow_after,
+    \   }
+    \ }
+
+    if get(l:completion_item, 'insertTextFormat', 1) == 2
+      " insert Snippet.
+      call lsp#utils#text_edit#apply_text_edits('%', [{ 'range': l:range, 'newText': '' }])
+      call cursor(lsp#utils#position#lsp_to_vim('%', l:range['start']))
+      if exists('g:lsp_snippet_expand') && len(g:lsp_snippet_expand) > 0
+        call g:lsp_snippet_expand[0]({ 'snippet': l:text })
+      else
+        call s:simple_expand_text(l:text)
+      endif
+    else
+      " apply TextEdit.
+      call lsp#utils#text_edit#apply_text_edits('%', [{ 'range': l:range, 'newText': l:text }])
+
+      " The VSCode always apply completion word as snippet.
+      " It means we should place cursor to end of new inserted text as snippet does.
+      let l:lines = lsp#utils#_split_by_eol(l:text)
+      let l:start = l:range.start
+      let l:start.line += len(l:lines) - 1
+      let l:start.character += strchars(l:lines[-1])
+      call cursor(lsp#utils#position#lsp_to_vim('%', l:start))
+    endif
+  endif
+
+  doautocmd <nomodeline> User lsp_complete_done
+  return ''
+endfunction
+
+"
+" is_expandable
+"
+function! s:is_expandable(done_line, done_position, complete_position, completion_item, complete_word) abort
+  if get(a:completion_item, 'textEdit', v:null) isnot# v:null
+    let l:range = lsp#utils#text_edit#get_range(a:completion_item['textEdit'])
+    if l:range['start']['line'] != l:range['end']['line']
+      return v:true
+    endif
+
+    " compute if textEdit will change text.
+    let l:completed_before = strcharpart(a:done_line, 0, a:complete_position['character'])
+    let l:completed_after = strcharpart(a:done_line, a:done_position['character'], strchars(a:done_line) - a:done_position['character'])
+    let l:completed_line = l:completed_before . l:completed_after
+    let l:text_edit_before = strcharpart(l:completed_line, 0, l:range['start']['character'])
+    let l:text_edit_after = strcharpart(l:completed_line, l:range['end']['character'], strchars(l:completed_line) - l:range['end']['character'])
+    return a:done_line !=# l:text_edit_before . s:trim_unmeaning_tabstop(a:completion_item['textEdit']['newText']) . l:text_edit_after
+  endif
+  return s:get_completion_text(a:completion_item) !=# s:trim_unmeaning_tabstop(a:complete_word)
+endfunction
+
+"
+" trim_unmeaning_tabstop
+"
+function! s:trim_unmeaning_tabstop(text) abort
+  return substitute(a:text, '\%(\$0\|\${0}\)$', '', 'g')
+endfunction
+
+"
+" Try `completionItem/resolve` if it possible.
+"
+function! s:resolve_completion_item(completion_item, server_name) abort
+  " server_name is not provided.
+  if empty(a:server_name)
+    return a:completion_item
+  endif
+
+  " check server capabilities.
+  if !lsp#capabilities#has_completion_resolve_provider(a:server_name)
+    return a:completion_item
+  endif
+
+  let l:ctx = {}
+  let l:ctx['response'] = {}
+  function! l:ctx['callback'](data) abort
+    let l:self['response'] = a:data['response']
+  endfunction
+
+  try
+    call lsp#send_request(a:server_name, {
+          \   'method': 'completionItem/resolve',
+          \   'params': a:completion_item,
+          \   'sync': 1,
+          \   'sync_timeout': g:lsp_completion_resolve_timeout,
+          \   'on_notification': function(l:ctx['callback'], [], l:ctx)
+          \ })
+  catch /.*/
+    call lsp#log('s:resolve_completion_item', 'request timeout.')
+  endtry
+
+  if empty(l:ctx['response'])
+    return a:completion_item
+  endif
+
+  if lsp#client#is_error(l:ctx['response'])
+    return a:completion_item
+  endif
+
+  if empty(l:ctx['response']['result'])
+    return a:completion_item
+  endif
+
+  return l:ctx['response']['result']
+endfunction
+
+"
+" Remove additional inserted text
+"
+" LSP server knows only `complete_position` so we should remove inserted text until complete_position.
+"
+function! s:clear_auto_inserted_text(done_line, done_position, complete_position) abort
+  let l:before = strcharpart(a:done_line, 0, a:complete_position['character'])
+  let l:after = strcharpart(a:done_line, a:done_position['character'], (strchars(a:done_line) - a:done_position['character']))
+  call setline(a:done_position['line'] + 1, l:before . l:after)
+  call cursor([a:done_position['line'] + 1, strlen(l:before) + 1])
+endfunction
+
+"
+" Expand text
+"
+function! s:simple_expand_text(text) abort
+  let l:pos = {
+        \   'line': line('.') - 1,
+        \   'character': lsp#utils#to_char('%', line('.'), col('.'))
+        \ }
+
+  " Remove placeholders and get first placeholder position that use to cursor position.
+  " e.g. `|getbufline(${1:expr}, ${2:lnum})${0}` to getbufline(|,)
+  let l:text = substitute(a:text, '\$\%({[0-9]\+\%(:\(\\.\|[^}]\+\)*\)}\|[0-9]\+\)', '\=substitute(submatch(1), "\\", "", "g")', 'g')
+  let l:offset = match(a:text, '\$\%({[0-9]\+\%(:\(\\.\|[^}]\+\)*\)}\|[0-9]\+\)')
+  if l:offset == -1
+    let l:offset = strchars(l:text)
+  endif
+
+  call lsp#utils#text_edit#apply_text_edits(lsp#utils#get_buffer_uri(bufnr('%')), [{
+        \   'range': {
+        \     'start': l:pos,
+        \     'end': l:pos
+        \   },
+        \   'newText': l:text
+        \ }])
+
+  let l:pos = lsp#utils#position#lsp_to_vim('%', {
+        \   'line': l:pos['line'],
+        \   'character': l:pos['character'] + l:offset
+        \ })
+  call cursor(l:pos)
+endfunction
+
+"
+" Get completion text from CompletionItem. Fallback to label when insertText
+" is falsy
+"
+function! s:get_completion_text(completion_item) abort
+  let l:text = get(a:completion_item, 'insertText', '')
+  if empty(l:text)
+    let l:text = a:completion_item['label']
+  endif
+  return l:text
+endfunction
+
+"
+" Get script id that uses to call `s:` function in feedkeys.
+"
+function! s:SID() abort
+  return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
+endfunction
+