Created
November 8, 2018 23:03
-
-
Save sevanspowell/896643d67b00d9a5a8029bcb9c9f6be8 to your computer and use it in GitHub Desktop.
A guide to setting up the Haskell tooling for Emacs in a Nix environment.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Running Emacs Haskell Tooling in a Nix environment | |
A guide to setting up the Haskell tooling for Emacs in a Nix environment. | |
## Configuration | |
### Requirements | |
You will need the following packages: | |
#### Packages | |
- [hie-nix](https://github.com/domenkozar/hie-nix) | |
#### Haskell packages | |
- apply-refact | |
- hasktags | |
- hlint | |
- hoogle | |
### Basic configuration | |
Add the following to your `~/.spacemacs` file: | |
;; ... | |
dotspacemacs-configuration-layers | |
'( | |
;; ... other layers | |
lsp | |
(haskell :variables haskell-process-type 'cabal-new-repl) | |
;; "cabal-repl" or "cabal-new-repl" will do here, but "cabal-new-repl" | |
;; is more robust since it works out of the box in projects with | |
;; multiple targets. You can add extra options using the | |
;; "haskell-process-wrapper-function" and even override this on a | |
;; per-project basis. | |
) | |
;; ... | |
dotspacemacs-additional-packages '( | |
;; ... other packages | |
nix-sandbox | |
(lsp-haskell :location (recipe :fetcher github :repo "emacs-lsp/lsp-haskell")) | |
) | |
**lsp**: Layer that adds general LSP support to Spacemacs. | |
**haskell**: Spacemacs Haskell layer, adds syntax highlighting, formatting, and | |
much more. | |
**nix-sandbox**: Package that adds a number of utilities to make working with | |
nix-shells easier. We'll be using it to find the `shell.nix` or `default.nix` | |
file at the root directory of our project. | |
**lsp-haskell**: Package for communicating with haskell-ide-engine using the | |
LSP. | |
Add this configuration to your `~/.spacemacs`: | |
(defun dotspacemacs/user-config () | |
;; ... | |
;; Haskell configuration | |
(require 'lsp-haskell) | |
;; Define a wrapper for the haskell-ide-engine process | |
(setq default-nix-wrapper | |
(lambda (args) | |
(append | |
;; Change this to match your home directory/preferences | |
(append (list "nix-shell" "-I" "ssh-config-file=/home/sam/.ssh/nixbuild.config" "--command" ) | |
(list (mapconcat 'identity args " ")) | |
) | |
(list (nix-current-sandbox)) | |
) | |
) | |
) | |
(setq haskell-nix-wrapper | |
(lambda (args) | |
(apply default-nix-wrapper (list (append args (list "--ghc-option" "-Wwarn")))) | |
) | |
) | |
;; Flycheck is for error checking | |
(setq flycheck-command-wrapper-function default-nix-wrapper | |
flycheck-executable-find | |
(lambda (cmd) (nix-executable-find (nix-current-sandbox) cmd))) | |
;; Haskell repl session that runs in the background | |
(setq haskell-process-wrapper-function haskell-nix-wrapper) | |
;; Haskell-ide-engine process | |
(setq lsp-haskell-process-wrapper-function default-nix-wrapper) | |
;; Haskell mode is activated whenever we open a .hs file buffer | |
;; Load flycheck when we activate haskell mode in a buffer | |
(add-hook 'haskell-mode-hook 'flycheck-mode) | |
;; Load lsp-haskell when we activate haskell mode in a buffer | |
(add-hook 'haskell-mode-hook #'lsp-haskell-enable) | |
;; Keep our haskell tags up to date (used for jumping to defn. etc.) | |
(custom-set-variables '(haskell-tags-on-save t)) | |
) | |
The important bit is that we're customizing four variables: | |
`flycheck-command-wrapper-function`, `flycheck-executable-find`, | |
`haskell-process-wrapper-function` and `lsp-haskell-process-wrapper-function`. | |
We set each of these functions to be a wrapper that wraps the commands executed | |
by the package in a nix shell. | |
I've used a custom `haskell-nix-wrapper` that wraps the `default-nix-wrapper` | |
and forces some ghc options. It's not helpful having `-Werror` in a REPL, the | |
'haskell-process' will end and you have to manually restart it every time it | |
errors. | |
### Advanced configuration | |
This above should serve you well enough for most projects, but if you need to | |
override any of the above for a specific project, you can use | |
[directory variables](https://www.emacswiki.org/emacs/DirectoryVariables). | |
Create a file called `.dir-locals.el` in the root of your project: | |
( ;; Begin list, maps Emacs mode names to alists | |
(nil . ;; Apply to all modes | |
((default-nix-wrapper . ( ;; Set default-nix-wrapper | |
lambda (args) | |
(append | |
(append (list "nix-shell" "--command" ) | |
(list (mapconcat 'identity args " ")) | |
) | |
(list (nix-current-sandbox)) | |
) | |
) | |
) | |
(haskell-tags-on-save . nil) ;; Set haskell-tags-on-save to off | |
) | |
) | |
) | |
On opening a file in this project, Emacs should ask if you want to apply these | |
variables, hit 'y' for yes. | |
### Keybindings | |
Unfortunately none of the default keybindings seem to get bound. So we have to | |
bind them ourselves. Feel free to change these bindings to match your | |
preference. | |
These are the bindings in my `~/.spacemacs`: | |
(defun dotspacemacs/user-config () | |
;; ... | |
(evil-leader/set-key-for-mode 'haskell-mode "gm" 'lsp-ui-imenu) | |
(evil-leader/set-key-for-mode 'haskell-mode "gg" 'lsp-ui-peek-find-definitions) | |
(evil-leader/set-key-for-mode 'haskell-mode "gr" 'lsp-ui-peek-find-references) | |
(evil-leader/set-key-for-mode 'haskell-mode "en" 'flycheck-next-error) | |
(evil-leader/set-key-for-mode 'haskell-mode "ep" 'flycheck-previous-error) | |
(evil-leader/set-key-for-mode 'haskell-mode "el" 'flycheck-list-errors) | |
(evil-leader/set-key-for-mode 'haskell-mode "ee" 'flycheck-explain-error-at-point) | |
(evil-leader/set-key-for-mode 'haskell-mode "rR" 'lsp-rename) | |
(evil-leader/set-key-for-mode 'haskell-mode "rf" 'lsp-format-buffer) | |
(evil-leader/set-key-for-mode 'haskell-mode "ra" 'lsp-ui-sideline-apply-code-actions) | |
(evil-leader/set-key-for-mode 'haskell-mode "lr" 'lsp-restart-workspace) | |
(evil-leader/set-key-for-mode 'haskell-mode "," 'completion-at-point) | |
(evil-leader/set-key-for-mode 'haskell-mode "." 'lsp-describe-thing-at-point) | |
) | |
This creates the following keybindings: | |
| Command | Keybinding | Description | | |
|:----------------------------------:|:----------:|:------------------------------------------------------------------------:| | |
| haskell-navigate-imports | ~, g i~ | Go to the imports at the top of the file | | |
| lsp-ui-imenu | ~, g m~ | See an outline of the current file (q to quit) | | |
| lsp-ui-peek-find-definitions | ~, g g~ | Jump to definition | | |
| lsp-ui-peek-find-references | ~, g r~ | View references to symbol in file | | |
| haskell-process-load-file | ~, s b~ | Load the current buffer into a REPL | | |
| haskell-interactive-switch | ~, s s~ | Switch back and forth between REPL and buffer | | |
| flycheck-next-error | ~, e n~ | Go to next error | | |
| flycheck-prev-error | ~, e p~ | Go to previous error | | |
| flycheck-list-errors | ~, e l~ | List all errors (q to quit) | | |
| flycheck-explain-error-at-point | ~, e e~ | Describe error at point in more detail (q to quit) | | |
| lsp-rename | ~, r R~ | Rename the given symbol and all occurences in project | | |
| lsp-format-buffer | ~, r f~ | Format the buffer | | |
| lsp-ui-sideline-apply-code-actions | ~, r a~ | Bring up suggested code actions and select to apply | | |
| lsp-restart-workspace | ~, l r~ | Restart the lsp-workspace (do this after changing default.nix/shell.nix) | | |
| completion-at-point | ~, ,~ | Bring up completion suggestions | | |
| lsp-describe-thing-at-point | ~, .~ | Describe thing at point | | |
### Features | |
Provided features and how to use them: | |
| Feature | How | | |
|:--------------------------------------:|:---------------------------------------------------------------------:| | |
| HLint warnings and GHC warnings/errors | Should appear in sidebar by default, or enable ~lsp-ui-sideline-mode~ | | |
| Error highlighting | Should appear by default, or enable ~flycheck-mode~ | | |
| Jump to next error | ~, e n~, or ~flycheck-next-error~ | | |
| Jump to prev error | ~, e p~, or ~flycheck-previous-error~ | | |
| Code actions | ~, r a~, or ~lsp-ui-sideline-apply-code-actions~ | | |
| Type information and doco on hover | Should appear in sidebar by default, or enable ~lsp-ui-doc-mode~ | | |
| Jump to definition | ~, g g~, or ~lsp-ui-peek-find-definitions~ | | |
| List all top-level definitions | ~, g m~, or ~lsp-ui-imenu~ | | |
| Highlight references in document | Should appear on hover | | |
| View references in file | ~, g r~, or ~lsp-ui-peek-find-references~ (q to quit) | | |
| Completion | ~, ,~, or ~completion-at-point~ | | |
| Formatting | ~, r f~, or ~lsp-format-buffer~ | | |
| Renaming | ~, r R~, or ~lsp-rename~ | | |
| Type quick fixes | Use "Code actions" | | |
| Add missing imports | Use "Code actions" | | |
| Add missing packages to cabal file | Not supported at this time | | |
| Description at point | ~, .~ or ~lsp-describe-thing-at-point~ | | |
| Load buffer in REPL | ~, s b~, or ~haskell-process-load-file~ | | |
| Switch to REPL | ~, s s~, or ~haskell-interactive-switch~ | | |
## Help it doesn't work | |
The first thing to try is that you can build this project without Spacemacs. | |
Navigate to the project directory, open a nix-shell and run `cabal new-repl` or | |
equivalent. The files generated by those commands should help bootstrap the | |
Spacemacs/HIE process. Just delete all buffers associated with the project and | |
re-open one of the project files to try again. | |
### Can't find dependencies listed in other targets | |
Most likely you're in a test file and it can't find dependencies only listed in | |
other targets. You just need to open a terminal, navigate to the project | |
directory, open a nix-shell and run `cabal configure --enable-tests` or `cabal | |
new-configure --enable-tests`. | |
You can jump into a Haskell REPL (in Spacemacs) and run | |
`haskell-session-change-target` to choose another target, which will let your | |
REPL access that target. You may also need to do the same in your buffer and run | |
`haskell-process-restart` and `lsp-restart-workspace`. | |
### Outstanding issues | |
- "Add missing packages to cabal file" feature does not work/has odd behaviour. | |
- Links in `lsp-ui-doc-mode` don't work (see | |
[this](https://github.com/emacs-lsp/lsp-ui/issues/188) issue). They are the | |
correct links but don't open a web page. The current workaround is to use | |
`lsp-describe-thing-at-point`, and use the links in that buffer. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment