neovim / neovim

Vim-fork focused on extensibility and usability
https://neovim.io
Other
81.02k stars 5.54k forks source link

LSP: user can easily discover, define client commands #28329

Open justinmk opened 4 months ago

justinmk commented 4 months ago

Problem

LSP servers have various custom capabilities and commands that are not part of the LSP spec, e.g.:

The current situation with nvim-lspconfig is that 10+ lines of code are needed to define each command or custom capability. That's unsustainable, we can't maintain that for 100s of LSP servers.

See also:

Expected behavior

Reduce friction for users to use custom (off-spec) server capabilities/commands:

  1. Provide a way to discover/iterate through server capabilities custom (off-spec) commands.
  2. Improve the commands interface and/or provide util functions so that defining commands/aliases to call custom (off-spec) server commands requires 2x less code.

Questions

Possible interface

From https://github.com/neovim/nvim-lspconfig/issues/1937#issuecomment-1481033025 (the names don't matter right now, they are just pseudo-code; the point is that we need a generic interface for (1) iterating custom shit from the LSP server and (2) enabling users to create their own mappings to this stuff):

some sort of "sub-command" entrypoint so that langserver-specific commands can be discovered. E.g.

  • :Lsp <tab> would complete available langserver-specific commands.
  • :LspCapabilties <tab> would show... something.
mfussenegger commented 4 months ago

We need a general mechanism that makes it easy for users and scripts (such as nvim-lspconfig) to:

  1. iterate through discovered server capabilities / commands
  2. define aliases to customer server commands and capabilities, in a non-verbose way

I think there's a bit of a misunderstanding here.

LSP defines two types of commands:

The current situation with nvim-lspconfig is that 10+ lines of code are needed to define each command or custom capability. That's unsustainable, we can't maintain that for 100s of LSP servers.

Some of the client commands could become part of the specification. But for some of them there are issues with no clear path forward (E.g. https://github.com/microsoft/language-server-protocol/issues/1641)

Other than that, I don't see what else neovim core could do. Other than delegating more of the work to language specific plugins.

justinmk commented 4 months ago

I think there's a bit of a misunderstanding here.

Yes, I'm just feeling around here. I updated the title + description.

LSP defines two types of commands ...

  • server commands server_capabilities.executeCommandProvider.commands ... user triggers code actions. We cannot provide a generic way for users to call these commands directly

✅ Those are discovered and presented via "code actions" menu, correct? So that's already covered.

  • client commands. ... These are not advertised by the server in server_capabilities. Typically the client informs the server that it supports them via settings or init_options in the initialize request.

Using rust-analyzer experimental/externalDocs as an example: is there any way to list all of its experimental/xx methods?

Are these goals tractable:

Example

Code required to add custom rust-analyzer commands:

Before

code ```lua local function reload_workspace(bufnr) bufnr = util.validate_bufnr(bufnr) local clients = vim.lsp.get_active_clients { name = 'rust_analyzer', bufnr = bufnr } for _, client in ipairs(clients) do vim.notify 'Reloading Cargo Workspace' client.request('rust-analyzer/reloadWorkspace', nil, function(err) if err then error(tostring(err)) end vim.notify 'Cargo workspace reloaded' end, 0) end end local function open_docs(bufnr) bufnr = util.validate_bufnr(bufnr) vim.lsp.buf_request(bufnr, 'experimental/externalDocs', vim.lsp.util.make_position_params(), function(err, url) if err then error(tostring(err)) else vim.fn['netrw#BrowseX'](url, 0) end end) end vim.lsp.start{ ..., commands = { CargoReload = { function() reload_workspace(0) end, description = 'Reload current cargo workspace', }, RustOpenDocs = { function() open_docs(0) end, description = 'Open documentation for the symbol under the cursor in default browser', }, }, } ```

After

Assuming we provide a util function like:

vim.lsp.command(name:string, on_done:function)
-- Works like buf_request_all ?
vim.lsp.command_all(name:string, on_done:function)

The code would now look like:

TODO
asmodeus812 commented 4 months ago

@justinmk other lsp clients, such as coc or vscode, use something similar to a command palette (other ides also have similar interfaces which group actions in an easily filterable/discoverable pick-list). What i suggest is we provide a way (e.g. an interface for attaching commands, and another interface on top of ui.select for the actual ui presentation) for other plugins (such as for example nvim-jdtls @mfussenegger, which has quite a few user commands exposing some of the internal offspec jdtls capabilities) to easily attach their own custom commands, instead of creating UserCommands as they do atm. It should look/feel similar, and it is pretty much an analogue to code_actions.

Here is how coc nvim exposes all the commands that the different extensions register. For nvim those should maybe be filtered by filetype, currently active clients etc etc, but this is the general idea. If we provide this, it will be easy for plugin users to discover custom commands that the lsp extension plugins expose and they will not need to learn new user commands, if they can fuzzy search them or their description.

image

There are others, which have a well defined behavior, which we can expose/add from nvim too, eventually, such as workspace related actions - displaying currently attached workspace folders, or inspecting workspace edits and so on.

image

mfussenegger commented 4 months ago

Are these goals tractable:

reduce the amount of boilerplate needed to define commands, by at least 2x. reduce the amount of documentation needed to explain how users can create custom commands provide concise (but fully-working) examples

We could document some examples, but I don't think you can reduce the amount of boilerplate by 2x.

Using rust-analyzer experimental/externalDocs as an example: is there any way to list all of its experimental/xx methods?

This isn't a client command. It's just a extra LSP method. There is no discovery mechanism for these. Just the documentation of the language server - or in many cases not even that, but reverse engineering of vscode extensions.

The main problems here are:

This is all specific to the method.

And the open_docs example is also kinda broken, it should use client.request like the reload_workspace example. vim.lsp.buf_request would make a request using all clients attached to a buffer, which means other servers not supporting it would receive the request too, and very likely fail.

Client commands on the other hand can be used instead of server commands, and are part of some other operations like code-actions. The difference to server commands is that they usually have more round-trips.

For example nvim-jdtls defines a java.action.generateToStringPrompt command (using vim.lsp.commands, which is documented), and advertises it in the settings of the initiailize request. The workflow is then like this:

This is all completely custom


Also note that what lspconfig does (or did?) with commands was to provide sugar for neovim user commands definitions. That's not really specific to the LSP module at all.

justinmk commented 4 months ago

Here is how coc nvim exposes all the commands that the different extensions register

@asmodeus812 but how are those commands discovered? Where is that info coming from?

Using rust-analyzer experimental/externalDocs as an example: is there any way to list all of its experimental/xx methods?

This isn't a client command. It's just a extra LSP method. There is no discovery mechanism for these. Just the documentation of the language server - or in many cases not even that, but reverse engineering of vscode extensions.

That isn't part of the "capabilities" response? It's strange that servers can't list their custom methods.

The main problems here are:

  • You need to know the arguments required, and logic to create them.
  • You need to know the response type
  • You need custom logic to process the response.

Understood. But even just listing the names, and point to the upstream docs, would be very helpful, and avoids support requests. Some UX tweaks may be enough.

nvim-lspconfig has a CI job that pulls package.json files to get various bits of info. But custom methods aren't declared in package.json files, they are just loosely documented as you mentioned.

asmodeus812 commented 4 months ago

Here is how coc nvim exposes all the commands that the different extensions register.

@justinmk they are registered through/using the coc-lsp client's api, by each extension, i think that was obvious from my reply. This is also what vs code does, the commands are registered by the extension, but the api to interact/add/register/use those commands is provided by the vs code lsp client as a library itself.

e.g. https://github.com/neoclide/coc-java/blob/43921b1eceb450a25d62545f6f206fd827235752/src/sourceAction.ts#L12

clason commented 4 months ago

This is also what vs code does, the commands are registered.

We're not VS Code, though, on purpose; neither do we plan on replacing coc.nvim. We implement the LSP specification and nothing but the specification. Anything beyond that is intentionally left for server-specific plugins (which correspond to VS Code extensions) built on top of the base LSP API (as well as other methods that compose nicely, which this may or may not be relevant to).

asmodeus812 commented 4 months ago

@clason in that case if nvim only adheres to the spec and only the spec then this issue can be closed as resolved / not planned.

clason commented 4 months ago

Not quite; the last part about "other methods that compose nicely" may still be actionable here. (We do want to add general API methods that makes it easier to write such custom plugins -- given a positive, individual, cost-benefit analysis, of course.)

mfussenegger commented 4 months ago

That isn't part of the "capabilities" response? It's strange that servers can't list their custom methods.

No, the capabilities only contain the capabilities defined in the specification and there is also no OpenAPI style dynamic introspection of methods.

We could suggest to add that, but even if it were added, due to the BWC nature of the protocol, it would remain optional.

Understood. But even just listing the names, and point to the upstream docs, would be very helpful, and avoids support requests. Some UX tweaks may be enough.

We could add a lsp-offspec-extensions doc section, that lists the two common scenarios for plugin devs, including examples and some prose on how to best implement them:

asmodeus812 commented 4 months ago

the commands are registered by the extension, but the api to interact/add/register/use those commands is provided by the client.

We do want to add general API methods that makes it easier to write such custom plugins

@clason Well yes then i agree and it does match up with i said those clients do already at the moment as a solution to this very problem