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.
1 function! lsp#utils#text_edit#get_range(text_edit) abort
2 if type(a:text_edit) != v:t_dict
5 let l:insert = get(a:text_edit, 'insert', v:null)
6 if type(l:insert) == v:t_dict
9 return get(a:text_edit, 'range', v:null)
12 function! lsp#utils#text_edit#apply_text_edits(uri, text_edits) abort
13 let l:current_bufname = bufname('%')
14 let l:target_bufname = lsp#utils#uri_to_path(a:uri)
15 let l:cursor_position = lsp#get_position()
17 call s:_switch(l:target_bufname)
18 for l:text_edit in s:_normalize(a:text_edits)
19 call s:_apply(bufnr(l:target_bufname), l:text_edit, l:cursor_position)
21 call s:_switch(l:current_bufname)
23 if bufnr(l:current_bufname) == bufnr(l:target_bufname)
24 call cursor(lsp#utils#position#lsp_to_vim('%', l:cursor_position))
28 " @summary Use this to convert textedit to vim list that is compatible with
29 " quickfix and locllist items
30 " @param uri = DocumentUri
31 " @param text_edit = TextEdit | TextEdit[]
33 function! lsp#utils#text_edit#_lsp_to_vim_list(uri, text_edit) abort
36 if type(a:text_edit) == type([]) " TextEdit[]
37 for l:text_edit in a:text_edit
38 let l:vim_loc = s:lsp_text_edit_item_to_vim(a:uri, l:text_edit, l:cache)
40 call add(l:result, l:vim_loc)
44 let l:vim_loc = s:lsp_text_edit_item_to_vim(a:uri, a:text_edit, l:cache)
46 call add(l:result, l:vim_loc)
52 " @param uri = DocumentUri
53 " @param text_edit = TextEdit
54 " @param cache = {} empty dict
61 function! s:lsp_text_edit_item_to_vim(uri, text_edit, cache) abort
62 if !lsp#utils#is_file_uri(a:uri)
66 let l:path = lsp#utils#uri_to_path(a:uri)
67 let l:range = a:text_edit['range']
68 let [l:line, l:col] = lsp#utils#position#lsp_to_vim(l:path, l:range['start'])
70 let l:index = l:line - 1
71 if has_key(a:cache, l:path)
72 let l:text = a:cache[l:path][l:index]
74 let l:contents = getbufline(l:path, 1, '$')
76 let l:text = get(l:contents, l:index, '')
78 let l:contents = readfile(l:path)
79 let a:cache[l:path] = l:contents
80 let l:text = get(l:contents, l:index, '')
95 function! s:_apply(bufnr, text_edit, cursor_position) abort
96 " create before/after line.
97 let l:start_line = getline(a:text_edit['range']['start']['line'] + 1)
98 let l:end_line = getline(a:text_edit['range']['end']['line'] + 1)
99 let l:before_line = strcharpart(l:start_line, 0, a:text_edit['range']['start']['character'])
100 let l:after_line = strcharpart(l:end_line, a:text_edit['range']['end']['character'], strchars(l:end_line) - a:text_edit['range']['end']['character'])
103 let l:new_lines = lsp#utils#_split_by_eol(a:text_edit['newText'])
104 let l:new_lines[0] = l:before_line . l:new_lines[0]
105 let l:new_lines[-1] = l:new_lines[-1] . l:after_line
108 let l:new_lines_len = len(l:new_lines)
109 let l:range_len = (a:text_edit['range']['end']['line'] - a:text_edit['range']['start']['line']) + 1
112 let l:buffer_length = len(getbufline(a:bufnr, '^', '$'))
113 let l:should_fixendofline = lsp#utils#buffer#_get_fixendofline(a:bufnr)
114 let l:should_fixendofline = l:should_fixendofline && l:new_lines[-1] ==# ''
115 let l:should_fixendofline = l:should_fixendofline && l:buffer_length <= a:text_edit['range']['end']['line']
116 let l:should_fixendofline = l:should_fixendofline && a:text_edit['range']['end']['character'] == 0
117 if l:should_fixendofline
118 call remove(l:new_lines, -1)
122 if a:text_edit['range']['end']['line'] < a:cursor_position['line']
124 let a:cursor_position['line'] += l:new_lines_len - l:range_len
125 elseif a:text_edit['range']['end']['line'] == a:cursor_position['line'] && a:text_edit['range']['end']['character'] <= a:cursor_position['character']
126 " fix cursor line and col
127 let a:cursor_position['line'] += l:new_lines_len - l:range_len
128 let l:end_character = strchars(l:new_lines[-1]) - strchars(l:after_line)
129 let l:end_offset = a:cursor_position['character'] - a:text_edit['range']['end']['character']
130 let a:cursor_position['character'] = l:end_character + l:end_offset
133 " append or delete lines.
134 if l:new_lines_len > l:range_len
135 call append(a:text_edit['range']['start']['line'], repeat([''], l:new_lines_len - l:range_len))
136 elseif l:new_lines_len < l:range_len
137 let l:offset = l:range_len - l:new_lines_len
138 call s:delete(a:bufnr, a:text_edit['range']['start']['line'] + 1, a:text_edit['range']['start']['line'] + l:offset)
142 call setline(a:text_edit['range']['start']['line'] + 1, l:new_lines)
148 function! s:_normalize(text_edits) abort
149 let l:text_edits = type(a:text_edits) == type([]) ? a:text_edits : [a:text_edits]
150 let l:text_edits = filter(copy(l:text_edits), { _, text_edit -> type(text_edit) == type({}) })
151 let l:text_edits = s:_range(l:text_edits)
152 let l:text_edits = sort(copy(l:text_edits), function('s:_compare', [], {}))
153 let l:text_edits = s:_check(l:text_edits)
154 return reverse(l:text_edits)
160 function! s:_range(text_edits) abort
161 for l:text_edit in a:text_edits
162 if l:text_edit.range.start.line > l:text_edit.range.end.line || (
163 \ l:text_edit.range.start.line == l:text_edit.range.end.line &&
164 \ l:text_edit.range.start.character > l:text_edit.range.end.character
166 let l:text_edit.range = { 'start': l:text_edit.range.end, 'end': l:text_edit.range.start }
175 " LSP Spec says `multiple text edits can not overlap those ranges`.
176 " This function check it. But does not throw error.
178 function! s:_check(text_edits) abort
179 if len(a:text_edits) > 1
180 let l:range = a:text_edits[0].range
181 for l:text_edit in a:text_edits[1 : -1]
182 if l:range.end.line > l:text_edit.range.start.line || (
183 \ l:range.end.line == l:text_edit.range.start.line &&
184 \ l:range.end.character > l:text_edit.range.start.character
186 call lsp#log('text_edit: range overlapped.')
188 let l:range = l:text_edit.range
197 function! s:_compare(text_edit1, text_edit2) abort
198 let l:diff = a:text_edit1.range.start.line - a:text_edit2.range.start.line
200 return a:text_edit1.range.start.character - a:text_edit2.range.start.character
208 function! s:_switch(path) abort
209 if bufnr(a:path) == -1
210 execute printf('badd %s', fnameescape(a:path))
212 execute printf('keepalt keepjumps %sbuffer!', bufnr(a:path))
218 function! s:delete(bufnr, start, end) abort
219 if exists('*deletebufline')
220 call deletebufline(a:bufnr, a:start, a:end)
222 let l:foldenable = &foldenable
223 setlocal nofoldenable
224 execute printf('%s,%sdelete _', a:start, a:end)
225 let &foldenable = l:foldenable