feat(lsp): handle disabled code actions (#34453)

This commit also makes it so that disabled code actions are not
displayed unless the trigger kind is "Invoked".
This commit is contained in:
Riley Bruins
2025-06-16 09:41:42 -07:00
committed by GitHub
parent 9f99bf48ea
commit 492ea28612
4 changed files with 93 additions and 12 deletions

View File

@@ -174,6 +174,7 @@ LSP
• Incremental selection is now supported via `textDocument/selectionRange`. • Incremental selection is now supported via `textDocument/selectionRange`.
`an` selects outwards and `in` selects inwards. `an` selects outwards and `in` selects inwards.
• Support for multiline semantic tokens. • Support for multiline semantic tokens.
• Support for the `disabled` field on code actions.
LUA LUA

View File

@@ -1141,20 +1141,26 @@ local function on_code_action_results(results, opts)
---@param a lsp.Command|lsp.CodeAction ---@param a lsp.Command|lsp.CodeAction
local function action_filter(a) local function action_filter(a)
-- filter by specified action kind -- filter by specified action kind
if opts and opts.context and opts.context.only then if opts and opts.context then
if not a.kind then if opts.context.only then
return false if not a.kind then
end return false
local found = false end
for _, o in ipairs(opts.context.only) do local found = false
-- action kinds are hierarchical with . as a separator: when requesting only 'type-annotate' for _, o in ipairs(opts.context.only) do
-- this filter allows both 'type-annotate' and 'type-annotate.foo', for example -- action kinds are hierarchical with . as a separator: when requesting only 'type-annotate'
if a.kind == o or vim.startswith(a.kind, o .. '.') then -- this filter allows both 'type-annotate' and 'type-annotate.foo', for example
found = true if a.kind == o or vim.startswith(a.kind, o .. '.') then
break found = true
break
end
end
if not found then
return false
end end
end end
if not found then -- Only show disabled code actions when the trigger kind is "Invoked".
if a.disabled and opts.context.triggerKind ~= lsp.protocol.CodeActionTriggerKind.Invoked then
return false return false
end end
end end
@@ -1223,6 +1229,11 @@ local function on_code_action_results(results, opts)
return return
end end
if action.disabled then
vim.notify(action.disabled.reason, vim.log.levels.ERROR)
return
end
if not (action.edit and action.command) and client:supports_method(ms.codeAction_resolve) then if not (action.edit and action.command) and client:supports_method(ms.codeAction_resolve) then
client:request(ms.codeAction_resolve, action, function(err, resolved_action) client:request(ms.codeAction_resolve, action, function(err, resolved_action)
if err then if err then
@@ -1253,6 +1264,10 @@ local function on_code_action_results(results, opts)
local clients = lsp.get_clients({ bufnr = item.ctx.bufnr }) local clients = lsp.get_clients({ bufnr = item.ctx.bufnr })
local title = item.action.title:gsub('\r\n', '\\r\\n'):gsub('\n', '\\n') local title = item.action.title:gsub('\r\n', '\\r\\n'):gsub('\n', '\\n')
if item.action.disabled then
title = title .. ' (disabled)'
end
if #clients == 1 then if #clients == 1 then
return title return title
end end

View File

@@ -430,6 +430,7 @@ function protocol.make_client_capabilities()
resolveSupport = { resolveSupport = {
properties = { 'edit', 'command' }, properties = { 'edit', 'command' },
}, },
disabledSupport = true,
}, },
codeLens = { codeLens = {
dynamicRegistration = false, dynamicRegistration = false,

View File

@@ -4718,6 +4718,70 @@ describe('LSP', function()
eq('workspace/executeCommand', result[5].method) eq('workspace/executeCommand', result[5].method)
eq('command:1', result[5].params.command) eq('command:1', result[5].params.command)
end) end)
it('supports disabled actions', function()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server({
capabilities = {
executeCommandProvider = {
commands = { 'command:1' },
},
codeActionProvider = {
resolveProvider = true,
},
},
handlers = {
['textDocument/codeAction'] = function(_, _, callback)
callback(nil, {
{
title = 'Code Action 1',
disabled = {
reason = 'This action is disabled',
},
},
})
end,
['codeAction/resolve'] = function(_, _, callback)
callback(nil, {
title = 'Code Action 1',
command = {
title = 'Command 1',
command = 'command:1',
},
})
end,
},
})
local client_id = assert(vim.lsp.start({
name = 'dummy',
cmd = server.cmd,
}))
--- @diagnostic disable-next-line:duplicate-set-field
vim.notify = function(message, code)
server.messages[#server.messages + 1] = {
params = {
message = message,
code = code,
},
}
end
vim.lsp.buf.code_action({ apply = true })
vim.lsp.stop_client(client_id)
return server.messages
end)
eq(
exec_lua(function()
return { message = 'This action is disabled', code = vim.log.levels.ERROR }
end),
result[4].params
)
-- No command is resolved/applied after selecting a disabled code action
eq('shutdown', result[5].method)
end)
end) end)
describe('vim.lsp.commands', function() describe('vim.lsp.commands', function()