• Stars
    star
    374
  • Rank 110,641 (Top 3 %)
  • Language
    Lua
  • License
    GNU General Publi...
  • Created over 1 year ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Supercharge your Haskell experience in neovim!

haskell-tools.nvim

Report Bug

Supercharge your Haskell experience in Neovim!

๐Ÿฆฅ

Neovim Lua Haskell Nix

GPL2 License Issues Build Status LuaRocks

All Contributors

Quick Links

Prerequisites

Required

Optional

Installation

This plugin is available on LuaRocks.

If you use a plugin manager that does not support LuaRocks, you have to declare the dependencies yourself.

Example using packer.nvim:

use {
  'mrcjkb/haskell-tools.nvim',
  requires = {
    'nvim-lua/plenary.nvim',
    'nvim-telescope/telescope.nvim', -- optional
  },
  branch = '1.x.x', -- recommended
}

Note

It is suggested to use the stable branch 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.

Quick Setup

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 manually, as doing so may cause conflicts.

To get started quickly with the default setup, add the following to ~/.config/nvim/ftplugin/haskell.lua1:

local ht = require('haskell-tools')
local def_opts = { noremap = true, silent = true, }
ht.start_or_attach {
  hls = {
    on_attach = function(client, bufnr)
      local opts = vim.tbl_extend('keep', def_opts, { buffer = bufnr, })
      -- haskell-language-server relies heavily on codeLenses,
      -- so auto-refresh (see advanced configuration) is enabled by default
      vim.keymap.set('n', '<space>ca', vim.lsp.codelens.run, opts)
      vim.keymap.set('n', '<space>hs', ht.hoogle.hoogle_signature, opts)
      vim.keymap.set('n', '<space>ea', ht.lsp.buf_eval_all, opts)
    end,
  },
}

-- Suggested keymaps that do not depend on haskell-language-server:
local bufnr = vim.api.nvim_get_current_buf()
-- set buffer = bufnr in ftplugin/haskell.lua
local opts = { noremap = true, silent = true, buffer = bufnr }

-- 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, def_opts)
vim.keymap.set('n', '<leader>rq', ht.repl.quit, opts)

-- Detect nvim-dap launch configurations
-- (requires nvim-dap and haskell-debug-adapter)
ht.dap.discover_configurations(bufnr)

Note

  • For more LSP related keymaps, see the nvim-lspconfig suggestions.
  • If using a local hoogle installation, follow these instructions to generate a database.
  • If you prefer, you can instead call require('haskell-tools').setup {} with the same options as start_or_attach() in your Neovim config. In this case, haskell-tools.nvim will set up filetype autocommands for you.

Features

  • 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:
  • Automatically refreshes code lenses by default, which haskell-language-server heavily relies on. Can be disabled.

codeLens

  • 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.

evalAll

  • 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()

hoogleSig

  • Hole-driven development powered by Hoogle

With the <C-r> keymap, the Hoogle search telescope integration can be used to fill holes.

hoogleHole

  • GHCi repl

Start a GHCi repl for the current project / buffer.

  • Automagically detects the appropriate command (cabal new-repl, stack ghci or ghci) 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.

repl

  • Open project/package files for the current buffer

commands

  • Hover actions

Inspired by rust-tools.nvim, this plugin adds the following hover actions (if available):

  • Hoogle search.
  • Open documentation in browser.
  • Open source in browser.
  • Go to definition.
  • Go to type definition.
  • Find references.

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.

hoverActions

  • 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 autocmds 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-detect haskell-debug-adapter configurations

If the nvim-dap plugin is installed, you can use haskell-tools.nvim to auto-detect haskell-debug-adapter configurations.

dap

Note

haskell-debug-adapter is an experimental design and implementation of a debug adapter for Haskell.

  • Planned

For planned features, refer to the issues.

Advanced configuration

To modify the default configs, call

-- defaults
require('haskell-tools').start_or_attach {
  tools = { -- haskell-tools options
    codeLens = {
      -- Whether to automatically display/refresh codeLenses
      -- (explicitly set to false to disable)
      autoRefresh = true,
    },
    hoogle = {
      -- 'auto': Choose a mode automatically, based on what is available.
      -- 'telescope-local': Force use of a local installation.
      -- 'telescope-web': The online version (depends on curl).
      -- 'browser': Open hoogle search in the default browser.
      mode = 'auto',
    },
    hover = {
      -- Whether to disable haskell-tools hover
      -- and use the builtin lsp's default handler
      disable = false,
      -- Set to nil to disable
      border = {
        { 'โ•ญ', 'FloatBorder' },
        { 'โ”€', 'FloatBorder' },
        { 'โ•ฎ', 'FloatBorder' },
        { 'โ”‚', 'FloatBorder' },
        { 'โ•ฏ', 'FloatBorder' },
        { 'โ”€', 'FloatBorder' },
        { 'โ•ฐ', 'FloatBorder' },
        { 'โ”‚', 'FloatBorder' },
      },
      -- Stylize markdown (the builtin lsp's default behaviour).
      -- Setting this option to false sets the file type to markdown and enables
      -- Treesitter syntax highligting for Haskell snippets
      -- if nvim-treesitter is installed
      stylize_markdown = false,
      -- Whether to automatically switch to the hover window
      auto_focus = false,
    },
    definition = {
      -- Configure vim.lsp.definition to fall back to hoogle search
      -- (does not affect vim.lsp.tagfunc)
      hoogle_signature_fallback = false,
    },
    repl = {
      -- 'builtin': Use the simple builtin repl
      -- 'toggleterm': Use akinsho/toggleterm.nvim
      handler = 'builtin',
      -- Which backend to prefer if both stack and cabal files are present
      prefer = vim.fn.executable('stack') and 'stack' or 'cabal',
      builtin = {
        create_repl_window = function(view)
          -- create_repl_split | create_repl_vsplit | create_repl_tabnew | create_repl_cur_win
          return view.create_repl_split { size = vim.o.lines / 3 }
        end
      },
      -- Can be overriden to either `true` or `false`.
      -- The default behaviour depends on the handler.
      auto_focus = nil,
    },
    -- Set up autocmds to generate tags (using fast-tags)
    -- e.g. so that `vim.lsp.tagfunc` can fall back to Haskell tags
    tags = {
      enable = vim.fn.executable('fast-tags') == 1,
      -- Events to trigger package tag generation
      package_events = { 'BufWritePost' },
    },
    dap = {
      cmd = { 'haskell-debug-adapter' },
    },
  },
  hls = { -- LSP client options
    -- ...
    default_settings = {
      haskell = { -- haskell-language-server options
        formattingProvider = 'ormolu',
        -- Setting this to true could have a performance impact on large mono repos.
        checkProject = true,
        -- ...
      }
    }
  }
}
  • The full list of defaults can be found here.
  • To view all available language server settings (including those not set by this plugin), run haskell-language-server generate-default-config.
  • For detailed descriptions of the configs, look at the haskell-language-server documentation.

How to dynamically load different haskell-language-server settings per project

By default, this plugin will look for a hls.json 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:

local ht = require('haskell-tools')
ht.start_or_attach {
  -- ...
  hls = {
    settings = function(project_root)
      return ht.lsp.load_hls_settings(project_root, {
        settings_file_pattern = 'hls.json'
      })
    end,
  },
}

How to disable individual code lenses

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,
        },
      },
    },
  },
},

Launch haskell-language-server on Cabal files

Since version 1.9.0.0, haskell-language-server can launch on Cabal files. You can either attach the LSP client in a ~/.config/nvim/ftplugin/cabal.lua file1, or call haskell-tools.setup().

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,
      },
    },
  },
}

Create haskell-debug-adapter launch configurations

There are two ways this plugin will detect haskell-debug-adapter launch configurations:

  1. Automatically, by parsing Cabal or Stack project files.
  2. By loading a launch.json file in the project root.

Available functions and commands

For a complete overview, enter :help haskell-tools in Neovim.

LSP

  • :HlsStart - Start the LSP client.
  • :HlsStop - Stop the LSP client.
  • :HlsRestart - Restart the LSP client.
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
ht.lsp.load_hls_settings(project_root)

-- Evaluate all code snippets in comments
ht.lsp.buf_eval_all()

Hoogle

local ht = require('haskell-tools')
-- Run a hoogle signature search for the value under the cursor
ht.hoogle.hoogle_signature()

Repl

local ht = require('haskell-tools')
-- Toggle a GHCi repl for the current project
ht.repl.toggle()

-- Toggle a GHCi repl for `file` (must be a Haskell file)
ht.repl.toggle(file)

-- Quit the repl
ht.repl.quit()

-- Paste a command to the repl from register `reg`. (`reg` defaults to '"')
ht.repl.paste(reg)

-- Query the repl for the type of register `reg`. (`reg` 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`. (`reg` 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
ht.repl.load_file(file)

-- Reload the repl
ht.repl.reload()

Project

  • :HsProjectFile - Open the project file for the current buffer (cabal.project or stack.yaml).
  • :HsPackageYaml - Open the package.yaml file for the current buffer.
  • :HsPackageCabal - 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: Optional telescope.nvim `find_files` options
ht.project.telescope_package_files(opts)
-- Live grep within the current (sub)package
---@param opts: Optional telescope.nvim `live_grep` options
ht.project.telescope_package_grep(opts)

Tags

The following functions depend on fast-tags.

local ht = require('haskell-tools')

-- Generate tags for the whole project
---@param path: An optional file path, defaults to the current buffer
---@param opts: Optional options:
---@param opts.refresh: 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: An optional file path, defaults to the current buffer
ht.tags.generate_package_tags(path)

Telescope extension

If telescope.nvim is installed, haskell-tools.nvim will register the ht extenstion with the following commands:

  • :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')

DAP

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)

Troubleshooting

For a health check, run :checkhealth haskell-tools

LSP features not working

Check which versions of hls and GHC you are using (haskell-language-server-wrapper --version). 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.

Minimal config

To troubleshoot this plugin with a minimal config in a temporary directory, you can try minimal.lua.

mkdir -p /tmp/minimal/
# The first start will install the plugins into the temporary directory
NVIM_DATA_MINIMAL=/tmp/minimal nvim -u minimal.lua
# Quit Neovim and start it up again with the plugins loaded
NVIM_DATA_MINIMAL=/tmp/minimal nvim -u minimal.lua

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 this has not been tested. Features that rely on external tools, such as hoogle, fast-tags or ghci are likely to break on non-Unix-like operating systems.

Logs

To enable debug logging, set the log level to DEBUG2:

require('haskell-tools').start_or_attach {
  tools = { -- haskell-tools options
    log = {
      level = vim.log.levels.DEBUG,
    },
  },
}

You can also temporarily set the log level by calling

: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()
:lua require('haskell-tools').log.nvim_open_hls_logfile()

Recommendations

Here are some other plugins I recommend for Haskell (and nix) development in neovim:

Contributors โœจ

Thanks goes to these wonderful people (emoji key):

fabfianda
fabfianda

๐Ÿ“–
Mango The Fourth
Mango The Fourth

๐Ÿš‡
Yen3
Yen3

๐Ÿ’ป
Sebastian Selander
Sebastian Selander

๐Ÿ’ป
Thomas Li
Thomas Li

๐Ÿ’ป
Matthieu Coudron
Matthieu Coudron

๐Ÿš‡ ๐Ÿ›
Michael Lan
Michael Lan

๐Ÿ›
Dhruva Srinivas
Dhruva Srinivas

๐Ÿ“–
Andrea Callea (he/him/his)
Andrea Callea (he/him/his)

๐Ÿ› ๐Ÿ““
Cyber Oliveira
Cyber Oliveira

๐Ÿ›
Br3akp01nt
Br3akp01nt

๐Ÿ““ ๐Ÿ›
Alper ร‡elik
Alper ร‡elik

๐Ÿ›
mauke
mauke

๐Ÿ’ป
Ravi Dayabhai
Ravi Dayabhai

๐Ÿ›

This project follows the all-contributors specification. Contributions of any kind welcome!

Footnotes

  1. See :help base-directories โ†ฉ โ†ฉ2

  2. See :help vim.log.levels: โ†ฉ

More Repositories

1

rustaceanvim

Supercharge your Rust experience in Neovim! A heavily modified fork of rust-tools.nvim
Lua
96
star
2

kickstart-nix.nvim

โ„๏ธ A dead simple Nix flake template repository for Neovim derivations
Lua
74
star
3

telescope-manix

A telescope.nvim extension for Manix - A fast documentation searcher for Nix
Lua
61
star
4

neotest-haskell

Neotest adapter for Haskell (cabal or stack) with support for Sydtest, Hspec and Tasty
Lua
48
star
5

lua-typecheck-action

A GitHub action that lets you leverage sumneko lua-language-server and EmmyLua to statically type check lua code.
Lua
28
star
6

haskell-snippets.nvim

My collection of Haskell snippets for LuaSnip. Powered by tree-sitter and LSP.
Lua
23
star
7

nvim-lua-nix-plugin-template

A starter template for a Neovim plugin written in Lua with a Nix CI
Nix
18
star
8

nvim

Lua
10
star
9

lfpBattery

LiFe-battery model TU Berlin
MATLAB
8
star
10

tcpip4diac

Enables TCP/IP client/server connection between Matlab and 4diac Communication Service Interface Function blocks (SERVER/CLIENT)
MATLAB
6
star
11

nix-flake-github-ci-template

A template for using nix flakes with GitHub Actions
Nix
4
star
12

.xmonad

My XMonad config (includes xmobar) and NixOS modules
Haskell
3
star
13

forte_http_comm

Simple HTTP communication layer for 4diac-RTE (FORTE) [migrated to official FORTE repo, no longer maintained here]
C++
3
star
14

jfx-filechooser-adapter

An adapter for a JavaFX File- and DirectoryChooser that allows for use in Swing
Kotlin
2
star
15

mtype340

Model of a thermal storage tank based on the TRNSYS "MULTIPORT Store-Model" Type 340 by H. Drueck
MATLAB
2
star
16

PVTControllerLib

IEC 61499 function block library for building forecast based energy system control applications.
MATLAB
2
star
17

flakes

My collection of nix flake templates (devShells, etc.)
Nix
2
star
18

nixfiles

โ„๏ธ My nixfiles
Nix
2
star
19

TODOS

Matlab: Use tags in code comments to create notes/todos and display links to them in the command window.
MATLAB
2
star
20

Polysun-4diac-ControllerPlugin

Open source Polysun plugin with a set of plugin controllers that can be used for communication in co-simulations with IEC 61499 applications running on 4diac-RTE (FORTE).
Java
2
star
21

nvim-lsp-foldexpr

neovim/pull/14306 extracted into a plugin.
Lua
2
star
22

expandaxes

More reliable implementation of the Matlab option "expand axes to fill figure"
MATLAB
2
star
23

module-finder

Gradle plugin that enables use of non-modular Java dependencies without an "Automatic Module Name" in their manifest.
Kotlin
2
star
24

keyboardio-atreus-firmware

My Keyboardio Atreus firmware
C++
1
star
25

nvim-minimal-packer

Minimal NeoVim config with packer (for reproducing plugin behaviour)
Lua
1
star
26

xml-prettify-text

Haskell app and library for pretty-printing XML to Text
Haskell
1
star
27

nix-resources

Nix resources that I found useful for learning Nix/NixOS
1
star
28

cats-doc

A CLI to generate vim/nvim help doc from LuaCATS. Forked from lemmy-help.
Rust
1
star
29

nix-gen-luarc-json

Generate a .luarc.json for Lua/Neovim devShells
Nix
1
star
30

nvim-treesitter-parsers

tree-sitter parsers packaged as lua rocks for Neovim
1
star