diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index f8b9db2d88..3f97fa3baa 100644 --- 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 index 21d07a9669..e3388fb1ff 100644 --- 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 index 6eeea94a76..d85f26c38c 100644 --- 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 index b54a3b2bd8..66b51f67f2 100644 --- 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()