commit 492ea28612b2e47e187d6a37f582267655ba79fe
parent 9f99bf48ea8dd7c1a98bb4adc1e32d7c763ac9a7
Author: Riley Bruins <ribru17@hotmail.com>
Date: Mon, 16 Jun 2025 09:41:42 -0700
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".
Diffstat:
4 files changed, 93 insertions(+), 12 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -174,6 +174,7 @@ LSP
• Incremental selection is now supported via `textDocument/selectionRange`.
`an` selects outwards and `in` selects inwards.
• Support for multiline semantic tokens.
+• Support for the `disabled` field on code actions.
LUA
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
@@ -1141,20 +1141,26 @@ 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 opts and opts.context and opts.context.only then
- if not a.kind then
- return false
- end
- local found = false
- 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
+ if opts and opts.context then
+ if opts.context.only then
+ if not a.kind then
+ return false
+ end
+ local found = false
+ 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
+ end
+ if not found then
+ return false
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
end
end
@@ -1223,6 +1229,11 @@ local function on_code_action_results(results, opts)
return
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
client:request(ms.codeAction_resolve, action, function(err, resolved_action)
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 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
return title
end
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
@@ -430,6 +430,7 @@ function protocol.make_client_capabilities()
resolveSupport = {
properties = { 'edit', 'command' },
},
+ disabledSupport = true,
},
codeLens = {
dynamicRegistration = false,
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
@@ -4718,6 +4718,70 @@ describe('LSP', function()
eq('workspace/executeCommand', result[5].method)
eq('command:1', result[5].params.command)
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)
describe('vim.lsp.commands', function()