aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lsp.txt6
-rw-r--r--runtime/lua/vim/lsp/codelens.lua39
-rw-r--r--runtime/lua/vim/lsp/util.lua60
-rw-r--r--test/functional/fixtures/fake-lsp-server.lua22
-rw-r--r--test/functional/plugin/lsp_spec.lua61
5 files changed, 162 insertions, 26 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 15292cd7cf..23902f8bc3 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1430,7 +1430,7 @@ display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()*
Display the lenses using virtual text
Parameters: ~
- • {lenses} (table) of lenses to display (`CodeLens[] | null`)
+ • {lenses} lsp.CodeLens[]|nil lenses to display
• {bufnr} (integer)
• {client_id} (integer)
@@ -1442,7 +1442,7 @@ get({bufnr}) *vim.lsp.codelens.get()*
buffer.
Return: ~
- (table) (`CodeLens[]`)
+ lsp.CodeLens[]
*vim.lsp.codelens.on_codelens()*
on_codelens({err}, {result}, {ctx}, {_})
@@ -1464,7 +1464,7 @@ save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()*
Store lenses for a specific buffer and client
Parameters: ~
- • {lenses} (table) of lenses to store (`CodeLens[] | null`)
+ • {lenses} lsp.CodeLens[]|nil lenses to store
• {bufnr} (integer)
• {client_id} (integer)
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 384d09ee48..9cccaa1d66 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -8,6 +8,7 @@ local M = {}
--- to throttle refreshes to at most one at a time
local active_refreshes = {}
+---@type table<integer, table<integer, lsp.CodeLens[]>>
--- bufnr -> client_id -> lenses
local lens_cache_by_buf = setmetatable({}, {
__index = function(t, b)
@@ -16,6 +17,8 @@ local lens_cache_by_buf = setmetatable({}, {
end,
})
+---@type table<integer, integer>
+---client_id -> namespace
local namespaces = setmetatable({}, {
__index = function(t, key)
local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key)
@@ -27,6 +30,18 @@ local namespaces = setmetatable({}, {
---@private
M.__namespaces = namespaces
+local augroup = api.nvim_create_augroup('vim_lsp_codelens', {})
+
+api.nvim_create_autocmd('LspDetach', {
+ group = augroup,
+ callback = function(ev)
+ M.clear(ev.data.client_id, ev.buf)
+ end,
+})
+
+---@param lens lsp.CodeLens
+---@param bufnr integer
+---@param client_id integer
local function execute_lens(lens, bufnr, client_id)
local line = lens.range.start.line
api.nvim_buf_clear_namespace(bufnr, namespaces[client_id], line, line + 1)
@@ -42,7 +57,7 @@ end
--- Return all lenses for the given buffer
---
---@param bufnr integer Buffer number. 0 can be used for the current buffer.
----@return table (`CodeLens[]`)
+---@return lsp.CodeLens[]
function M.get(bufnr)
local lenses_by_client = lens_cache_by_buf[bufnr or 0]
if not lenses_by_client then
@@ -98,12 +113,17 @@ end
---@param client_id integer|nil filter by client_id. All clients if nil
---@param bufnr integer|nil filter by buffer. All buffers if nil
function M.clear(client_id, bufnr)
- local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(lens_cache_by_buf)
+ bufnr = bufnr and resolve_bufnr(bufnr)
+ local buffers = bufnr and { bufnr }
+ or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
for _, iter_bufnr in pairs(buffers) do
local client_ids = client_id and { client_id } or vim.tbl_keys(namespaces)
for _, iter_client_id in pairs(client_ids) do
local ns = namespaces[iter_client_id]
- lens_cache_by_buf[iter_bufnr][iter_client_id] = {}
+ -- there can be display()ed lenses, which are not stored in cache
+ if lens_cache_by_buf[iter_bufnr] then
+ lens_cache_by_buf[iter_bufnr][iter_client_id] = {}
+ end
api.nvim_buf_clear_namespace(iter_bufnr, ns, 0, -1)
end
end
@@ -111,7 +131,7 @@ end
--- Display the lenses using virtual text
---
----@param lenses table of lenses to display (`CodeLens[] | null`)
+---@param lenses? lsp.CodeLens[] lenses to display
---@param bufnr integer
---@param client_id integer
function M.display(lenses, bufnr, client_id)
@@ -124,7 +144,8 @@ function M.display(lenses, bufnr, client_id)
api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
return
end
- local lenses_by_lnum = {}
+
+ local lenses_by_lnum = {} ---@type table<integer, lsp.CodeLens[]>
for _, lens in pairs(lenses) do
local line_lenses = lenses_by_lnum[lens.range.start.line]
if not line_lenses then
@@ -160,7 +181,7 @@ end
--- Store lenses for a specific buffer and client
---
----@param lenses table of lenses to store (`CodeLens[] | null`)
+---@param lenses? lsp.CodeLens[] lenses to store
---@param bufnr integer
---@param client_id integer
function M.save(lenses, bufnr, client_id)
@@ -174,7 +195,7 @@ function M.save(lenses, bufnr, client_id)
lens_cache_by_buf[bufnr] = lenses_by_client
local ns = namespaces[client_id]
api.nvim_buf_attach(bufnr, false, {
- on_detach = function(b)
+ on_detach = function(_, b)
lens_cache_by_buf[b] = nil
end,
on_lines = function(_, b, _, first_lnum, last_lnum)
@@ -185,6 +206,10 @@ function M.save(lenses, bufnr, client_id)
lenses_by_client[client_id] = lenses
end
+---@param lenses? lsp.CodeLens[]
+---@param bufnr integer
+---@param client_id integer
+---@param callback fun()
local function resolve_lenses(lenses, bufnr, client_id, callback)
lenses = lenses or {}
local num_lens = vim.tbl_count(lenses)
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}};