mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 05:05:00 -07:00
fix(lsp): handle absence of a trailing newline #25194
Fixes #24339 rust-analyzer sends "Invalid offset" error in such cases. Some other servers handle it specially. LSP spec mentions that "A range is comparable to a selection in an editor". Most editors don't handle trailing newlines the same way Neovim/Vim does, it's clearly visible if it's present or not. With that in mind it's understandable why sending end position as simply the start of the line after the last one is considered invalid in such cases.
This commit is contained in:
parent
8bd6f7c20b
commit
345bd91db2
@ -2230,6 +2230,35 @@ function M.lookup_section(settings, section)
|
|||||||
return settings
|
return settings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Converts line range (0-based, end-inclusive) to lsp range,
|
||||||
|
--- handles absence of a trailing newline
|
||||||
|
---
|
||||||
|
---@param bufnr integer
|
||||||
|
---@param start_line integer
|
||||||
|
---@param end_line integer
|
||||||
|
---@param offset_encoding lsp.PositionEncodingKind
|
||||||
|
---@return lsp.Range
|
||||||
|
local function make_line_range_params(bufnr, start_line, end_line, offset_encoding)
|
||||||
|
local last_line = api.nvim_buf_line_count(bufnr) - 1
|
||||||
|
|
||||||
|
---@type lsp.Position
|
||||||
|
local end_pos
|
||||||
|
|
||||||
|
if end_line == last_line and not vim.api.nvim_get_option_value('endofline', { buf = bufnr }) then
|
||||||
|
end_pos = {
|
||||||
|
line = end_line,
|
||||||
|
character = M.character_offset(bufnr, end_line, #get_line(bufnr, end_line), offset_encoding),
|
||||||
|
}
|
||||||
|
else
|
||||||
|
end_pos = { line = end_line + 1, character = 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
start = { line = start_line, character = 0 },
|
||||||
|
['end'] = end_pos,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
--- Request updated LSP information for a buffer.
|
--- Request updated LSP information for a buffer.
|
||||||
---
|
---
|
||||||
@ -2253,6 +2282,8 @@ function M._refresh(method, opts)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local textDocument = M.make_text_document_params(bufnr)
|
||||||
|
|
||||||
local only_visible = opts.only_visible or false
|
local only_visible = opts.only_visible or false
|
||||||
|
|
||||||
if only_visible then
|
if only_visible then
|
||||||
@ -2260,28 +2291,25 @@ function M._refresh(method, opts)
|
|||||||
if api.nvim_win_get_buf(window) == bufnr then
|
if api.nvim_win_get_buf(window) == bufnr then
|
||||||
local first = vim.fn.line('w0', window)
|
local first = vim.fn.line('w0', window)
|
||||||
local last = vim.fn.line('w$', window)
|
local last = vim.fn.line('w$', window)
|
||||||
local params = {
|
|
||||||
textDocument = M.make_text_document_params(bufnr),
|
|
||||||
range = {
|
|
||||||
start = { line = first - 1, character = 0 },
|
|
||||||
['end'] = { line = last, character = 0 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, client in ipairs(clients) do
|
for _, client in ipairs(clients) do
|
||||||
client.request(method, params, nil, bufnr)
|
client.request(method, {
|
||||||
|
textDocument = textDocument,
|
||||||
|
range = make_line_range_params(bufnr, first - 1, last - 1, client.offset_encoding),
|
||||||
|
}, nil, bufnr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local params = {
|
|
||||||
textDocument = M.make_text_document_params(bufnr),
|
|
||||||
range = {
|
|
||||||
start = { line = 0, character = 0 },
|
|
||||||
['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, client in ipairs(clients) do
|
for _, client in ipairs(clients) do
|
||||||
client.request(method, params, nil, bufnr)
|
client.request(method, {
|
||||||
|
textDocument = textDocument,
|
||||||
|
range = make_line_range_params(
|
||||||
|
bufnr,
|
||||||
|
0,
|
||||||
|
api.nvim_buf_line_count(bufnr) - 1,
|
||||||
|
client.offset_encoding
|
||||||
|
),
|
||||||
|
}, nil, bufnr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -949,6 +949,28 @@ function tests.set_defaults_all_capabilities()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function tests.inlay_hint()
|
||||||
|
skeleton {
|
||||||
|
on_init = function(params)
|
||||||
|
local expected_capabilities = protocol.make_client_capabilities()
|
||||||
|
assert_eq(params.capabilities, expected_capabilities)
|
||||||
|
return {
|
||||||
|
capabilities = {
|
||||||
|
inlayHintProvider = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end;
|
||||||
|
body = function()
|
||||||
|
notify('start')
|
||||||
|
expect_request('textDocument/inlayHint', function()
|
||||||
|
return nil, {}
|
||||||
|
end)
|
||||||
|
expect_notification("finish")
|
||||||
|
notify('finish')
|
||||||
|
end;
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
-- Tests will be indexed by test_name
|
-- Tests will be indexed by test_name
|
||||||
local test_name = arg[1]
|
local test_name = arg[1]
|
||||||
local timeout = arg[2]
|
local timeout = arg[2]
|
||||||
|
@ -1244,6 +1244,67 @@ describe('LSP', function()
|
|||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('should send correct range for inlay hints with noeol', function()
|
||||||
|
local expected_handlers = {
|
||||||
|
{NIL, {}, {method="shutdown", client_id=1}};
|
||||||
|
{NIL, {}, {method="finish", client_id=1}};
|
||||||
|
{NIL, {}, {
|
||||||
|
method="textDocument/inlayHint",
|
||||||
|
params = {
|
||||||
|
textDocument = {
|
||||||
|
uri = 'file://',
|
||||||
|
},
|
||||||
|
range = {
|
||||||
|
start = { line = 0, character = 0 },
|
||||||
|
['end'] = { line = 1, character = 3 },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bufnr=2,
|
||||||
|
client_id=1,
|
||||||
|
}};
|
||||||
|
{NIL, {}, {method="start", client_id=1}};
|
||||||
|
}
|
||||||
|
local client
|
||||||
|
test_rpc_server {
|
||||||
|
test_name = "inlay_hint";
|
||||||
|
on_setup = function()
|
||||||
|
exec_lua [[
|
||||||
|
BUFFER = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||||||
|
"testing";
|
||||||
|
"123";
|
||||||
|
})
|
||||||
|
vim.bo[BUFFER].eol = false
|
||||||
|
]]
|
||||||
|
end;
|
||||||
|
on_init = function(_client)
|
||||||
|
client = _client
|
||||||
|
eq(true, client.supports_method('textDocument/inlayHint'))
|
||||||
|
exec_lua [[
|
||||||
|
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||||||
|
]]
|
||||||
|
end;
|
||||||
|
on_exit = function(code, signal)
|
||||||
|
eq(0, code, "exit code")
|
||||||
|
eq(0, signal, "exit signal")
|
||||||
|
end;
|
||||||
|
on_handler = function(err, result, ctx)
|
||||||
|
if ctx.method == 'start' then
|
||||||
|
exec_lua [[
|
||||||
|
vim.lsp.inlay_hint(BUFFER, true)
|
||||||
|
]]
|
||||||
|
end
|
||||||
|
if ctx.method == 'textDocument/inlayHint' then
|
||||||
|
client.notify('finish')
|
||||||
|
end
|
||||||
|
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||||
|
if ctx.method == 'finish' then
|
||||||
|
client.stop()
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
it('should check the body and didChange incremental', function()
|
it('should check the body and didChange incremental', function()
|
||||||
local expected_handlers = {
|
local expected_handlers = {
|
||||||
{NIL, {}, {method="shutdown", client_id=1}};
|
{NIL, {}, {method="shutdown", client_id=1}};
|
||||||
|
Loading…
Reference in New Issue
Block a user