-
-
Notifications
You must be signed in to change notification settings - Fork 387
Plugin tutorial, more changes #4570
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
base: master
Are you sure you want to change the base?
Conversation
39a170a
to
519de6e
Compare
Some changes are mine, but many have been cherry picked and amended from PR haskell#3655 by Christian Georgii <cgeorgii@gmail.com>.
519de6e
to
808ddbf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking up this PR!
I have some comments and suggestions.
* Request handlers respond to requests from an LSP client. They must fulfill these requests as quickly as possible. | ||
* Notification handlers receive notifications from code not directly triggered by a user or client. | ||
* Rules add new targets to the Shake build graph. Most plugins do not need to define new rules. | ||
* Commands are an LSP abstraction for user-initiated actions that the server handles. These actions can be long-running and involve multiple modules. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the rules could be discussed later on instead of here.
I would rather write this as:
* Request handlers respond to requests from an LSP client. They must fulfill these requests as quickly as possible. | |
* Notification handlers receive notifications from code not directly triggered by a user or client. | |
* Rules add new targets to the Shake build graph. Most plugins do not need to define new rules. | |
* Commands are an LSP abstraction for user-initiated actions that the server handles. These actions can be long-running and involve multiple modules. | |
There are two main groups of communication via the Language Server Protocol, namely: | |
* A request-response type interaction where one party sends a message which the other party has to respond to somehow | |
* `pluginHandlers` handle client requests and provide a response | |
* Example: When you want to format a file the client sends the [`textDocument/formatting`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting) to the server which triggers the server to format the file, the server then responds with the formatted file content | |
* A notification, which is a one-way interaction, where one party sends a message without expecting any response | |
* `pluginNotificationHandlers` handle notifications sent from the client to the server | |
* `pluginCommands` are a special type of notification, these are also sent to the server but can be triggered by the user instead of happening automatically | |
* Example: Whenever you modify a Haskell file, the client sends a notification, informing HLS about how the file has changed | |
> **Note:** The LSP client and server can both send requests or notifications to the other party. |
|
||
Providers are functions that receive some inputs and produce an IO computation that returns either an error or some result. | ||
These will be assembled together in the `descriptor` function of the plugin, which contains all the information wrapped in the `PluginDescriptor` datatype mentioned above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These will be assembled together in the `descriptor` function of the plugin, which contains all the information wrapped in the `PluginDescriptor` datatype mentioned above. | |
These will be assembled in the `descriptor` function of the plugin, which contains all the information wrapped in the `PluginDescriptor` datatype mentioned above. |
1. `use*` combinators block and propagate errors, | ||
2. `useWithStale*` combinators block and switch to stale data in case of an error, | ||
3. `useWithStaleFast*` combinators return immediately with stale data if any, or block otherwise. | ||
In short, commands work like this: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In short, commands work like this: | |
In short, LSP commands work like this: |
3. `useWithStaleFast*` combinators return immediately with stale data if any, or block otherwise. | ||
In short, commands work like this: | ||
- The LSP server (HLS) initially sends a command descriptor to the client, in this case as part of a code lens. | ||
- Whenever the client decides to execute the command on behalf of a user (in this case a click on the code lens), it sends this same descriptor back to the LSP server. The server then handles and executes the command; this latter part is implemented by the `commandFunc` field of our `PluginCommand` value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Whenever the client decides to execute the command on behalf of a user (in this case a click on the code lens), it sends this same descriptor back to the LSP server. The server then handles and executes the command; this latter part is implemented by the `commandFunc` field of our `PluginCommand` value. | |
- When the user clicks on the code lens, the client asks HLS to execute the command with the given descriptor. The server then handles and executes the command; this latter part is implemented by the `commandFunc` field of our `PluginCommand` value. |
``` | ||
|
||
`runImportCommand` [sends a request](https://hackage.haskell.org/package/lsp/docs/Language-LSP-Server.html#v:sendRequest) to the client using the method `SWorkspaceApplyEdit` and the parameters `ApplyWorkspaceEditParams Nothing edit`, providing a response handler that does nothing. It then returns `Right Null`, which is an empty `Aeson.Value` wrapped in `Right`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe explain why we have an Either
type here, and that Left
is used for failures?
|
||
The provider takes the usual `LspFuncs` and `IdeState` argument, as well as a `CodeLensParams` value containing the URI | ||
for a file, and returns an IO action producing either an error or a list of code lenses for that file. | ||
The provider takes the usual `LspFuncs` and `IdeState` arguments, as well as a `CodeLensParams` value containing a file URI. It returns an IO action that produces either an error or a list of code lenses for that file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LspFuncs
does not exist anymore, they're now part of the plugin monad.
The full code as used in this tutorial, including imports, can be found in [this Gist](https://gist.github.com/pepeiborra/49b872b2e9ad112f61a3220cdb7db967) as well as in this [branch](https://github.com/pepeiborra/ide/blob/imports-lens/src/Ide/Plugin/ImportLens.hs) | ||
Integrating the plugin into HLS itself requires changes to several configuration files. | ||
|
||
A good approach is to use the ID of an existing plugin (e.g., `hls-class-plugin`) as a guide: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A good approach is to use the ID of an existing plugin (e.g., `hls-class-plugin`) as a guide: | |
A good approach is looking for the ID (f.e. `hls-class-plugin`) of an existing plugin as a reference: |
Thanks for your detailed review @VeryMilkyJoe , I have this on my radar, just didn't get to it yet! |
Some changes are mine, but many have been cherry picked and amended from PR #3655 by Christian Georgii cgeorgii@gmail.com. @cgeorgii
This PR now includes most changes of #3655. I did not include the last sections which are behind a "what shall we do with those" comment in the aforementioned PR. I am not sure how to deal with those.
Another issue is that the link to the gist is outdated (and still links to the original one by Pepe Iborra). So I think we either have to remove the link or write a new gist and replace the link. What are your opinions?
I did not implement the literate file stuff, it seems too complicated to achieve in one PR.
Supersedes #3655.