]> git.madduck.net Git - etc/vim.git/blob - autoload/lsp/utils/text_edit.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

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.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Squashed '.vim/bundle/vim-lsp/' content from commit 04428c92
[etc/vim.git] / autoload / lsp / utils / text_edit.vim
1 function! lsp#utils#text_edit#get_range(text_edit) abort
2   if type(a:text_edit) != v:t_dict
3     return v:null
4   endif
5   let l:insert = get(a:text_edit, 'insert', v:null)
6   if type(l:insert) == v:t_dict
7     return l:insert
8   endif
9   return get(a:text_edit, 'range', v:null)
10 endfunction
11
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()
16
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)
20     endfor
21     call s:_switch(l:current_bufname)
22
23     if bufnr(l:current_bufname) == bufnr(l:target_bufname)
24         call cursor(lsp#utils#position#lsp_to_vim('%', l:cursor_position))
25     endif
26 endfunction
27
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[]
32 " @returns []
33 function! lsp#utils#text_edit#_lsp_to_vim_list(uri, text_edit) abort
34     let l:result = []
35     let l:cache = {}
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)
39             if !empty(l:vim_loc)
40                 call add(l:result, l:vim_loc)
41             endif
42         endfor
43     else " TextEdit
44         let l:vim_loc = s:lsp_text_edit_item_to_vim(a:uri, a:text_edit, l:cache)
45         if !empty(l:vim_loc)
46             call add(l:result, l:vim_loc)
47         endif
48     endif
49     return l:result
50 endfunction
51
52 " @param uri = DocumentUri
53 " @param text_edit = TextEdit
54 " @param cache = {} empty dict
55 " @returns {
56 "   'filename',
57 "   'lnum',
58 "   'col',
59 "   'text',
60 " }
61 function! s:lsp_text_edit_item_to_vim(uri, text_edit, cache) abort
62     if !lsp#utils#is_file_uri(a:uri)
63         return v:null
64     endif
65
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'])
69
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]
73     else
74         let l:contents = getbufline(l:path, 1, '$')
75         if !empty(l:contents)
76             let l:text = get(l:contents, l:index, '')
77         else
78             let l:contents = readfile(l:path)
79             let a:cache[l:path] = l:contents
80             let l:text = get(l:contents, l:index, '')
81         endif
82     endif
83
84     return {
85         \ 'filename': l:path,
86         \ 'lnum': l:line,
87         \ 'col': l:col,
88         \ 'text': l:text
89         \ }
90 endfunction
91
92 "
93 " _apply
94 "
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'])
101
102     " create new lines.
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
106
107   " save length.
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
110
111     " fixendofline
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)
119     endif
120
121     " fix cursor pos
122     if a:text_edit['range']['end']['line'] < a:cursor_position['line']
123         " fix cursor 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
131     endif
132
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)
139     endif
140
141     " set lines.
142     call setline(a:text_edit['range']['start']['line'] + 1, l:new_lines)
143 endfunction
144
145 "
146 " _normalize
147 "
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)
155 endfunction
156
157 "
158 " _range
159 "
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
165           \ )
166       let l:text_edit.range = { 'start': l:text_edit.range.end, 'end': l:text_edit.range.start }
167     endif
168   endfor
169   return a:text_edits
170 endfunction
171
172 "
173 " _check
174 "
175 " LSP Spec says `multiple text edits can not overlap those ranges`.
176 " This function check it. But does not throw error.
177 "
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
185       \ )
186         call lsp#log('text_edit: range overlapped.')
187       endif
188       let l:range = l:text_edit.range
189     endfor
190   endif
191   return a:text_edits
192 endfunction
193
194 "
195 " _compare
196 "
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
199   if l:diff == 0
200     return a:text_edit1.range.start.character - a:text_edit2.range.start.character
201   endif
202   return l:diff
203 endfunction 
204
205 "
206 " _switch
207 "
208 function! s:_switch(path) abort
209   if bufnr(a:path) == -1
210     execute printf('badd %s', fnameescape(a:path))
211   endif
212   execute printf('keepalt keepjumps %sbuffer!', bufnr(a:path))
213 endfunction 
214
215 "
216 " delete
217 "
218 function! s:delete(bufnr, start, end) abort
219   if exists('*deletebufline')
220       call deletebufline(a:bufnr, a:start, a:end)
221   else
222       let l:foldenable = &foldenable
223       setlocal nofoldenable
224       execute printf('%s,%sdelete _', a:start, a:end)
225       let &foldenable = l:foldenable
226   endif
227 endfunction
228