Skip to content

make contentChanges smaller in DidChangeTextDocumentParams #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jan 14, 2019
34 changes: 34 additions & 0 deletions LICENSE-THIRD-PARTY
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
The vim-lsp source code bundle part of third party's code following which carry
their own copyright notices and license terms:

* vim-lsc - https://github.com/natebosch/vim-lsc
* autoload/lsc/diff.vim
====================================================================

Copyright 2017 vim-lsc authors

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ if executable('typescript-language-server')
endif
```

vim-lsp supports incremental changes of Language Server Protocol.

## auto-complete

Refer to docs on configuring omnifunc or [asyncomplete.vim](https://github.com/prabirshrestha/asyncomplete.vim).
Expand Down
86 changes: 66 additions & 20 deletions autoload/lsp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ let s:servers = {} " { lsp_id, server_info, init_callbacks, init_result, buffers

let s:notification_callbacks = [] " { name, callback }

" This hold previous content for each language servers to make
" DidChangeTextDocumentParams. The key is buffer numbers:
" {
" 1: {
" "golsp": [ "first-line", "next-line", ... ],
" "bingo": [ "first-line", "next-line", ... ]
" },
" 2: {
" "pyls": [ "first-line", "next-line", ... ]
" }
" }
let s:file_content = {}

" do nothing, place it here only to avoid the message
augroup _lsp_silent_
autocmd!
Expand Down Expand Up @@ -135,6 +148,7 @@ function! s:register_events() abort
autocmd BufReadPost * call s:on_text_document_did_open()
autocmd BufWritePost * call s:on_text_document_did_save()
autocmd BufWinLeave * call s:on_text_document_did_close()
autocmd BufWipeout * call s:on_buf_wipeout(bufnr('<afile>'))
autocmd InsertLeave * call s:on_text_document_did_change()
autocmd CursorMoved * call s:on_cursor_moved()
augroup END
Expand All @@ -157,18 +171,18 @@ function! s:on_text_document_did_open() abort
endfunction

function! s:on_text_document_did_save() abort
call lsp#log('s:on_text_document_did_save()', bufnr('%'))
let l:buf = bufnr('%')
call lsp#log('s:on_text_document_did_save()', l:buf)
for l:server_name in lsp#get_whitelisted_servers()
call s:ensure_flush(bufnr('%'), l:server_name, {result->s:call_did_save(l:buf, l:server_name, result, function('s:Noop'))})
call s:ensure_flush(l:buf, l:server_name, {result->s:call_did_save(l:buf, l:server_name, result, function('s:Noop'))})
endfor
endfunction

function! s:on_text_document_did_change() abort
call lsp#log('s:on_text_document_did_change()', bufnr('%'))
let l:buf = bufnr('%')
call lsp#log('s:on_text_document_did_change()', l:buf)
for l:server_name in lsp#get_whitelisted_servers()
call s:ensure_flush(bufnr('%'), l:server_name, function('s:Noop'))
call s:ensure_flush(l:buf, l:server_name, function('s:Noop'))
endfor
endfunction

Expand Down Expand Up @@ -214,7 +228,28 @@ function! s:call_did_save(buf, server_name, result, cb) abort
endfunction

function! s:on_text_document_did_close() abort
call lsp#log('s:on_text_document_did_close()', bufnr('%'))
let l:buf = bufnr('%')
call lsp#log('s:on_text_document_did_close()', l:buf)
endfunction

function! s:get_last_file_content(server_name, buf) abort
if has_key(s:file_content, a:buf) && has_key(s:file_content[a:buf], a:server_name)
return s:file_content[a:buf][a:server_name]
endif
return []
endfunction

function! s:update_file_content(server_name, buf, new) abort
if !has_key(s:file_content, a:buf)
let s:file_content[a:buf] = {}
endif
let s:file_content[a:buf][a:server_name] = a:new
endfunction

function! s:on_buf_wipeout(buf) abort
if has_key(s:file_content, a:buf)
call remove(s:file_content, a:buf)
endif
endfunction

function! s:ensure_flush_all(buf, server_names) abort
Expand Down Expand Up @@ -399,6 +434,30 @@ function! s:ensure_conf(buf, server_name, cb) abort
call a:cb(l:msg)
endfunction

function! s:text_changes(server_name, buf) abort
let l:sync_kind = lsp#capabilities#get_text_document_change_sync_kind(a:server_name)

" When syncKind is None, return null for contentChanges.
if l:sync_kind == 0
return v:null
endif

" When syncKind is Incremental and previous content is saved.
if l:sync_kind == 2 && has_key(s:file_content, a:buf)
" compute diff
let l:old_content = s:get_last_file_content(a:server_name, a:buf)
let l:new_content = getbufline(a:buf, 1, '$')
let l:changes = lsp#utils#diff#compute(l:old_content, l:new_content)
call s:update_file_content(a:server_name, a:buf, l:new_content)
return [l:changes]
endif

let l:new_content = getbufline(a:buf, 1, '$')
let l:changes = {'text': join(l:new_content, "\n")}
call s:update_file_content(a:server_name, a:buf, l:new_content)
return [l:changes]
endfunction

function! s:ensure_changed(buf, server_name, cb) abort
let l:server = s:servers[a:server_name]
let l:path = lsp#utils#get_buffer_uri(a:buf)
Expand All @@ -418,15 +477,11 @@ function! s:ensure_changed(buf, server_name, cb) abort
let l:buffer_info['changed_tick'] = l:changed_tick
let l:buffer_info['version'] = l:buffer_info['version'] + 1

" todo: support range in contentChanges

call s:send_notification(a:server_name, {
\ 'method': 'textDocument/didChange',
\ 'params': {
\ 'textDocument': s:get_text_document_identifier(a:buf, l:buffer_info),
\ 'contentChanges': [
\ { 'text': s:get_text_document_text(a:buf) },
\ ],
\ 'contentChanges': s:text_changes(a:server_name, a:buf),
\ }
\ })

Expand Down Expand Up @@ -599,17 +654,8 @@ function! lsp#get_whitelisted_servers(...) abort
return l:active_servers
endfunction

function! s:requires_eol_at_eof(buf) abort
let l:file_ends_with_eol = getbufvar(a:buf, '&eol')
let l:vim_will_save_with_eol = !getbufvar(a:buf, '&binary') &&
\ getbufvar(a:buf, '&fixeol')
return l:file_ends_with_eol || l:vim_will_save_with_eol
endfunction

function! s:get_text_document_text(buf) abort
let l:buf_fileformat = getbufvar(a:buf, '&fileformat')
let l:eol = {'unix': "\n", 'dos': "\r\n", 'mac': "\r"}[l:buf_fileformat]
return join(getbufline(a:buf, 1, '$'), l:eol).(s:requires_eol_at_eof(a:buf) ? l:eol : '')
return join(getbufline(a:buf, 1, '$'), "\n")
endfunction

function! s:get_text_document(buf, buffer_info) abort
Expand Down
20 changes: 20 additions & 0 deletions autoload/lsp/capabilities.vim
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,23 @@ function! lsp#capabilities#get_text_document_save_registration_options(server_na
endif
return [0, { 'includeText': 0 }]
endfunction

" supports_did_change (boolean)
function! lsp#capabilities#get_text_document_change_sync_kind(server_name) abort
let l:capabilities = lsp#get_server_capabilities(a:server_name)
if !empty(l:capabilities) && has_key(l:capabilities, 'textDocumentSync')
if type(l:capabilities['textDocumentSync']) == type({})
if has_key(l:capabilities['textDocumentSync'], 'change') && type(l:capabilities['textDocumentSync']) == type(1)
let l:val = l:capabilities['textDocumentSync']['change']
return l:val >= 0 && l:val <= 2 ? l:val : 1
else
return 1
endif
elseif type(l:capabilities['textDocumentSync']) == type(1)
return l:capabilities['textDocumentSync']
else
return 1
endif
endif
return 1
endfunction
115 changes: 115 additions & 0 deletions autoload/lsp/utils/diff.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
" This is copied from https://github.com/natebosch/vim-lsc/blob/master/autoload/lsc/diff.vim
"
" Computes a simplistic diff between [old] and [new].
"
" Returns a dict with keys `range`, `rangeLength`, and `text` matching the LSP
" definition of `TextDocumentContentChangeEvent`.
"
" Finds a single change between the common prefix, and common postfix.
function! lsp#utils#diff#compute(old, new) abort
let [l:start_line, l:start_char] = s:FirstDifference(a:old, a:new)
let [l:end_line, l:end_char] =
\ s:LastDifference(a:old[l:start_line :], a:new[l:start_line :], l:start_char)

let l:text = s:ExtractText(a:new, l:start_line, l:start_char, l:end_line, l:end_char)
let l:length = s:Length(a:old, l:start_line, l:start_char, l:end_line, l:end_char)

let l:adj_end_line = len(a:old) + l:end_line
let l:adj_end_char = l:end_line == 0 ? 0 : strlen(a:old[l:end_line]) + l:end_char + 1

let l:result = { 'range': {'start': {'line': l:start_line, 'character': l:start_char},
\ 'end': {'line': l:adj_end_line, 'character': l:adj_end_char}},
\ 'text': l:text,
\ 'rangeLength': l:length,
\}

return l:result
endfunction

" Finds the line and character of the first different character between two
" list of Strings.
function! s:FirstDifference(old, new) abort
let l:line_count = min([len(a:old), len(a:new)])
let l:i = 0
while l:i < l:line_count
if a:old[l:i] !=# a:new[l:i] | break | endif
let l:i += 1
endwhile
if i >= l:line_count
return [l:line_count - 1, strlen(a:old[l:line_count - 1])]
endif
let l:old_line = a:old[l:i]
let l:new_line = a:new[l:i]
let l:length = min([strlen(l:old_line), strlen(l:new_line)])
let l:j = 0
while l:j < l:length
if l:old_line[l:j : l:j] !=# l:new_line[l:j : l:j] | break | endif
let l:j += 1
endwhile
return [l:i, l:j]
endfunction

function! s:LastDifference(old, new, start_char) abort
let l:line_count = min([len(a:old), len(a:new)])
if l:line_count == 0 | return [0, 0] | endif
let l:i = -1
while l:i >= -1 * l:line_count
if a:old[l:i] !=# a:new[l:i] | break | endif
let l:i -= 1
endwhile
if l:i <= -1 * l:line_count
let l:i = -1 * l:line_count
let l:old_line = a:old[l:i][a:start_char :]
let l:new_line = a:new[l:i][a:start_char :]
else
let l:old_line = a:old[l:i]
let l:new_line = a:new[l:i]
endif
let l:length = min([strlen(l:old_line), strlen(l:new_line)])
let l:j = -1
while l:j >= -1 * l:length
if l:old_line[l:j : l:j] !=# l:new_line[l:j : l:j] | break | endif
let l:j -= 1
endwhile
return [l:i, l:j]
endfunction

function! s:ExtractText(lines, start_line, start_char, end_line, end_char) abort
if a:start_line == len(a:lines) + a:end_line
if a:end_line == 0 | return '' | endif
let l:result = a:lines[a:start_line][a:start_char : a:end_char]
" json_encode treats empty string computed this was as 'null'
if strlen(l:result) == 0 | let l:result = '' | endif
return l:result
endif
let l:result = a:lines[a:start_line][a:start_char :]."\n"
let l:adj_end_line = len(a:lines) + a:end_line
for l:line in a:lines[a:start_line + 1:a:end_line - 1]
let l:result .= l:line."\n"
endfor
if a:end_line != 0
let l:result .= a:lines[a:end_line][:a:end_char]
endif
return l:result
endfunction

function! s:Length(lines, start_line, start_char, end_line, end_char)
\ abort
let l:adj_end_line = len(a:lines) + a:end_line
if l:adj_end_line >= len(a:lines)
let l:adj_end_char = a:end_char - 1
else
let l:adj_end_char = strlen(a:lines[l:adj_end_line]) + a:end_char
endif
if a:start_line == l:adj_end_line
return l:adj_end_char - a:start_char + 1
endif
let l:result = strlen(a:lines[a:start_line]) - a:start_char + 1
let l:line = a:start_line + 1
while l:line < l:adj_end_line
let l:result += strlen(a:lines[l:line]) + 1
let l:line += 1
endwhile
let l:result += l:adj_end_char + 1
return l:result
endfunction