Editors & Tooling
- Introduction
- Using IHP with Visual Studio Code / VSCode
- Using IHP with Sublime Text
- Using IHP with Emacs
- Using IHP with Vim / NeoVim
- Haskell Language Server
- IHP Dev Server
Introduction
This place describes all the steps needed to get your code editors and other tooling working with IHP. When your favorite code editor is not described here, feel free to add your setup to this list.
You will also find steps on how to get autocompletion and smart IDE features. This is provided by haskell-language-server. IHP already comes with a bundled haskell-language-server, so you don’t need to install it manually.
Using IHP with Visual Studio Code / VSCode
Install the following extensions:
-
direnv
, this loads the project.envrc
file, so all the right Haskell packages are available to VSCode -
Haskell
, this gets smart IDE features with haskell-language-server -
Haskell HSX
, provides support for HSX
To make file paths clickable inside the web browser (e.g. when a type error happens), export this env var in your shell (e.g. in .bashrc
):
export IHP_EDITOR="code --goto"
Video Tutorial:
You can also watch “IHP + Visual Studio Code: Autocompletion & Smart IDE Features with Haskell Language Server” on Youtube
VSCode + Haskell Language Server Troubleshooting
-
"Couldn't figure out what GHC version the project is using"
If you get an error
Couldn't figure out what GHC version the project is using
in Visual Studio Code make sure that the Nix Env Selector plugin was started correctly:- Open the project in VS Code
-
Click
View
->Command Palette
->Nix-Env: Select Environment
->default.nix
- This will restart VS Code. After that Haskell Language Server should be working.
-
"Failed to get project GHC executable path: CradleError"
If you get an error
Failed to get project GHC executable path
with aCradleError
, check your Haskell extension’s settings and make sure you are using"haskell.manageHLS": "PATH"
:- Open the project in VS Code
-
Click
Extensions
(Ctrl + Shift + X
) -> Right-clickHaskell
->Extension Settings
->Haskell: Manage HLS
->PATH
This error happens because the
GHCup
setting ignores allPATH
variables pointing toHLS
and instead uses the/home/$USER/.cache/ghcup
directory. This is contrary to IHP’s usage of Nix which places the necessary tooling into yourPATH
whenever your Nix environment is selected.A caveat of this fix is that every time you want to use GHCup for tooling version management, you’ll need to switch the setting back. The reason for this can be found in this issue.
VSCode on Windows with Windows Subsystem for Linux
It is important to not access the files within the WSL from Windows itself (however, the other way around is ok). You can seamlessly (including auto-save) work on your projects within WSL from VS Code in Windows by adding the Remote WSL
extension from Microsoft.
Using IHP with Sublime Text
Works great already out of the box.
Recommended packages:
-
Nix
for syntax highlighting of nix files -
Haskell HSX
for syntax highlighting of Haskell with HSX -
Direnv
to load the.envrc
file of the project. -
LSP
for smart IDE features. UseLSP: Enable Language Server in Project -> Haskell Language Server
to activate. In you don’t have that option in the menu, selectOpen Preferences > Package Settings > LSP > Settings
and add the"haskell-language-server"
client configuration to the"clients"
:{ "clients": { "haskell-language-server": { "enabled": true, "command": ["haskell-language-server-wrapper", "--lsp"], "selector": "source.haskell" } } }
To make file paths clickable inside the web browser (e.g. when a type error happens), export this env var in your shell (e.g. in .bashrc
):
export IHP_EDITOR="sublime"
Using IHP with Emacs
This section describes the minimal setup needed to get syntax highlighting, goto-definition, type errors and linting of IHP projects in Emacs with few external dependencies.
If you have Emacs 29 or later, the language server package eglot
is
already included. If you’re stuck on an older Emacs version, install
it from ELPA with M-x package-install eglot RET
.
Install the following additional packages from Melpa:
-
haskell-mode
– enable basic Haskell support, syntax highlighting, ghci interaction -
envrc-mode
– lets eglot find the PATH to Haskell Language Server etc.
At the very least you need (add-hook 'haskell-mode-hook #'eglot-ensure)
and (envrc-global-mode +1)
in your ~/.emacs.d/init.el
, but you may also
want to set up some keybindings to common language server functions
(since by default none are included). Here’s an example init file:
(use-package envrc
:config
(envrc-global-mode +1))
(use-package eglot
:config
(add-hook 'haskell-mode-hook #'eglot-ensure)
;; Optionally add keybindings to some common functions:
:bind ((:map eglot-mode-map
("C-c C-e r" . eglot-rename)
("C-c C-e l" . flymake-show-buffer-diagnostics)
("C-c C-e p" . flymake-show-project-diagnostics)
("C-c C-e C" . eglot-show-workspace-configuration)
("C-c C-e R" . eglot-reconnect)
("C-c C-e S" . eglot-shutdown)
("C-c C-e A" . eglot-shutdown-all)
("C-c C-e a" . eglot-code-actions)
("C-c C-e f" . eglot-format))))
;; Optional: Show/pick completions on tab, sane max height:
(setq tab-always-indent 'complete
completions-max-height 20
completion-auto-select 'second-tab)
(server-start) ; for emacsclient / quick startup
(The built-in completion menu isn’t very modern-looking, a good alternative if you want to add another plugin is corfu.)
To make file paths clickable inside the web browser (e.g. when a type error happens), you’ll first need a little helper script to translate file paths from the file:line:col
format to +line:col file
which emacs expects. Put this in e.g. ~/bin/emacs-line
:
#!/bin/sh
path="${1%%:*}"
col="${1##*:}"
line="${1%:*}"; line="${line##*:}"
emacsclient -n +"${line}:${col}" "${path}"
and chmod +x ~/bin/emacs-line
, then export this env var in your shell (e.g. in .bashrc
):
export IHP_EDITOR="$HOME/bin/emacs-line"
Another useful package set that integrates with lsp/lsp-ui and loads the default nix environment from direnv as well as removing common flycheck issue. This config also adds a jump to definition for functions bound to “C-c p”:
(use-package direnv
:defer
:custom
(direnv-always-show-summary nil)
:config
(direnv-mode))
(use-package lsp-mode
:custom
(lsp-lens-enable nil)
(lsp-enable-symbol-highlighting nil)
:hook
(lsp-mode . lsp-enable-which-key-integration)
:config
;; This is to make `lsp-mode' work with `direnv' and pick up the correct
;; version of GHC.
(advice-add 'lsp :before #'direnv-update-environment)
(setq lsp-modeline-code-actions-enable nil))
(use-package lsp-ui
:hook (prog-mode . lsp-ui-mode)
:bind (("C-c p" . lsp-ui-peek-find-definitions))
:config
(setq lsp-ui-doc-position 'bottom))
;; (add-hook 'haskell-mode-hook #'lsp)
(use-package flycheck-haskell
;; Disabling this package, since it only gives error:
;; "Reading Haskell configuration failed with exit code Segmentation fault and
;; output:", when trying to run it in Nix/direnv setup.
:disabled
:hook (haskell-mode . flycheck-haskell-setup))
(add-hook 'haskell-mode-hook
(lambda ()
(rainbow-mode -1)
;; we aren't evil:
;; (evil-leader/set-key "x h" 'haskell-hoogle)
;; (setq evil-shift-width 2)
(define-key haskell-mode-map (kbd "C-c C-c C-s")
'haskell-mode-stylish-buffer)
(bind-key (kbd "C-c C-c C-a") 'haskell-sort-imports)
(setq haskell-auto-insert-module-format-string
"module %s\n () where\n\n")
(haskell-auto-insert-module-template)
(smartparens-mode)
(sp-local-pair 'haskell-mode "{" "}")
(setq haskell-hoogle-command nil)
(ligature-mode)))
(use-package lsp-haskell
:hook ((haskell-mode . lsp-deferred)
(haskell-literate-mode . lsp-deferred))
:custom
(lsp-haskell-server-path "haskell-language-server"))
(use-package haskell-mode
:defer)
Using IHP with Vim / NeoVim
Using CoC
Provided you already have CoC setup, just run :CocConfig
and add the following segment.
{
"languageserver": {
"haskell": {
"command": "haskell-language-server-wrapper",
"args": ["--lsp"],
"rootPatterns": [
"*.cabal",
"stack.yaml",
"cabal.project",
"package.yaml",
"hie.yaml"
],
"filetypes": ["haskell", "lhaskell"]
}
}
}
Haskell Language Server
Because haskell-language-server is tightly coupled to the GHC version it comes pre-bundled with IHP. In case you also have a local install of haskell-language-server you need to make sure that the one provided by IHP is used. Usually, this is done automatically when your editor is picking up the .envrc
file.
When something goes wrong you can also run haskell-language-server
inside the project directory (within a nix-shell
). This might output some helpful error messages.
IHP Dev Server
Customizing the Web Browser used by IHP
When running devenv up
the application will automatically be opened in your default browser. You can manually specify a browser by setting the env var IHP_BROWSER
in .envrc
:
export IHP_BROWSER=firefox
You can disable the auto-start of the browser completely using echo
as your browser:
export IHP_BROWSER=echo
Running the IHP Dev Server On a Host Different From localhost
If you run the IHP dev server on computer different from your local machine (e.g. a second computer in your network or a Cloud Dev Env like GitPod), you need to specify the right base url:
export IHP_BASEURL=http://some-other-host:8000 # App Url, Default: http://localhost:8000
export IHP_IDE_BASEURL=http://some-other-host:8001 # SchemaDesigner etc., Default: http://localhost:8001
Next time you use the dev server via devenv up
all links will use the right IHP_BASEURL
instead of using localhost:8000
;
Hoogle
To quickly look up function type signatures you can use the built-in hoogle server.
To install it:
-
Open
flake.nix
-
Add
withHoogle = true;
to theihp
project block, insideperSystem
function invocation like this:
...
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
];
withHoogle = true; # <-------
};
};
};
Run devenv up
to remake your dev environment.
After that you can use the following command to start hoogle at localhost:8080
:
hoogle server --local -p 8080