diff options
author | Mathias Fussenegger <f.mathias@zignar.net> | 2023-11-02 09:59:41 +0100 |
---|---|---|
committer | Mathias Fußenegger <mfussenegger@users.noreply.github.com> | 2023-11-04 08:17:42 +0100 |
commit | 9281edb334a374e7753d4a6b7a05e31120e39772 (patch) | |
tree | 9489c24d75cd5af1870984edcdb8ba0890fc11e7 /runtime/lua/vim/lsp/buf.lua | |
parent | 468292dcb743c79714b030557cf2754b7b5bf07d (diff) | |
download | rneovim-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.lua | 141 |
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. |