From 771755300a719ccfb78cd99c2ccd633871db3596 Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:19:16 +0900 Subject: [PATCH] =?UTF-8?q?Implemented=20word=20correction=20in=20cases=20?= =?UTF-8?q?where=20startcol=20was=20corrected=20by=20=E2=80=A6=20(#1340)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- autoload/lsp.vim | 2 +- autoload/lsp/omni.vim | 94 ++++++++++++++++++++---------- autoload/lsp/ui/vim/completion.vim | 17 +++--- autoload/lsp/utils/text_edit.vim | 11 ++++ test/lsp/omni.vimspec | 6 +- 5 files changed, 88 insertions(+), 42 deletions(-) diff --git a/autoload/lsp.vim b/autoload/lsp.vim index 49dc7316..8639ac4b 100644 --- a/autoload/lsp.vim +++ b/autoload/lsp.vim @@ -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 diff --git a/autoload/lsp/omni.vim b/autoload/lsp/omni.vim index 7bb744ed..308e2605 100644 --- a/autoload/lsp/omni.vim +++ b/autoload/lsp/omni.vim @@ -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 + " }}} diff --git a/autoload/lsp/ui/vim/completion.vim b/autoload/lsp/ui/vim/completion.vim index 94d0521b..66b600f3 100644 --- a/autoload/lsp/ui/vim/completion.vim +++ b/autoload/lsp/ui/vim/completion.vim @@ -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("\=%d_on_complete_done_after()\", 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 or . 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']) diff --git a/autoload/lsp/utils/text_edit.vim b/autoload/lsp/utils/text_edit.vim index 717506e2..6f64b47d 100644 --- a/autoload/lsp/utils/text_edit.vim +++ b/autoload/lsp/utils/text_edit.vim @@ -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) diff --git a/test/lsp/omni.vimspec b/test/lsp/omni.vimspec index c62f251d..ec9f0844 100644 --- a/test/lsp/omni.vimspec +++ b/test/lsp/omni.vimspec @@ -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