diff options
author | TJ DeVries <devries.timothyj@gmail.com> | 2021-01-11 11:39:11 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-11 11:39:11 -0500 |
commit | e0a4399adc4472b9b02a26c7f93b056c4725a6b9 (patch) | |
tree | 81980fd4f008fd4f404a913bd3d40d86c327b1de | |
parent | 0c0c3ee330c9e3aa7d6f054073b999c5f8001472 (diff) | |
download | rneovim-e0a4399adc4472b9b02a26c7f93b056c4725a6b9.tar.gz rneovim-e0a4399adc4472b9b02a26c7f93b056c4725a6b9.tar.bz2 rneovim-e0a4399adc4472b9b02a26c7f93b056c4725a6b9.zip |
fix(lsp): Allow subsequent text document edits to pass (#13534)
* fix: Allow subsequent text document edits to pass
* fixup: cleaner code
* add tests
-rw-r--r-- | runtime/doc/lsp.txt | 10 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 24 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 105 |
3 files changed, 127 insertions, 12 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 6ca7b52fff..88e8275b44 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1400,9 +1400,15 @@ show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id}) Lua module: vim.lsp.util *lsp-util* *vim.lsp.util.apply_text_document_edit()* -apply_text_document_edit({text_document_edit}) +apply_text_document_edit({text_document_edit}, {index}) + Applies a `TextDocumentEdit` , which is a list of changes to a + single document. + Parameters: ~ - {text_document_edit} (table) a `TextDocumentEdit` object + {text_document_edit} table: a `TextDocumentEdit` object + {index} number: Optional index of the edit, + if from a list of edits (or nil, if + not from a list) See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ecff95f61e..e33e0109b6 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -254,19 +254,27 @@ function M.extract_completion_items(result) end --- Applies a `TextDocumentEdit`, which is a list of changes to a single --- document. +--- document. --- ---@param text_document_edit (table) a `TextDocumentEdit` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit -function M.apply_text_document_edit(text_document_edit) +---@param text_document_edit table: a `TextDocumentEdit` object +---@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list) +---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit +function M.apply_text_document_edit(text_document_edit, index) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) + -- For lists of text document edits, + -- do not check the version after the first edit. + local should_check_version = true + if index and index > 1 then + should_check_version = false + end + -- `VersionedTextDocumentIdentifier`s version may be null -- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier - if text_document.version + if should_check_version and (text_document.version and M.buf_versions[bufnr] - and M.buf_versions[bufnr] > text_document.version then + and M.buf_versions[bufnr] > text_document.version) then print("Buffer ", text_document.uri, " newer than edits.") return end @@ -459,12 +467,12 @@ end -- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then - for _, change in ipairs(workspace_edit.documentChanges) do + for idx, change in ipairs(workspace_edit.documentChanges) do if change.kind then -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile error(string.format("Unsupported change: %q", vim.inspect(change))) else - M.apply_text_document_edit(change) + M.apply_text_document_edit(change, idx) end end return diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index ec06cb0639..e8caa05260 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1045,7 +1045,7 @@ describe('LSP', function() return { edits = { make_edit(0, 0, 0, 3, "First ↥ 🤦 🦄") - }, + }, textDocument = { uri = "file://fake/uri"; version = editVersion @@ -1086,7 +1086,7 @@ describe('LSP', function() local args = {...} local versionedBuf = args[2] vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(...) + vim.lsp.util.apply_text_document_edit(args[1]) ]], edit, versionedBuf) end @@ -1109,6 +1109,7 @@ describe('LSP', function() }, buf_lines(target_bufnr)) end) end) + describe('workspace_apply_edit', function() it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() local expected = { @@ -1124,6 +1125,106 @@ describe('LSP', function() ]]) end) end) + + describe('apply_workspace_edit', function() + local replace_line_edit = function(row, new_line, editVersion) + return { + edits = { + -- NOTE: This is a hack if you have a line longer than 1000 it won't replace it + make_edit(row, 0, row, 1000, new_line) + }, + textDocument = { + uri = "file://fake/uri"; + version = editVersion + } + } + end + + -- Some servers send all the edits separately, but with the same version. + -- We should not stop applying the edits + local make_workspace_edit = function(changes) + return { + documentChanges = changes + } + end + + local target_bufnr, changedtick = nil, nil + + before_each(function() + local ret = exec_lua [[ + local bufnr = vim.uri_to_bufnr("file://fake/uri") + local lines = { + "Original Line #1", + "Original Line #2" + } + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + + local update_changed_tick = function() + vim.lsp.util.buf_versions[bufnr] = vim.api.nvim_buf_get_var(bufnr, 'changedtick') + end + + update_changed_tick() + vim.api.nvim_buf_attach(bufnr, false, { + on_changedtick = function() + update_changed_tick() + end + }) + + return {bufnr, vim.api.nvim_buf_get_var(bufnr, 'changedtick')} + ]] + + target_bufnr = ret[1] + changedtick = ret[2] + end) + + it('apply_workspace_edit applies a single edit', function() + local new_lines = { + "First Line", + } + + local edits = {} + for row, line in ipairs(new_lines) do + table.insert(edits, replace_line_edit(row - 1, line, changedtick)) + end + + eq({ + "First Line", + "Original Line #2", + }, exec_lua([[ + local args = {...} + local workspace_edits = args[1] + local target_bufnr = args[2] + + vim.lsp.util.apply_workspace_edit(workspace_edits) + + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + ]], make_workspace_edit(edits), target_bufnr)) + end) + + it('apply_workspace_edit applies multiple edits', function() + local new_lines = { + "First Line", + "Second Line", + } + + local edits = {} + for row, line in ipairs(new_lines) do + table.insert(edits, replace_line_edit(row - 1, line, changedtick)) + end + + eq(new_lines, exec_lua([[ + local args = {...} + local workspace_edits = args[1] + local target_bufnr = args[2] + + vim.lsp.util.apply_workspace_edit(workspace_edits) + + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + ]], make_workspace_edit(edits), target_bufnr)) + end) + end) + describe('completion_list_to_complete_items', function() -- Completion option precedence: -- textEdit.newText > insertText > label |