fix(lsp): Allow subsequent text document edits to pass (#13534)

* fix: Allow subsequent text document edits to pass

* fixup: cleaner code

* add tests
This commit is contained in:
TJ DeVries
2021-01-11 11:39:11 -05:00
committed by GitHub
parent 0c0c3ee330
commit e0a4399adc
3 changed files with 127 additions and 12 deletions

View File

@@ -1400,9 +1400,15 @@ show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id})
Lua module: vim.lsp.util *lsp-util* Lua module: vim.lsp.util *lsp-util*
*vim.lsp.util.apply_text_document_edit()* *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: ~ 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: ~ See also: ~
https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit

View File

@@ -254,19 +254,27 @@ function M.extract_completion_items(result)
end end
--- Applies a `TextDocumentEdit`, which is a list of changes to a single --- Applies a `TextDocumentEdit`, which is a list of changes to a single
-- document. --- document.
--- ---
--@param text_document_edit (table) a `TextDocumentEdit` object ---@param text_document_edit table: a `TextDocumentEdit` object
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit ---@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list)
function M.apply_text_document_edit(text_document_edit) ---@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 text_document = text_document_edit.textDocument
local bufnr = vim.uri_to_bufnr(text_document.uri) 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 -- `VersionedTextDocumentIdentifier`s version may be null
-- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier -- 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]
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.") print("Buffer ", text_document.uri, " newer than edits.")
return return
end end
@@ -459,12 +467,12 @@ end
-- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
function M.apply_workspace_edit(workspace_edit) function M.apply_workspace_edit(workspace_edit)
if workspace_edit.documentChanges then 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 if change.kind then
-- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile
error(string.format("Unsupported change: %q", vim.inspect(change))) error(string.format("Unsupported change: %q", vim.inspect(change)))
else else
M.apply_text_document_edit(change) M.apply_text_document_edit(change, idx)
end end
end end
return return

View File

@@ -1086,7 +1086,7 @@ describe('LSP', function()
local args = {...} local args = {...}
local versionedBuf = args[2] local versionedBuf = args[2]
vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion 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) ]], edit, versionedBuf)
end end
@@ -1109,6 +1109,7 @@ describe('LSP', function()
}, buf_lines(target_bufnr)) }, buf_lines(target_bufnr))
end) end)
end) end)
describe('workspace_apply_edit', function() describe('workspace_apply_edit', function()
it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function()
local expected = { local expected = {
@@ -1124,6 +1125,106 @@ describe('LSP', function()
]]) ]])
end) end)
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() describe('completion_list_to_complete_items', function()
-- Completion option precedence: -- Completion option precedence:
-- textEdit.newText > insertText > label -- textEdit.newText > insertText > label