mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 19:38:20 +00:00
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:
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user