Explore the docs Β»
Report Bug
Β·
Request Feature
Β·
Ask Question
Supercharge your Haskell experience in Neovim!
π¦₯
Note
- Just works. No need to call
setup
! - No dependency on
lspconfig
. - Lazy initialization by design.
- π Prerequisites
- π₯ Installation
- β‘ Quick Setup
- π Features
- βοΈ Advanced configuration
- π©Ί Troubleshooting
- π Recommendations
- π Contributing
If you are starting out with Haskell, nvim-lspconfig.hls
is probably enough for you.
It provides the lowest common denominator of LSP support.
This plugin is for those who would like additional features
that are specific to Haskell tooling.
neovim >= 0.10
haskell-language-server
(recommended).telescope.nvim
.- A local
hoogle
installation (recommended for better hoogle search performance). fast-tags
(for automatic tag generation as a fallback forvim.lsp.tagfunc
).haskell-debug-adapter
andnvim-dap
.
This plugin is available on LuaRocks:
:Rocks install haskell-tools.nvim
Example using lazy.nvim
:
{
'mrcjkb/haskell-tools.nvim',
version = '^4', -- Recommended
lazy = false, -- This plugin is already lazy
}
Tip
It is suggested to pin to tagged releases if you would like to avoid breaking changes.
To manually generate documentation, use :helptags ALL
.
Note
For NixOS users with flakes enabled, this project provides outputs in the
form of a package and an overlay; use it as you wish in your NixOS or
home-manager configuration.
It is also available in nixpkgs
.
This plugin automatically configures the haskell-language-server
builtin LSP
client and integrates with other haskell tools.
See the Features section for more info.
Warning
Do not call the nvim-lspconfig.hls
setup or set up the lsp client for haskell-language-server
manually,
as doing so may cause conflicts.
This is a filetype plugin that works out of the box,
so there is no need to call a setup
function or configure anything
to get this plugin working.
You will most likely want to add some keymaps.
Most keymaps are only useful in haskell and/or cabal files,
so I suggest you define them in ~/.config/nvim/after/ftplugin/haskell.lua
1
and/or ~/.config/nvim/after/ftplugin/cabal.lua
1.
Some suggestions:
-- ~/.config/nvim/after/ftplugin/haskell.lua
local ht = require('haskell-tools')
local bufnr = vim.api.nvim_get_current_buf()
local opts = { noremap = true, silent = true, buffer = bufnr, }
-- haskell-language-server relies heavily on codeLenses,
-- so auto-refresh (see advanced configuration) is enabled by default
vim.keymap.set('n', '<space>cl', vim.lsp.codelens.run, opts)
-- Hoogle search for the type signature of the definition under the cursor
vim.keymap.set('n', '<space>hs', ht.hoogle.hoogle_signature, opts)
-- Evaluate all code snippets
vim.keymap.set('n', '<space>ea', ht.lsp.buf_eval_all, opts)
-- Toggle a GHCi repl for the current package
vim.keymap.set('n', '<leader>rr', ht.repl.toggle, opts)
-- Toggle a GHCi repl for the current buffer
vim.keymap.set('n', '<leader>rf', function()
ht.repl.toggle(vim.api.nvim_buf_get_name(0))
end, opts)
vim.keymap.set('n', '<leader>rq', ht.repl.quit, opts)
Tip
- For more LSP related keymaps, see the
nvim-lspconfig
suggestions. - If using a local
hoogle
installation, follow these instructions to generate a database. - See the Advanced configuration section for more configuration options.
Important
- Do not set
vim.g.haskell_tools
inafter/ftplugin/haskell.lua
, as the file is sourced after the plugin is initialized.
- Set up a
haskell-language-server
client. - Dynamically load
haskell-language-server
settings per project from JSON files. - Clean shutdown of language server on exit to prevent corrupted files (see ghc #14533).
- Automatically adds capabilities for the following plugins, if loaded:
- cmp-nvim-lsp (provides completion sources for nvim-cmp).
- nvim-lsp-selection-range (Adds expand selection support).
- nvim-ufo. (Adds folding range support).
- Automatically refreshes code lenses by default,
which
haskell-language-server
heavily relies on. Can be disabled.
- Evaluate all code snippets at once
haskell-language-server
can evaluate code snippets using code lenses.
haskell-tools.nvim
provides a require('haskell-tools').lsp.buf_eval_all()
shortcut to evaluate all of them at once.
-
Hoogle-search for signature
-
Search for the type signature under the cursor.
-
Falls back to the word under the cursor if the type signature cannot be determined.
-
Telescope keymaps:
<CR>
to copy the selected entry (<name> :: <signature>
) to the clipboard.<C-b>
to open the selected entry's Hackage URL in a browser.<C-r>
to replace the word under the cursor with the selected entry.
require('haskell-tools').hoogle.hoogle_signature()
- Hole-driven development powered by Hoogle
With the <C-r>
keymap,
the Hoogle search telescope integration can be used to fill holes.
- GHCi repl
Start a GHCi repl for the current project / buffer.
- Automagically detects the appropriate command (
cabal repl
,stack ghci
orghci
) for your project. - Choose between a builtin handler or
toggleterm.nvim
. - Dynamically create a repl command for
iron.nvim
(see advanced configuration). - Interact with the repl from within Haskell files using a lua API.
- Open project/package files for the current buffer
- Hover actions
Inspired by rust-tools.nvim, this plugin adds the following hover actions (if available):
- Hoogle search
(no dedicated
<Plug>
mapping because there can be multiple signatures to search for). - Open documentation in browser (
<Plug>HaskellHoverActionDocs
). - Open source in browser (
<Plug>HaskellHoverActionSource
). - Go to definition (
<Plug>HaskellHoverActionDefinition
). - Go to type definition (
<Plug>HaskellHoverActionTypeDefinition
). - Find references (
<Plug>HaskellHoverActionReferences
).
You can invoke them by switching to the hover window and entering <CR>
on the respective line, or with a keymap for the respective <Plug>
mapping.
If you do not wish to create a separate keymap for each <Plug>
mapping,
you can also create a keymap for <Plug>HaskellHoverAction
, which accepts
a <count>
prefix as the (1-based) index of the hover action to invoke.
For example, if you set the following keymap:
vim.keymap.set('n', '<space>a', '<Plug>HaskellHoverAction')
you can invoke the third hover action with 3<space>a
.
Additionally, the default behaviour of stylizing markdown is disabled. And the hover buffer's filetype is set to markdown, so that nvim-treesitter users can benefit from syntax highlighting of code snippets.
Note
Hover actions are disabled if Neovim is not built with luajit.
- Automatically generate tags
On attaching, Neovim's LSP client will set up tagfunc
to query the language server for locations to jump to.
If no location is found, it will fall back to a tags
file.
If fast-tags
is installed,
this plugin will set up autocmd
s to automatically generate tags:
- For the whole project, when starting a session.
- For the current (sub)package, when writing a file.
This feature can be tweaked or disabled in the advanced configuration.
- Auto-discover
haskell-debug-adapter
configurations
If the nvim-dap
plugin is installed,
haskell-tools.nvim
will automatically discover haskell-debug-adapter
configurations.
Note
haskell-debug-adapter
is an experimental design and implementation of
a debug adapter for Haskell.
- Planned
For planned features, refer to the issues.
To modify the default configuration, set vim.g.haskell_tools
.
- See
:help haskell-tools.config
for a detailed documentation of all available configuration options. You may need to run:helptags ALL
if the documentation has not been installed. - The default configuration can be found here (see
HTDefaultConfig
). - To view all available
haskell-language-server
settings (including those not set by this plugin), runhaskell-language-server generate-default-config
.- For detailed descriptions of the configs,
look at the
haskell-language-server
documentation.
- For detailed descriptions of the configs,
look at the
vim.g.haskell_tools = {
---@type ToolsOpts
tools = {
-- ...
},
---@type HaskellLspClientOpts
hls = {
---@param client number The LSP client ID.
---@param bufnr number The buffer number
---@param ht HaskellTools = require('haskell-tools')
on_attach = function(client, bufnr, ht)
-- Set keybindings, etc. here.
end,
-- ...
},
---@type HTDapOpts
dap = {
-- ...
},
}
Tip
vim.g.haskell_tools
can also be a function that returns
a table.
By default, this plugin will look for a hls.json
2 file in the project root directory,
and attempt to load it.
If the file does not exist, or it can't be decoded,
the hls.default_settings
will be used.
You can change this behaviour with the hls.settings
config:
vim.g.haskell_tools = {
-- ...
hls = {
---@param project_root string Path to the project root
settings = function(project_root)
local ht = require('haskell-tools')
return ht.lsp.load_hls_settings(project_root, {
settings_file_pattern = 'hls.json'
})
end,
},
}
Some code lenses might be more interesting than others.
For example, the importLens
could be annoying if you prefer to import
everything or use a custom prelude.
Individual code lenses can be turned off by disabling them in the respective
plugin configurations:
hls = {
settings = {
haskell = {
plugin = {
class = { -- missing class methods
codeLensOn = false,
},
importLens = { -- make import lists fully explicit
codeLensOn = false,
},
refineImports = { -- refine imports
codeLensOn = false,
},
tactics = { -- wingman
codeLensOn = false,
},
moduleName = { -- fix module names
globalOn = false,
},
eval = { -- evaluate code snippets
globalOn = false,
},
['ghcide-type-lenses'] = { -- show/add missing type signatures
globalOn = false,
},
},
},
},
},
Note
Alternatively, you can dynamically enable/disable different code lenses per project.
Since version 1.9.0.0
, haskell-language-server
can launch on Cabal files,
but it does not support all features that it has for Haskell files.
You can add cabal-specific keymaps, etc. in ~/.config/nvim/after/ftplugin/cabal.lua
.
Set up iron.nvim
to use haskell-tools.nvim
Depends on iron.nvim/#300.
local iron = require("iron.core")
iron.setup {
config = {
repl_definition = {
haskell = {
command = function(meta)
local file = vim.api.nvim_buf_get_name(meta.current_bufnr)
-- call `require` in case iron is set up before haskell-tools
return require('haskell-tools').repl.mk_repl_cmd(file)
end,
},
},
},
}
There are two ways this plugin will detect haskell-debug-adapter
launch configurations:
- Automatically, by parsing Cabal or Stack project files.
- By loading a
launch.json
file in the project root.
For a complete overview, enter :help haskell-tools
in Neovim.
Command | Description |
---|---|
:Hls start |
Start the LSP client |
:Hls stop |
Stop the LSP client |
:Hls restart |
Restart the LSP client |
:Hls evalAll |
Evaluate all code snippets |
local ht = require('haskell-tools')
--- Start or attach the LSP client.
ht.lsp.start()
--- Stop the LSP client.
ht.lsp.stop()
--- Restart the LSP client.
ht.lsp.restart()
--- Callback for dynamically loading haskell-language-server settings
--- Falls back to the `hls.default_settings` if no file is found
--- or one is found, but it cannot be read or decoded.
--- @param project_root string? The project root
ht.lsp.load_hls_settings(project_root)
--- Evaluate all code snippets in comments
ht.lsp.buf_eval_all()
local ht = require('haskell-tools')
--- Run a hoogle signature search for the value under the cursor
ht.hoogle.hoogle_signature()
Command | Description | Arguments |
---|---|---|
:Haskell repl toggle {file?} |
Toggle a GHCi repl | filepath (optional) |
:Haskell repl quit |
Quit the current repl | |
:Haskell repl load {file?} |
Load a file into the current repl | filepath (optional) |
:Haskell repl reload |
Reload the current repl | |
:Haskell repl paste_type {register?} |
Query the repl for the type of {register} | |
:Haskell repl cword_type |
Query the repl for the type of the word under the cursor | |
:Haskell repl paste_info {register?} |
Query the repl for info on {register} | |
:Haskell repl cword_info {register?} |
Query the repl for info on the word under the cursor |
local ht = require('haskell-tools')
--- Toggle a GHCi repl for the current project
ht.repl.toggle()
--- Toggle a GHCi repl for `file`
--- @param file string Path to a Haskell file
ht.repl.toggle(file)
--- Quit the repl
ht.repl.quit()
--- Paste a command to the repl from register `reg`.
--- @param reg string? Register to paste from (:h registers), defaults to '"'.
ht.repl.paste(reg)
--- Query the repl for the type of register `reg`, and paste it to the repl.
--- @param reg string? Register to paste from (:h registers), defaults to '"'.
ht.repl.paste_type(reg)
--- Query the repl for the type of word under the cursor
ht.repl.cword_type()
--- Query the repl for info on register `reg`.
--- @param reg string? Register to paste from (:h registers), defaults to '"'.
ht.repl.paste_info(reg)
--- Query the repl for info on the word under the cursor
ht.repl.cword_info()
--- Load a file into the repl
--- @param file string The absolute file path
ht.repl.load_file(file)
--- Reload the repl
ht.repl.reload()
Command | Description |
---|---|
:Haskell projectFile |
Open the project file for the current buffer (cabal.project or stack.yaml) |
:Haskell packageYaml |
Open the package.yaml file for the current buffer |
:Haskell packageCabal |
Open the *.cabal file for the current buffer |
local ht = require('haskell-tools')
--- Open the project file for the current buffer (cabal.project or stack.yaml)
ht.project.open_project_file()
--- Open the package.yaml file for the current buffer
ht.project.open_package_yaml()
--- Open the *.cabal file for the current buffer
ht.project.open_package_cabal()
--- Search for files within the current (sub)package
--- @param opts table Optional telescope.nvim `find_files` options
ht.project.telescope_package_files(opts)
--- Live grep within the current (sub)package
--- @param opts table Optional telescope.nvim `live_grep` options
ht.project.telescope_package_grep(opts)
The following functions depend on fast-tags
.
local ht = require('haskell-tools')
-- Generate tags for the whole project
---@param path string? An optional file path, defaults to the current buffer
---@param opts table Optional options:
--- opts.refresh boolean
--- - Whether to refresh tags if they have already been generated for a project
ht.tags.generate_project_tags(path, opts)
-- Generate tags for the whole project
---@param path string? An optional file path, defaults to the current buffer
ht.tags.generate_package_tags(path)
Note
By default, haskell-tools
will automate generating project and package
tags, if fast-tags
is detected.
local ht = require('haskell-tools')
---@param bufnr integer The buffer number
---@param opts table? Optional
---@param opts.autodetect: (boolean)
--- Whether to auto-detect launch configurations
---@param opts.settings_file_pattern: (string)
--- File name or pattern to search for. Defaults to 'launch.json'
ht.dap.discover_configurations(bufnr, opts)
Note
haskell-tools.nvim
will discover DAP launch configurations automatically,
if nivm-dap
is installed and the debug adapter server is executable.
There is typically no need to call this function manually.
If telescope.nvim
is installed,
haskell-tools.nvim
will register the ht
extension
with the following commands:
Command | Description |
---|---|
:Telescope ht package_files |
Search for files within the current (sub)package |
:Telescope ht package_hsfiles |
Search for Haskell files within the current (sub)package |
:Telescope ht package_grep |
Live grep within the current (sub)package |
:Telescope ht package_hsgrep |
Live grep Haskell files within the current (sub)package |
:Telescope ht hoogle_signature |
Run a Hoogle search for the type signature under the cursor |
To load the extension, call
require('telescope').load_extension('ht')
Important
If you lazy-load this plugin, make sure it is loaded before registering the Telescope extension.
For a health check, run :checkhealth haskell-tools
If hls
is unable to show diagnostics, or shows an error diagnostic
at the top of your file, you should first check if you can compile
your project with cabal or stack.
If there are compile errors, open the files that cannot be compiled,
and hls
should be able to show the error diagnostics for those files.
Check which versions of hls
and GHC you are using
(e.g. by calling haskell-language-server-wrapper --probe-tools
or haskell-language-server --probe-tools
).
Sometimes, certain features take some time to be implemented for the latest GHC versions.
You can see how well a specific GHC version is supported here.
To troubleshoot this plugin with a minimal config in a temporary directory, you can try minimal.lua.
nvim -u minimal.lua
Note
If you use Nix, you can run
nix run "github:mrcjkb/haskell-tools.nvim#nvim-minimal-stable"
.
or
nix run "github:mrcjkb/haskell-tools.nvim#nvim-minimal-nightly"
.
If you cannot reproduce your issue with a minimal config,
it may be caused by another plugin.
In this case, add additional plugins and their configurations to minimal.lua
,
until you can reproduce it.
Note
This plugin is only tested on Linux.
It should work on MacOS, and basic features should also work on Windows
(since version 1.9.5
), but I have no way to test this myself.
Features that rely on external tools, such as hoogle
,
fast-tags
or ghci
might break on non-Unix-like operating systems.
To enable debug logging, set the log level to DEBUG
3:
vim.g.haskell_tools = {
tools = { -- haskell-tools options
log = {
level = vim.log.levels.DEBUG,
},
},
}
You can also temporarily set the log level by calling
Command | Argument |
---|---|
:Haskell log setLevel |
One of debug error warn info trace off |
or
:lua require('haskell-tools').log.set_level(vim.log.levels.DEBUG)
You can find the log files by calling
-- haskell-tools.nvim log
:lua =require('haskell-tools').log.get_logfile()
-- haskell-language-server logs
:lua =require('haskell-tools').log.get_hls_logfile()
or open them by calling
:lua require('haskell-tools').log.nvim_open_logfile() -- or :Haskell log openLog
:lua require('haskell-tools').log.nvim_open_hls_logfile() -- or :Haskell log openHlsLog
Here are some other plugins I recommend for Haskell (and nix) development in neovim:
- neotest-haskell: Interact with tests in neovim.
- haskell-snippets.nvim Collection of Haskell snippets for LuaSnip.
- telescope_hoogle: Live Hoogle search.
- telescope-manix: Nix search.
- nvim-lint: As a fallback in case there are problems with haskell-language-server (e.g. in large mono repos).
- nvim-treesitter: For syntax highlighting, and much more.
- nvim-treesitter-textobjects: For TreeSitter-based textobjects.
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
Footnotes
-
haskell-language-server
can generate such a file with thegenerate-default-config
CLI argument. β© -
See
:help vim.log.levels
: β©