aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp/buf.lua
diff options
context:
space:
mode:
authorMathias Fussenegger <f.mathias@zignar.net>2023-11-02 09:59:41 +0100
committerMathias Fußenegger <mfussenegger@users.noreply.github.com>2023-11-04 08:17:42 +0100
commit9281edb334a374e7753d4a6b7a05e31120e39772 (patch)
tree9489c24d75cd5af1870984edcdb8ba0890fc11e7 /runtime/lua/vim/lsp/buf.lua
parent468292dcb743c79714b030557cf2754b7b5bf07d (diff)
downloadrneovim-9281edb334a374e7753d4a6b7a05e31120e39772.tar.gz
rneovim-9281edb334a374e7753d4a6b7a05e31120e39772.tar.bz2
rneovim-9281edb334a374e7753d4a6b7a05e31120e39772.zip
fix(lsp): create per client params in lsp.buf.code_action
`code_action` used the same parameters for all clients, which led to the following warning and incorrect start/end column locations if using clients with mixed encodings: warning: multiple different client offset_encodings detected for buffer, this is not supported yet
Diffstat (limited to 'runtime/lua/vim/lsp/buf.lua')
-rw-r--r--runtime/lua/vim/lsp/buf.lua141
1 files changed, 91 insertions, 50 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index b9b7aefae6..cf9acc0808 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -579,6 +579,17 @@ function M.clear_references()
util.buf_clear_references()
end
+---@class vim.lsp.CodeActionResultEntry
+---@field error? lsp.ResponseError
+---@field result? (lsp.Command|lsp.CodeAction)[]
+---@field ctx lsp.HandlerContext
+
+---@class vim.lsp.buf.code_action.opts
+---@field context? lsp.CodeActionContext
+---@field filter? fun(x: lsp.CodeAction|lsp.Command):boolean
+---@field apply? boolean
+---@field range? {start: integer[], end: integer[]}
+
--- This is not public because the main extension point is
--- vim.ui.select which can be overridden independently.
---
@@ -587,17 +598,18 @@ end
--- from multiple clients to have 1 single UI prompt for the user, yet we still
--- need to be able to link a `CodeAction|Command` to the right client for
--- `codeAction/resolve`
-local function on_code_action_results(results, ctx, options)
- local action_tuples = {}
-
+---@param results table<integer, vim.lsp.CodeActionResultEntry>
+---@param opts? vim.lsp.buf.code_action.opts
+local function on_code_action_results(results, opts)
+ ---@param a lsp.Command|lsp.CodeAction
local function action_filter(a)
-- filter by specified action kind
- if options and options.context and options.context.only then
+ if opts and opts.context and opts.context.only then
if not a.kind then
return false
end
local found = false
- for _, o in ipairs(options.context.only) do
+ for _, o in ipairs(opts.context.only) do
-- action kinds are hierarchical with . as a separator: when requesting only 'type-annotate'
-- this filter allows both 'type-annotate' and 'type-annotate.foo', for example
if a.kind == o or vim.startswith(a.kind, o .. '.') then
@@ -610,26 +622,31 @@ local function on_code_action_results(results, ctx, options)
end
end
-- filter by user function
- if options and options.filter and not options.filter(a) then
+ if opts and opts.filter and not opts.filter(a) then
return false
end
-- no filter removed this action
return true
end
- for client_id, result in pairs(results) do
+ ---@type {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}[]
+ local actions = {}
+ for _, result in pairs(results) do
for _, action in pairs(result.result or {}) do
if action_filter(action) then
- table.insert(action_tuples, { client_id, action })
+ table.insert(actions, { action = action, ctx = result.ctx })
end
end
end
- if #action_tuples == 0 then
+ if #actions == 0 then
vim.notify('No code actions available', vim.log.levels.INFO)
return
end
- local function apply_action(action, client)
+ ---@param action lsp.Command|lsp.CodeAction
+ ---@param client lsp.Client
+ ---@param ctx lsp.HandlerContext
+ local function apply_action(action, client, ctx)
if action.edit then
util.apply_workspace_edit(action.edit, client.offset_encoding)
end
@@ -639,8 +656,9 @@ local function on_code_action_results(results, ctx, options)
end
end
- local function on_user_choice(action_tuple)
- if not action_tuple then
+ ---@param choice {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
+ local function on_user_choice(choice)
+ if not choice then
return
end
-- textDocument/codeAction can return either Command[] or CodeAction[]
@@ -656,10 +674,11 @@ local function on_code_action_results(results, ctx, options)
-- arguments?: any[]
--
---@type lsp.Client
- local client = assert(vim.lsp.get_client_by_id(action_tuple[1]))
- local action = action_tuple[2]
+ local client = assert(vim.lsp.get_client_by_id(choice.ctx.client_id))
+ local action = choice.action
+ local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number')
- local reg = client.dynamic_capabilities:get(ms.textDocument_codeAction, { bufnr = ctx.bufnr })
+ local reg = client.dynamic_capabilities:get(ms.textDocument_codeAction, { bufnr = bufnr })
local supports_resolve = vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider')
or client.supports_method(ms.codeAction_resolve)
@@ -668,44 +687,37 @@ local function on_code_action_results(results, ctx, options)
client.request(ms.codeAction_resolve, action, function(err, resolved_action)
if err then
if action.command then
- apply_action(action, client)
+ apply_action(action, client, choice.ctx)
else
vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
end
else
- apply_action(resolved_action, client)
+ apply_action(resolved_action, client, choice.ctx)
end
- end, ctx.bufnr)
+ end, bufnr)
else
- apply_action(action, client)
+ apply_action(action, client, choice.ctx)
end
end
-- If options.apply is given, and there are just one remaining code action,
-- apply it directly without querying the user.
- if options and options.apply and #action_tuples == 1 then
- on_user_choice(action_tuples[1])
+ if opts and opts.apply and #actions == 1 then
+ on_user_choice(actions[1])
return
end
- vim.ui.select(action_tuples, {
+ ---@param item {action: lsp.Command|lsp.CodeAction}
+ local function format_item(item)
+ local title = item.action.title:gsub('\r\n', '\\r\\n')
+ return title:gsub('\n', '\\n')
+ end
+ local select_opts = {
prompt = 'Code actions:',
kind = 'codeaction',
- format_item = function(action_tuple)
- local title = action_tuple[2].title:gsub('\r\n', '\\r\\n')
- return title:gsub('\n', '\\n')
- end,
- }, on_user_choice)
-end
-
---- Requests code actions from all clients and calls the handler exactly once
---- with all aggregated results
-local function code_action_request(params, options)
- local bufnr = api.nvim_get_current_buf()
- vim.lsp.buf_request_all(bufnr, ms.textDocument_codeAction, params, function(results)
- local ctx = { bufnr = bufnr, method = ms.textDocument_codeAction, params = params }
- on_code_action_results(results, ctx, options)
- end)
+ format_item = format_item,
+ }
+ vim.ui.select(actions, select_opts, on_user_choice)
end
--- Selects a code action available at the current
@@ -752,21 +764,50 @@ function M.code_action(options)
local bufnr = api.nvim_get_current_buf()
context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr)
end
- local params
local mode = api.nvim_get_mode().mode
- if options.range then
- assert(type(options.range) == 'table', 'code_action range must be a table')
- local start = assert(options.range.start, 'range must have a `start` property')
- local end_ = assert(options.range['end'], 'range must have a `end` property')
- params = util.make_given_range_params(start, end_)
- elseif mode == 'v' or mode == 'V' then
- local range = range_from_selection(0, mode)
- params = util.make_given_range_params(range.start, range['end'])
- else
- params = util.make_range_params()
+ local bufnr = api.nvim_get_current_buf()
+ local win = api.nvim_get_current_win()
+ local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_codeAction })
+ local remaining = #clients
+ if remaining == 0 then
+ if next(vim.lsp.get_clients({ bufnr = bufnr })) then
+ vim.notify(vim.lsp._unsupported_method(ms.textDocument_codeAction), vim.log.levels.WARN)
+ end
+ return
+ end
+
+ ---@type table<integer, vim.lsp.CodeActionResultEntry>
+ local results = {}
+
+ ---@param err? lsp.ResponseError
+ ---@param result? (lsp.Command|lsp.CodeAction)[]
+ ---@param ctx lsp.HandlerContext
+ local function on_result(err, result, ctx)
+ results[ctx.client_id] = { error = err, result = result, ctx = ctx }
+ remaining = remaining - 1
+ if remaining == 0 then
+ on_code_action_results(results, options)
+ end
+ end
+
+ for _, client in ipairs(clients) do
+ ---@type lsp.CodeActionParams
+ local params
+ if options.range then
+ assert(type(options.range) == 'table', 'code_action range must be a table')
+ local start = assert(options.range.start, 'range must have a `start` property')
+ local end_ = assert(options.range['end'], 'range must have a `end` property')
+ params = util.make_given_range_params(start, end_, bufnr, client.offset_encoding)
+ elseif mode == 'v' or mode == 'V' then
+ local range = range_from_selection(bufnr, mode)
+ params =
+ util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding)
+ else
+ params = util.make_range_params(win, client.offset_encoding)
+ end
+ params.context = context
+ client.request(ms.textDocument_codeAction, params, on_result, bufnr)
end
- params.context = context
- code_action_request(params, options)
end
--- Executes an LSP server command.