-
Notifications
You must be signed in to change notification settings - Fork 818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
What's the correct way for an LSP server to know whether a command is available on the client? #642
Comments
Asking for input is not supported in the LSP right now and using commands for this is IMO the wrong way to approach this. If we thing a server should be able to ask for user input we should add corresponding requests send from the client to the server. However in general I am more a fan of having more refactoring requests that flow from the client to the server and the client queries the input parameters upfront. |
+1, the LSP should work on identifying and spec'ing usual refactoring
contracts instead of introducing some too generic and not sustainable
framework to orchestrate interaction with users. The risk is that is we
start allowing user interaction, we'll basically redefine a form toolkit
like many have existed and failed; we can do better and reach better
quality and sustainability by bringing the knowledge inside the LSP spec.
Do you think you could spec the refactoring you're working on as LSP
operations?
|
I don't think this changes the problem. The server needs to know whether it can provide a code action that relies on getting user input, and that means the server needs to know if the command that it'll assign to the code action is available. For example, in v1 of my LSP server, I have 0 code actions. In v2, I add a code action for "Extract Widget" which requires some input from the user. Even if I implement that user input in the client extension (which I think increases the burden on the client extension dev, but that aside..) I need to know whether the connected client has the implementation for this, and therefore I need to know if I can send the code action that references that command? (So I think the rename of this issue may be misleading - I think it's valuable for a server to know whether a client extension implements/supports a command, whether it's for asking for input or not?). |
In this case, the one I'm trying to implement is "Extract Widget". It's basically similar to behaviour of Extract Method, however it needs to exist alongside it (because it creates a special class, not just a method, and creating a method is also valid). This is very specific to Flutter so I don't think it's likely the LSP spec would ever have a specific request for it. Of course, we can implement a lot of this by just adding more code to the client, but then we lose some of the benefit of LSP (making it simple to get the language into other editors). I understand the concerns with exposing things like input to the server (since it could lead to a crappy experience if I request 5 pieces of input individually in an editor like VS where the user would expect a dialog with them all) but without this, I think there would need to be some fairly flexible API that allows for custom refactors that are fairly specific to a server. |
Sorry, I think I misunderstood this as something that we should do ourselves, but I guess you mean extending LSP to be able to query the server for this metadata and then let the client (editor, not extension) handle collecting the input? |
Indeed, protocol extensions loose most benefits of using LSP.
Can't you instead of requesting user input upfront, deal with parameter capturing in a similar way as a CompletionItem, allowing Snippet mode which allows to modify parameters after the refactoring? |
In this specific case (Extract Widget) it probably would, but it'd require That said - I still think the original request is very valid. A client should be able to communicate which commands it supports to the server. Servers can return identifiers of commands against code actions - why shouldn't they be able to know whether or not the client can actually handle it? If I want a code action that requires a lot of custom code running in the client extension, I'd want to do something like:
The first step here should be able to read a capability from the client to know what the client command is available. I'm not saying the client should transmit all its internal commands, it's fine for the client extension to have to declare them (like the server declares which it can handle, so the client knows when to send them on to it). |
A server at the end should only trigger those commands it implements or has documented as something the client extension needs to provide. I think it will not be enough if a client lists these commands. It would require specing their behavior as well. This is why I think we are better off specing a corresponding request or notification. |
But then how do I add a new code action that requires a command from the client extension that wasn't in the first shipping version? Isn't the purpose of client capabilities to communicate this sort of thing? The server already advertises which commands it supports, so it seems natural (to me) for the client to do the same - they they both know which commands are available on the other. |
The reason why a server advertises its commands is that a client knows about them and delegates the execution of the commands back to the client. If is more like a function dispatching. As said I am not a fan of starting to spec client commands for the above reasons. Even if clients advertise them we would need to specify what they are support to do. Or am I misunderstanding you? |
I think requiring a client to be code specifically for a given feature in a server is a bad idea, and goes against the whole point of LSP. For the haskell refactorer I eventually realized you can go a long way using just code actions and rename, if you apply default names to say a newly extracted widget. |
No commands need specifying, this is just a mechanism for the server to know if a client extension implements some command that it can use in a code action. Imagine some code action that needs some work on both the server and the client. The server wants to include the code action in its list with a command ID that exists on the client - but it wants to know the client has that command implemented, else it should exclude it. For example, let's say my server gains the ability to identify when a referenced symbol is from a first-party package that is not imported in the users workspace. It wants to give a code fix for "install package xxx" but that action needs to be executed on the client (it's like nuget install, npm install, whatever). The LSP server can document that a client extension should implement the "myext.packages.install" command that takes a string argument of the package name, however there's no guarantee it exists because client extensions may have shipped before it existed. If the client extension (when setting up LSP, or dynamically) can tell the server (in its capabilities) "Hey, I have a (note: I'm not doing package install, it just seemed like a convenient example that wasn't refactors, to avoid any confusion with whether the refactor APIs can/should be improved..) Is that any clearer? Is there a better way you think this should be handled?
I somewhat agree - that's why I originally asked for being able to get input directly from the server. However, in hindsight I don't think the user experience would be great across editors if all requests for input had to be encoded into LSP messages. It'd be nice if an editor that has big fancy dialogs (eg. Visual Studio) can implement a nice dialog for the user, then send all the info in one go, while VS Code can use it's input boxes/quick picks/etc.. To get the best experience, I think it will sometimes be necessary for the client extension to implement some local functionality (and that's what my request for including commands in capabilities is for - to allow the server to be able to detect client functionality it can call).
Extract Widget may fit into this, but many of our refactors have options that it'd be nice to expose to the user - which will require some UI. |
I.m.o. that would actually be a good idea. I mean I always thought there was every intention to eventually standardize a list of client-side commands in the spec. As it is now client-side commands have to be implemented by server providers as custom client-side extension for every client they support. Getting started on a growing list of 'standard' commands that clients (may optionally) implement would not be bad idea at all. |
Isn't this list of standard comands the LSP operations themselves? ie if a command is standard enough, why not creating a typed LSP request/response for it? Just like As for user input, a nice-ish and generic way could be to have a |
I never thought of it that way, but I think @mickaelistria is right. If some command is 'standard enough' to merrit detailed description, then it could probably rise to the level of being a protocol message. The only potential issue I see is that you can't put 'protocol messages' as a 'command id' for something like a code action. But I suppose you can still have the server implement a server-side command, register that as a 'server capability' and then have the command implementation call the protocol message. |
To be clear, I'm asking for non-standard commands here. Something agreed between my LSP server and the client extensions, which they need to communicate capabilities for. I agree that if something is common, it should be a real LSP request/response. I gave an example above for a code fix for installing a package locally - this is going to need the client to handle it (for ex. in Visual Studio it might open the NuGet package manager, in VS Code it would be handled differently). In this case, the server needs to know if the client has that capability, and since it's invoking it by returning a code action with a command, it makes sense to do it by advertising that the client supports that command.
This doesn't solve the general case - for example the example given above - Visual Studio has the NuGet Package Manager they might want to open when you invoke the "Install package xxx" quick-fix (note: I'm making this quick-fix up as an example). But also, I think trying to encode dialogs into LSP won't result in the best UI/UX. Client extensions should be able to choose the most appropriate way of requesting input, whether it's from multi-step inputs like VS Code, a dialog (Visual Studio) or even text inputs for terminal editors 😄 |
You're right. That said, while we're brainstorming about general case, #592 still seems to be a more affordable solution to the initial trigger. |
This seems to be getting awfully complicated for something that could be so simple. If the client can tell the server what commands it supports, the server could just use them in the code action. The client is then free to call whatever APIs it wants on the server, get whatever info it needs, show the user whatever it wants, etc.. It's very extensible and allows the client extension to implement things in the best way without being restricted by what LSP has deemed common enough to add to the spec.
It may solve |
The thing here is that it means LS needs to be somehow aware of client-specific support; and IDEs are supposed to implement commands that are not standardized in the protocol. That's making the LSP re-enable the N*M integration effort it's been trying to shutdown. |
@DanTup I think you can [ab]use In general, I think that complex IntelliJ-style refactorings are really sub-protocols between the client and the server, which require several rounds of back and forth, and which could use language specific messages and language specific UI. As usual, I am a big fan of the Dart protocol, and there they have refactoring-specific Options (client -> server JSON) and refactoring-specific Feedback (server -> client JSON). Refactoring action then is a repeated back-and forth of options/feedback which continues until the user is satisfied with results. Rendering of options & feedback is refactoring-specific. |
Right, but that's already the case. This is from the LSP spec:
Yes it's more work, but it'll necessary to provide some functionality. LSP can't cover everything, so some experiences will need custom extension code. Without this, all experiences will be limited to the set of functionality that every editor can provide out of the box.
This is going to be unavoidable for some experiences - the LSP server/API isn't going to include a UI framework and client extensions will want to provide a UI that fits in with their application. Of course, LSP should minimise the amount of work done this way, but it shouldn't shut the door on it.
Right, but it'd be nice for there to be a standard way. The quoted text above already says that LSP client extensions may need to implement commands that the server will call - so why don't the capabilities allow for communicating which are supported? It seems strange for the spec to explicitly support something, but require some custom hack in order for compatibility.
I agree with this - and my proposal here (which is clients advertising which commands they support, not what's in the title - @dbaeumer would you object to renaming it again?) is to support that - it's letter the server know that it can include a quick-fix that can kick off this flow with a client command. The client can't decide when to show the refactors because it doesn't know when they should be available. |
@DanTup I renamed it back. Regarding
I need to update this. It is in there from the days where a server couldn't handle command execution. This is why it said |
If the commands are not standardize why do we need a generic way to advertise them. If I understand @DanTup now correctly he wants to advertise commands that need to be implemented by the tool extension anyways. So to make this work you need specific extensions for every tool. Given that the extension could simply send a custom message right after the |
Indeed, given the level of flexibility required by @DanTup, protocol extensions seem actually to be a good way to implement it already. |
I was not clear: they are called by the client as a side effect of the server putting them into a code action or completion item. But the client doesn't do any interpretation of the command id value. IMO providing locally executed commands requires to spec them in terms of their command id, the params they take and the result they deliver. This is like specifying a request :-). So I rather prefer adding requests. Given this is it OK for you to close the issue ? |
I don't really agree with this... In VS Code, I can enumerate a list of commands ( The LSP spec says that servers can provide command IDs to the client and "the tool extension code could handle the command.". If this is valid, why is the server not allowed to know in advance which commands the client could actually handle? If the server gains new functionality that relies on a local command, it needs to know if the client extension supports it. Yes it's possible for us to build our own way of communicating these capabilities, but it's extra work and seems to go against the idea of keeping things within the spec'd protocol.
For functionality that's useful to multiple languages, this makes perfect sense. However there are going to be many features that are "too specific" and will just be rejected from the spec so it should be easy to extend. (I don't want to flog a dead horse so if you really don't think it makes sense, feel free to close it - there are other ways around this with custom requests - it just seemed to fit that this could be symmetrical between client and server, since server already advertises the command IDs it supports). |
What this tries to say is that the extension code is allowed to define a command that the server can invoke. In this case the code parts must match. However this is still not a built in editor command. However this forces every integrator of the language server to provide that command as well. I am against allowing clients to advertise the commands. I think this make programming against the LSP more complicated except we would standardize these commands which I would do through requests. I will close the issue as suggested since I don't see the benefit of standardizing commands over defining requests. |
There may have been some misunderstanding.. I was not requesting that built-in editor commands would be transmitted the server, I was specifically asking for a client extension to be able to provide some custom commands it has that the server is allowed to invoke, exactly as you detail there.
It doesn't, if this feature was implemented :) The purpose of this request was specifically to allow the server to know if the client supported the feature, so it would know when it can use it.
I agree with that, but I think you may have misunderstood what was being asked for. I wanted the server to know that the client supports "dart.someCustomCommand" so it may include additional code actions (or completions, or whatever) that would call it. There are many things the server cannot do and will require some custom code locally - without this they'll need to have custom requests even if they could've been encoded into a simple command. |
Definitelly :-) Reopening the issue then. |
@dbaeumer said
@DanTup said
It is not clear to me that these two cases are different, and it does mean that client developers will be forced to add features driven by the language server, to provide a decent experience. And over time the server becomes specialised to only a handful or only one client. |
I think one (calling editor-specific commands from the server) would start down the path of trying to standardise the commands across editors (which in some ways seems attractive, but I think will also result in poor experiences (for example there's a world of difference between Vim and Visual Studio - trying to encode things like UIs into commands will suck). The other, is to provide an extension point for features that just won't ever make it into LSP (things that are very specific to a particular integration). For complicated cases, we'd add custom requests, but in the simple case it may be sufficient to just have the client execute a command. The big difference here is that the second one is language specific and makes not attempt at being standardised. If you're integrating with my LSP server and you'd like to support SomeCoolFeature then you can do it by implementing this command, and advertising that you can handle it in the ClientCapabilities. That makes it ok for me to return code actions/completions/whatever that have this command ID. Yes, it's limited - you can't pass arguments or receive results - but sometimes that's enough. |
FWIW I found another place where I think this would help. We have some CodeLens links like this in the Dart plugin: The server provides the info on where the tests are but the client has to handle the command to launch a debug session. We could build some custom way for the client to request the test locations from the server and then have a client-side CodeLens provider, but that feels weird when the server can already provide them. It would be much nicer if we could spec a client-command for launching of tests and then have the server know if the client supports it. |
Another place where I think this would be useful... We're adding URLs for diagnostics that load documentation that might help a user understand/fix some errors. It'd be nice if we could attach code actions so that when they right-click the error in the Problems view, the context menu has an entry to open the docs to help with that error. Spawning the browser would require a client-side command be implemented, so it'd be nice if the server could conditionally include those code actions if it knows the client implements (a custom - provided by the client extension) "open docs in browser" command. |
I came to try and implement "extract method" without using any client command as discussed above (just use a code action, apply the edit with a default name and then rename it). However, as far as I can tell, there's no way to invoke the rename, so this doesn't work. So if I understand correctly, some work is required to support even just a simple "extract method" code action? I've opened #764 as a more specific request about this. I'm currently blocked unless I implement my own mechanism for this (which doesn't seem like a good solution, since "extract method" is not a concept specific to my language). |
Just a late reaction/insight around why a client may need/want to advertise commands it can execute even if there is not a 'standardized' list of commands enshrined in the spec. This is in scenarios where multiple LS need to somehow collaborate / coordinate. E.g. the Java language server may advertise commands that are very specific to Java and they do not belong in a general LSP spec. However, other language servers also operating on Java code or related artefacts, might still want to rely on these commands which have a well-define and understood meaning / function in the context of Java tooling. For example, the spring-boot language server relies on the Java language server for classpath information. This information is obtained by requesting it via a command. Boot LS basically just assumes the command is there (which should be the case if the Java language tooling is installed and so the Java language server provides it). But it would probably be useful if rather than blindly assuming these kinds of 'inter server commands' exist, there would be a better way to check that (i.e. client capability to advertise the known commands). For this purpose a simple list of commands that 'exist' would be sufficient. We wouldn't need to standardize on the meaning of the commands in the spec. The meaning is an implicit agreement between two ls servers, one that defines the command and a second one that depends on it. |
Good point in regards of using commands for inter server communication. |
rust-analyzer now implements this as a custom extension: Client CommandsExperimental Client Capability: Certain LSP types originating on the server, notably code lenses, embed commands. This extensions allows the client to communicate this info. export interface ClientCommandOptions {
/**
* The commands to be executed on the client
*/
commands: string[];
} See neovim/neovim#15183 for the context where this is required (TL;DR: code lenses) |
I implemented something similar in terraform-ls recently - opt-in code lens. Instead of letting clients pass an arbitrary list of commands though I let them opt into a particular behaviour by passing their command ID. This gives clients a little bit more freedom at the cost of making the implementation "less universal". It reflects that clients may already implement the same command under a different name and potentially saves it from registering another command in the global namespace just for the server. For example if/when VS Code decides to make See https://github.com/hashicorp/terraform-ls/blob/cd6d3c2/docs/language-clients.md#code-lens for more. |
I'm implementing some refactors where the server needs some input from the user. In order to get input from the user I'm going to need to trigger a command on the client that can ask the user for input (for example, by having a command in the client extension that can call window.showInputBox or some equiv).
However, I can't find a way for the client to advertise (or server to know) which commands are supported on the client.
What's the expected way to handle this? Although I can document the commands that a client needs to implement for specific functionality in the server, that's not very compatible - if I wanted to add new requirements in the future, I'd need to know if the connected client supports them.
The text was updated successfully, but these errors were encountered: