Neovim Setup for OCaml
Updates:
- 2025-09-07: Make necessary changes because I use the Neovim v0.11 native LSP feature instead of relying on
nvim-lspconfig
. - 2025-03-22: Replace the
null-ls/none-ls
plugin withconform.nvim
for simplicity :)
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 do you configure Neovim to support a third-party code formatting tool?
- How do you change configuration files when adding a new programming language?
Recently, I started learning OCaml, and I thought it would be a good opportunity to answer these 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, OCaml offers a REPL tool called utop. We can install this to practice programming in 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 hierarchy of your setup will be like
.
├── init.lua
├── lazy-lock.json
├── lsp
│ └── ty.lua
└── lua
├── colorscheme.lua
├── keymaps.lua
├── lsp.lua
├── options.lua
└── plugins.lua
Previously, we leveraged mason.nvim to set up LSPs. To add LSP for OCaml:
- Open Neovim and type
:Mason
, findocaml-lsp
in the(2) LSP
section, and pressi
when the cursor stays on the line. - Open the configurations file for LSP(
lsp.lua
in my configurations), add the following Lua code
return {
cmd = { "ocamllsp" },
filetypes = { "ocaml", "menhir", "ocamlinterface", "ocamllex", "reason", "dune" },
root_markers = { "*.opam", "esy.json", "package.json", ".git", "dune-project", "dune-workspace" },
}
- Open the
lsp.lua
and addocamllsp
tovim.lsp.enable
... -- rest of the configurations
vim.lsp.enable({ "ty", "ocamllsp" })
You may have your configuration files and do not follow my previous post. Thus, only adding the ocamllsp.lua
under the lsp
folder may not work for you. However, you can check the full configuration here to understand how it works and adapt to your configurations.
We can restart Neovim to make the configurations take effect.
Now you can create a new dune project to see if LSP works. If you type :checkhealth vim.lsp
when you’re editing a .ml
file, you should see similar output like this.
vim.lsp: Active Clients ~
- ocamllsp (id: 1)
- Version: 1.23.0
- Root directory: /private/tmp/dummy
- Command: { "ocamllsp" }
- Settings: {}
- Attached buffers: 1
OCaml Formatter configurations
The ocaml-lsp
LSP does not support formatting OCaml code. If you try to format 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 linters and formatters. 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 conform.nvim plugin which can do us this favor.
Open the plugins.lua
file and add the Lua code. After saving the changes and restarting Neovim, you should see that lazy.nvim
is installing the plugin.
... -- rest of the configurations, omit for simplicity
require("lazy").setup({
{
"stevearc/conform.nvim",
opts = {
formatters_by_ft = {
ocaml = { "ocamlformat" },
},
formatters = {
ocamlformat = {
prepend_args = {
"--if-then-else",
"vertical",
"--break-cases",
"fit-or-vertical",
"--type-decl",
"sparse",
},
},
},
},
}
... -- rest of the configurations, omit for simplicity
})
The code above seems to be complicated at first glance, but I am sure that you will find the patterns
formatters_by_ft
- add an entry for formatterformatters
- add more configurations if needed
FAQs:
- How do I find which formatter should be used for a programming language? Type
:help conform-formatters
, search for the programming language, and you should see some options. Pick one and install it with:Mason
. - How do I add a formatter to
conform.nvim
? Add a new entry informatters_by_ft
, as shown in the example. - How do I customize a formatter? Check the official manual of the formatter.
Finally, we also need to modify the lsp.lua
file. The original lines related to code formmating look like this:
... -- rest of the configurations, omit for simplicity
vim.api.nvim_create_autocmd("LspAttach", {
callback = function (args)
... -- rest of the configurations, omit for simplicity
keymap.set("n", "<space>f", function()
vim.lsp.buf.format({ async = true })
end, bufopts)
end
})
... -- rest of the configurations, omit for simplicity
Now we are using conform.nvim
to do formatting. Thus, we need to use the format
function in conform.nvim
.
... -- rest of the configurations, omit for simplicity
vim.api.nvim_create_autocmd("LspAttach", {
callback = function (args)
... -- rest of the configurations, omit for simplicity
keymap.set("n", "<space>f", function()
require("conform").format({ async = true, lsp_fallback = true })
end, bufopts)
end
})
... -- rest of the configurations, omit for simplicity
Save the changes and restart Neovim to see if it works.
Wrap-up
Now, let’s summarize how to set up Neovim for a new programming language. If you also use mason.nvim and conform.nvim, you can follow the instructions to set up:
- 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 documentation and check whether it includes a built-in formatter. If it does not, use:Mason
to install one. - Create a new file for this lsp under the
lsp
folder. If you have trouble writing this file, you can always refer tonvim-lspconfig
. Note that you also need to modify thelsp.lua
file - put the new LSP invim.lsp.enable
. - If you install the formatter with
:Mason
by yourself, you also need to change the configurations forconform.nvim
.
For anything not clearly stated in this post, you can check my dotfiles configurations repo for more details.