mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(lsp): add a registry for client side code action commands
This builds on https://github.com/neovim/neovim/pull/14112 and closes https://github.com/neovim/neovim/issues/12326
This commit is contained in:
		| @@ -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) | ||||
|     end | ||||
|   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 | ||||
|  | ||||
|   | ||||
| @@ -2374,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
	 Mathias Fussenegger
					Mathias Fussenegger