-
Notifications
You must be signed in to change notification settings - Fork 303
/
text_edit.vim
139 lines (123 loc) · 4.64 KB
/
text_edit.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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)
let l:cursor_pos = getpos('.')[1 : 3]
let l:cursor_offset = 0
let l:topline = line('w0')
call s:_switch(l:target_bufname)
for l:text_edit in s:_normalize(a:text_edits)
let l:cursor_offset += s:_apply(bufnr(l:target_bufname), l:text_edit, l:cursor_pos)
endfor
call s:_switch(l:current_bufname)
if bufnr(l:current_bufname) == bufnr(l:target_bufname)
let l:length = strlen(getline(l:cursor_pos[0])) + 1
let l:cursor_pos[2] = max([0, l:cursor_pos[1] + l:cursor_pos[2] - l:length])
let l:cursor_pos[1] = min([l:length, l:cursor_pos[1] + l:cursor_pos[2]])
call cursor(l:cursor_pos)
call winrestview({ 'topline': l:topline + l:cursor_offset })
endif
endfunction
"
" _apply
"
function! s:_apply(bufnr, text_edit, cursor_pos) abort
" create before/after line.
let l:start_line = getline(a:text_edit.range.start.line + 1)
let l:end_line = getline(a:text_edit.range.end.line + 1)
let l:before_line = strcharpart(l:start_line, 0, a:text_edit.range.start.character)
let l:after_line = strcharpart(l:end_line, a:text_edit.range.end.character, strchars(l:end_line) - a:text_edit.range.end.character)
" create new lines.
let l:new_lines = lsp#utils#_split_by_eol(a:text_edit.newText)
let l:new_lines[0] = l:before_line . l:new_lines[0]
let l:new_lines[-1] = l:new_lines[-1] . l:after_line
" fixendofline
let l:buffer_length = len(getbufline(a:bufnr, '^', '$'))
let l:should_fixendofline = lsp#utils#buffer#_get_fixendofline(a:bufnr)
let l:should_fixendofline = l:should_fixendofline && l:new_lines[-1] ==# ''
let l:should_fixendofline = l:should_fixendofline && l:buffer_length <= a:text_edit['range']['end']['line']
let l:should_fixendofline = l:should_fixendofline && a:text_edit['range']['end']['character'] == 0
if l:should_fixendofline
call remove(l:new_lines, -1)
endif
let l:new_lines_len = len(l:new_lines)
" fix cursor pos
let l:cursor_offset = 0
if a:text_edit.range.end.line + 1 < a:cursor_pos[0]
let l:cursor_offset = l:new_lines_len - (a:text_edit.range.end.line - a:text_edit.range.start.line) - 1
let a:cursor_pos[0] += l:cursor_offset
endif
" append new lines.
call append(a:text_edit.range.start.line, l:new_lines)
" remove old lines
execute printf('%s,%sdelete _',
\ l:new_lines_len + a:text_edit.range.start.line + 1,
\ min([l:new_lines_len + a:text_edit.range.end.line + 1, line('$')])
\ )
return l:cursor_offset
endfunction
"
" _normalize
"
function! s:_normalize(text_edits) abort
let l:text_edits = type(a:text_edits) == type([]) ? a:text_edits : [a:text_edits]
let l:text_edits = filter(copy(l:text_edits), { _, text_edit -> type(text_edit) == type({}) })
let l:text_edits = s:_range(l:text_edits)
let l:text_edits = sort(copy(l:text_edits), function('s:_compare', [], {}))
let l:text_edits = s:_check(l:text_edits)
return reverse(l:text_edits)
endfunction
"
" _range
"
function! s:_range(text_edits) abort
for l:text_edit in a:text_edits
if l:text_edit.range.start.line > l:text_edit.range.end.line || (
\ l:text_edit.range.start.line == l:text_edit.range.end.line &&
\ l:text_edit.range.start.character > l:text_edit.range.end.character
\ )
let l:text_edit.range = { 'start': l:text_edit.range.end, 'end': l:text_edit.range.start }
endif
endfor
return a:text_edits
endfunction
"
" _check
"
" LSP Spec says `multiple text edits can not overlap those ranges`.
" This function check it. But does not throw error.
"
function! s:_check(text_edits) abort
if len(a:text_edits) > 1
let l:range = a:text_edits[0].range
for l:text_edit in a:text_edits[1 : -1]
if l:range.end.line > l:text_edit.range.start.line || (
\ l:range.end.line == l:text_edit.range.start.line &&
\ l:range.end.character > l:text_edit.range.start.character
\ )
call lsp#log('text_edit: range overlapped.')
endif
let l:range = l:text_edit.range
endfor
endif
return a:text_edits
endfunction
"
" _compare
"
function! s:_compare(text_edit1, text_edit2) abort
let l:diff = a:text_edit1.range.start.line - a:text_edit2.range.start.line
if l:diff == 0
return a:text_edit1.range.start.character - a:text_edit2.range.start.character
endif
return l:diff
endfunction
"
" _switch
"
function! s:_switch(path) abort
if bufnr(a:path) >= 0
execute printf('keepalt keepjumps %sbuffer!', bufnr(a:path))
else
execute printf('keepalt keepjumps edit! %s', fnameescape(a:path))
endif
endfunction