It's 2024 so why not setup Neovim (w/ NvChad) for Haskell
Tags: neovim, haskell, functional, nvchad
Published: 2024-07-13 03:07:51

## Introduction

Recently, I've been spending time on a persistent penboot installation of Fedora Workstation on a 32 GB SanDisk drive.

I've avoided installing any GUI text/code editors such as VSCode (my defacto choice) or Sublime Text, and decided to stick to a basic terminal-and-vim based workflow keeping in mind the limited storage space I have to operate with.

I wanted to divide my usage between the two most popular vim editors out there by
- Using Vim for learning from tutorials, blogs, and basic books
- Using Neovim for projects and advanced books

Why? Vim's lack of features means it's more or less just a text editor with syntax highlighting. This makes writing code tedious, but tends to sharpen my command of what I'm learning. Whenever I find myself going back to basics (learning a language/framework/toolchain, following a book on some fundamentals), the minimalism of Vim has me paying more attention to what I'm typing onto the screen and be more careful of what I'm doing since I don't have a linter/LSP to catch me from falling if I make a mistake. Needless to say, I didn't configure my vim installation even a little as a result.

But when I'm working on projects or going through advanced stage books, I prefer to bring along my entire belt of gadgets - lsps, linters, formatters, debuggers in order to maximize my DX and developer velocity, in order to focus more on what I'm learning conceptually and offload the menial and tedious to as much tooling as I can. Neovim's rich and modern featureset and extensibility with its Lua-based plugin system makes it perfect for this task, while also allowing me to use my familiar _vim motions_ to move about.

---

I've been playing with Neovim and trying to configure it for Haskell. I just started going through [Learn Physics with Functional Programming - A Hands-on Guide to Exploring Physics with Haskell
by _Scott N. Walck_](https://nostarch.com/learn-physics-functional-programming) so I figured this was the perfect opportunity to figure out how well Neovim and Haskell play together. After much surfing of the world wide web, I couldn't find a straightforward, up-to-date guide to setup Neovim for Haskell in 2024, so I decided I'd make one myself and document the process along the way.

## Install GHCup

If you're new to Haskell, the de-facto toolchain is based around:

1. GHC - Glasgow Haskell Compiler, the go-to standard compiler
2. Cabal - Haskell build system
3. Stack - Similar to and sometimes an alternative of Cabal
4. HLS - The Haskell Language Server, Haskell's implementation of the Language Server Protocol for IDEs/code editors speak the Language Server Protocol (such as Neovim).

This stack is managed coherently by GHCup, the de-facto toolchain manager for Haskell.

If you don't have GHCup installed on your system, first follow their well elucidated [installation guide](https://www.haskell.org/ghcup/install/) since you'll need to ensure some system dependencies are available for GHCup to operate correctly.

Since I'm on Fedora Workstation, I'll use `dnf` to install the system dependencies listed in [System Requirements](https://www.haskell.org/ghcup/install/#system-requirements) that are specific to my OS and architecture.

```bash
$ sudo dnf update -y
$ sudo dnf install -y gcc gcc-c++ gmp gmp-devel make ncurses ncurses-compat-libs xz perl
```

Next, run GHCup's installation script for your platform as given in [How to install](https://www.haskell.org/ghcup/install/#how-to-install). I'll run the Linux one in bash using curl

```bash
$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
```

Next, make sure to choose all defaults, which will
- Install all _recommended_ versions of the toolchain components and set them as _defaults_. Read [Which versions get installed?](https://www.haskell.org/ghcup/install/#how-to-install) to understand the options and differences.
- NOT install HLS (Haskell Language Server), since we will be installing it separately using Mason via NvChad, which uses GHCup underneath the hood to install HLS anyway.
- Setup Stack to play well with GHCup's GHC version.

After installation, GHCup and the _defaults_ will be placed in PATH and available to further shell sessions. Either restart your current shell session or type the following in Bash to source the changes into your current shell

```bash
$ . $HOME/.ghcup/env
```
You can open `ghcup tui` to make sure that all the recommended versions are installed and set as defaults

> Note that,
> - :white_check_mark: means installed
> - :white_check_mark: :white_check_mark: means installed and set as default

## Install Neovim

If you don't have Neovim, the quickest way to get it is via your [system package manager](https://github.com/neovim/neovim/blob/master/INSTALL.md#install-from-package)

Otherwise, alternate methods are listed on the same page, which contains the official [installation instructions](https://github.com/neovim/neovim/blob/master/INSTALL.md)

## Install NvChad

NvChad is a _very_ opinionated but _very_ convenient Neovim configuration to have you hit the road running with Neovim.

The quickest way to install NvChad is to clone a starter NvChad config and use `:MasonInstallAll` to install all necessary plugins right after, as given in [quickstart/install](https://nvchad.com/docs/quickstart/install/). Visit this guide if you have issues installing NvChad, as it does require some system dependencies to be installed and for you to have a Nerd Font installed and selected as your terminal's font.

> Remember to backup any preexisting Neovim config before you download NvChad as your Neovim config by running
`$ mv ~/.config/nvim ~/.config/nvim-old` first 

## Overrides and Configs

Next, we need to use Mason to install HLS for language support and Haskell's Tree Sitter configuration for syntax highlighting support

We specify this by overriding their respective Neovim plugins

`~/.config/nvim/lua/plugins/init.lua`
```lua
return {
  -- ...
  -- ...
  {
    "williamboman/mason.nvim",
    opts = {
      ensure_installed = {
        "haskell-language-server"
      },
    },
  },

  {
    "neovim/nvim-lspconfig",
    config = function()
      require("nvchad.configs.lspconfig").defaults()
      require "configs.lspconfig"
    end,
  },
 
  {
    "nvim-treesitter/nvim-treesitter",
    opts = {
      ensure_installed = {
        "haskell"
      },
    },
  },
}
```

and configure Neovim to talk LSP with our Haskell language server.

`~/.config/nvim/lua/configs/lspconfig.lua`
```lua
local on_attach = require("nvchad.configs.lspconfig").on_attach
local on_init = require("nvchad.configs.lspconfig").on_init
local capabilities = require("nvchad.configs.lspconfig").capabilities

local lspconfig = require "lspconfig"

-- ...
-- ...

lspconfig.hls.setup {
  on_attach = on_attach,
  on_init = on_init,
  capabilities = capabilities,
  filetypes = { 'haskell', 'lhaskell', 'cabal'},
}
```

That's it! We're done.

---

Now, open a Haskell file using `nvim`

```bash
$ nvim main.hs
```

Behold, your Neovim is now a powerful wielder of the wizardry that is Haskell and Functional Programming

{% embed https://www.youtube.com/watch?v=hDp_NyusA-M %}

---

Full disclaimer: I wrote this mainly for my own reference because I hate forgetting build/config steps. Feel free to leave any feedback and/or suggestions in the comments below!