Skip to content

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

dschrempf
Copy link
Contributor

@dschrempf dschrempf commented Apr 23, 2025

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.

@dschrempf dschrempf force-pushed the plugin-tutorial-2 branch 3 times, most recently from 39a170a to 519de6e Compare April 27, 2025 19:22
@dschrempf dschrempf marked this pull request as ready for review April 27, 2025 19:32
@dschrempf dschrempf requested a review from michaelpj as a code owner April 27, 2025 19:32
Some changes are mine, but many have been cherry picked and amended from
PR haskell#3655 by Christian Georgii <cgeorgii@gmail.com>.
Copy link
Collaborator

@VeryMilkyJoe VeryMilkyJoe left a 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.

Comment on lines +105 to +108
* 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.
Copy link
Collaborator

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:

Suggested change
* 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- 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`.
Copy link
Collaborator

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.
Copy link
Collaborator

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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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:

@dschrempf
Copy link
Contributor Author

Thanks for your detailed review @VeryMilkyJoe , I have this on my radar, just didn't get to it yet!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants