diff options
-rw-r--r-- | runtime/doc/lsp.txt | 26 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 56 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 1 | ||||
-rw-r--r-- | test/functional/fixtures/fake-lsp-server.lua | 30 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 36 |
5 files changed, 125 insertions, 24 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index b704d2d6e8..bf4c2b59d1 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -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 diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index eb7ec579f1..00919c6b04 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -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. diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 86c9e2fd58..2cd728ea36 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -645,6 +645,7 @@ function protocol.make_client_capabilities() end)(); }; }; + isPreferredSupport = true; dataSupport = true; resolveSupport = { properties = { 'edit', } diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index 7ff3713d41..3375a5dda9 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -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() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 436b431e38..03ed167bac 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -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() |