fix(lsp): handle locations exceeding line length #30253

Problem:
LSP spec [states](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position)
that "if the character value is greater than the line length it defaults
back to the line length", but `locations_to_items` fails in that case.

Solution:
Adjust locations_to_items to follow the spec.

closes #28281
This commit is contained in:
Tristan Knight
2024-09-05 08:23:11 +01:00
committed by GitHub
parent 220b8aa6fe
commit 882a450a29
2 changed files with 39 additions and 19 deletions

View File

@ -1795,8 +1795,18 @@ function M.locations_to_items(locations, offset_encoding)
local row = pos.line local row = pos.line
local end_row = end_pos.line local end_row = end_pos.line
local line = lines[row] or '' local line = lines[row] or ''
local col = M._str_byteindex_enc(line, pos.character, offset_encoding) local line_len = vim.fn.strcharlen(line)
local end_col = M._str_byteindex_enc(lines[end_row] or '', end_pos.character, offset_encoding) local end_line = lines[end_row] or ''
local end_line_len = vim.fn.strcharlen(end_line)
-- LSP spec: if character > line length, default to the line length.
-- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position
local col = pos.character <= line_len
and M._str_byteindex_enc(line, pos.character, offset_encoding)
or line_len
local end_col = end_pos.character <= end_line_len
and M._str_byteindex_enc(end_line, end_pos.character, offset_encoding)
or end_line_len
table.insert(items, { table.insert(items, {
filename = filename, filename = filename,
lnum = row + 1, lnum = row + 1,

View File

@ -2673,7 +2673,7 @@ describe('LSP', function()
describe('lsp.util.locations_to_items', function() describe('lsp.util.locations_to_items', function()
it('Convert Location[] to items', function() it('Convert Location[] to items', function()
local expected = { local expected_template = {
{ {
filename = '/fake/uri', filename = '/fake/uri',
lnum = 1, lnum = 1,
@ -2681,20 +2681,11 @@ describe('LSP', function()
col = 3, col = 3,
end_col = 4, end_col = 4,
text = 'testing', text = 'testing',
user_data = { user_data = {},
uri = 'file:///fake/uri',
range = {
start = { line = 0, character = 2 },
['end'] = { line = 1, character = 3 },
},
},
}, },
} }
local actual = exec_lua(function() local test_params = {
local bufnr = vim.uri_to_bufnr('file:///fake/uri') {
local lines = { 'testing', '123' }
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
local locations = {
{ {
uri = 'file:///fake/uri', uri = 'file:///fake/uri',
range = { range = {
@ -2702,10 +2693,29 @@ describe('LSP', function()
['end'] = { line = 1, character = 3 }, ['end'] = { line = 1, character = 3 },
}, },
}, },
} },
return vim.lsp.util.locations_to_items(locations, 'utf-16') {
end) {
eq(expected, actual) uri = 'file:///fake/uri',
range = {
start = { line = 0, character = 2 },
-- LSP spec: if character > line length, default to the line length.
['end'] = { line = 1, character = 10000 },
},
},
},
}
for _, params in ipairs(test_params) do
local actual = exec_lua(function(params0)
local bufnr = vim.uri_to_bufnr('file:///fake/uri')
local lines = { 'testing', '123' }
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
return vim.lsp.util.locations_to_items(params0, 'utf-16')
end, params)
local expected = vim.deepcopy(expected_template)
expected[1].user_data = params[1]
eq(expected, actual)
end
end) end)
it('Convert LocationLink[] to items', function() it('Convert LocationLink[] to items', function()