De-mystifying Emacs, lsp-haskell and haskell-language-server Setup

2020-09-26

Introduction

I recently managed to set up my Emacs environment to work with haskell-language-server. I took me a while to get this working and come up with a workflow. Since, it was not that straight forward to get this working, I have decided to share some of the nuances I learned during this process in this post.

Ideally, it would be nice if this kind of set up worked out of the box. But, we are not there yet. Therefore, it helps to understand some of the plumbing work going on below so that we can make sense of why something is not working and find our solution.

Understanding the Setup process

Some terminology:

In this post, I discuss setting up haskell-language-server or also referred to as hls. For readers, who have been following recent development in this area, might be aware that this is an effort to bring together various IDE related tools (hie-core, hie-bios and hie) into one integrated experience. In short, this blog talks about haskell-language-server setup and not hie setup.

lsp-haskell is the Emacs mode that supports talking to three different tools currently, that currently emulate a language-server. Currently, haskell-lsp supports three of these tools: hie, haskell-language-server and ghcide. We will focus on making this tool working with haskell-language-server. (Note: The latest version oflsp-haskell, seems to have replaced this with a custom variablelsp-haskell-server-path) and the default value is set tohaskell-language-server-wrapper`).

I will assume you are on version of Linux and using stack for the rest of the blog post. The concepts described below applies to cabal setup as well.

As we go through this steps, I try to explain why we perform these steps and what is happening in the background. Knowing this allows us to debug the problems we face in the diverse environments these setup steps are designed to support.

Setting up haskell-language server

You should pretty much follow the instructions for this from (haskell-language-server)[https://github.com/haskell/haskell-language-server}. The README has lot more information for different scenarios. Let’s distill this to what we need.

    git clone https://github.com/haskell/haskell-language-server --recurse-submodules
    cd haskell-language-server

Then, comes the build step.

    stack ./install.hs hls   # to install for latest available GHC version

But, this step is usually not sufficient. The haskell-language-server uses ghcide under the hood. If you tried to use the version of haskell-language-server which does not match the version of GHC your current project is using, then the haskell-language-server would either not load successfully or partially load leading to all kinds of confusion.

Therefore, I suggest, based on your projects GHC version dependencies, you can choose to build multiple versions of haskell-language-server using the supported GHC versions. You can do this as follows:


    stack ./install.hs hls-8.8.3

Tip: Usually, I have multiple version of haskell-language-server as I have projects using different GHC versions at any point in time.

# Here are some version that are supported, as of this writing

stack ./install.hs --help

...
  - hls-8.10.1
  - hls-8.10.2
  - hls-8.6.4
  - hls-8.6.5
  - hls-8.8.2
  - hls-8.8.3
  - hls-8.8.4
  - latest

This step installs, different versions of the haskell-language-server into your .local/bin folder (on Linux version I am on). Here is what I have on my machine:


    /home/foouser/.local/bin/haskell-language-server
    /home/foouser/.local/bin/haskell-language-server-8.10
    /home/foouser/.local/bin/haskell-language-server-8.10.1
    /home/foouser/.local/bin/haskell-language-server-8.10.2
    /home/foouser/.local/bin/haskell-language-server-8.6
    /home/foouser/.local/bin/haskell-language-server-8.6.5
    /home/foouser/.local/bin/haskell-language-server-8.8
    /home/foouser/.local/bin/haskell-language-server-8.8.3
    /home/foouser/.local/bin/haskell-language-server-wrapper

This completes our set up of haskell-language-server. What we want to take away from this step, is the need for differenthaskell-language-server versions to successfully work with the lsp client. Note, that later versions of language server report an error to the lsp-client when the ghcide versions don’t match. But, the earlier version may not report this which could lead to IDE environment not working as expected.

Bridging haskell-language-server with the GHC API using hie-bios cradles

This is an important step that I had glossed over initially and paid a dear 2-hours figuring out what was broken. haskell-language-server uses this information to set up a GHC API session. Its sufficient to understand this we need to generate some file-discovery information that hls can use while interacting with GHC API. This is required, since hls uses the GHC API for all its parsing needs. This is a way to ensure hls can keep up with changes to GHC rather than having its own implementation.

As of this writing, sometime the errors you get running hls with this step are not intuitive. You may notice the IDE integration works but does not work fully. If you notices any such behavior one of the potential issues, is that you have not performed this step.

So, here is what you need to do at the very least. You can

git clone https://github.com/Avi-D-coder/implicit-hie.git
cd implicit-hie

stack build

Identify the executable, this step creates and add it to your local path if required. Now, from the project folder you are on, run

    cd `to-your-project`
    gen-hie > hie.yaml

This step will create an hie.yaml file which automatically discovers the metadata information that is required by the hls to load your project successfully. The hie.yaml file is used by hls via hie-bios library.

For advanced configuration of hie-bios files, you can check out instructions at hie-bios.

Once, you have a hie.yaml file set up in your project folder, we can move on to the next step.

Also, note that there are efforts to make this step automatic.

Time to check if haskell-language-server + hie.yaml loads the project

Before, we use our Emacs, it is helpful to run the hls server from the project folder and make sure the server loads without any errors. Therefore, let’s do the following

# cd into the project folder.
# make sure you  have the generated `hie.yaml` file in that folder.

# run hls

haskell-language-server-wrapper

If we have performed all the steps so far and if it has worked, then you will see a bunch of information rendered by this command. You will also notice, that the haskell-language-server-wrapper was also able to determine the versions of GHC and then also the corresponding ghcide appropriately. Here is a small section you will observe:


Tool versions found on the $PATH
cabal:          2.4.1.0
stack:          2.3.1
ghc:            8.8.3


Consulting the cradle to get project GHC version...
Project GHC version: 8.8.3
haskell-language-server exe candidates: ["haskell-language-server-8.8.3","haskell-language-server-8.8","haskell-language-server"]

Now, say, if you or the lsp-client were to run a different version of hls, then you are likely to see this message as part of the printed output.

Severity: DsError
Message:
  ghcide compiled against GHC 8.10.2 but currently using 8.8.3
  This is unsupported, ghcide must be compiled with the same GHC version as the project.

It is good to keep this in mind, if you plan on updating the lsp-haskell-server-path variable in the next step.

Setting up Emacs

As of this writing, I am using lsp-haskell 20200924.1508. You can view this using the list-packages interactive command in Emacs. I think the lsp-haskell authors have stream-lined a lot of issues dealing with haskell-language-server version incompatibility issues. I really appreciate the work which has gone into this since the last release. I recommend you upgrade your lsp-haskell to at least this version. This version now supports haskell-language-server-wrapper out of the box. (The earlier versions had the default command set to hie, with options to switch to ghcide or hie). As, I suggested earlier, since there is a coordinated effort to bring all of this together, this switch makes perfect sense.

Once you have this step completed, ie. lsp-haskell package installed. Start your Emacs from the project folder and you have a working version of fully integrated IDE.

You might want to read up some some nice lsp commands you might want to use from lsp-ui. You might also want to read a little about how Emacs lsp-mode add/organizes lsp projects.

Here are some demonstrations on capabilities I am currently able to use with my Emacs setup.

Resources/References

  1. Munihac 2019: Making a Haskell IDE
  2. lsp-haskell
  3. haskell-language-server