Implemented word correction in cases where startcol was corrected by … (#1340)

* Implemented word correction in cases where startcol was corrected by TextEdit.range.start.character

* Don't touch abbr

* Don't use `..`

* relax get_server_info

* Improve for deno lsp item

* Fix start character

* minimal `insertReplaceTextEdit` suppoort.

* Change to filter logic similar to asyncomplete-lsp.vim

* Improve word creation

* relax lsp#utils#text_edit#get_range

* Add start_character for test case
This commit is contained in:
hrsh7th
2022-07-22 11:19:16 +09:00
committed by GitHub
parent 9a510cd419
commit 771755300a
5 changed files with 88 additions and 42 deletions

View File

@@ -95,7 +95,7 @@ function! lsp#get_server_names() abort
endfunction
function! lsp#get_server_info(server_name) abort
return s:servers[a:server_name]['server_info']
return get(get(s:servers, a:server_name, {}), 'server_info', {})
endfunction
function! lsp#get_server_root_uri(server_name) abort

View File

@@ -59,23 +59,10 @@ function! lsp#omni#complete(findstart, base) abort
let s:completion['status'] = s:completion_status_pending
endif
" Find first item which has refresh_pattern
let l:refresh_pattern = '\(\k\+$\)'
for l:server_name in l:info['server_names']
let l:server_info = lsp#get_server_info(l:server_name)
if has_key(l:server_info, 'config') && has_key(l:server_info['config'], 'refresh_pattern')
let l:refresh_pattern = l:server_info['config']['refresh_pattern']
break
endif
endfor
let l:curpos = getcurpos()
let l:left = strpart(getline(l:curpos[1]), 0, l:curpos[2]-1)
let l:left = strpart(getline('.'), 0, col('.')-1)
" Initialize the default startcol. It will be updated if the completion items has textEdit.
let s:completion['startcol'] = 1 + matchstrpos(l:left, l:refresh_pattern)[1]
if s:completion['startcol'] == 0
let s:completion['startcol'] = strlen(l:left) + 1
endif
let s:completion['startcol'] = s:get_startcol(l:left, l:info['server_names'])
" The `l:info` variable will be filled with completion results after request was finished.
call s:send_completion_request(l:info)
@@ -95,17 +82,6 @@ function! lsp#omni#complete(findstart, base) abort
endfunction
function! s:get_filter_label(item) abort
let l:user_data = lsp#omni#get_managed_user_data_from_completed_item(a:item)
if has_key(l:user_data, 'completion_item') && has_key(l:user_data['completion_item'], 'filterText')
let l:filter_text = l:user_data['completion_item']['filterText']
if empty(l:filter_text) && has_key(l:user_data['completion_item'], 'label')
" When filterText is `falsy` the label is used as the filter text
let l:filter_text = l:user_data['completion_item']['label']
endif
if !empty(l:filter_text)
return lsp#utils#_trim(l:filter_text)
endif
endif
return lsp#utils#_trim(a:item['word'])
endfunction
@@ -298,6 +274,10 @@ function! lsp#omni#get_vim_completion_items(options) abort
let l:server_name = l:server['name']
let l:kind_text_mappings = s:get_kind_text_mappings(l:server)
let l:complete_position = a:options['position']
let l:current_line = getline('.')
let l:default_startcol = s:get_startcol(strcharpart(l:current_line, 0, l:complete_position['character']), [l:server_name])
let l:default_start_character = strchars(strpart(l:current_line, 0, l:default_startcol - 1))
let l:refresh_pattern = s:get_refresh_pattern([l:server_name])
let l:result = a:options['response']['result']
if type(l:result) == type([])
@@ -319,6 +299,8 @@ function! lsp#omni#get_vim_completion_items(options) abort
endif
let l:start_character = l:complete_position['character']
let l:start_characters = [] " The mapping of item specific start_character.
let l:vim_complete_items = []
for l:completion_item in l:items
let l:expandable = get(l:completion_item, 'insertTextFormat', 1) == 2
@@ -328,13 +310,33 @@ function! lsp#omni#get_vim_completion_items(options) abort
\ 'empty': 1,
\ 'icase': 1,
\ }
if has_key(l:completion_item, 'textEdit') && type(l:completion_item['textEdit']) == s:t_dict && has_key(l:completion_item['textEdit'], 'range') && has_key(l:completion_item['textEdit'], 'newText')
let l:vim_complete_item['word'] = l:completion_item['textEdit']['newText']
let l:start_character = min([l:completion_item['textEdit']['range']['start']['character'], l:start_character])
let l:range = lsp#utils#text_edit#get_range(get(l:completion_item, 'textEdit', {}))
if has_key(l:completion_item, 'textEdit') && type(l:completion_item['textEdit']) == s:t_dict && !empty(l:range) && has_key(l:completion_item['textEdit'], 'newText')
let l:text_edit_new_text = l:completion_item['textEdit']['newText']
if has_key(l:completion_item, 'filterText') && !empty(l:completion_item['filterText']) && matchstr(l:text_edit_new_text, '^' . l:refresh_pattern) ==# ''
" Use filterText as word.
let l:vim_complete_item['word'] = l:completion_item['filterText']
let l:start_characters += [l:default_start_character]
else
" Use textEdit.newText as word.
let l:item_start_character = l:range['start']['character']
let l:vim_complete_item['word'] = l:text_edit_new_text
if l:item_start_character < l:default_start_character
" Add already typed word. The typescript-language-server returns `[Symbol]` item for the line of `Hoo.|`. So we should add `.` (`.[Symbol]`) .
let l:overlap_text = strcharpart(l:current_line, l:item_start_character, l:default_start_character - l:item_start_character)
if stridx(l:vim_complete_item['word'], l:overlap_text) != 0
let l:vim_complete_item['word'] = l:overlap_text . l:vim_complete_item['word']
endif
endif
let l:start_character = min([l:item_start_character, l:start_character])
let l:start_characters += [l:item_start_character]
endif
elseif has_key(l:completion_item, 'insertText') && !empty(l:completion_item['insertText'])
let l:vim_complete_item['word'] = l:completion_item['insertText']
let l:start_characters += [l:default_start_character]
else
let l:vim_complete_item['word'] = l:completion_item['label']
let l:start_characters += [l:default_start_character]
endif
if l:expandable
@@ -345,12 +347,22 @@ function! lsp#omni#get_vim_completion_items(options) abort
endif
if s:is_user_data_support
let l:vim_complete_item['user_data'] = s:create_user_data(l:completion_item, l:server_name, l:complete_position)
let l:vim_complete_item['user_data'] = s:create_user_data(l:completion_item, l:server_name, l:complete_position, l:start_characters[len(l:start_characters) - 1])
endif
let l:vim_complete_items += [l:vim_complete_item]
endfor
" Add the additional text for startcol correction.
if l:start_character != l:default_start_character
for l:i in range(len(l:start_characters))
let l:item_start_character = l:start_characters[l:i]
if l:start_character < l:item_start_character
let l:item = l:vim_complete_items[l:i]
let l:item['word'] = strcharpart(l:current_line, l:start_character, l:item_start_character - l:start_character) . l:item['word']
endif
endfor
endif
let l:startcol = lsp#utils#position#lsp_character_to_vim('%', { 'line': l:complete_position['line'], 'character': l:start_character })
return { 'items': l:vim_complete_items, 'incomplete': l:incomplete, 'startcol': l:startcol }
@@ -369,12 +381,13 @@ endfunction
"
" create item's user_data.
"
function! s:create_user_data(completion_item, server_name, complete_position) abort
function! s:create_user_data(completion_item, server_name, complete_position, start_character) abort
let l:user_data_key = s:create_user_data_key(s:managed_user_data_key_base)
let s:managed_user_data_map[l:user_data_key] = {
\ 'complete_position': a:complete_position,
\ 'server_name': a:server_name,
\ 'completion_item': a:completion_item
\ 'completion_item': a:completion_item,
\ 'start_character': a:start_character,
\ }
let s:managed_user_data_key_base += 1
return l:user_data_key
@@ -419,4 +432,21 @@ endfunction
function! s:create_user_data_key(base) abort
return '{"vim-lsp/key":"' . a:base . '"}'
endfunction
function! s:get_startcol(left, server_names) abort
" Initialize the default startcol. It will be updated if the completion items has textEdit.
let l:startcol = 1 + matchstrpos(a:left, s:get_refresh_pattern(a:server_names))[1]
return l:startcol == 0 ? strlen(a:left) + 1 : l:startcol
endfunction
function! s:get_refresh_pattern(server_names) abort
for l:server_name in a:server_names
let l:server_info = lsp#get_server_info(l:server_name)
if has_key(l:server_info, 'config') && has_key(l:server_info['config'], 'refresh_pattern')
return l:server_info['config']['refresh_pattern']
endif
endfor
return '\(\k\+$\)'
endfunction
" }}}

View File

@@ -54,6 +54,7 @@ function! s:on_complete_done() abort
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']
call feedkeys(printf("\<C-r>=<SNR>%d_on_complete_done_after()\<CR>", s:SID()), 'n')
endfunction
@@ -75,7 +76,7 @@ function! s:on_complete_done_after() abort
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:complete_start_character = l:done_position['character'] - strchars(l:completed_item['word'])
let l:start_character = s:context['start_character']
" check the commit characters are <BS> or <C-w>.
if strlen(getline('.')) < strlen(l:done_line)
@@ -106,8 +107,9 @@ function! s:on_complete_done_after() abort
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:overflow_before = max([0, l:complete_start_character - l:completion_item['textEdit']['range']['start']['character']])
let l:overflow_after = max([0, l:completion_item['textEdit']['range']['end']['character'] - l:complete_position['character']])
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
@@ -120,7 +122,7 @@ function! s:on_complete_done_after() abort
let l:range = {
\ 'start': {
\ 'line': l:position['line'],
\ 'character': l:position['character'] - (l:complete_position['character'] - l:complete_start_character) - l:overflow_before,
\ 'character': l:position['character'] - (l:complete_position['character'] - l:start_character) - l:overflow_before,
\ },
\ 'end': {
\ 'line': l:position['line'],
@@ -160,7 +162,8 @@ endfunction
"
function! s:is_expandable(done_line, done_position, complete_position, completion_item, completed_item) abort
if get(a:completion_item, 'textEdit', v:null) isnot# v:null
if a:completion_item['textEdit']['range']['start']['line'] != a:completion_item['textEdit']['range']['end']['line']
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
@@ -168,8 +171,8 @@ function! s:is_expandable(done_line, done_position, complete_position, completio
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, a:completion_item['textEdit']['range']['start']['character'])
let l:text_edit_after = strcharpart(l:completed_line, a:completion_item['textEdit']['range']['end']['character'], strchars(l:completed_line) - a:completion_item['textEdit']['range']['end']['character'])
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:completed_item['word'])

View File

@@ -1,3 +1,14 @@
function! lsp#utils#text_edit#get_range(text_edit) abort
if type(a:text_edit) != v:t_dict
return v:null
endif
let l:insert = get(a:text_edit, 'insert', v:null)
if type(l:insert) == v:t_dict
return l:insert
endif
return get(a:text_edit, 'range', v:null)
endfunction
function! lsp#utils#text_edit#apply_text_edits(uri, text_edits) abort
let l:current_bufname = bufname('%')
let l:target_bufname = lsp#utils#uri_to_path(a:uri)

View File

@@ -82,7 +82,8 @@ Describe lsp#omni
Assert Equals(lsp#omni#get_managed_user_data_from_completed_item(got['items'][0]), {
\ 'server_name': 'dummy-server',
\ 'completion_item': item,
\ 'complete_position': { 'line': 1, 'character': 1 }
\ 'complete_position': { 'line': 1, 'character': 1 },
\ 'start_character': 0,
\ })
End
@@ -184,7 +185,8 @@ Describe lsp#omni
Assert Equals(lsp#omni#get_managed_user_data_from_completed_item(got['items'][0]), {
\ 'server_name': 'dummy-server',
\ 'completion_item': item,
\ 'complete_position': { 'line': 1, 'character': 1 }
\ 'complete_position': { 'line': 1, 'character': 1 },
\ 'start_character': 0,
\ })
End