haskell / haskell/lsp

A vision for consistent static vs dynamic registration

Open
#583 5 comments 0 reactions 0 assignees View on GitHub
v3
Dominant language
Haskell
Stars
423
Forks
105
PR merge metrics
No merged PRs in 30d

Description

At the moment static versus dynamic registration works quite differently.

Specifically:
- When you register a method dynamically, you have to provide the `RegistrationOptions`
- When you register a method statically, you _don't_ have to provide the `ServerCapability` (from https://github.com/haskell/lsp/pull/581)
- Instead, we try to _infer_ the appropriate capability. See `inferServerCapabilities`
- But, this requires a bunch of choices that we can't guess! So we put in a side channel in the form of `Options`, which basically lets us pass in things that need to go in the capabilities.

I think we could unify these. Specifically, I think we should make the user provide the capability in _both_ cases. That means that we don't need to pass the extra information out-of-band in `Options`, instead the user can just set things in the capability directly.

Of course, we probably want a way to provide a sensible default capability... but we _also_ want this for the dynamically registered capabilities! So we should try and provide this for everything.

The main complication is that for both static and dynamic registration we may need to register several method handlers under the same capability, and what the capability is will depend on all of them. For example:
- `textDocument/completion` and `completionItem/resolve`. The latter is not registered separately, but is rather indicated by a field in the completion capability.
- `textDocument/semanticTokens/full` and the other semantic tokens methods. Similar.

I think we can do this reasonably nicely by pretending that we can register everything independently, and then merging all the capabilities when we actually do the registration. So for example, we might have a default capability for the `textDocument/completion` handler that doesn't say it supports resolve, and then a capability for `completionItem/resolve` that says it supports resolve, and then we merge those together into the final capability that we register. I think this should work for both static and dynamic capabilities.

So I think we want something like this:
```
-- We might not be able to have these actually instances due to singletons stuff, but morally we want them
instance Semigroup (ServerCapability m)
instance Semigroup (ClientCapability m)
instance Semigroup (RegistrationOptions m)

type StaticRegistration m = (Handler m, ServerCapability m)
type DynamicRegistration m = (Handler m, RegistrationOptions m)
```

We probably also need the server definition to tell us its static registrations as part of the initialize handler instead of beforehand. This is necessary _anyway_, since you might need to decide whether to use static or dynamic registration depending on the client's capabilities.

(It would also be nice if we could provide a relatively simple way to have a handler that is either statically or dynamically registered depending on whether the client supports dynamic registration...)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.