mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	Merge pull request #14115 from mfussenegger/lsp-commands
lsp: Add a registry for client side code action commands
This commit is contained in:
		| @@ -224,6 +224,11 @@ For |lsp-request|, each |lsp-handler| has this signature: > | ||||
|                                             The ID of the |vim.lsp.client|. | ||||
|                             {bufnr}     (Buffer) | ||||
|                                            Buffer handle, or 0 for current. | ||||
|  | ||||
|                             {params}    (table|nil) | ||||
|                                            The parameters used in the original request | ||||
|                                            which resulted in this handler | ||||
|                                            call. | ||||
|             {config}    (table) | ||||
|                             Configuration for the handler. | ||||
|  | ||||
| @@ -234,6 +239,7 @@ For |lsp-request|, each |lsp-handler| has this signature: > | ||||
|                             To configure a particular |lsp-handler|, see: | ||||
|                                 |lsp-handler-configuration| | ||||
|  | ||||
|  | ||||
|         Returns: ~ | ||||
|             The |lsp-handler| can respond by returning two values: `result, err` | ||||
|               Where `err` must be shaped like an RPC error: | ||||
|   | ||||
| @@ -896,7 +896,7 @@ function lsp.start_client(config) | ||||
|  | ||||
|     local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr) | ||||
|     return rpc.request(method, params, function(err, result) | ||||
|       handler(err, result, {method=method, client_id=client_id, bufnr=bufnr}) | ||||
|       handler(err, result, {method=method, client_id=client_id, bufnr=bufnr, params=params}) | ||||
|     end) | ||||
|   end | ||||
|  | ||||
| @@ -1534,5 +1534,34 @@ function lsp._with_extend(name, options, user_config) | ||||
|   return resulting_config | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Registry for client side commands. | ||||
| --- This is an extension point for plugins to handle custom commands which are | ||||
| --- not part of the core language server protocol specification. | ||||
| --- | ||||
| --- The registry is a table where the key is a unique command name, | ||||
| --- and the value is a function which is called if any LSP action | ||||
| --- (code action, code lenses, ...) triggers the command. | ||||
| --- | ||||
| --- If a LSP response contains a command for which no matching entry is | ||||
| --- available in this registry, the command will be executed via the LSP server | ||||
| --- using `workspace/executeCommand`. | ||||
| --- | ||||
| --- The first argument to the function will be the `Command`: | ||||
| --    Command | ||||
| --      title: String | ||||
| --      command: String | ||||
| --      arguments?: any[] | ||||
| -- | ||||
| --- The second argument is the `ctx` of |lsp-handler| | ||||
| lsp.commands = setmetatable({}, { | ||||
|   __newindex = function(tbl, key, value) | ||||
|     assert(type(key) == 'string', "The key for commands in `vim.lsp.commands` must be a string") | ||||
|     assert(type(value) == 'function', "Command added to `vim.lsp.commands` must be a function") | ||||
|     rawset(tbl, key, value) | ||||
|   end; | ||||
| }) | ||||
|  | ||||
|  | ||||
| return lsp | ||||
| -- vim:sw=2 ts=2 et | ||||
|   | ||||
| @@ -110,7 +110,7 @@ M['client/registerCapability'] = function(_, _, ctx) | ||||
| end | ||||
|  | ||||
| --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction | ||||
| M['textDocument/codeAction'] = function(_, result) | ||||
| M['textDocument/codeAction'] = function(_, result, ctx) | ||||
|   if result == nil or vim.tbl_isempty(result) then | ||||
|     print("No code actions available") | ||||
|     return | ||||
| @@ -127,19 +127,28 @@ M['textDocument/codeAction'] = function(_, result) | ||||
|   if choice < 1 or choice > #result then | ||||
|     return | ||||
|   end | ||||
|   local action_chosen = result[choice] | ||||
|   -- textDocument/codeAction can return either Command[] or CodeAction[]. | ||||
|   -- If it is a CodeAction, it can have either an edit, a command or both. | ||||
|   -- Edits should be executed first | ||||
|   if action_chosen.edit or type(action_chosen.command) == "table" then | ||||
|     if action_chosen.edit then | ||||
|       util.apply_workspace_edit(action_chosen.edit) | ||||
|     end | ||||
|     if type(action_chosen.command) == "table" then | ||||
|       buf.execute_command(action_chosen.command) | ||||
|   local action = result[choice] | ||||
|   -- textDocument/codeAction can return either Command[] or CodeAction[] | ||||
|   -- | ||||
|   -- CodeAction | ||||
|   --  ... | ||||
|   --  edit?: WorkspaceEdit    -- <- must be applied before command | ||||
|   --  command?: Command | ||||
|   -- | ||||
|   -- Command: | ||||
|   --  title: string | ||||
|   --  command: string | ||||
|   --  arguments?: any[] | ||||
|   -- | ||||
|   if action.edit then | ||||
|     util.apply_workspace_edit(action.edit) | ||||
|   end | ||||
|   local command = type(action.command) == 'table' and action.command or action | ||||
|   local fn = vim.lsp.commands[command.command] | ||||
|   if fn then | ||||
|     fn(command, ctx) | ||||
|   else | ||||
|     buf.execute_command(action_chosen) | ||||
|     buf.execute_command(command) | ||||
|   end | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -2353,6 +2353,10 @@ describe('LSP', function() | ||||
|           eq(0, signal, "exit signal", fake_lsp_logfile) | ||||
|         end; | ||||
|         on_handler = function(err, result, ctx) | ||||
|           -- Don't compare & assert params, they're not relevant for the testcase | ||||
|           -- This allows us to be lazy and avoid declaring them | ||||
|           ctx.params = nil | ||||
|  | ||||
|           eq(table.remove(test.expected_handlers), {err, result, ctx}, "expected handler") | ||||
|           if ctx.method == 'start' then | ||||
|             exec_lua("vim.lsp.buf.rename()") | ||||
| @@ -2370,4 +2374,42 @@ describe('LSP', function() | ||||
|     end | ||||
|   end) | ||||
|  | ||||
|   describe('vim.lsp.buf.code_action', function() | ||||
|     it('Calls client side command if available', function() | ||||
|       eq(1, exec_lua [[ | ||||
|         local dummy_calls = 0 | ||||
|         vim.lsp.commands.dummy = function() | ||||
|           dummy_calls = dummy_calls + 1 | ||||
|         end | ||||
|         local actions = { | ||||
|           { | ||||
|             title = 'Dummy command', | ||||
|             command = 'dummy', | ||||
|           }, | ||||
|         } | ||||
|         -- inputlist would require input and block the test; | ||||
|         vim.fn.inputlist = function() | ||||
|           return 1 | ||||
|         end | ||||
|         local params = {} | ||||
|         local handler = require'vim.lsp.handlers'['textDocument/codeAction'] | ||||
|         handler(nil, actions, { method = 'textDocument/codeAction', params = params }, nil) | ||||
|         return dummy_calls | ||||
|       ]]) | ||||
|     end) | ||||
|   end) | ||||
|   describe('vim.lsp.commands', function() | ||||
|     it('Accepts only string keys', function() | ||||
|       matches( | ||||
|         '.*The key for commands in `vim.lsp.commands` must be a string', | ||||
|         pcall_err(exec_lua, 'vim.lsp.commands[1] = function() end') | ||||
|       ) | ||||
|     end) | ||||
|     it('Accepts only function values', function() | ||||
|       matches( | ||||
|         '.*Command added to `vim.lsp.commands` must be a function', | ||||
|         pcall_err(exec_lua, 'vim.lsp.commands.dummy = 10') | ||||
|       ) | ||||
|     end) | ||||
|   end) | ||||
| end) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Lingelbach
					Michael Lingelbach