Skip to content

Commit feba6d6

Browse files
tsufekiprabirshrestha
authored andcommitted
Support additionalTextEdits in completion items (#372)
* Support additionalTextEdits in completion items. * Make cursor move as expected when applying completion edits. * Fix for neovim.
1 parent 74988e4 commit feba6d6

File tree

1 file changed

+69
-24
lines changed

1 file changed

+69
-24
lines changed

autoload/lsp/omni.vim

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ let s:completion_status_pending = 'pending'
3434

3535
let s:is_user_data_support = has('patch-8.0.1493')
3636
let s:user_data_key = 'vim-lsp/textEdit'
37+
let s:user_data_additional_edits_key = 'vim-lsp/additionalTextEdits'
3738

3839
" }}}
3940

@@ -207,17 +208,23 @@ function! lsp#omni#get_vim_completion_item(item, ...) abort
207208

208209
" add user_data in completion item, when
209210
" 1. provided user_data
210-
" 2. provided textEdit
211-
" 3. textEdit value is Dictionary
212-
if g:lsp_text_edit_enabled && has_key(a:item, 'textEdit')
213-
let l:text_edit = a:item['textEdit']
211+
" 2. provided textEdit or additionalTextEdits
212+
" 3. textEdit value is Dictionary or additionalTextEdits is non-empty list
213+
if g:lsp_text_edit_enabled
214+
let l:text_edit = get(a:item, 'textEdit', v:null)
215+
let l:additional_text_edits = get(a:item, 'additionalTextEdits', v:null)
216+
let l:user_data = {}
214217

215218
" type check
216219
if type(l:text_edit) == type({})
217-
let l:user_data = {
218-
\ s:user_data_key : l:text_edit
219-
\ }
220+
let l:user_data[s:user_data_key] = l:text_edit
221+
endif
222+
223+
if type(l:additional_text_edits) == type([]) && !empty(l:additional_text_edits)
224+
let l:user_data[s:user_data_additional_edits_key] = l:additional_text_edits
225+
endif
220226

227+
if !empty(l:user_data)
221228
let l:completion['user_data'] = json_encode(l:user_data)
222229
endif
223230
endif
@@ -237,18 +244,25 @@ endfunction
237244

238245
augroup lsp_completion_item_text_edit
239246
autocmd!
240-
autocmd CompleteDone * call <SID>apply_text_edit()
247+
autocmd CompleteDone * call <SID>apply_text_edits()
241248
augroup END
242249

243-
function! s:apply_text_edit() abort
250+
function! s:apply_text_edits() abort
244251
" textEdit support function(callin from CompleteDone).
245252
"
246253
" expected user_data structure:
247254
" v:completed_item['user_data']: {
248255
" 'vim-lsp/textEdit': {
249256
" 'range': { ...(snip) },
250257
" 'newText': 'yyy'
251-
" },
258+
" },
259+
" 'vim-lsp/additionalTextEdits': [
260+
" {
261+
" 'range': { ...(snip) },
262+
" 'newText': 'yyy'
263+
" },
264+
" ...
265+
" ],
252266
" }
253267
if !g:lsp_text_edit_enabled
254268
return
@@ -272,30 +286,61 @@ function! s:apply_text_edit() abort
272286
return
273287
endtry
274288

275-
if !(type(l:user_data) == type({}) && has_key(l:user_data, s:user_data_key))
289+
if type(l:user_data) != type({})
276290
return
277291
endif
278292

293+
let l:all_text_edits = []
294+
279295
" expand textEdit range, for omni complet inserted text.
280-
let l:text_edit = l:user_data[s:user_data_key]
281-
let l:expanded_text_edit = s:expand_range(l:text_edit, len(v:completed_item['word']))
282-
283-
" apply textEdit
284-
call lsp#utils#text_edit#apply_text_edits(expand('%:p'), [l:expanded_text_edit])
285-
286-
" move to end of newText
287-
let l:start = l:text_edit['range']['start']
288-
let l:line = l:start['line'] + 1
289-
let l:col = l:start['character']
290-
let l:new_text_length = len(l:text_edit['newText']) + 1
291-
call cursor(l:line, l:col + l:new_text_length)
296+
let l:text_edit = get(l:user_data, s:user_data_key, {})
297+
if !empty(l:text_edit)
298+
let l:expanded_text_edit = s:expand_range(l:text_edit, len(v:completed_item['word']))
299+
call add(l:all_text_edits, l:expanded_text_edit)
300+
endif
301+
302+
if has_key(l:user_data, s:user_data_additional_edits_key)
303+
let l:all_text_edits += l:user_data[s:user_data_additional_edits_key]
304+
endif
305+
306+
" save cursor position in a mark, vim will move it appropriately when
307+
" applying edits
308+
let l:saved_mark = getpos("'a")
309+
" move to end of newText but in two steps (as column may not exist yet)
310+
let [l:pos, l:col_offset] = s:get_cursor_pos_and_edit_length(l:text_edit)
311+
call setpos("'a", l:pos)
312+
313+
" apply textEdits
314+
if !empty(l:all_text_edits)
315+
call lsp#utils#text_edit#apply_text_edits(lsp#utils#get_buffer_uri(), l:all_text_edits)
316+
endif
317+
318+
let l:pos = getpos("'a")
319+
let l:pos[2] += l:col_offset
320+
call setpos("'a", l:saved_mark)
321+
call setpos('.', l:pos)
292322
endfunction
293323

294324
function! s:expand_range(text_edit, expand_length) abort
295-
let expanded_text_edit = a:text_edit
325+
let l:expanded_text_edit = a:text_edit
296326
let l:expanded_text_edit['range']['end']['character'] += a:expand_length
297327

298328
return l:expanded_text_edit
299329
endfunction
300330

331+
function! s:get_cursor_pos_and_edit_length(text_edit) abort
332+
if !empty(a:text_edit)
333+
let l:start = a:text_edit['range']['start']
334+
let l:line = l:start['line'] + 1
335+
let l:col = l:start['character'] + 1
336+
let l:length = len(a:text_edit['newText'])
337+
let l:pos = [0, l:line, l:col, 0]
338+
else
339+
let l:length = 0
340+
let l:pos = getpos('.')
341+
endif
342+
343+
return [l:pos, l:length]
344+
endfunction
345+
301346
" }}}

0 commit comments

Comments
 (0)