yegappan / lsp

Language Server Protocol (LSP) plugin for Vim9
MIT License
478 stars 56 forks source link

How to ExecuteCommand for servers which provides custom commands #563

Open raiansantos opened 1 week ago

raiansantos commented 1 week ago

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?

Konfekt commented 1 week ago

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.

raiansantos commented 1 week ago

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.

Konfekt commented 1 week ago

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.
raiansantos commented 1 week ago

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.

Konfekt commented 1 week ago

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)?