mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 11:28:22 +00:00
feat(lsp): support annotated text edits (#34508)
This commit is contained in:

committed by
GitHub

parent
a5c55d200b
commit
835f11595f
@@ -2334,7 +2334,8 @@ 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}, {index}, {position_encoding})
|
apply_text_document_edit({text_document_edit}, {index}, {position_encoding},
|
||||||
|
{change_annotations})
|
||||||
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.
|
||||||
|
|
||||||
@@ -2343,18 +2344,21 @@ apply_text_document_edit({text_document_edit}, {index}, {position_encoding})
|
|||||||
• {index} (`integer?`) Optional index of the edit, if from
|
• {index} (`integer?`) Optional index of the edit, if from
|
||||||
a list of edits (or nil, if not from a list)
|
a list of edits (or nil, if not from a list)
|
||||||
• {position_encoding} (`'utf-8'|'utf-16'|'utf-32'?`)
|
• {position_encoding} (`'utf-8'|'utf-16'|'utf-32'?`)
|
||||||
|
• {change_annotations} (`table<string, lsp.ChangeAnnotation>?`)
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
*vim.lsp.util.apply_text_edits()*
|
*vim.lsp.util.apply_text_edits()*
|
||||||
apply_text_edits({text_edits}, {bufnr}, {position_encoding})
|
apply_text_edits({text_edits}, {bufnr}, {position_encoding},
|
||||||
|
{change_annotations})
|
||||||
Applies a list of text edits to a buffer.
|
Applies a list of text edits to a buffer.
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {text_edits} (`lsp.TextEdit[]`)
|
• {text_edits} (`(lsp.TextEdit|lsp.AnnotatedTextEdit)[]`)
|
||||||
• {bufnr} (`integer`) Buffer id
|
• {bufnr} (`integer`) Buffer id
|
||||||
• {position_encoding} (`'utf-8'|'utf-16'|'utf-32'`)
|
• {position_encoding} (`'utf-8'|'utf-16'|'utf-32'`)
|
||||||
|
• {change_annotations} (`table<string, lsp.ChangeAnnotation>?`)
|
||||||
|
|
||||||
See also: ~
|
See also: ~
|
||||||
• https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
|
• https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
|
||||||
|
@@ -182,6 +182,7 @@ LSP
|
|||||||
• Support for the `disabled` field on code actions.
|
• Support for the `disabled` field on code actions.
|
||||||
• The function form of `cmd` in a vim.lsp.Config or vim.lsp.ClientConfig
|
• The function form of `cmd` in a vim.lsp.Config or vim.lsp.ClientConfig
|
||||||
receives the resolved config as the second arg: `cmd(dispatchers, config)`.
|
receives the resolved config as the second arg: `cmd(dispatchers, config)`.
|
||||||
|
• Support for annotated text edits.
|
||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
|
@@ -431,6 +431,7 @@ function protocol.make_client_capabilities()
|
|||||||
properties = { 'edit', 'command' },
|
properties = { 'edit', 'command' },
|
||||||
},
|
},
|
||||||
disabledSupport = true,
|
disabledSupport = true,
|
||||||
|
honorsChangeAnnotations = true,
|
||||||
},
|
},
|
||||||
codeLens = {
|
codeLens = {
|
||||||
dynamicRegistration = false,
|
dynamicRegistration = false,
|
||||||
@@ -529,6 +530,7 @@ function protocol.make_client_capabilities()
|
|||||||
rename = {
|
rename = {
|
||||||
dynamicRegistration = true,
|
dynamicRegistration = true,
|
||||||
prepareSupport = true,
|
prepareSupport = true,
|
||||||
|
honorsChangeAnnotations = true,
|
||||||
},
|
},
|
||||||
publishDiagnostics = {
|
publishDiagnostics = {
|
||||||
tagSupport = {
|
tagSupport = {
|
||||||
@@ -562,6 +564,7 @@ function protocol.make_client_capabilities()
|
|||||||
workspaceEdit = {
|
workspaceEdit = {
|
||||||
resourceOperations = { 'rename', 'create', 'delete' },
|
resourceOperations = { 'rename', 'create', 'delete' },
|
||||||
normalizesLineEndings = true,
|
normalizesLineEndings = true,
|
||||||
|
changeAnnotationSupport = { groupsOnLabel = true },
|
||||||
},
|
},
|
||||||
semanticTokens = {
|
semanticTokens = {
|
||||||
refreshSupport = true,
|
refreshSupport = true,
|
||||||
|
@@ -287,14 +287,16 @@ local function get_line_byte_from_position(bufnr, position, position_encoding)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Applies a list of text edits to a buffer.
|
--- Applies a list of text edits to a buffer.
|
||||||
---@param text_edits lsp.TextEdit[]
|
---@param text_edits (lsp.TextEdit|lsp.AnnotatedTextEdit)[]
|
||||||
---@param bufnr integer Buffer id
|
---@param bufnr integer Buffer id
|
||||||
---@param position_encoding 'utf-8'|'utf-16'|'utf-32'
|
---@param position_encoding 'utf-8'|'utf-16'|'utf-32'
|
||||||
|
---@param change_annotations? table<string, lsp.ChangeAnnotation>
|
||||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
|
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
|
||||||
function M.apply_text_edits(text_edits, bufnr, position_encoding)
|
function M.apply_text_edits(text_edits, bufnr, position_encoding, change_annotations)
|
||||||
validate('text_edits', text_edits, 'table', false)
|
validate('text_edits', text_edits, 'table', false)
|
||||||
validate('bufnr', bufnr, 'number', false)
|
validate('bufnr', bufnr, 'number', false)
|
||||||
validate('position_encoding', position_encoding, 'string', false)
|
validate('position_encoding', position_encoding, 'string', false)
|
||||||
|
validate('change_annotations', change_annotations, 'table', true)
|
||||||
|
|
||||||
if not next(text_edits) then
|
if not next(text_edits) then
|
||||||
return
|
return
|
||||||
@@ -307,95 +309,156 @@ function M.apply_text_edits(text_edits, bufnr, position_encoding)
|
|||||||
end
|
end
|
||||||
vim.bo[bufnr].buflisted = true
|
vim.bo[bufnr].buflisted = true
|
||||||
|
|
||||||
-- Fix reversed range and indexing each text_edits
|
|
||||||
for index, text_edit in ipairs(text_edits) do
|
|
||||||
--- @cast text_edit lsp.TextEdit|{_index: integer}
|
|
||||||
text_edit._index = index
|
|
||||||
|
|
||||||
if
|
|
||||||
text_edit.range.start.line > text_edit.range['end'].line
|
|
||||||
or text_edit.range.start.line == text_edit.range['end'].line
|
|
||||||
and text_edit.range.start.character > text_edit.range['end'].character
|
|
||||||
then
|
|
||||||
local start = text_edit.range.start
|
|
||||||
text_edit.range.start = text_edit.range['end']
|
|
||||||
text_edit.range['end'] = start
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @cast text_edits (lsp.TextEdit|{_index: integer})[]
|
|
||||||
|
|
||||||
-- Sort text_edits
|
|
||||||
---@param a lsp.TextEdit | { _index: integer }
|
|
||||||
---@param b lsp.TextEdit | { _index: integer }
|
|
||||||
---@return boolean
|
|
||||||
table.sort(text_edits, function(a, b)
|
|
||||||
if a.range.start.line ~= b.range.start.line then
|
|
||||||
return a.range.start.line > b.range.start.line
|
|
||||||
end
|
|
||||||
if a.range.start.character ~= b.range.start.character then
|
|
||||||
return a.range.start.character > b.range.start.character
|
|
||||||
end
|
|
||||||
return a._index > b._index
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- save and restore local marks since they get deleted by nvim_buf_set_lines
|
|
||||||
local marks = {} --- @type table<string,[integer,integer]>
|
local marks = {} --- @type table<string,[integer,integer]>
|
||||||
for _, m in pairs(vim.fn.getmarklist(bufnr)) do
|
local has_eol_text_edit = false
|
||||||
if m.mark:match("^'[a-z]$") then
|
|
||||||
marks[m.mark:sub(2, 2)] = { m.pos[2], m.pos[3] - 1 } -- api-indexed
|
local function apply_text_edits()
|
||||||
|
-- Fix reversed range and indexing each text_edits
|
||||||
|
for index, text_edit in ipairs(text_edits) do
|
||||||
|
--- @cast text_edit lsp.TextEdit|{_index: integer}
|
||||||
|
text_edit._index = index
|
||||||
|
|
||||||
|
if
|
||||||
|
text_edit.range.start.line > text_edit.range['end'].line
|
||||||
|
or text_edit.range.start.line == text_edit.range['end'].line
|
||||||
|
and text_edit.range.start.character > text_edit.range['end'].character
|
||||||
|
then
|
||||||
|
local start = text_edit.range.start
|
||||||
|
text_edit.range.start = text_edit.range['end']
|
||||||
|
text_edit.range['end'] = start
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @cast text_edits (lsp.TextEdit|lsp.AnnotatedTextEdit|{_index: integer})[]
|
||||||
|
|
||||||
|
-- Sort text_edits
|
||||||
|
---@param a (lsp.TextEdit|lsp.AnnotatedTextEdit|{_index: integer})
|
||||||
|
---@param b (lsp.TextEdit|lsp.AnnotatedTextEdit|{_index: integer})
|
||||||
|
---@return boolean
|
||||||
|
table.sort(text_edits, function(a, b)
|
||||||
|
if a.range.start.line ~= b.range.start.line then
|
||||||
|
return a.range.start.line > b.range.start.line
|
||||||
|
end
|
||||||
|
if a.range.start.character ~= b.range.start.character then
|
||||||
|
return a.range.start.character > b.range.start.character
|
||||||
|
end
|
||||||
|
return a._index > b._index
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- save and restore local marks since they get deleted by nvim_buf_set_lines
|
||||||
|
for _, m in pairs(vim.fn.getmarklist(bufnr)) do
|
||||||
|
if m.mark:match("^'[a-z]$") then
|
||||||
|
marks[m.mark:sub(2, 2)] = { m.pos[2], m.pos[3] - 1 } -- api-indexed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, text_edit in ipairs(text_edits) do
|
||||||
|
-- Normalize line ending
|
||||||
|
text_edit.newText, _ = string.gsub(text_edit.newText, '\r\n?', '\n')
|
||||||
|
|
||||||
|
-- Convert from LSP style ranges to Neovim style ranges.
|
||||||
|
local start_row = text_edit.range.start.line
|
||||||
|
local start_col = get_line_byte_from_position(bufnr, text_edit.range.start, position_encoding)
|
||||||
|
local end_row = text_edit.range['end'].line
|
||||||
|
local end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], position_encoding)
|
||||||
|
local text = vim.split(text_edit.newText, '\n', { plain = true })
|
||||||
|
|
||||||
|
local max = api.nvim_buf_line_count(bufnr)
|
||||||
|
-- If the whole edit is after the lines in the buffer we can simply add the new text to the end
|
||||||
|
-- of the buffer.
|
||||||
|
if max <= start_row then
|
||||||
|
api.nvim_buf_set_lines(bufnr, max, max, false, text)
|
||||||
|
else
|
||||||
|
local last_line_len = #(get_line(bufnr, math.min(end_row, max - 1)) or '')
|
||||||
|
-- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't
|
||||||
|
-- accept it so we should fix it here.
|
||||||
|
if max <= end_row then
|
||||||
|
end_row = max - 1
|
||||||
|
end_col = last_line_len
|
||||||
|
has_eol_text_edit = true
|
||||||
|
else
|
||||||
|
-- If the replacement is over the end of a line (i.e. end_col is equal to the line length and the
|
||||||
|
-- replacement text ends with a newline We can likely assume that the replacement is assumed
|
||||||
|
-- to be meant to replace the newline with another newline and we need to make sure this
|
||||||
|
-- doesn't add an extra empty line. E.g. when the last line to be replaced contains a '\r'
|
||||||
|
-- in the file some servers (clangd on windows) will include that character in the line
|
||||||
|
-- while nvim_buf_set_text doesn't count it as part of the line.
|
||||||
|
if
|
||||||
|
end_col >= last_line_len
|
||||||
|
and text_edit.range['end'].character > end_col
|
||||||
|
and #text_edit.newText > 0
|
||||||
|
and string.sub(text_edit.newText, -1) == '\n'
|
||||||
|
then
|
||||||
|
table.remove(text, #text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Make sure we don't go out of bounds for end_col
|
||||||
|
end_col = math.min(last_line_len, end_col)
|
||||||
|
|
||||||
|
api.nvim_buf_set_text(bufnr, start_row, start_col, end_row, end_col, text)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply text edits.
|
--- Track how many times each change annotation is applied to build up the final description.
|
||||||
local has_eol_text_edit = false
|
---@type table<string, integer>
|
||||||
|
local change_count = {}
|
||||||
|
|
||||||
|
-- If there are any annotated text edits, we need to confirm them before applying the edits.
|
||||||
|
local confirmations = {} ---@type table<string, integer>
|
||||||
for _, text_edit in ipairs(text_edits) do
|
for _, text_edit in ipairs(text_edits) do
|
||||||
-- Normalize line ending
|
if text_edit.annotationId then
|
||||||
text_edit.newText, _ = string.gsub(text_edit.newText, '\r\n?', '\n')
|
assert(
|
||||||
|
change_annotations ~= nil,
|
||||||
|
'change_annotations must be provided for annotated text edits'
|
||||||
|
)
|
||||||
|
|
||||||
-- Convert from LSP style ranges to Neovim style ranges.
|
local annotation = assert(
|
||||||
local start_row = text_edit.range.start.line
|
change_annotations[text_edit.annotationId],
|
||||||
local start_col = get_line_byte_from_position(bufnr, text_edit.range.start, position_encoding)
|
string.format('No change annotation found for ID: %s', text_edit.annotationId)
|
||||||
local end_row = text_edit.range['end'].line
|
)
|
||||||
local end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], position_encoding)
|
|
||||||
local text = vim.split(text_edit.newText, '\n', { plain = true })
|
|
||||||
|
|
||||||
local max = api.nvim_buf_line_count(bufnr)
|
if annotation.needsConfirmation then
|
||||||
-- If the whole edit is after the lines in the buffer we can simply add the new text to the end
|
confirmations[text_edit.annotationId] = (confirmations[text_edit.annotationId] or 0) + 1
|
||||||
-- of the buffer.
|
|
||||||
if max <= start_row then
|
|
||||||
api.nvim_buf_set_lines(bufnr, max, max, false, text)
|
|
||||||
else
|
|
||||||
local last_line_len = #(get_line(bufnr, math.min(end_row, max - 1)) or '')
|
|
||||||
-- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't
|
|
||||||
-- accept it so we should fix it here.
|
|
||||||
if max <= end_row then
|
|
||||||
end_row = max - 1
|
|
||||||
end_col = last_line_len
|
|
||||||
has_eol_text_edit = true
|
|
||||||
else
|
|
||||||
-- If the replacement is over the end of a line (i.e. end_col is equal to the line length and the
|
|
||||||
-- replacement text ends with a newline We can likely assume that the replacement is assumed
|
|
||||||
-- to be meant to replace the newline with another newline and we need to make sure this
|
|
||||||
-- doesn't add an extra empty line. E.g. when the last line to be replaced contains a '\r'
|
|
||||||
-- in the file some servers (clangd on windows) will include that character in the line
|
|
||||||
-- while nvim_buf_set_text doesn't count it as part of the line.
|
|
||||||
if
|
|
||||||
end_col >= last_line_len
|
|
||||||
and text_edit.range['end'].character > end_col
|
|
||||||
and #text_edit.newText > 0
|
|
||||||
and string.sub(text_edit.newText, -1) == '\n'
|
|
||||||
then
|
|
||||||
table.remove(text, #text)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
-- Make sure we don't go out of bounds for end_col
|
|
||||||
end_col = math.min(last_line_len, end_col)
|
|
||||||
|
|
||||||
api.nvim_buf_set_text(bufnr, start_row, start_col, end_row, end_col, text)
|
change_count[text_edit.annotationId] = (change_count[text_edit.annotationId] or 0) + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if next(confirmations) then
|
||||||
|
local message = { 'Apply all changes?' }
|
||||||
|
for id, count in pairs(confirmations) do
|
||||||
|
local annotation = assert(change_annotations)[id]
|
||||||
|
message[#message + 1] = annotation.label
|
||||||
|
.. (annotation.description and (string.format(': %s', annotation.description)) or '')
|
||||||
|
.. (count > 1 and string.format(' (%d)', count) or '')
|
||||||
|
end
|
||||||
|
|
||||||
|
local response = vim.fn.confirm(table.concat(message, '\n'), '&Yes\n&No', 1, 'Question')
|
||||||
|
if response == 1 then
|
||||||
|
-- Proceed with applying text edits.
|
||||||
|
apply_text_edits()
|
||||||
|
else
|
||||||
|
-- Don't apply any text edits.
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- No confirmations needed, apply text edits directly.
|
||||||
|
apply_text_edits()
|
||||||
|
end
|
||||||
|
|
||||||
|
if change_annotations ~= nil and next(change_count) then
|
||||||
|
local change_message = { 'Applied changes:' }
|
||||||
|
for id, count in pairs(change_count) do
|
||||||
|
local annotation = change_annotations[id]
|
||||||
|
change_message[#change_message + 1] = annotation.label
|
||||||
|
.. (annotation.description and (': ' .. annotation.description) or '')
|
||||||
|
.. (count > 1 and string.format(' (%d)', count) or '')
|
||||||
|
end
|
||||||
|
vim.notify(table.concat(change_message, '\n'), vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
|
||||||
local max = api.nvim_buf_line_count(bufnr)
|
local max = api.nvim_buf_line_count(bufnr)
|
||||||
|
|
||||||
-- no need to restore marks that still exist
|
-- no need to restore marks that still exist
|
||||||
@@ -427,8 +490,14 @@ end
|
|||||||
---@param text_document_edit lsp.TextDocumentEdit
|
---@param text_document_edit lsp.TextDocumentEdit
|
||||||
---@param index? integer: Optional index of the edit, if from a list of edits (or nil, if not from a list)
|
---@param index? integer: Optional index of the edit, if from a list of edits (or nil, if not from a list)
|
||||||
---@param position_encoding? 'utf-8'|'utf-16'|'utf-32'
|
---@param position_encoding? 'utf-8'|'utf-16'|'utf-32'
|
||||||
|
---@param change_annotations? table<string, lsp.ChangeAnnotation>
|
||||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit
|
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit
|
||||||
function M.apply_text_document_edit(text_document_edit, index, position_encoding)
|
function M.apply_text_document_edit(
|
||||||
|
text_document_edit,
|
||||||
|
index,
|
||||||
|
position_encoding,
|
||||||
|
change_annotations
|
||||||
|
)
|
||||||
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)
|
||||||
if position_encoding == nil then
|
if position_encoding == nil then
|
||||||
@@ -455,7 +524,7 @@ function M.apply_text_document_edit(text_document_edit, index, position_encoding
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
M.apply_text_edits(text_document_edit.edits, bufnr, position_encoding)
|
M.apply_text_edits(text_document_edit.edits, bufnr, position_encoding, change_annotations)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function path_components(path)
|
local function path_components(path)
|
||||||
@@ -637,7 +706,7 @@ function M.apply_workspace_edit(workspace_edit, position_encoding)
|
|||||||
elseif change.kind then --- @diagnostic disable-line:undefined-field
|
elseif change.kind then --- @diagnostic disable-line:undefined-field
|
||||||
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, idx, position_encoding)
|
M.apply_text_document_edit(change, idx, position_encoding, workspace_edit.changeAnnotations)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
@@ -650,7 +719,7 @@ function M.apply_workspace_edit(workspace_edit, position_encoding)
|
|||||||
|
|
||||||
for uri, changes in pairs(all_changes) do
|
for uri, changes in pairs(all_changes) do
|
||||||
local bufnr = vim.uri_to_bufnr(uri)
|
local bufnr = vim.uri_to_bufnr(uri)
|
||||||
M.apply_text_edits(changes, bufnr, position_encoding)
|
M.apply_text_edits(changes, bufnr, position_encoding, workspace_edit.changeAnnotations)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -2069,13 +2069,16 @@ describe('LSP', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe('apply_text_edits', function()
|
describe('apply_text_edits', function()
|
||||||
|
local buffer_text = {
|
||||||
|
'First line of text',
|
||||||
|
'Second line of text',
|
||||||
|
'Third line of text',
|
||||||
|
'Fourth line of text',
|
||||||
|
'å å ɧ 汉语 ↥ 🤦 🦄',
|
||||||
|
}
|
||||||
|
|
||||||
before_each(function()
|
before_each(function()
|
||||||
insert(dedent([[
|
insert(dedent(table.concat(buffer_text, '\n')))
|
||||||
First line of text
|
|
||||||
Second line of text
|
|
||||||
Third line of text
|
|
||||||
Fourth line of text
|
|
||||||
å å ɧ 汉语 ↥ 🤦 🦄]]))
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('applies simple edits', function()
|
it('applies simple edits', function()
|
||||||
@@ -2226,6 +2229,34 @@ describe('LSP', function()
|
|||||||
eq({ 2, 1 }, api.nvim_buf_get_mark(1, 'a'))
|
eq({ 2, 1 }, api.nvim_buf_get_mark(1, 'a'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('applies edit based on confirmation response', function()
|
||||||
|
--- @type lsp.AnnotatedTextEdit
|
||||||
|
local edit = make_edit(0, 0, 5, 0, 'foo')
|
||||||
|
edit.annotationId = 'annotation-id'
|
||||||
|
|
||||||
|
local function test(response)
|
||||||
|
exec_lua(function()
|
||||||
|
---@diagnostic disable-next-line: duplicate-set-field
|
||||||
|
vim.fn.confirm = function()
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.lsp.util.apply_text_edits(
|
||||||
|
{ edit },
|
||||||
|
1,
|
||||||
|
'utf-16',
|
||||||
|
{ ['annotation-id'] = { label = 'Insert "foo"', needsConfirmation = true } }
|
||||||
|
)
|
||||||
|
end, { response })
|
||||||
|
end
|
||||||
|
|
||||||
|
test(2) -- 2 = No
|
||||||
|
eq(buffer_text, buf_lines(1))
|
||||||
|
|
||||||
|
test(1) -- 1 = Yes
|
||||||
|
eq({ 'foo' }, buf_lines(1))
|
||||||
|
end)
|
||||||
|
|
||||||
describe('cursor position', function()
|
describe('cursor position', function()
|
||||||
it("don't fix the cursor if the range contains the cursor", function()
|
it("don't fix the cursor if the range contains the cursor", function()
|
||||||
api.nvim_win_set_cursor(0, { 2, 6 })
|
api.nvim_win_set_cursor(0, { 2, 6 })
|
||||||
|
Reference in New Issue
Block a user