Neovim Setup for OCaml
Updates:
- 2024-09-14. If an LSP contains a Formatter, then we don’t need to install the corresponding Formatter on our own.
Intro
In previous post, I elaborated on how to set up Neovim from scratch. However, I didn’t answer the following questions:
- What if an LSP does not support formatting, how to configure Neovim to support a third-party code formatting tool?
- How to change configuration files when adding a new programming language?
Recently I was learning OCaml, I thought it would be a good chance to answer the questions.
Install OCaml
It is quite handy to install OCaml on a Mac with Homebrew.
$ brew install opam
According to the output of Homebrew, we need to perform additional configurations and modify ~/.zshrc
(for Zsh users) or ~/.bashrc
(for Bash users).
$ opam init
# press `y` to confirm
To make the modifications work, run the following command in your terminal
$ source ~/.zshrc
# $ source ~/.bashrc # if you use bash
Finally, we can use this command to check if everything goes right
$ opam switch
The output will be like
# switch compiler description
-> 5.0.0 ocaml-base-compiler.5.0.0 5.0.0
default ocaml.4.14.0 default
Similar to IPython for Python, the OCaml offers a REPL tool called utop. We can install this to practice programming OCaml
$ opam install utop dune
Just try the utop REPL yourself :)
$ utop
The output would be like
─────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────
│ Welcome to utop version 2.13.1 (using OCaml version 5.0.0)! │
└─────────────────────────────────────────────────────────────┘
Type #utop_help for help about using utop.
─( 10:33:26 )─< command 0 >────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────{ counter: 0 }─
utop #
┌──────────────┬──────────────┬───────────────┬─────┬───────────┬────┬───┬──────────┬─────┬───────────┬──────┬────────────┬───────┬───────────┬──────────────┬──────────┬────────┐
│Afl_instrument│Alias_analysis│Allocated_const│Annot│Applicative│Arch│Arg│Arg_helper│Array│ArrayLabels│Asmgen│Asmlibrarian│Asmlink│Asmpackager│Assert_failure│Ast_helper│Ast_inva│
└──────────────┴──────────────┴───────────────┴─────┴───────────┴────┴───┴──────────┴─────┴───────────┴──────┴────────────┴───────┴───────────┴──────────────┴──────────┴────────┘
OCaml LSP configurations
If you follow my previous post, then the file hierarch of your setup will be like
nvim
├── init.lua
└── lua
├── colorscheme.lua
├── config
│ └── nvim-cmp.lua
├── keymaps.lua
├── lsp.lua
├── options.lua
└── plugins.lua
Previously, we leveraged mason.nvim to set up LSPs. To add a new LSP for a specific programming language, we can also use this. Steps to follow:
- Open Neovim and type
:Mason
, foundocaml-lsp
in the(2) LSP
section. Pressi
when the cursor stays on the line. - Open the configurations file for LSP(
lsp.lua
in my configurations), add the following lua code
... -- rest of the configurations
lspconfig.ocamllsp.setup({
on_attach = on_attach,
})
After modifying lsp.lua
, we can restart Neovim to make the configurations take effect.
ocaml-lsp
in a project but not a single file.Now you can create a new dune project to see if LSP works.
OCaml Formatter configurations
The ocaml-lsp
LSP does not support formatting OCaml code. If you try to format an OCaml code, you will get an error message:
[LSP] Format request failed, no matching language servers.
Now, I will talk about how to set up a formatter for OCaml.
You may have noticed that the Mason tool also offers Linter and Formatter. The formatter for OCaml is ocamlformat. So We can install it just like we install an LSP.
However, ocamlformat is a CLI tool which can not be integrated into Neovim. Luckily, we have none-ls plugin, which can expose linter, formatter, and other CLI tools as LSP for Neovim.
To make it easier, we can use the mason-null-ls.nvim plugin, developed by the same author of mason.nvim. Open plugins.lua
file and append these Lua codes. After saving the changes and restarting Neovim you should see lazy.nvim
is installing these plugins.
... -- rest of the configurations
require("lazy").setup({
-- Add hooks to LSP to support Linter && Formatter
{
"jay-babu/mason-null-ls.nvim",
event = { "BufReadPre", "BufNewFile" },
dependencies = {
"williamboman/mason.nvim",
"nvimtools/none-ls.nvim",
},
config = function()
-- Note:
-- the default search path for `require` is ~/.config/nvim/lua
-- use a `.` as a path seperator
-- the suffix `.lua` is not needed
require("config.mason-null-ls")
end,
},
... -- rest of the configurations
})
Then, create a new file called mason-null-ls.lua
in config
folder and put the following code
local mason_ok, mason = pcall(require, "mason")
if not mason_ok then
return
end
local null_ls_ok, null_ls = pcall(require, "null-ls")
if not null_ls_ok then
return
end
local mason_null_ls_ok, mason_null_ls = pcall(require, "mason-null-ls")
if not mason_null_ls_ok then
return
end
mason.setup()
mason_null_ls.setup({
-- A list of sources to install if they're not already installed.
-- This setting has no relation with the `automatic_installation` setting.
ensure_installed = {},
-- Run `require("null-ls").setup`.
-- Will automatically install masons tools based on selected sources in `null-ls`.
-- Can also be an exclusion list.
-- Example: `automatic_installation = { exclude = { "rust_analyzer", "solargraph" } }`
automatic_installation = false,
-- Sources found installed in mason will automatically be set up for null-ls.
automatic_setup = true,
handlers = {
-- Hint: see https://github.com/nvimtools/none-ls.nvim/blob/main/doc/BUILTIN_CONFIG.md
-- to see what sources are available
-- Hint: see https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTIN_CONFIG.md
-- to check what we can configure for each source
ocamlformat = function(source_name, methods)
null_ls.register(null_ls.builtins.formatting.ocamlformat.with({
-- Add more arguments to a source's defaults
-- Default: { "--enable-outside-detected-project", "--name", "$FILENAME", "-" }
-- Type `ocamlformat --help` in your terminal to check more args
extra_args = { "--if-then-else", "vertical" },
}))
end,
},
})
null_ls.setup()
When you set automatic_setup = true
, the mason-null-ls will help you configure the installed formatter automatically. If you want to configure a specific formatter, you should make some changes in handlers
.
FAQs:
- How to find the default configurations of a formatter? See here
- How to configure
handlers
? See here - How to know other configurations of a formatter? See the official formatter page. For example, In the README file of the ocamlformat, it says we can type
ocamlformat --help
to see more options.
Finally, we also need to change the lsp.lua
file. The original liens related to code formmating looks like this:
... -- rest of the configurations
vim.keymap.set("n", "<space>f", function()
vim.lsp.buf.format({ async = true })
end, bufopts)
... -- rest of the configurations
Now we are using none-ls to do formatting. So we need to set the filter
argument as follows
... -- rest of the configurations
vim.keymap.set("n", "<space>f", function()
vim.lsp.buf.format({
async = true,
-- Predicate used to filter clients. Receives a client as
-- argument and must return a boolean. Clients matching the
-- predicate are included.
filter = function(client)
return client.name == "null-ls"
end,
})
end, bufopts)
... -- rest of the configurations
Save the changes and restart Neovim to see if it works.
What if an LSP contains a Formatter? For example, the Haskell-language-server
does contain a Formatter called ormolu
. In such circumstances, we do need to use null-ls
. All we need to do is change the filter
... -- rest of the configurations
vim.keymap.set("n", "<space>f", function()
vim.lsp.buf.format({
async = true,
-- Predicate used to filter clients. Receives a client as
-- argument and must return a boolean. Clients matching the
-- predicate are included.
filter = function(client)
-- hls stands for Haskell-language-server
return client.name == "null-ls" or client.name == "hls"
end,
})
end, bufopts)
... -- rest of the configurations
Wrap-up
Now let’s summarize how to set up Neovim for a new programming language. If you also use mason.nvim, mason-lspconfig, none-ls.nvim and mason-null-ls.nvim, you can follow the instructions to setup:
- Read the official pages and install the programming language’s environment on your machine.
- Open Neovim, type
:Mason
, and install the corresponding LSP. Refer to the official LSP pages and check if the LSP contains a built-in Formatter. If the answer is no, just use:Mason
to install one. - Open the
lsp.lua
file and configure LSP - If you install Formatter by yourself, open the
mason-null-ls.lua
file to configurehandlers
For things that are not clearly stated in this post, you can view my dotfiles configurations repo to learn more information.