mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
feat(lsp): options to filter and auto-apply code actions (#18221)
Implement two new options to vim.lsp.buf.code_action(): - filter (function): predicate taking an Action as input, and returning a boolean. - apply (boolean): when set to true, and there is just one remaining action (after filtering), the action is applied without user query. These options can, for example, be used to filter out, and automatically apply, the action indicated by the server to be preferred: vim.lsp.buf.code_action({ filter = function(action) return action.isPreferred end, apply = true, }) Fix #17514.
This commit is contained in:
parent
de2232878f
commit
df09e03cf7
@ -969,18 +969,28 @@ add_workspace_folder({workspace_folder})
|
||||
clear_references() *vim.lsp.buf.clear_references()*
|
||||
Removes document highlights from current buffer.
|
||||
|
||||
code_action({context}) *vim.lsp.buf.code_action()*
|
||||
code_action({options}) *vim.lsp.buf.code_action()*
|
||||
Selects a code action available at the current cursor
|
||||
position.
|
||||
|
||||
Parameters: ~
|
||||
{context} table|nil `CodeActionContext` of the LSP specification:
|
||||
• diagnostics: (table|nil) LSP`Diagnostic[]` . Inferred from the current position if not
|
||||
provided.
|
||||
• only: (string|nil) LSP `CodeActionKind` used
|
||||
to filter the code actions. Most language
|
||||
servers support values like `refactor` or
|
||||
`quickfix`.
|
||||
{options} table|nil Optional table which holds the
|
||||
following optional fields:
|
||||
• context (table|nil): Corresponds to `CodeActionContext` of the LSP specification:
|
||||
• diagnostics (table|nil): LSP`Diagnostic[]` . Inferred from the current position if not
|
||||
provided.
|
||||
• only (string|nil): LSP `CodeActionKind`
|
||||
used to filter the code actions. Most
|
||||
language servers support values like
|
||||
`refactor` or `quickfix`.
|
||||
|
||||
• filter (function|nil): Predicate function
|
||||
taking an `CodeAction` and returning a
|
||||
boolean.
|
||||
• apply (boolean|nil): When set to `true`, and
|
||||
there is just one remaining action (after
|
||||
filtering), the action is applied without
|
||||
user query.
|
||||
|
||||
See also: ~
|
||||
https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
||||
|
@ -491,11 +491,14 @@ 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)
|
||||
local function on_code_action_results(results, ctx, options)
|
||||
local action_tuples = {}
|
||||
local filter = options and options.filter
|
||||
for client_id, result in pairs(results) do
|
||||
for _, action in pairs(result.result or {}) do
|
||||
table.insert(action_tuples, { client_id, action })
|
||||
if not filter or filter(action) then
|
||||
table.insert(action_tuples, { client_id, action })
|
||||
end
|
||||
end
|
||||
end
|
||||
if #action_tuples == 0 then
|
||||
@ -557,6 +560,13 @@ local function on_code_action_results(results, 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])
|
||||
return
|
||||
end
|
||||
|
||||
vim.ui.select(action_tuples, {
|
||||
prompt = 'Code actions:',
|
||||
kind = 'codeaction',
|
||||
@ -571,35 +581,49 @@ end
|
||||
--- Requests code actions from all clients and calls the handler exactly once
|
||||
--- with all aggregated results
|
||||
---@private
|
||||
local function code_action_request(params)
|
||||
local function code_action_request(params, options)
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local method = 'textDocument/codeAction'
|
||||
vim.lsp.buf_request_all(bufnr, method, params, function(results)
|
||||
on_code_action_results(results, { bufnr = bufnr, method = method, params = params })
|
||||
local ctx = { bufnr = bufnr, method = method, params = params}
|
||||
on_code_action_results(results, ctx, options)
|
||||
end)
|
||||
end
|
||||
|
||||
--- Selects a code action available at the current
|
||||
--- cursor position.
|
||||
---
|
||||
---@param context table|nil `CodeActionContext` of the LSP specification:
|
||||
--- - diagnostics: (table|nil)
|
||||
--- LSP `Diagnostic[]`. Inferred from the current
|
||||
--- position if not provided.
|
||||
--- - only: (string|nil)
|
||||
--- LSP `CodeActionKind` used to filter the code actions.
|
||||
--- Most language servers support values like `refactor`
|
||||
--- or `quickfix`.
|
||||
---@param options table|nil Optional table which holds the following optional fields:
|
||||
--- - context (table|nil):
|
||||
--- Corresponds to `CodeActionContext` of the LSP specification:
|
||||
--- - diagnostics (table|nil):
|
||||
--- LSP `Diagnostic[]`. Inferred from the current
|
||||
--- position if not provided.
|
||||
--- - only (string|nil):
|
||||
--- LSP `CodeActionKind` used to filter the code actions.
|
||||
--- Most language servers support values like `refactor`
|
||||
--- or `quickfix`.
|
||||
--- - filter (function|nil):
|
||||
--- Predicate function taking an `CodeAction` and returning a boolean.
|
||||
--- - apply (boolean|nil):
|
||||
--- When set to `true`, and there is just one remaining action
|
||||
--- (after filtering), the action is applied without user query.
|
||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
||||
function M.code_action(context)
|
||||
validate { context = { context, 't', true } }
|
||||
context = context or {}
|
||||
function M.code_action(options)
|
||||
validate { options = { options, 't', true } }
|
||||
options = options or {}
|
||||
-- Detect old API call code_action(context) which should now be
|
||||
-- code_action({ context = context} )
|
||||
if options.diagnostics or options.only then
|
||||
options = { options = options }
|
||||
end
|
||||
local context = options.context or {}
|
||||
if not context.diagnostics then
|
||||
context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
|
||||
end
|
||||
local params = util.make_range_params()
|
||||
params.context = context
|
||||
code_action_request(params)
|
||||
code_action_request(params, options)
|
||||
end
|
||||
|
||||
--- Performs |vim.lsp.buf.code_action()| for a given range.
|
||||
|
@ -645,6 +645,7 @@ function protocol.make_client_capabilities()
|
||||
end)();
|
||||
};
|
||||
};
|
||||
isPreferredSupport = true;
|
||||
dataSupport = true;
|
||||
resolveSupport = {
|
||||
properties = { 'edit', }
|
||||
|
@ -673,6 +673,36 @@ function tests.code_action_with_resolve()
|
||||
}
|
||||
end
|
||||
|
||||
function tests.code_action_filter()
|
||||
skeleton {
|
||||
on_init = function()
|
||||
return {
|
||||
capabilities = {
|
||||
codeActionProvider = {
|
||||
resolveProvider = false
|
||||
}
|
||||
}
|
||||
}
|
||||
end;
|
||||
body = function()
|
||||
notify('start')
|
||||
local action = {
|
||||
title = 'Action 1',
|
||||
command = 'command'
|
||||
}
|
||||
local preferred_action = {
|
||||
title = 'Action 2',
|
||||
isPreferred = true,
|
||||
command = 'preferred_command',
|
||||
}
|
||||
expect_request('textDocument/codeAction', function()
|
||||
return nil, { action, preferred_action, }
|
||||
end)
|
||||
notify('shutdown')
|
||||
end;
|
||||
}
|
||||
end
|
||||
|
||||
function tests.clientside_commands()
|
||||
skeleton {
|
||||
on_init = function()
|
||||
|
@ -2665,6 +2665,42 @@ describe('LSP', function()
|
||||
end
|
||||
}
|
||||
end)
|
||||
it('Filters and automatically applies action if requested', function()
|
||||
local client
|
||||
local expected_handlers = {
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
test_rpc_server {
|
||||
test_name = 'code_action_filter',
|
||||
on_init = function(client_)
|
||||
client = client_
|
||||
end,
|
||||
on_setup = function()
|
||||
end,
|
||||
on_exit = function(code, signal)
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end,
|
||||
on_handler = function(err, result, ctx)
|
||||
eq(table.remove(expected_handlers), {err, result, ctx})
|
||||
if ctx.method == 'start' then
|
||||
exec_lua([[
|
||||
vim.lsp.commands['preferred_command'] = function(cmd)
|
||||
vim.lsp.commands['executed_preferred'] = function()
|
||||
end
|
||||
end
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
|
||||
vim.lsp.buf.code_action({ filter = function(a) return a.isPreferred end, apply = true, })
|
||||
]])
|
||||
elseif ctx.method == 'shutdown' then
|
||||
eq('function', exec_lua[[return type(vim.lsp.commands['executed_preferred'])]])
|
||||
client.stop()
|
||||
end
|
||||
end
|
||||
}
|
||||
end)
|
||||
end)
|
||||
describe('vim.lsp.commands', function()
|
||||
it('Accepts only string keys', function()
|
||||
|
Loading…
Reference in New Issue
Block a user