haskell / haskell/haskell-language-server

Improving the "Let's write a Haskell Language Server Plugin" tutorial

Open
#3,877 2 comments 1 reaction 0 assignees View on GitHub
documentation type: enhancement
Dominant language
Haskell
Stars
3k
Forks
455
Avg merge
2d 19h
Merged PRs (30d)
11

Description

(This issue is WIP -- I'm going to record all the things I found confusing about writing a plugin and then suggest changes, but welcome comments as I go.)

I'm currently following the plugin writing documentation / tutorial, and encountered the following things. I'm using the plugin name `ArgSwap` because that's the plugin I was writing.

## Creating a new plugin and linking it

I had to edit a load of files and do a load of work just to include my plugin in my build:

* create `plugins/hls-argswap-plugin`
* create `plugins/hls-argswap-plugin/hls-argswap-plugin.cabal` (for which I copied the cabal file of `hls-module-name-plugin`
* create `plugins/hls-argswap-plusin/src/Ide/Plugin/ArgSwap.hs`

I then had to "link" it:

1. add my plugin to `cabal.project` by adding `./plugins/hls-argswap-plugin` to `packages`
2. create a new flag in `haskell-language-server.cabal`

```patch
diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal
index c925b916..5d54dd67 100644
--- a/haskell-language-server.cabal
+++ b/haskell-language-server.cabal
@@ -164,6 +164,11 @@ flag overloadedRecordDot
default: True
manual: True

+flag argSwap
+ description: Enable the args swapping plugin
+ default: True
+ manual: True
+
-- formatters

flag floskell
@@ -306,6 +311,11 @@ common overloadedRecordDot
build-depends: hls-overloaded-record-dot-plugin == 2.4.0.0
cpp-options: -Dhls_overloaded_record_dot

+common argSwap
+ if flag(argSwap)
+ build-depends: hls-argswap-plugin == 2.4.0.0
+ cpp-options: -Dhls_argswap
+
-- formatters

common floskell
@@ -365,6 +375,7 @@ library
, stylishHaskell
, refactor
, overloadedRecordDot
+ , argSwap

exposed-modules:
Ide.Arguments
```

3. Use that flag to enable the plugin in `src/HlsPlugins.hs`

```patch
diff --git a/src/HlsPlugins.hs b/src/HlsPlugins.hs
index 4d371859..cb4b12b2 100644
--- a/src/HlsPlugins.hs
+++ b/src/HlsPlugins.hs
@@ -94,6 +94,10 @@ import qualified Ide.Plugin.ExplicitFields as ExplicitFields
import qualified Ide.Plugin.OverloadedRecordDot as OverloadedRecordDot
#endif

+#if hls_argswap
+import qualified Ide.Plugin.ArgSwap as ArgSwap
+#endif
+
-- formatters

#if hls_floskell
@@ -223,6 +227,9 @@ idePlugins recorder = pluginDescToIdePlugins allPlugins
#endif
#if hls_overloaded_record_dot
let pId = "overloaded-record-dot" in OverloadedRecordDot.descriptor (pluginRecorder pId) pId :
+#endif
+#if hls_argswap
+ let pId = "arg-swap" in ArgSwap.descriptor (pluginRecorder pId) pId :
#endif
GhcIde.descriptors (pluginRecorder "ghcide")
```

4. add the plugin to `stack.yaml` and `stack-lts21.yaml` under `packages:`

## Not knowing what is imported whence

Example plugins I looked at used some implicit imports. HLS wouldn't work for me on the HLS codebase, so I had to make a lot of guesses as to what came whence.

## Some outdated types (and missing links)

Near the beginning of the tutorial, there is a description of the `PluginDescriptor` datatype as being defined in `Ide.Plugin` as:

```haskell
data PluginDescriptor =
PluginDescriptor { pluginId :: !PluginId
, pluginRules :: !(Rules ())
, pluginCommands :: ![PluginCommand]
, pluginCodeActionProvider :: !(Maybe CodeActionProvider)
, pluginCodeLensProvider :: !(Maybe CodeLensProvider)
, pluginHoverProvider :: !(Maybe HoverProvider)
, pluginSymbolsProvider :: !(Maybe SymbolsProvider)
, pluginFormattingProvider :: !(Maybe (FormattingProvider IO))
, pluginCompletionProvider :: !(Maybe CompletionProvider)
, pluginRenameProvider :: !(Maybe RenameProvider)
}
```

In fact (at least in 2.4.0) it is defined in `Ide.Types` as:

```haskell
data PluginDescriptor (ideState :: Type) =
PluginDescriptor { pluginId :: !PluginId
-- ^ Unique identifier of the plugin.
, pluginPriority :: Natural
-- ^ Plugin handlers are called in priority order, higher priority first
, pluginRules :: !(Rules ())
, pluginCommands :: ![PluginCommand ideState]
, pluginHandlers :: PluginHandlers ideState
, pluginConfigDescriptor :: ConfigDescriptor
, pluginNotificationHandlers :: PluginNotificationHandlers ideState
, pluginModifyDynflags :: DynFlagsModifications
, pluginCli :: Maybe (ParserInfo (IdeCommand ideState))
, pluginFileType :: [T.Text]
-- ^ File extension of the files the plugin is responsible for.
-- The plugin is only allowed to handle files with these extensions.
-- When writing handlers, etc. for this plugin it can be assumed that all handled files are of this type.
-- The file extension must have a leading '.'.
}
```

Keeping documentation up to date is hard, but one of the things which would be really helpful (especially as HLS just won't run on the HLS codebase for me) is links to the definitions.

Alternatively, I'd love it if HLS was packaged up on Hackage and therefore Hoogleable, because at the moment building local hoogle (via `stack hoogle`) exhausted my machine's memory (and I have 32GB of RAM on this laptop).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.