[!NOTE]
- Just works. No need to call
setup
!- No dependency on
lspconfig
.- Lazy initialization by design.
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
.hoogle
installation (recommended for better hoogle search performance).fast-tags
(for automatic tag generation as a fallback for vim.lsp.tagfunc
).haskell-debug-adapter
and
nvim-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 forhaskell-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].
[^1]: See :help base-directories
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.
haskell-language-server
client.haskell-language-server
settings per project
from JSON files.haskell-language-server
heavily relies on. Can be disabled.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()
With the <C-r>
keymap,
the Hoogle search telescope integration can be used to fill holes.
Start a GHCi repl for the current project / buffer.
cabal repl
, stack ghci
or ghci
) for your project.toggleterm.nvim
.iron.nvim
(see advanced configuration).Inspired by rust-tools.nvim, this plugin adds the following hover actions (if available):
<Plug>
mapping
because there can be multiple signatures to search for).<Plug>HaskellHoverActionDocs
).<Plug>HaskellHoverActionSource
).<Plug>HaskellHoverActionDefinition
).<Plug>HaskellHoverActionTypeDefinition
).<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.
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:
This feature can be tweaked or disabled in the advanced configuration.
haskell-debug-adapter
configurationsIf 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.
For planned features, refer to the issues.
To modify the default configuration, set vim.g.haskell_tools
.
: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.HTDefaultConfig
).haskell-language-server
settings
(including those not set by this plugin), run haskell-language-server generate-default-config
.haskell-language-server
documentation.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.
haskell-language-server
settings per projectBy 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.
[^2]: haskell-language-server
can generate such a file with the generate-default-config
CLI argument.
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.
haskell-language-server
on Cabal filesSince 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
.
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,
},
},
},
}
haskell-debug-adapter
launch configurationsThere are two ways this plugin will detect haskell-debug-adapter
launch configurations:
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, iffast-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, ifnivm-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"
. ornix 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 ashoogle
,fast-tags
orghci
might break on non-Unix-like operating systems.
To enable debug logging, set the log level to DEBUG
[^3]:
[^3]: See :help vim.log.levels
:
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:
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!