2020-10-26 03:50:57 -07:00
|
|
|
local util = require('vim.lsp.util')
|
2022-05-05 09:50:12 -07:00
|
|
|
local log = require('vim.lsp.log')
|
2023-08-03 04:03:48 -07:00
|
|
|
local ms = require('vim.lsp.protocol').Methods
|
2020-10-26 03:50:57 -07:00
|
|
|
local api = vim.api
|
|
|
|
local M = {}
|
|
|
|
|
|
|
|
--- bufnr → true|nil
|
|
|
|
--- to throttle refreshes to at most one at a time
|
2023-12-13 05:00:11 -07:00
|
|
|
local active_refreshes = {} --- @type table<integer,true>
|
2020-10-26 03:50:57 -07:00
|
|
|
|
2023-09-21 00:56:15 -07:00
|
|
|
---@type table<integer, table<integer, lsp.CodeLens[]>>
|
2020-10-26 03:50:57 -07:00
|
|
|
--- bufnr -> client_id -> lenses
|
|
|
|
local lens_cache_by_buf = setmetatable({}, {
|
|
|
|
__index = function(t, b)
|
|
|
|
local key = b > 0 and b or api.nvim_get_current_buf()
|
|
|
|
return rawget(t, key)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
2023-09-21 00:56:15 -07:00
|
|
|
---@type table<integer, integer>
|
|
|
|
---client_id -> namespace
|
2020-10-26 03:50:57 -07:00
|
|
|
local namespaces = setmetatable({}, {
|
|
|
|
__index = function(t, key)
|
|
|
|
local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key)
|
|
|
|
rawset(t, key, value)
|
|
|
|
return value
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
2021-08-22 13:55:28 -07:00
|
|
|
---@private
|
2020-10-26 03:50:57 -07:00
|
|
|
M.__namespaces = namespaces
|
|
|
|
|
2023-09-21 00:56:15 -07:00
|
|
|
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
|
2020-10-26 03:50:57 -07:00
|
|
|
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)
|
|
|
|
|
2021-11-01 03:14:59 -07:00
|
|
|
local client = vim.lsp.get_client_by_id(client_id)
|
|
|
|
assert(client, 'Client is required to execute lens, client_id=' .. client_id)
|
2024-02-07 10:22:03 -07:00
|
|
|
client:_exec_cmd(lens.command, { bufnr = bufnr }, function(...)
|
2023-08-03 04:03:48 -07:00
|
|
|
vim.lsp.handlers[ms.workspace_executeCommand](...)
|
2020-10-26 03:50:57 -07:00
|
|
|
M.refresh()
|
2023-06-20 09:36:18 -07:00
|
|
|
end)
|
2020-10-26 03:50:57 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
--- Return all lenses for the given buffer
|
|
|
|
---
|
2023-03-06 23:17:52 -07:00
|
|
|
---@param bufnr integer Buffer number. 0 can be used for the current buffer.
|
2023-09-21 00:56:15 -07:00
|
|
|
---@return lsp.CodeLens[]
|
2020-10-26 03:50:57 -07:00
|
|
|
function M.get(bufnr)
|
2021-09-01 04:29:11 -07:00
|
|
|
local lenses_by_client = lens_cache_by_buf[bufnr or 0]
|
2020-10-26 03:50:57 -07:00
|
|
|
if not lenses_by_client then
|
|
|
|
return {}
|
|
|
|
end
|
|
|
|
local lenses = {}
|
|
|
|
for _, client_lenses in pairs(lenses_by_client) do
|
|
|
|
vim.list_extend(lenses, client_lenses)
|
|
|
|
end
|
|
|
|
return lenses
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Run the code lens in the current line
|
|
|
|
---
|
|
|
|
function M.run()
|
|
|
|
local line = api.nvim_win_get_cursor(0)[1]
|
|
|
|
local bufnr = api.nvim_get_current_buf()
|
2023-12-13 05:00:11 -07:00
|
|
|
local options = {} --- @type {client: integer, lens: lsp.CodeLens}[]
|
2020-10-26 03:50:57 -07:00
|
|
|
local lenses_by_client = lens_cache_by_buf[bufnr] or {}
|
|
|
|
for client, lenses in pairs(lenses_by_client) do
|
|
|
|
for _, lens in pairs(lenses) do
|
|
|
|
if lens.range.start.line == (line - 1) then
|
|
|
|
table.insert(options, { client = client, lens = lens })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if #options == 0 then
|
|
|
|
vim.notify('No executable codelens found at current line')
|
|
|
|
elseif #options == 1 then
|
|
|
|
local option = options[1]
|
|
|
|
execute_lens(option.lens, bufnr, option.client)
|
|
|
|
else
|
2021-10-18 11:52:22 -07:00
|
|
|
vim.ui.select(options, {
|
|
|
|
prompt = 'Code lenses:',
|
2023-06-03 18:03:25 -07:00
|
|
|
kind = 'codelens',
|
2021-10-18 11:52:22 -07:00
|
|
|
format_item = function(option)
|
|
|
|
return option.lens.command.title
|
|
|
|
end,
|
|
|
|
}, function(option)
|
|
|
|
if option then
|
|
|
|
execute_lens(option.lens, bufnr, option.client)
|
|
|
|
end
|
|
|
|
end)
|
2020-10-26 03:50:57 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-31 08:16:21 -07:00
|
|
|
local function resolve_bufnr(bufnr)
|
|
|
|
return bufnr == 0 and api.nvim_get_current_buf() or bufnr
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Clear the lenses
|
|
|
|
---
|
2023-03-06 23:17:52 -07:00
|
|
|
---@param client_id integer|nil filter by client_id. All clients if nil
|
2024-01-28 23:21:37 -07:00
|
|
|
---@param bufnr integer|nil filter by buffer. All buffers if nil, 0 for current buffer
|
2022-12-31 08:16:21 -07:00
|
|
|
function M.clear(client_id, bufnr)
|
2023-09-21 00:56:15 -07:00
|
|
|
bufnr = bufnr and resolve_bufnr(bufnr)
|
|
|
|
local buffers = bufnr and { bufnr }
|
|
|
|
or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
|
2022-12-31 08:16:21 -07:00
|
|
|
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]
|
2023-09-21 00:56:15 -07:00
|
|
|
-- 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
|
2022-12-31 08:16:21 -07:00
|
|
|
api.nvim_buf_clear_namespace(iter_bufnr, ns, 0, -1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-10-26 03:50:57 -07:00
|
|
|
--- Display the lenses using virtual text
|
|
|
|
---
|
2023-09-21 00:56:15 -07:00
|
|
|
---@param lenses? lsp.CodeLens[] lenses to display
|
2023-03-06 23:17:52 -07:00
|
|
|
---@param bufnr integer
|
|
|
|
---@param client_id integer
|
2020-10-26 03:50:57 -07:00
|
|
|
function M.display(lenses, bufnr, client_id)
|
2023-06-13 08:17:35 -07:00
|
|
|
if not api.nvim_buf_is_loaded(bufnr) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2022-12-31 08:16:21 -07:00
|
|
|
local ns = namespaces[client_id]
|
2020-10-26 03:50:57 -07:00
|
|
|
if not lenses or not next(lenses) then
|
2022-12-31 08:16:21 -07:00
|
|
|
api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
2020-10-26 03:50:57 -07:00
|
|
|
return
|
|
|
|
end
|
2023-09-21 00:56:15 -07:00
|
|
|
|
|
|
|
local lenses_by_lnum = {} ---@type table<integer, lsp.CodeLens[]>
|
2020-10-26 03:50:57 -07:00
|
|
|
for _, lens in pairs(lenses) do
|
|
|
|
local line_lenses = lenses_by_lnum[lens.range.start.line]
|
|
|
|
if not line_lenses then
|
|
|
|
line_lenses = {}
|
|
|
|
lenses_by_lnum[lens.range.start.line] = line_lenses
|
|
|
|
end
|
|
|
|
table.insert(line_lenses, lens)
|
|
|
|
end
|
|
|
|
local num_lines = api.nvim_buf_line_count(bufnr)
|
|
|
|
for i = 0, num_lines do
|
2021-07-10 12:35:38 -07:00
|
|
|
local line_lenses = lenses_by_lnum[i] or {}
|
2020-10-26 03:50:57 -07:00
|
|
|
api.nvim_buf_clear_namespace(bufnr, ns, i, i + 1)
|
|
|
|
local chunks = {}
|
2021-07-10 12:35:38 -07:00
|
|
|
local num_line_lenses = #line_lenses
|
2022-06-13 10:31:48 -07:00
|
|
|
table.sort(line_lenses, function(a, b)
|
|
|
|
return a.range.start.character < b.range.start.character
|
|
|
|
end)
|
2021-07-10 12:35:38 -07:00
|
|
|
for j, lens in ipairs(line_lenses) do
|
2020-10-26 03:50:57 -07:00
|
|
|
local text = lens.command and lens.command.title or 'Unresolved lens ...'
|
|
|
|
table.insert(chunks, { text, 'LspCodeLens' })
|
2021-07-10 12:35:38 -07:00
|
|
|
if j < num_line_lenses then
|
|
|
|
table.insert(chunks, { ' | ', 'LspCodeLensSeparator' })
|
|
|
|
end
|
2020-10-26 03:50:57 -07:00
|
|
|
end
|
|
|
|
if #chunks > 0 then
|
2021-10-19 09:41:57 -07:00
|
|
|
api.nvim_buf_set_extmark(bufnr, ns, i, 0, {
|
|
|
|
virt_text = chunks,
|
|
|
|
hl_mode = 'combine',
|
|
|
|
})
|
2020-10-26 03:50:57 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Store lenses for a specific buffer and client
|
|
|
|
---
|
2023-09-21 00:56:15 -07:00
|
|
|
---@param lenses? lsp.CodeLens[] lenses to store
|
2023-03-06 23:17:52 -07:00
|
|
|
---@param bufnr integer
|
|
|
|
---@param client_id integer
|
2020-10-26 03:50:57 -07:00
|
|
|
function M.save(lenses, bufnr, client_id)
|
2023-06-13 08:17:35 -07:00
|
|
|
if not api.nvim_buf_is_loaded(bufnr) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2020-10-26 03:50:57 -07:00
|
|
|
local lenses_by_client = lens_cache_by_buf[bufnr]
|
|
|
|
if not lenses_by_client then
|
|
|
|
lenses_by_client = {}
|
|
|
|
lens_cache_by_buf[bufnr] = lenses_by_client
|
|
|
|
local ns = namespaces[client_id]
|
|
|
|
api.nvim_buf_attach(bufnr, false, {
|
2023-09-21 00:56:15 -07:00
|
|
|
on_detach = function(_, b)
|
2020-10-26 03:50:57 -07:00
|
|
|
lens_cache_by_buf[b] = nil
|
|
|
|
end,
|
|
|
|
on_lines = function(_, b, _, first_lnum, last_lnum)
|
|
|
|
api.nvim_buf_clear_namespace(b, ns, first_lnum, last_lnum)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
lenses_by_client[client_id] = lenses
|
|
|
|
end
|
|
|
|
|
2023-09-21 00:56:15 -07:00
|
|
|
---@param lenses? lsp.CodeLens[]
|
|
|
|
---@param bufnr integer
|
|
|
|
---@param client_id integer
|
|
|
|
---@param callback fun()
|
2020-10-26 03:50:57 -07:00
|
|
|
local function resolve_lenses(lenses, bufnr, client_id, callback)
|
|
|
|
lenses = lenses or {}
|
|
|
|
local num_lens = vim.tbl_count(lenses)
|
|
|
|
if num_lens == 0 then
|
|
|
|
callback()
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local function countdown()
|
|
|
|
num_lens = num_lens - 1
|
|
|
|
if num_lens == 0 then
|
|
|
|
callback()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local ns = namespaces[client_id]
|
|
|
|
local client = vim.lsp.get_client_by_id(client_id)
|
|
|
|
for _, lens in pairs(lenses or {}) do
|
|
|
|
if lens.command then
|
|
|
|
countdown()
|
|
|
|
else
|
2023-12-13 05:00:11 -07:00
|
|
|
assert(client)
|
2021-09-06 08:30:53 -07:00
|
|
|
client.request('codeLens/resolve', lens, function(_, result)
|
2023-06-13 08:17:35 -07:00
|
|
|
if api.nvim_buf_is_loaded(bufnr) and result and result.command then
|
2020-10-26 03:50:57 -07:00
|
|
|
lens.command = result.command
|
|
|
|
-- Eager display to have some sort of incremental feedback
|
|
|
|
-- Once all lenses got resolved there will be a full redraw for all lenses
|
|
|
|
-- So that multiple lens per line are properly displayed
|
2023-06-13 08:17:35 -07:00
|
|
|
|
|
|
|
local num_lines = api.nvim_buf_line_count(bufnr)
|
|
|
|
if lens.range.start.line <= num_lines then
|
|
|
|
api.nvim_buf_set_extmark(
|
|
|
|
bufnr,
|
|
|
|
ns,
|
|
|
|
lens.range.start.line,
|
|
|
|
0,
|
|
|
|
{ virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' }
|
|
|
|
)
|
|
|
|
end
|
2020-10-26 03:50:57 -07:00
|
|
|
end
|
2023-06-13 08:17:35 -07:00
|
|
|
|
2020-10-26 03:50:57 -07:00
|
|
|
countdown()
|
|
|
|
end, bufnr)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--- |lsp-handler| for the method `textDocument/codeLens`
|
|
|
|
---
|
2024-02-15 10:16:04 -07:00
|
|
|
---@param err lsp.ResponseError?
|
|
|
|
---@param result lsp.CodeLens[]
|
2023-12-13 05:00:11 -07:00
|
|
|
---@param ctx lsp.HandlerContext
|
feat(lsp)!: change handler signature
Previously, the handler signature was:
function(err, method, params, client_id, bufnr, config)
In order to better support external plugins that wish to extend the
protocol, there is other information which would be advantageous to
forward to the client, such as the original params of the request that
generated the callback.
In order to do this, we would need to break symmetry of the handlers, to
add an additional "params" as the 7th argument.
Instead, this PR changes the signature of the handlers to:
function(err, result, ctx, config)
where ctx (the context) includes params, client_id, and bufnr. This also leaves
flexibility for future use-cases.
BREAKING_CHANGE: changes the signature of the built-in client handlers, requiring
updating handler calls
2021-08-27 21:12:30 -07:00
|
|
|
function M.on_codelens(err, result, ctx, _)
|
2022-05-05 09:50:12 -07:00
|
|
|
if err then
|
2023-12-13 05:00:11 -07:00
|
|
|
active_refreshes[assert(ctx.bufnr)] = nil
|
2024-02-08 02:24:47 -07:00
|
|
|
log.error('codelens', err)
|
2022-05-05 09:50:12 -07:00
|
|
|
return
|
|
|
|
end
|
2020-10-26 03:50:57 -07:00
|
|
|
|
feat(lsp)!: change handler signature
Previously, the handler signature was:
function(err, method, params, client_id, bufnr, config)
In order to better support external plugins that wish to extend the
protocol, there is other information which would be advantageous to
forward to the client, such as the original params of the request that
generated the callback.
In order to do this, we would need to break symmetry of the handlers, to
add an additional "params" as the 7th argument.
Instead, this PR changes the signature of the handlers to:
function(err, result, ctx, config)
where ctx (the context) includes params, client_id, and bufnr. This also leaves
flexibility for future use-cases.
BREAKING_CHANGE: changes the signature of the built-in client handlers, requiring
updating handler calls
2021-08-27 21:12:30 -07:00
|
|
|
M.save(result, ctx.bufnr, ctx.client_id)
|
2020-10-26 03:50:57 -07:00
|
|
|
|
|
|
|
-- Eager display for any resolved (and unresolved) lenses and refresh them
|
|
|
|
-- once resolved.
|
feat(lsp)!: change handler signature
Previously, the handler signature was:
function(err, method, params, client_id, bufnr, config)
In order to better support external plugins that wish to extend the
protocol, there is other information which would be advantageous to
forward to the client, such as the original params of the request that
generated the callback.
In order to do this, we would need to break symmetry of the handlers, to
add an additional "params" as the 7th argument.
Instead, this PR changes the signature of the handlers to:
function(err, result, ctx, config)
where ctx (the context) includes params, client_id, and bufnr. This also leaves
flexibility for future use-cases.
BREAKING_CHANGE: changes the signature of the built-in client handlers, requiring
updating handler calls
2021-08-27 21:12:30 -07:00
|
|
|
M.display(result, ctx.bufnr, ctx.client_id)
|
|
|
|
resolve_lenses(result, ctx.bufnr, ctx.client_id, function()
|
2023-12-13 05:00:11 -07:00
|
|
|
active_refreshes[assert(ctx.bufnr)] = nil
|
2022-05-05 09:50:12 -07:00
|
|
|
M.display(result, ctx.bufnr, ctx.client_id)
|
2020-10-26 03:50:57 -07:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2024-02-27 08:20:32 -07:00
|
|
|
--- @class vim.lsp.codelens.refresh.Opts
|
|
|
|
--- @inlinedoc
|
2024-01-28 23:21:37 -07:00
|
|
|
--- @field bufnr integer? filter by buffer. All buffers if nil, 0 for current buffer
|
|
|
|
|
|
|
|
--- Refresh the lenses.
|
2020-10-26 03:50:57 -07:00
|
|
|
---
|
|
|
|
--- It is recommended to trigger this using an autocmd or via keymap.
|
|
|
|
---
|
2022-11-23 04:31:49 -07:00
|
|
|
--- Example:
|
2020-10-26 03:50:57 -07:00
|
|
|
---
|
2023-09-14 06:23:01 -07:00
|
|
|
--- ```vim
|
2024-01-28 23:21:37 -07:00
|
|
|
--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh({ bufnr = 0 })
|
2023-09-14 06:23:01 -07:00
|
|
|
--- ```
|
2024-01-28 23:21:37 -07:00
|
|
|
---
|
2024-02-27 08:20:32 -07:00
|
|
|
--- @param opts? vim.lsp.codelens.refresh.Opts Optional fields
|
2024-01-28 23:21:37 -07:00
|
|
|
function M.refresh(opts)
|
|
|
|
opts = opts or {}
|
|
|
|
local bufnr = opts.bufnr and resolve_bufnr(opts.bufnr)
|
|
|
|
local buffers = bufnr and { bufnr }
|
|
|
|
or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
|
|
|
|
|
|
|
|
for _, buf in ipairs(buffers) do
|
|
|
|
if not active_refreshes[buf] then
|
2024-03-17 13:04:59 -07:00
|
|
|
local params = {
|
|
|
|
textDocument = util.make_text_document_params(buf),
|
|
|
|
}
|
2024-01-28 23:21:37 -07:00
|
|
|
active_refreshes[buf] = true
|
|
|
|
vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens)
|
|
|
|
end
|
2020-10-26 03:50:57 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return M
|