De-mystifying Emacs, lsp-haskell and haskell-language-server Setup
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 of
lsp-haskell, seems to have replaced this with a custom variable
lsp-haskell-server-path) and the default value is set to
haskell-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.