diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 988057f5f9..0d1e3cc0d1 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2230,6 +2230,35 @@ function M.lookup_section(settings, section) return settings 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 --- Request updated LSP information for a buffer. --- @@ -2253,6 +2282,8 @@ function M._refresh(method, opts) return end + local textDocument = M.make_text_document_params(bufnr) + local only_visible = opts.only_visible or false if only_visible then @@ -2260,28 +2291,25 @@ function M._refresh(method, opts) if api.nvim_win_get_buf(window) == bufnr then local first = vim.fn.line('w0', 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 - 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 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 - 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 diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index ef87f6c21a..0db9265a29 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -949,6 +949,28 @@ function tests.set_defaults_all_capabilities() } 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 local test_name = arg[1] local timeout = arg[2] diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 7e30af5058..155c9ad96c 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1244,6 +1244,67 @@ describe('LSP', function() } 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() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}};