Skip to content

Commit f769a45

Browse files
hrsh7thprabirshrestha
authored andcommitted
Fix CompleteDone (#637)
* Move some codes to completion.vim that related to handling CompleteDone. * Add abort * Fix test * Add l: prefix to self vars * Disable unused vars * Restore position * Support snippet simple case and vim-lsp-snippets * Support g:lsp_text_edit_enabled * Fix for v:null results * Fix multi-byte chars * Add document * Fix for vim-lsp-snippets for now * Prepare to future improvements * More clalify line comment * User get_user_data instead of extract_user_data * Always use [''] pattern for accessing dictionary property * Fix misspelling * More strict check for completionProvider.resolveProvider * Fix obj.key to obj['key'] * Store completed_item id to the user_data * Fix omni tests * Apply for the review - Rename expand_text_simply -> simple_expand_text - to_col -> _lsp_to_vim - Rename user_data key * Add example tests * Add timeout to completionItem/resolve in completion.vim * Fix timeout feature * Add timeout log * Fix documents
1 parent 4468bbc commit f769a45

File tree

8 files changed

+387
-248
lines changed

8 files changed

+387
-248
lines changed

autoload/lsp.vim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function! lsp#enable() abort
5858
if g:lsp_highlights_enabled | call lsp#ui#vim#highlights#enable() | endif
5959
if g:lsp_textprop_enabled | call lsp#ui#vim#diagnostics#textprop#enable() | endif
6060
endif
61+
call lsp#ui#vim#completion#_setup()
6162
call s:register_events()
6263
endfunction
6364

autoload/lsp/client.vim

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,13 @@ function! s:lsp_send(id, opts, type) abort " opts = { id?, method?, result?, par
247247
if (a:type == s:send_type_request)
248248
let l:id = l:request['id']
249249
if get(a:opts, 'sync', 0) !=# 0
250+
let l:start_time = reltime()
251+
252+
let l:timeout = get(a:opts, 'sync_timeout', -1)
250253
while has_key(l:ctx['requests'], l:request['id'])
254+
if (reltimefloat(reltime(l:start_time)) * 1000) > l:timeout && l:timeout != -1
255+
throw 'lsp#client: timeout'
256+
endif
251257
sleep 1m
252258
endwhile
253259
endif

autoload/lsp/omni.vim

Lines changed: 46 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,8 @@ let s:completion_status_failed = 'failed'
3737
let s:completion_status_pending = 'pending'
3838

3939
let s:is_user_data_support = has('patch-8.0.1493')
40-
let s:user_data_key = 'vim-lsp/textEdit'
41-
let s:user_data_additional_edits_key = 'vim-lsp/additionalTextEdits'
42-
let s:user_data_insert_start_key = 'vim-lsp/insertStart'
43-
let s:user_data_insert_format_key = 'vim-lsp/insertFormat'
44-
let s:user_data_filtertext_key = 'vim-lsp/filterText'
40+
let s:managed_user_data_key_base = 0
41+
let s:managed_user_data_map = {}
4542

4643
" }}}
4744

@@ -80,10 +77,11 @@ function! lsp#omni#complete(findstart, base) abort
8077
endfunction
8178

8279
function! s:get_insertion_point(item, current_line, typed_pattern) abort
83-
if !has_key(a:item, 'user_data')
84-
let l:insert_start = -1
85-
else
86-
let l:insert_start = get(json_decode(a:item['user_data']), s:user_data_insert_start_key, -1)
80+
let l:insert_start = -1
81+
82+
let l:user_data = lsp#omni#get_managed_user_data_from_completed_item(a:item)
83+
if has_key(l:user_data, 'completion_item') && has_key(l:user_data['completion_item'], 'textEdit')
84+
let l:insert_start = l:user_data['completion_item']['textEdit']['range']['start']['character']
8785
endif
8886

8987
if l:insert_start >= 0
@@ -94,12 +92,11 @@ function! s:get_insertion_point(item, current_line, typed_pattern) abort
9492
endfunction
9593

9694
function! s:get_filter_label(item) abort
97-
if !has_key(a:item, 'user_data')
98-
return trim(a:item['word'])
95+
let l:user_data = lsp#omni#get_managed_user_data_from_completed_item(a:item)
96+
if has_key(l:user_data, 'completion_item') && has_key(l:user_data['completion_item'], 'filterText')
97+
return trim(l:user_data['completion_item']['filterText'])
9998
endif
100-
101-
let l:user_data = json_decode(a:item['user_data'])
102-
return trim(get(l:user_data, s:user_data_filtertext_key, a:item['word']))
99+
return trim(a:item['word'])
103100
endfunction
104101

105102
function! s:prefix_filter(item, last_typed_word) abort
@@ -252,12 +249,6 @@ function! lsp#omni#default_get_vim_completion_item(item, ...) abort
252249
let l:abbr = a:item['label']
253250
endif
254251

255-
if has_key(a:item, 'insertTextFormat') && a:item['insertTextFormat'] == 2
256-
let l:word = substitute(l:word, '\<\$[0-9]\+\|\${[^}]\+}\>', '', 'g')
257-
endif
258-
259-
let l:kind = lsp#omni#get_kind_text(a:item, l:server_name)
260-
261252
let l:completion = {
262253
\ 'word': l:word,
263254
\ 'abbr': l:abbr,
@@ -266,7 +257,8 @@ function! lsp#omni#default_get_vim_completion_item(item, ...) abort
266257
\ 'icase': 1,
267258
\ 'dup': 1,
268259
\ 'empty': 1,
269-
\ 'kind': l:kind}
260+
\ 'kind': lsp#omni#get_kind_text(a:item, l:server_name)
261+
\ }
270262

271263
" check support user_data.
272264
" if not support but g:lsp_text_edit_enabled enabled,
@@ -277,38 +269,9 @@ function! lsp#omni#default_get_vim_completion_item(item, ...) abort
277269
call lsp#log(l:no_support_error_message)
278270
endif
279271

280-
let l:user_data = {}
281-
282-
" Use '-1' to signal "no specific insertion point" set.
283-
let l:user_data[s:user_data_insert_start_key] = -1
284-
285-
" add user_data in completion item, when
286-
" 1. provided user_data
287-
" 2. provided textEdit or additionalTextEdits
288-
" 3. textEdit value is Dictionary or additionalTextEdits is non-empty list
289-
if g:lsp_text_edit_enabled
290-
let l:text_edit = get(a:item, 'textEdit', v:null)
291-
let l:additional_text_edits = get(a:item, 'additionalTextEdits', v:null)
292-
293-
" type check
294-
if type(l:text_edit) == type({})
295-
let l:user_data[s:user_data_key] = l:text_edit
296-
let l:user_data[s:user_data_insert_start_key] = l:text_edit['range']['start']['character']
297-
let l:user_data[s:user_data_insert_format_key] = get(a:item, 'insertTextFormat', 0)
298-
endif
299-
300-
if type(l:additional_text_edits) == type([]) && !empty(l:additional_text_edits)
301-
let l:user_data[s:user_data_additional_edits_key] = l:additional_text_edits
302-
endif
303-
endif
304-
305-
" Store filterText in user_data
306-
if s:is_user_data_support && has_key(a:item, 'filterText')
307-
let l:user_data[s:user_data_filtertext_key] = a:item['filterText']
308-
endif
309-
310-
if !empty(l:user_data)
311-
let l:completion['user_data'] = json_encode(l:user_data)
272+
" Add user_data.
273+
if s:is_user_data_support
274+
let l:completion['user_data'] = s:create_user_data(a:item, l:server_name)
312275
endif
313276

314277
if has_key(a:item, 'detail') && !empty(a:item['detail'])
@@ -332,139 +295,42 @@ function! lsp#omni#get_vim_completion_item(...) abort
332295
return call(g:lsp_get_vim_completion_item[0], a:000)
333296
endfunction
334297

335-
augroup lsp_completion_item_text_edit
336-
autocmd!
337-
autocmd CompleteDone * call <SID>apply_text_edits()
338-
augroup END
339-
340-
function! s:apply_text_edits() abort
341-
" textEdit support function(callin from CompleteDone).
342-
"
343-
" expected user_data structure:
344-
" v:completed_item['user_data']: {
345-
" 'vim-lsp/textEdit': {
346-
" 'range': { ...(snip) },
347-
" 'newText': 'yyy'
348-
" },
349-
" 'vim-lsp/additionalTextEdits': [
350-
" {
351-
" 'range': { ...(snip) },
352-
" 'newText': 'yyy'
353-
" },
354-
" ...
355-
" ],
356-
" }
357-
if !g:lsp_text_edit_enabled
358-
doautocmd User lsp_complete_done
359-
return
360-
endif
361-
362-
" completion faild or not select complete item
363-
if empty(v:completed_item)
364-
doautocmd User lsp_complete_done
365-
return
366-
endif
367-
368-
" check user_data
369-
if !has_key(v:completed_item, 'user_data')
370-
doautocmd User lsp_complete_done
371-
return
372-
endif
373-
374-
" check user_data type is Dictionary and user_data['vim-lsp/textEdit']
375-
try
376-
let l:user_data = json_decode(v:completed_item['user_data'])
377-
catch
378-
" do nothing if user_data is not json type string.
379-
doautocmd User lsp_complete_done
380-
return
381-
endtry
382-
383-
if type(l:user_data) != type({})
384-
doautocmd User lsp_complete_done
385-
return
386-
endif
387-
388-
let l:all_text_edits = []
389-
390-
" if newText contains snippet markers, remove all them.
391-
let l:snippet_marker_pos = -1
392-
393-
" expand textEdit range, for omni complet inserted text.
394-
let l:text_edit = get(l:user_data, s:user_data_key, {})
395-
if !empty(l:text_edit)
396-
let l:expanded_text_edit = s:expand_range(l:text_edit, strchars(v:completed_item['word']))
397-
" InsertTextFormat:Snippet
398-
if get(l:user_data, s:user_data_insert_format_key, 0) == 2
399-
let l:new_text = l:expanded_text_edit['newText']
400-
let l:marker_pattern = '\<\$[0-9]\+\|\${[^}]\+}\>'
401-
let l:snippet_marker_pos = matchstrpos(l:new_text, l:marker_pattern)[1] - 1
402-
let l:expanded_text_edit['newText'] = substitute(l:new_text, l:marker_pattern, '', 'g')
403-
endif
404-
call add(l:all_text_edits, l:expanded_text_edit)
405-
endif
406-
407-
if has_key(l:user_data, s:user_data_additional_edits_key)
408-
let l:all_text_edits += l:user_data[s:user_data_additional_edits_key]
409-
endif
410-
411-
" save cursor position in a mark, vim will move it appropriately when
412-
" applying edits
413-
let l:saved_mark = getpos("'a")
414-
" move to end of newText but in two steps (as column may not exist yet)
415-
let [l:pos, l:col_offset] = s:get_cursor_pos_and_edit_length(l:text_edit)
416-
call setpos("'a", l:pos)
417-
418-
" apply textEdits
419-
if !empty(l:all_text_edits)
420-
call lsp#utils#text_edit#apply_text_edits(lsp#utils#get_buffer_uri(), l:all_text_edits)
421-
" When user typed something character while popup menu is shwon, vim
422-
" insert typed-character after CompleteDone occured. but the character
423-
" should not be duplicated since the textEdit include the character.
424-
" this remove the following character.
425-
if l:snippet_marker_pos != -1
426-
let l:oldpos = line('.')
427-
let l:oldline = getline('.')
428-
call timer_start(1, {_-> [
429-
\ setline(l:oldpos, l:oldline),
430-
\ execute('redraw', 1),
431-
\ execute('doautocmd User lsp_complete_done', 1),
432-
\] })
433-
endif
434-
return
435-
endif
436-
437-
let l:pos = getpos("'a")
438-
if l:snippet_marker_pos >= 0
439-
let l:pos[2] += l:snippet_marker_pos
440-
else
441-
let l:pos[2] += l:col_offset
442-
endif
443-
call setpos("'a", l:saved_mark)
444-
call setpos('.', l:pos)
445-
446-
doautocmd User lsp_complete_done
298+
"
299+
" Clear internal user_data map.
300+
"
301+
" This function should call at `CompleteDone` only if not empty `v:completed_item`.
302+
"
303+
function! lsp#omni#_clear_managed_user_data_map() abort
304+
let s:managed_user_data_key_base = 0
305+
let s:managed_user_data_map = {}
447306
endfunction
448307

449-
function! s:expand_range(text_edit, expand_length) abort
450-
let l:expanded_text_edit = a:text_edit
451-
let l:expanded_text_edit['range']['end']['character'] += a:expand_length
452-
453-
return l:expanded_text_edit
308+
"
309+
" create item's user_data.
310+
"
311+
function! s:create_user_data(completion_item, server_name) abort
312+
let l:user_data_key = 'vim-lsp/key/' . string(s:managed_user_data_key_base)
313+
let s:managed_user_data_map[l:user_data_key] = {
314+
\ 'server_name': a:server_name,
315+
\ 'completion_item': a:completion_item
316+
\ }
317+
let s:managed_user_data_key_base += 1
318+
return l:user_data_key
454319
endfunction
455320

456-
function! s:get_cursor_pos_and_edit_length(text_edit) abort
457-
if !empty(a:text_edit)
458-
let l:start = a:text_edit['range']['start']
459-
let [l:line, l:col] = lsp#utils#position#_lsp_to_vim('%', l:start)
460-
let l:length = len(a:text_edit['newText'])
461-
let l:pos = [0, l:line, l:col, 0]
462-
else
463-
let l:length = 0
464-
let l:pos = getpos('.')
321+
function! lsp#omni#get_managed_user_data_from_completed_item(completed_item) abort
322+
" the item has no user_data.
323+
if !has_key(a:completed_item, 'user_data')
324+
return {}
325+
endif
326+
327+
" Check managed user_data.
328+
let l:user_data_key = get(a:completed_item, 'user_data', '')
329+
if !has_key(s:managed_user_data_map, l:user_data_key)
330+
return {}
465331
endif
466332

467-
return [l:pos, l:length]
333+
return s:managed_user_data_map[l:user_data_key]
468334
endfunction
469335

470336
function! lsp#omni#get_completion_item_kinds() abort

0 commit comments

Comments
 (0)