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 technically heavy books, I prefer to bring along my entire belt of gadgets - LSPs, linters, formatters, debuggers, what have you, so that I can maximize my developer velocity and streamline the DX as much as possible. This allows me 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 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:
- GHC - Glasgow Haskell Compiler, the go-to standard compiler
- Cabal - Haskell build system
- Stack - Similar to and sometimes an alternative of Cabal
- HLS - The Haskell Language Server, Haskell's implementation of the Language Server Protocol for IDEs/code editors that 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 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 that are specific to my OS and architecture.
$ 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. I'll run the Linux one in bash using curl
$ 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? 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
$ . $HOME/.ghcup/env
You can open ghcup tui
to make sure that all the recommended versions are installed and set as defaults
Note that,
- ✅ means installed
- ✅ ✅ 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
Otherwise, alternate methods are listed on the same page, which contains the official installation instructions
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. 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
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
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
$ nvim main.hs
Behold, your Neovim is now a powerful wielder of the wizardry that is Haskell and Functional Programming
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!
Top comments (0)