X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/5a4872f466ebd76ddd532bdf2798554421c53df4..fe3919e725e156d751069662d11e38f7b4791de1:/.vim/bundle/vim-lsp/autoload/lsp/utils/text_edit.vim diff --git a/.vim/bundle/vim-lsp/autoload/lsp/utils/text_edit.vim b/.vim/bundle/vim-lsp/autoload/lsp/utils/text_edit.vim new file mode 100644 index 00000000..e6e0c835 --- /dev/null +++ b/.vim/bundle/vim-lsp/autoload/lsp/utils/text_edit.vim @@ -0,0 +1,228 @@ +function! lsp#utils#text_edit#get_range(text_edit) abort + if type(a:text_edit) != v:t_dict + return v:null + endif + let l:insert = get(a:text_edit, 'insert', v:null) + if type(l:insert) == v:t_dict + return l:insert + endif + return get(a:text_edit, 'range', v:null) +endfunction + +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_position = lsp#get_position() + + call s:_switch(l:target_bufname) + for l:text_edit in s:_normalize(a:text_edits) + call s:_apply(bufnr(l:target_bufname), l:text_edit, l:cursor_position) + endfor + call s:_switch(l:current_bufname) + + if bufnr(l:current_bufname) == bufnr(l:target_bufname) + call cursor(lsp#utils#position#lsp_to_vim('%', l:cursor_position)) + endif +endfunction + +" @summary Use this to convert textedit to vim list that is compatible with +" quickfix and locllist items +" @param uri = DocumentUri +" @param text_edit = TextEdit | TextEdit[] +" @returns [] +function! lsp#utils#text_edit#_lsp_to_vim_list(uri, text_edit) abort + let l:result = [] + let l:cache = {} + if type(a:text_edit) == type([]) " TextEdit[] + for l:text_edit in a:text_edit + let l:vim_loc = s:lsp_text_edit_item_to_vim(a:uri, l:text_edit, l:cache) + if !empty(l:vim_loc) + call add(l:result, l:vim_loc) + endif + endfor + else " TextEdit + let l:vim_loc = s:lsp_text_edit_item_to_vim(a:uri, a:text_edit, l:cache) + if !empty(l:vim_loc) + call add(l:result, l:vim_loc) + endif + endif + return l:result +endfunction + +" @param uri = DocumentUri +" @param text_edit = TextEdit +" @param cache = {} empty dict +" @returns { +" 'filename', +" 'lnum', +" 'col', +" 'text', +" } +function! s:lsp_text_edit_item_to_vim(uri, text_edit, cache) abort + if !lsp#utils#is_file_uri(a:uri) + return v:null + endif + + let l:path = lsp#utils#uri_to_path(a:uri) + let l:range = a:text_edit['range'] + let [l:line, l:col] = lsp#utils#position#lsp_to_vim(l:path, l:range['start']) + + let l:index = l:line - 1 + if has_key(a:cache, l:path) + let l:text = a:cache[l:path][l:index] + else + let l:contents = getbufline(l:path, 1, '$') + if !empty(l:contents) + let l:text = get(l:contents, l:index, '') + else + let l:contents = readfile(l:path) + let a:cache[l:path] = l:contents + let l:text = get(l:contents, l:index, '') + endif + endif + + return { + \ 'filename': l:path, + \ 'lnum': l:line, + \ 'col': l:col, + \ 'text': l:text + \ } +endfunction + +" +" _apply +" +function! s:_apply(bufnr, text_edit, cursor_position) 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 + + " save length. + let l:new_lines_len = len(l:new_lines) + let l:range_len = (a:text_edit['range']['end']['line'] - a:text_edit['range']['start']['line']) + 1 + + " 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 + + " fix cursor pos + if a:text_edit['range']['end']['line'] < a:cursor_position['line'] + " fix cursor line + let a:cursor_position['line'] += l:new_lines_len - l:range_len + elseif a:text_edit['range']['end']['line'] == a:cursor_position['line'] && a:text_edit['range']['end']['character'] <= a:cursor_position['character'] + " fix cursor line and col + let a:cursor_position['line'] += l:new_lines_len - l:range_len + let l:end_character = strchars(l:new_lines[-1]) - strchars(l:after_line) + let l:end_offset = a:cursor_position['character'] - a:text_edit['range']['end']['character'] + let a:cursor_position['character'] = l:end_character + l:end_offset + endif + + " append or delete lines. + if l:new_lines_len > l:range_len + call append(a:text_edit['range']['start']['line'], repeat([''], l:new_lines_len - l:range_len)) + elseif l:new_lines_len < l:range_len + let l:offset = l:range_len - l:new_lines_len + call s:delete(a:bufnr, a:text_edit['range']['start']['line'] + 1, a:text_edit['range']['start']['line'] + l:offset) + endif + + " set lines. + call setline(a:text_edit['range']['start']['line'] + 1, l:new_lines) +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) == -1 + execute printf('badd %s', fnameescape(a:path)) + endif + execute printf('keepalt keepjumps %sbuffer!', bufnr(a:path)) +endfunction + +" +" delete +" +function! s:delete(bufnr, start, end) abort + if exists('*deletebufline') + call deletebufline(a:bufnr, a:start, a:end) + else + let l:foldenable = &foldenable + setlocal nofoldenable + execute printf('%s,%sdelete _', a:start, a:end) + let &foldenable = l:foldenable + endif +endfunction +