diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
commit | 931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch) | |
tree | d8c1843a95da5ea0bb4acc09f7e37843d9995c86 /runtime/lua/vim/lsp/buf.lua | |
parent | 142d9041391780ac15b89886a54015fdc5c73995 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.gz rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.bz2 rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.zip |
Merge remote-tracking branch 'upstream/master' into userreguserreg
Diffstat (limited to 'runtime/lua/vim/lsp/buf.lua')
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 380 |
1 files changed, 204 insertions, 176 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 6ac885c78f..cf9acc0808 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -2,10 +2,10 @@ local api = vim.api local validate = vim.validate local util = require('vim.lsp.util') local npcall = vim.F.npcall +local ms = require('vim.lsp.protocol').Methods local M = {} ----@private --- Sends an async request to all active clients attached to the current --- buffer. --- @@ -13,10 +13,11 @@ local M = {} ---@param params (table|nil) Parameters to send to the server ---@param handler (function|nil) See |lsp-handler|. Follows |lsp-handler-resolution| -- ----@returns 2-tuple: ---- - Map of client-id:request-id pairs for all successful requests. ---- - Function which can be used to cancel all the requests. You could instead ---- iterate all clients and call their `cancel_request()` methods. +---@return table<integer, integer> client_request_ids Map of client-id:request-id pairs +---for all successful requests. +---@return function _cancel_all_requests Function which can be used to +---cancel all the requests. You could instead +---iterate all clients and call their `cancel_request()` methods. --- ---@see |vim.lsp.buf_request()| local function request(method, params, handler) @@ -30,8 +31,10 @@ end --- Checks whether the language servers attached to the current buffer are --- ready. --- ----@returns `true` if server responds. +---@return boolean if server responds. +---@deprecated function M.server_ready() + vim.deprecate('vim.lsp.buf.server_ready', nil, '0.10.0') return not not vim.lsp.buf_notify(0, 'window/progress', {}) end @@ -39,10 +42,9 @@ end --- window. Calling the function twice will jump into the floating window. function M.hover() local params = util.make_position_params() - request('textDocument/hover', params) + request(ms.textDocument_hover, params) end ----@private local function request_with_options(name, params, options) local req_handler if options then @@ -60,66 +62,71 @@ end --- ---@param options table|nil additional options --- - reuse_win: (boolean) Jump to existing window if buffer is already open. ---- - on_list: (function) handler for list results. See |lsp-on-list-handler| +--- - on_list: (function) |lsp-on-list-handler| replacing the default handler. +--- Called for any non-empty result. function M.declaration(options) local params = util.make_position_params() - request_with_options('textDocument/declaration', params, options) + request_with_options(ms.textDocument_declaration, params, options) end --- Jumps to the definition of the symbol under the cursor. --- ---@param options table|nil additional options --- - reuse_win: (boolean) Jump to existing window if buffer is already open. ---- - on_list: (function) handler for list results. See |lsp-on-list-handler| +--- - on_list: (function) |lsp-on-list-handler| replacing the default handler. +--- Called for any non-empty result. function M.definition(options) local params = util.make_position_params() - request_with_options('textDocument/definition', params, options) + request_with_options(ms.textDocument_definition, params, options) end --- Jumps to the definition of the type of the symbol under the cursor. --- ---@param options table|nil additional options --- - reuse_win: (boolean) Jump to existing window if buffer is already open. ---- - on_list: (function) handler for list results. See |lsp-on-list-handler| +--- - on_list: (function) |lsp-on-list-handler| replacing the default handler. +--- Called for any non-empty result. function M.type_definition(options) local params = util.make_position_params() - request_with_options('textDocument/typeDefinition', params, options) + request_with_options(ms.textDocument_typeDefinition, params, options) end --- Lists all the implementations for the symbol under the cursor in the --- quickfix window. --- ---@param options table|nil additional options ---- - on_list: (function) handler for list results. See |lsp-on-list-handler| +--- - on_list: (function) |lsp-on-list-handler| replacing the default handler. +--- Called for any non-empty result. function M.implementation(options) local params = util.make_position_params() - request_with_options('textDocument/implementation', params, options) + request_with_options(ms.textDocument_implementation, params, options) end --- Displays signature information about the symbol under the cursor in a --- floating window. function M.signature_help() local params = util.make_position_params() - request('textDocument/signatureHelp', params) + request(ms.textDocument_signatureHelp, params) end --- Retrieves the completion items at the current cursor position. Can only be --- called in Insert mode. --- ----@param context (context support not yet implemented) Additional information +---@param context table (context support not yet implemented) Additional information --- about the context in which a completion was triggered (how it was triggered, --- and by which trigger character, if applicable) --- ----@see vim.lsp.protocol.constants.CompletionTriggerKind +---@see vim.lsp.protocol.CompletionTriggerKind function M.completion(context) local params = util.make_position_params() params.context = context - return request('textDocument/completion', params) + return request(ms.textDocument_completion, params) end ----@private ----@return table {start={row, col}, end={row, col}} using (1, 0) indexing -local function range_from_selection() +---@param bufnr integer +---@param mode "v"|"V" +---@return table {start={row,col}, end={row,col}} using (1, 0) indexing +local function range_from_selection(bufnr, mode) -- TODO: Use `vim.region()` instead https://github.com/neovim/neovim/pull/13896 -- [bufnum, lnum, col, off]; both row and column 1-indexed @@ -138,6 +145,11 @@ local function range_from_selection() start_row, end_row = end_row, start_row start_col, end_col = end_col, start_col end + if mode == 'V' then + start_col = 1 + local lines = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true) + end_col = #lines[1] + end return { ['start'] = { start_row, start_col - 1 }, ['end'] = { end_row, end_col - 1 }, @@ -150,8 +162,8 @@ end --- @param options table|nil Optional table which holds the following optional fields: --- - formatting_options (table|nil): --- Can be used to specify FormattingOptions. Some unspecified options will be ---- automatically derived from the current Neovim options. ---- See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#formattingOptions +--- automatically derived from the current Nvim options. +--- See https://microsoft.github.io/language-server-protocol/specification/#formattingOptions --- - timeout_ms (integer|nil, default 1000): --- Time in milliseconds to block for formatting requests. No effect if async=true --- - bufnr (number|nil): @@ -160,13 +172,11 @@ end --- --- - filter (function|nil): --- Predicate used to filter clients. Receives a client as argument and must return a ---- boolean. Clients matching the predicate are included. Example: ---- ---- <pre>lua ---- -- Never request typescript-language-server for formatting ---- vim.lsp.buf.format { ---- filter = function(client) return client.name ~= "tsserver" end ---- } +--- boolean. Clients matching the predicate are included. Example: <pre>lua +--- -- Never request typescript-language-server for formatting +--- vim.lsp.buf.format { +--- filter = function(client) return client.name ~= "tsserver" end +--- } --- </pre> --- --- - async boolean|nil @@ -180,39 +190,34 @@ end --- Restrict formatting to the client with name (client.name) matching this field. --- --- - range (table|nil) Range to format. ---- Table must contain `start` and `end` keys with {row, col} tuples using +--- Table must contain `start` and `end` keys with {row,col} tuples using --- (1,0) indexing. --- Defaults to current selection in visual mode --- Defaults to `nil` in other modes, formatting the full buffer function M.format(options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() - local clients = vim.lsp.get_active_clients({ + local mode = api.nvim_get_mode().mode + local range = options.range + if not range and mode == 'v' or mode == 'V' then + range = range_from_selection(bufnr, mode) + end + local method = range and ms.textDocument_rangeFormatting or ms.textDocument_formatting + + local clients = vim.lsp.get_clients({ id = options.id, bufnr = bufnr, name = options.name, + method = method, }) - if options.filter then clients = vim.tbl_filter(options.filter, clients) end - local mode = api.nvim_get_mode().mode - local range = options.range - if not range and mode == 'v' or mode == 'V' then - range = range_from_selection() - end - local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting' - - clients = vim.tbl_filter(function(client) - return client.supports_method(method) - end, clients) - if #clients == 0 then vim.notify('[LSP] Format request failed, no matching language servers.') end - ---@private local function set_range(client, params) if range then local range_params = @@ -264,19 +269,16 @@ end function M.rename(new_name, options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() - local clients = vim.lsp.get_active_clients({ + local clients = vim.lsp.get_clients({ bufnr = bufnr, name = options.name, + -- Clients must at least support rename, prepareRename is optional + method = ms.textDocument_rename, }) if options.filter then clients = vim.tbl_filter(options.filter, clients) end - -- Clients must at least support rename, prepareRename is optional - clients = vim.tbl_filter(function(client) - return client.supports_method('textDocument/rename') - end, clients) - if #clients == 0 then vim.notify('[LSP] Rename, no matching language servers with rename capability.') end @@ -286,7 +288,6 @@ function M.rename(new_name, options) -- Compute early to account for cursor movements after going async local cword = vim.fn.expand('<cword>') - ---@private local function get_text_at_range(range, offset_encoding) return api.nvim_buf_get_text( bufnr, @@ -304,21 +305,20 @@ function M.rename(new_name, options) return end - ---@private local function rename(name) local params = util.make_position_params(win, client.offset_encoding) params.newName = name - local handler = client.handlers['textDocument/rename'] - or vim.lsp.handlers['textDocument/rename'] - client.request('textDocument/rename', params, function(...) + local handler = client.handlers[ms.textDocument_rename] + or vim.lsp.handlers[ms.textDocument_rename] + client.request(ms.textDocument_rename, params, function(...) handler(...) try_use_client(next(clients, idx)) end, bufnr) end - if client.supports_method('textDocument/prepareRename') then + if client.supports_method(ms.textDocument_prepareRename) then local params = util.make_position_params(win, client.offset_encoding) - client.request('textDocument/prepareRename', params, function(err, result) + client.request(ms.textDocument_prepareRename, params, function(err, result) if err or result == nil then if next(clients, idx) then try_use_client(next(clients, idx)) @@ -357,7 +357,7 @@ function M.rename(new_name, options) end, bufnr) else assert( - client.supports_method('textDocument/rename'), + client.supports_method(ms.textDocument_rename), 'Client must support textDocument/rename' ) if new_name then @@ -393,7 +393,7 @@ function M.references(context, options) params.context = context or { includeDeclaration = true, } - request_with_options('textDocument/references', params, options) + request_with_options(ms.textDocument_references, params, options) end --- Lists all symbols in the current buffer in the quickfix window. @@ -402,10 +402,9 @@ end --- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.document_symbol(options) local params = { textDocument = util.make_text_document_params() } - request_with_options('textDocument/documentSymbol', params, options) + request_with_options(ms.textDocument_documentSymbol, params, options) end ----@private local function pick_call_hierarchy_item(call_hierarchy_items) if not call_hierarchy_items then return @@ -425,10 +424,9 @@ local function pick_call_hierarchy_item(call_hierarchy_items) return choice end ----@private local function call_hierarchy(method) local params = util.make_position_params() - request('textDocument/prepareCallHierarchy', params, function(err, result, ctx) + request(ms.textDocument_prepareCallHierarchy, params, function(err, result, ctx) if err then vim.notify(err.message, vim.log.levels.WARN) return @@ -450,21 +448,21 @@ end --- |quickfix| window. If the symbol can resolve to multiple --- items, the user can pick one in the |inputlist()|. function M.incoming_calls() - call_hierarchy('callHierarchy/incomingCalls') + call_hierarchy(ms.callHierarchy_incomingCalls) end --- Lists all the items that are called by the symbol under the --- cursor in the |quickfix| window. If the symbol can resolve to --- multiple items, the user can pick one in the |inputlist()|. function M.outgoing_calls() - call_hierarchy('callHierarchy/outgoingCalls') + call_hierarchy(ms.callHierarchy_outgoingCalls) end --- List workspace folders. --- function M.list_workspace_folders() local workspace_folders = {} - for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do + for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do for _, folder in pairs(client.workspace_folders or {}) do table.insert(workspace_folders, folder.name) end @@ -485,11 +483,13 @@ function M.add_workspace_folder(workspace_folder) print(workspace_folder, ' is not a valid directory') return end - local params = util.make_workspace_params( - { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } }, - {} - ) - for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do + local new_workspace = { + uri = vim.uri_from_fname(workspace_folder), + name = workspace_folder, + } + local params = { event = { added = { new_workspace }, removed = {} } } + local bufnr = vim.api.nvim_get_current_buf() + for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do local found = false for _, folder in pairs(client.workspace_folders or {}) do if folder.name == workspace_folder then @@ -499,11 +499,11 @@ function M.add_workspace_folder(workspace_folder) end end if not found then - vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + client.notify(ms.workspace_didChangeWorkspaceFolders, params) if not client.workspace_folders then client.workspace_folders = {} end - table.insert(client.workspace_folders, params.event.added[1]) + table.insert(client.workspace_folders, new_workspace) end end end @@ -518,14 +518,16 @@ function M.remove_workspace_folder(workspace_folder) if not (workspace_folder and #workspace_folder > 0) then return end - local params = util.make_workspace_params( - { {} }, - { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } } - ) - for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do + local workspace = { + uri = vim.uri_from_fname(workspace_folder), + name = workspace_folder, + } + local params = { event = { added = {}, removed = { workspace } } } + local bufnr = vim.api.nvim_get_current_buf() + for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do for idx, folder in pairs(client.workspace_folders) do if folder.name == workspace_folder then - vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + client.notify(ms.workspace_didChangeWorkspaceFolders, params) client.workspace_folders[idx] = nil return end @@ -540,7 +542,7 @@ end --- call, the user is prompted to enter a string on the command line. An empty --- string means no filtering is done. --- ----@param query (string, optional) +---@param query string|nil optional ---@param options table|nil additional options --- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.workspace_symbol(query, options) @@ -549,17 +551,18 @@ function M.workspace_symbol(query, options) return end local params = { query = query } - request_with_options('workspace/symbol', params, options) + request_with_options(ms.workspace_symbol, params, options) end --- Send request to the server to resolve document highlights for the current --- text document position. This request can be triggered by a key mapping or --- by events such as `CursorHold`, e.g.: ---- <pre>vim ---- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() ---- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() ---- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() ---- </pre> +--- +--- ```vim +--- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() +--- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() +--- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() +--- ``` --- --- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups --- to be defined or you won't be able to see the actual highlights. @@ -568,17 +571,25 @@ end --- |hl-LspReferenceWrite| function M.document_highlight() local params = util.make_position_params() - request('textDocument/documentHighlight', params) + request(ms.textDocument_documentHighlight, params) end --- Removes document highlights from current buffer. ---- function M.clear_references() util.buf_clear_references() end ----@private --- +---@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,21 +598,21 @@ 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 = {} - - ---@private +---@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 - -- action kinds are hierarchical with . as a separator: when requesting only - -- 'quickfix' this filter allows both 'quickfix' and 'quickfix.foo', for example - if a.kind:find('^' .. o .. '$') or a.kind:find('^' .. o .. '%.') then + 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 found = true break end @@ -611,53 +622,43 @@ 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 - ---@private - 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 if action.command then local command = type(action.command) == 'table' and action.command or action - local fn = client.commands[command.command] or vim.lsp.commands[command.command] - if fn then - local enriched_ctx = vim.deepcopy(ctx) - enriched_ctx.client_id = client.id - fn(command, enriched_ctx) - else - -- Not using command directly to exclude extra properties, - -- see https://github.com/python-lsp/python-lsp-server/issues/146 - local params = { - command = command.command, - arguments = command.arguments, - workDoneToken = command.workDoneToken, - } - client.request('workspace/executeCommand', params, nil, ctx.bufnr) - end + client._exec_cmd(command, ctx) end end - ---@private - 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[] @@ -672,52 +673,51 @@ local function on_code_action_results(results, ctx, options) -- command: string -- arguments?: any[] -- - local client = vim.lsp.get_client_by_id(action_tuple[1]) - local action = action_tuple[2] - if - not action.edit - and client - and vim.tbl_get(client.server_capabilities, 'codeActionProvider', 'resolveProvider') - then - client.request('codeAction/resolve', action, function(err, resolved_action) + ---@type lsp.Client + 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 = bufnr }) + + local supports_resolve = vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider') + or client.supports_method(ms.codeAction_resolve) + + if not action.edit and client and supports_resolve then + client.request(ms.codeAction_resolve, action, function(err, resolved_action) if err then - vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR) - return + if action.command then + apply_action(action, client, choice.ctx) + else + vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR) + end + else + apply_action(resolved_action, client, choice.ctx) end - apply_action(resolved_action, client) - end) + 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 ----@private -local function code_action_request(params, options) - local bufnr = api.nvim_get_current_buf() - local method = 'textDocument/codeAction' - vim.lsp.buf_request_all(bufnr, method, params, function(results) - local ctx = { bufnr = bufnr, method = method, 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 @@ -743,11 +743,11 @@ end --- - range: (table|nil) --- Range for which code actions should be requested. --- If in visual mode this defaults to the active selection. ---- Table must contain `start` and `end` keys with {row, col} tuples +--- Table must contain `start` and `end` keys with {row,col} tuples --- using mark-like indexing. See |api-indexing| --- ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction ----@see vim.lsp.protocol.constants.CodeActionTriggerKind +---@see vim.lsp.protocol.CodeActionTriggerKind function M.code_action(options) validate({ options = { options, 't', true } }) options = options or {} @@ -764,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() - 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. @@ -795,8 +824,7 @@ function M.execute_command(command_params) arguments = command_params.arguments, workDoneToken = command_params.workDoneToken, } - request('workspace/executeCommand', command_params) + request(ms.workspace_executeCommand, command_params) end return M --- vim:sw=2 ts=2 et |