haskell / lsp

Haskell library for the Microsoft Language Server Protocol
366 stars 92 forks source link

Automatically downgrade responses in order to match client capabilities #367

Open michaelpj opened 2 years ago

michaelpj commented 2 years ago

Many specification features take the form of optional additional fields on responses. This means that so long as clients ignore fields they don't recognize or handle, servers can be fairly lax about checking the client capabilities when they return responses.

However:

  1. Sometimes you can't do this at all. An InsertReplaceTextEdit is not a subclass of a TextEdit, so a client that doesn't support InsertReplaceTextEdits will probably just fail if you send it one.
  2. Sometimes you lose information this way. If you have populated the labelDetails field for a CompletionItem, you might lose useful information for the user if it is just dropped, which otherwise you might have put into the label.
  3. It's just a bit ugly to send stuff that the client doesn't understand, and we it's not easy to notice this (previously: https://github.com/haskell/lsp/issues/306).

But doing better currently relies a lot of discipline to check the client capabilities everywhere and have lots of branching logic.

So here's an idea: provide a way to automatically downgrade a response to match the client capabilities. In many cases this will be trivial: drop the unsupported fields. But in other cases we can do a translation that does its best to carry on as much information as possible: turn an InsertReplaceTextEdit into a TextEdit by taking the insertion edit; drop labelDetails by adding it to the label, etc. And if we can't do anything we can perhaps emit a warning.

Take that, stick it in a typeclass, and then we can give people a generic downgrader that they can stick onto the end of their handlers to ensure they get appropriate output.

michaelpj commented 2 years ago

Example in the wild: https://github.com/haskell/haskell-language-server/blob/9415e55672a0c60a353f0bff3ae8546bdf0e7903/hls-plugin-api/src/Ide/Types.hs#L185

This handles the case of missing code action support by making a fallback command. That would fit nicely into this framework.