269 lines
8.8 KiB
Markdown
Executable file
269 lines
8.8 KiB
Markdown
Executable file
---
|
|
title: Language Server
|
|
date: 2019-09-11
|
|
tags:
|
|
- NixOS
|
|
summary: >
|
|
How to setup your projects using lsp, emacs (or your favorite editor), direnv and nix-shell.
|
|
---
|
|
|
|
In this article we are discussing a concrete setup with concrete tools and a concrete language.
|
|
But the solutions described here, are intended to help you with your set up.
|
|
|
|
## Setup
|
|
|
|
Here are the Tools used.
|
|
|
|
* [Spacemacs](http://spacemacs.org/) : my editor or choice.
|
|
* [all-hies](https://github.com/Infinisil/all-hies) : to start a [haskell-ide-engine](https://github.com/haskell/haskell-ide-engine) which is the haskell-lsp-server.
|
|
* [direnv](https://direnv.net/) : to automatically load `shell.nix` configuration in my editor, when opening a file.
|
|
* [lsp-haskell.el](https://github.com/emacs-lsp/lsp-haskell) : the emacs plugin to interact with the haskell-ide-engine.
|
|
* [nix-shell](https://nixos.wiki/wiki/Development_environment_with_nix-shell) : because all projects should have one.
|
|
|
|
### Goal
|
|
|
|
* Configure Spacemacs as much as possible via `configuration.nix`, without the `lsp-server` being configured by the `configuration.nix`.
|
|
* The `lsp-server` setup should be fully defined inside the `shell.nix` of the project I'm working on.
|
|
|
|
This way project specific tweaks are stored in the place where it belongs,
|
|
and other people can use their favorite IDE with the same setup.
|
|
|
|
## Configure Spacemacs
|
|
|
|
Spacemacs is basically an `~/.emacs.d` folder and a mutable file `~/.spacemacs`.
|
|
I tried to configure `~/.spacemacs` via [home-manager](https://github.com/rycee/home-manager)
|
|
but this does not play well with updates and with `customization`.
|
|
|
|
Now I'm using [home-manager](https://github.com/rycee/home-manager)
|
|
to configure files in `~/.spacemacs.d/` and `load` them in
|
|
the configuration functions inside `~/.spacemacs`. A simple `(load "~/.spacemacs.d/hook-user-config.el")`
|
|
inside the `dotspacemacs/user-config` function is enough, to make it work.
|
|
|
|
```
|
|
{ pkgs, lib, config, ... }:
|
|
let
|
|
|
|
user = "mainUser";
|
|
userName = config.users.users."${user}".name;
|
|
home = config.users.users."${user}".home;
|
|
fontSize = 14;
|
|
|
|
startupBanner = pkgs.fetchurl{
|
|
url = "https://github.com/NixOS/nixos-homepage/raw/master/logo/nix-wiki.png";
|
|
sha256 = "1hrz7wr7i0b2bips60ygacbkmdzv466lsbxi22hycg42kv4m0173";
|
|
};
|
|
|
|
in
|
|
{
|
|
|
|
systemd.services =
|
|
let
|
|
clone =
|
|
repository: folder: branch:
|
|
{
|
|
enable = true;
|
|
wantedBy = [ "multi-user.target" ];
|
|
description = "clone ${repository} to ${folder}";
|
|
serviceConfig.User = userName;
|
|
unitConfig.ConditionPathExists = "!${folder}";
|
|
script = ''
|
|
${pkgs.git}/bin/git clone ${repository} --branch ${branch} ${folder}
|
|
'';
|
|
};
|
|
in
|
|
{
|
|
emacs-pull = clone "https://github.com/syl20bnr/spacemacs" "${home}/.emacs.d" "master";
|
|
};
|
|
|
|
home-manager.users."${user}" = {
|
|
|
|
home.file.".spacemacs.d/hook-init.el".text = ''
|
|
;; just add (load "~/.spacemacs.d/hook-init.el")
|
|
;; at the end of dotspacemacs/init function
|
|
|
|
;; overrides of dotspacemacs/init ()
|
|
(setq-default
|
|
dotspacemacs-themes '(solarized-light solarized-dark)
|
|
dotspacemacs-startup-banner "${startupBanner}"
|
|
dotspacemacs-default-font '("Terminus"
|
|
:size ${toString fontSize}
|
|
:weight normal
|
|
:width normal
|
|
:powerline-scale 1.1))
|
|
'';
|
|
|
|
home.file.".spacemacs.d/hook-layers.el".text = ''
|
|
;; just add (load "~/.spacemacs.d/hook-layers.el")
|
|
;; at the end of dotspacemacs/layers function
|
|
|
|
(let
|
|
((user-layers dotspacemacs-configuration-layers))
|
|
(setq
|
|
dotspacemacs-configuration-layers
|
|
(append user-layers
|
|
'( spell-checking
|
|
syntax-checking
|
|
(haskell :variables
|
|
haskell-enable-hindent t
|
|
haskell-completion-backend 'lsp
|
|
haskell-enable-hindent-style "gibiansky"
|
|
haskell-process-type 'cabal-new-repl)
|
|
))))
|
|
|
|
(let
|
|
((user-packages dotspacemacs-additional-packages ))
|
|
(setq
|
|
dotspacemacs-additional-packages
|
|
(append user-packages
|
|
'( lsp-mode
|
|
lsp-ui
|
|
lsp-haskell
|
|
direnv
|
|
))))
|
|
'';
|
|
|
|
home.file.".spacemacs.d/hook-user-config.el".text = ''
|
|
;; just add (load "~/.spacemacs.d/hook-user-config.el")
|
|
;; at the end of dotspacemacs/user-config function
|
|
|
|
;; lsp setup for haskell
|
|
;; hie-wrapper must be installed and configured in the direnv setup
|
|
(setq lsp-haskell-process-path-hie "hie-wrapper")
|
|
(setq lsp-response-timeout 60)
|
|
(require 'lsp-haskell)
|
|
(add-hook 'haskell-mode-hook #'lsp)
|
|
(add-hook 'haskell-mode-hook #'direnv-update-environment)
|
|
'';
|
|
|
|
};
|
|
}
|
|
```
|
|
|
|
We setup emacs to run `direnve-update-environment` and `lsp` once we start the `haskell-mode`.
|
|
But we did not install `lsp`.
|
|
In my setups the `lsp-server` is installed by the project file (lsp.nix), and is loaded via `direnv` (`direnv-update-environment` in emacs).
|
|
If you don't like that just use the snippet from the next section.
|
|
|
|
### Alternative Configuration (install lsp in the configuration.nix)
|
|
|
|
You can install the `lsp` (in our case `hie-wrapper`) globally in your `configuration.nix` .
|
|
I usually do this in my projects (via `lsp.nix`). Here is the part that differs.
|
|
|
|
```
|
|
home.file.".spacemacs.d/hook-user-config.el".text =
|
|
let
|
|
all-hies = import (fetchTarball "https://github.com/infinisil/all-hies/tarball/master") {};
|
|
in ''
|
|
;; just add (load "~/.spacemacs.d/hook-user-config.el")
|
|
;; at the end of dotspacemacs/user-config function
|
|
|
|
;; lsp setup for haskell
|
|
(setq lsp-haskell-process-path-hie
|
|
"${all-hies.selection{ selector = p: { inherit (p) ghc864;}; } }/bin/hie-wrapper")
|
|
(setq lsp-response-timeout 60)
|
|
(require 'lsp-haskell)
|
|
(add-hook 'haskell-mode-hook #'lsp)
|
|
(add-hook 'haskell-mode-hook #'direnv-update-environment) ;; still needed
|
|
'';
|
|
```
|
|
|
|
## Setup the project
|
|
|
|
For a Haskell project I have this minimal setup of files.
|
|
|
|
### lsp.nix
|
|
|
|
This file is to setup the `lsp-server`.
|
|
If you already installed the `lsp-server` via the `configuration.nix`, this file is not necessary,
|
|
but also does not hurt.
|
|
|
|
```
|
|
{ pkgs ? import <nixpkgs> {} }:
|
|
let
|
|
all-hies = import (fetchTarball "https://github.com/infinisil/all-hies/tarball/master") {};
|
|
in
|
|
pkgs.mkShell {
|
|
buildInputs = with pkgs; [
|
|
haskellPackages.hoogle
|
|
haskellPackages.hindent
|
|
haskellPackages.hlint
|
|
haskellPackages.stylish-haskell
|
|
(all-hies.selection { selector = p: {inherit (p) ghc864; }; })
|
|
];
|
|
}
|
|
```
|
|
|
|
### env.nix
|
|
|
|
Provides the environment to run
|
|
`cabal test` and `cabal build`.
|
|
All package files (e.g. `./current-project.nix`) are created by `cabal2nix`.
|
|
|
|
```
|
|
{ pkgs ? import <nixpkgs> {
|
|
overlays = [
|
|
(self: super: {
|
|
haskellPackages = super.haskellPackages.override {
|
|
overrides = self: super: {
|
|
datetime = super.callPackage ./datetime.nix {};
|
|
current-project = super.callPackage ./current-project.nix { };
|
|
};
|
|
};
|
|
})];
|
|
}}:
|
|
pkgs.haskellPackages.current-project.env
|
|
```
|
|
|
|
### `shell.nix`
|
|
|
|
For other scripts and tooling important for development.
|
|
|
|
```
|
|
{ pkgs ? import <nixpkgs> {} }:
|
|
let
|
|
updateCabal = pkgs.writeShellScriptBin "update-cabal" /* sh */ ''
|
|
echo "# created by cabal2nix " > ${toString ./.}/current-project.nix
|
|
${pkgs.cabal2nix}/bin/cabal2nix ${toString ./.} >> ${toString ./.}/current-project.nix
|
|
'';
|
|
in
|
|
pkgs.mkShell {
|
|
buildInputs = with pkgs; [
|
|
updateCabal
|
|
openapi-generator-cli
|
|
openssl
|
|
cabal2nix
|
|
];
|
|
}
|
|
```
|
|
|
|
### `.envrc`
|
|
|
|
finally we need a `direnv` configuration file.
|
|
`direnv` and the `direnv-mode` make it possible
|
|
to load the environment needed and provided by the `*.nix` files.
|
|
|
|
```
|
|
use nix ./env.nix
|
|
use nix ./lsp.nix
|
|
use nix ./shell.nix
|
|
```
|
|
Don't forget to run `direnv allowed . ` in the project folder.
|
|
|
|
## Conclusion
|
|
|
|
Now we are capable to use the `lsp-server` configured in all our projects,
|
|
with the editor we prefer.
|
|
Your colleagues will have little problems with the setup and improve it.
|
|
|
|
If you prefer to install the lsp globally, you can simply do that as described,
|
|
this will not interfere with the `lsp` server setup in the `lsp.nix` file.
|
|
|
|
I'm running this setup for quite a while now, and
|
|
I experience little to no problems with it.
|
|
The most common thing is that I have to fire `lsp-restart-workspace` to remove old errors,
|
|
but doing this every hour is not a problem for me.
|
|
|
|
### Support
|
|
|
|
If you have comments or problems just ping me `palo @ irc.freenode.net`
|