Skip to content

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

Open
@gfarrell

Description

@gfarrell

(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
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
  1. Use that flag to enable the plugin in src/HlsPlugins.hs
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")
  1. 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:

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:

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).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions