How to ExecuteCommand for servers which provides custom commands
TSServer implements custom commands which can be used to trigger some actions. https://github.com/typescript-language-server/typescript-language-server?tab=readme-ov-file#workspace-commands-workspaceexecutecommand
Is there a way in the client to call those custom actions?
Maybe a :LspExecuteCommand _typescript.organizeImports?
The whole setup could be more filetype specific, see also https://github.com/yegappan/lsp/pull/562
As a new user, the list of :Lsp... commands felt long, and often redundant. Aftplugin/&ft.vim suggesting sensible default mappings could foster adaption.
I suggested a new command for it, but not sure if we really need it. I mean, LSP sends the available custom commands through the executeCommandProvider capability.
We could have those commands appended to the Code Actions (since they work pretty similar)?
btw, I used tsserver as example, but figured out that ruff server used for python formatting also has some custom commands.
I am new to this plug-in. What was missing in :help lsp-custom-commands then to implement something similar to g:LspRegisterCmdHandler('java.apply.workspaceEdit', WorkspaceEdit) for tsserver or ruff server?
doc/lsp.txt (lines 1750-1773)
14. Custom Command Handlers *lsp-custom-commands*
When applying a code action, the language server may issue a non-standard
command. For example, the Java language server uses non-standard commands
(e.g. java.apply.workspaceEdit). To handle these commands, you can register a
callback function for each command using the LspRegisterCmdHandler() function.
For example: >
vim9script
import autoload "lsp/textedit.vim"
def WorkspaceEdit(cmd: dict<any>)
for editAct in cmd.arguments
textedit.ApplyWorkspaceEdit(editAct)
endfor
enddef
g:LspRegisterCmdHandler('java.apply.workspaceEdit', WorkspaceEdit)
<
Place the above code in a file named lsp_java/plugin/lsp_java.vim and load
this plugin.
The callback function should accept a Dict argument. The Dict argument
contains the LSP Command interface fields. Refer to the LSP specification for
more information about the "Command" interface.
I don't believe it has the behavior I need. RegisterCmdHandler listen to commands that comes from the server and do actions as needed. The example you shared (from the doc) wait for a java.apply.workspaceEdit from the server and calls textedit.Apply.. to current buffer. Its a communication Server => Client.
What I need is a way to call a command from the client. Calling a source.removeUnused.ts from the client, by example, would ask to the server remove all unused variables, which would generate a response and the client would apply as usual.
Okay, so this is for receiving from the server instead of sending. I wonder how :help LspCodeAction solves this as it can only be applied to a diagnostic in the current line:
# doc/lsp.txt (lines 704-718)
:LspCodeAction [query] Apply the code action supplied by the language server
to the diagnostic in the current line. This works only
if there is a diagnostic message for the current line.
You can use the ":LspDiag current" command to display
the diagnostic for the current line.
When [query] is given the code action starting with
[query] will be applied. [query] can be a regexp
pattern, or a digit corresponding to the index of the
code actions in the created prompt.
When [query] is not given you will be prompted to
select one of the actions supplied by the language
server.
But in principle
# autoload/lsp/codeaction.vim (lines 15-26)
export def DoCommand(lspserver: dict<any>, cmd: dict<any>)
if cmd->has_key('command') && CommandHandlers->has_key(cmd.command)
var CmdHandler: func = CommandHandlers[cmd.command]
try
call CmdHandler(cmd)
catch
util.ErrMsg($'"{cmd.command}" handler raised exception {v:exception}')
endtry
else
lspserver.executeCommand(cmd)
endif
enddef
could be used, which at the moment is only used in CodeAction/Lens. Maybe that's what you meant with
I suggested a new command for it, but not sure if we really need it. I mean, LSP sends the available custom commands through the executeCommandProvider capability. We could have those commands appended to the Code Actions (since they work pretty similar)?
You can now make custom requests to the server and map it to a command
https://github.com/yegappan/lsp/pull/640
Thank you! Sounds like @itsraian 's concerns had been addressed?
@jclsn Do I understand correctly #640 allows to send custom message asynchronously but there's no way to process response as there's currently no way to pass custom callback and pre-set one does nothing?
I am trying to run sequence of code actions to format code and organize imports as BufWritePre autocommand with gopls. I am able to use this to send message, but not able to intercept and process the response:
09/22/25 02:21:06: Sent {"method":"textDocument/codeAction","params":{"context":{"only":["source.organizeImports"]},"range":{"end":{"character":0,"line":8},"start":{"character":0,"line":0}},"textDocument":{"uri":"file:///...foo.go"}}}
09/22/25 02:21:06: Received {"id":2,"jsonrpc":"2.0","result":[{"edit":{"documentChanges":[{"edits":[{"range":{"end":{"character":11,"line":2},"start":{"character":7,"line":2}},"newText":""},{"range":{"end":{"character":0,"line":4},"start":{"character":0,"line":3}},"newText":""}],"textDocument":{"uri":"file:///...foo.go","version":3}}]},"kind":"source.organizeImports","title":"Organize Imports"}]}
As far as I understand there's no method so I can't create custom hook for the response.
@blami I haven't looked into this. The current implementation was enough for my needs. I thought about also adding a synchronous command, but @yegappan merged it quickly without comments. It could be easily done, but no idea how to intercept the response.
There is this command, which sends an asynchronous request. You can process the reply there probably, but I haven't tried to do this.
https://github.com/yegappan/lsp/blob/21cd18535d63b18fcab58b36f07e23f161e5506b/autoload/lsp/lspserver.vim#L1050
Currently it's not doing anything with it according to this function. The responses are somehow handled and printed to the statusline though. It worked out of the box for me.
https://github.com/yegappan/lsp/blob/21cd18535d63b18fcab58b36f07e23f161e5506b/autoload/lsp/lspserver.vim#L1789
@jclsn got it, I proposed change in #663 and also proposed to have both sync and async variant.