From 7240a7f8236580347b3884ab1a2141fe9a8841a7 Mon Sep 17 00:00:00 2001 From: Dominik Schrempf Date: Wed, 23 Apr 2025 21:04:50 +0200 Subject: [PATCH] Plugin tutorial, more changes This includes changes up until (but excluding) the section "Anatomy of a plugin". Some changes are mine, but many have been cherry picked from PR #3655, @cgeorgii. --- docs/contributing/plugin-tutorial.md | 77 ++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/docs/contributing/plugin-tutorial.md b/docs/contributing/plugin-tutorial.md index c952ef9eb2..26a497bdaa 100644 --- a/docs/contributing/plugin-tutorial.md +++ b/docs/contributing/plugin-tutorial.md @@ -1,26 +1,61 @@ # Let’s write a Haskell Language Server plugin Originally written by Pepe Iborra, maintained by the Haskell community. -Haskell Language Server (HLS) is an LSP server for the Haskell programming language. It builds on several previous efforts -to create a Haskell IDE. You can find many more details on the history and architecture in the [IDE 2020](https://mpickering.github.io/ide/index.html) community page. - +Haskell Language Server (HLS) is an LSP server for the Haskell programming language. It builds on several previous efforts to create a Haskell IDE. +You can find many more details on the history and architecture on the [IDE 2020](https://mpickering.github.io/ide/index.html) community page. In this article we are going to cover the creation of an HLS plugin from scratch: a code lens to display explicit import lists. -Along the way we will learn about HLS, its plugin model, and the relationship with `ghcide` and LSP. +Along the way we will learn about HLS, its plugin model, and the relationship with [ghcide](https://github.com/haskell/haskell-language-server/tree/master/ghcide) and LSP. ## Introduction Writing plugins for HLS is a joy. Personally, I enjoy the ability to tap into the gigantic bag of goodies that is GHC, as well as the IDE integration thanks to LSP. -In the last couple of months I have written various HLS (and `ghcide`) plugins for things like: +In the last couple of months, I have written various HLS plugins, including: 1. Suggest imports for variables not in scope, 2. Remove redundant imports, -2. Evaluate code in comments (à la [doctest](https://docs.python.org/3/library/doctest.html)), -3. Integrate the [retrie](https://github.com/facebookincubator/retrie) refactoring library. +3. Evaluate code in comments (à la [doctest](https://docs.python.org/3/library/doctest.html)), +4. Integrate the [retrie](https://github.com/facebookincubator/retrie) refactoring library. + +These plugins are small but meaningful steps towards a more polished IDE experience. +While writing them, I didn't have to worry about performance, UI, or distribution; another tool (usually GHC) always did the heavy lifting. + +The plugins also make these tools much more accessible to all users of HLS. + +## Plugins in the HLS codebase + +The HLS codebase includes several plugins (found in `./plugins`). For example: -These plugins are small but meaningful steps towards a more polished IDE experience, and in writing them I didn't have to worry about performance, UI, distribution, or even think for the most part, since it's always another tool (usually GHC) doing all the heavy lifting. The plugins also make these tools much more accessible to all users of HLS. +- The `ormolu`, `fourmolu`, `floskell` and `stylish-haskell` plugins used to format code +- The `eval` plugin, a code lens provider to evaluate code in comments +- The `retrie` plugin, a code action provider to execute retrie commands -## The task +I recommend looking at the existing plugins for inspiration and reference. A few conventions shared by all plugins are: + +- Plugins are in the `./plugins` folder +- Plugins implement their code under the `Ide.Plugin.*` namespace +- Folders containing the plugin follow the `hls-pluginname-plugin` naming convention +- Plugins are "linked" in `src/HlsPlugins.hs#idePlugins`. New plugin descriptors + must be added there. + ```haskell -- src/HlsPlugins.hs + + idePlugins = pluginDescToIdePlugins allPlugins + where + allPlugins = + [ GhcIde.descriptor "ghcide" + , Pragmas.descriptor "pragmas" + , Floskell.descriptor "floskell" + , Fourmolu.descriptor "fourmolu" + , Ormolu.descriptor "ormolu" + , StylishHaskell.descriptor "stylish-haskell" + , Retrie.descriptor "retrie" + , Eval.descriptor "eval" + , NewPlugin.descriptor "new-plugin" -- Add new plugins here. + ] + ``` +To add a new plugin, extend the list of `allPlugins` and rebuild. + +## The goal of the plugin we will write Here is a visual statement of what we want to accomplish: @@ -29,25 +64,23 @@ Here is a visual statement of what we want to accomplish: And here is the gist of the algorithm: 1. Request the type checking artifacts from the `ghcide` subsystem -2. Extract the actual import lists from the type-checked AST, -3. Ask GHC to produce the minimal import lists for this AST, -4. For every import statement without an explicit import list, find out the minimal import list, and produce a code lens to display it together with a command to graft it on. +2. Extract the actual import lists from the type-checked AST +3. Ask GHC to produce the minimal import lists for this AST +4. For every import statement without an explicit import list: + - Determine the minimal import list + - Produce a code lens to display it and a command to apply it ## Setup -To get started, let’s fetch the HLS repository and build it. You need at least GHC 9.0 for this: +To get started, fetch the HLS repository and build it by following the [installation instructions](https://haskell-language-server.readthedocs.io/en/latest/contributing/contributing.html#building). -``` -git clone --recursive http://github.com/haskell/haskell-language-server hls -cd hls -cabal update -cabal build -``` +If you run into any issues trying to build the binaries, you can get in touch with the HLS team using one of the [contact channels](https://haskell-language-server.readthedocs.io/en/latest/contributing/contributing.html#how-to-contact-the-haskell-ide-team) or [open an issue](https://github.com/haskell/haskell-language-server/issues) in the HLS repository. -If you run into any issues trying to build the binaries, the `#haskell-language-server` IRC chat room in -[Libera Chat](https://libera.chat/) is always a good place to ask for help. +Once the build is done, you can find the location of the HLS binary with `cabal list-bin exe:haskell-language-server` and point your LSP client to it. +This way you can simply test your changes by reloading your editor after rebuilding the binary. -Once cabal is done take a note of the location of the `haskell-language-server` binary and point your LSP client to it. In VSCode this is done by editing the "Haskell Server Executable Path" setting. This way you can simply test your changes by reloading your editor after rebuilding the binary. +> **Note:** In VSCode, edit the "Haskell Server Executable Path" setting. +> **Note:** In Emacs, edit the `lsp-haskell-server-path` variable. ![Settings](settings-vscode.png)