aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp/buf.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 21:52:58 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 21:52:58 +0000
commit931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch)
treed8c1843a95da5ea0bb4acc09f7e37843d9995c86 /runtime/lua/vim/lsp/buf.lua
parent142d9041391780ac15b89886a54015fdc5c73995 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-userreg.tar.gz
rneovim-userreg.tar.bz2
rneovim-userreg.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.lua380
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