Neovim Setup for OCaml

Info

Updates:

  • 2025-03-22: Replace the null-ls/none-ls plugin with conform.nvim for simplicity :)

In previous post, I elaborated on how to set up Neovim from scratch. However, I didn’t answer the following questions:

  1. What if an LSP does not support formatting, how do you configure Neovim to support a third-party code formatting tool?
  2. 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.

Info
I assume you have already read my previous post, so I will omit some details here.
Info
If you are not using a Mac, you may refer to the official instructions for more details.

It is quite handy to install OCaml on a Mac with Homebrew.

sh

$ 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).

sh

$ opam init
# press `y` to confirm

To make the modifications work, run the following command in your terminal

sh

$ source ~/.zshrc
# $ source ~/.bashrc # if you use bash

Finally, we can use this command to check if everything goes right.

sh

$ opam switch

The output will be like

text

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

sh

$ opam install utop dune

Just try the utop REPL yourself :)

sh

$ utop

The output would be like

text

─────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────
                                                          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_instrumentAlias_analysisAllocated_constAnnotApplicativeArchArgArg_helperArrayArrayLabelsAsmgenAsmlibrarianAsmlinkAsmpackagerAssert_failureAst_helperAst_inva
└──────────────┴──────────────┴───────────────┴─────┴───────────┴────┴───┴──────────┴─────┴───────────┴──────┴────────────┴───────┴───────────┴──────────────┴──────────┴────────┘
Info

If you follow my previous post, then the file hierarch of your setup will be like

text

.
├── init.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:

  1. Open Neovim and type :Mason, find ocaml-lsp in the (2) LSP section, and press i when the cursor stays on the line.
  2. Open the configurations file for LSP(lsp.lua in my configurations), add the following Lua code

lua

... -- rest of the configurations
lspconfig.ocamllsp.setup({
	on_attach = on_attach,
})
Warning

You may have your configuration files and do not follow my previous post. Thus, only adding the aforementioned Lua code may not work for you. However, you can check the full configuration here to understand how it works and adapt to your configurations.

After modifying lsp.lua, we can restart Neovim to make the configurations take effect.

Warning

Similar to rust-analyzer, we can only use ocaml-lsp in a project but not a single file.

Now you can create a new dune project to see if LSP works.

The ocaml-lsp LSP does not support formatting OCaml code. If you try to format an OCaml code, you will get an error message:

text

[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 codes. After saving the changes and restarting Neovim, you should see that lazy.nvim is installing the plugin.

lua

... -- rest of the configurations, omit for simplicity
require("lazy").setup({
    {
        "stevearc/conform.nvim",
        opts = {
            formatters_by_ft = {
                ocaml = { "ocamlformat" },
            },

            formatters = {
                ocamlformat = {
                    command = "ocamlformat",
                    args = {
                        "--if-then-else",
                        "vertical",
                        "--break-cases",
                        "fit-or-vertical",
                        "--type-decl",
                        "sparse",
                        -- $FILENAME - absolute path to the file
                        "$FILENAME",
                    },
                },
            },
        },
    }
    ... -- 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 formatter
  • formatters - add more configurations if needed
Tip

FAQs

  1. 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. Picking one and install it with :Mason.
  2. How do I add a formatter to conform.nvim? Add a new entry in formatters_by_ft, as shown in the example.
  3. 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:

lua

... -- rest of the configurations, omit for simplicity
vim.keymap.set("n", "<space>f", function()
    vim.lsp.buf.format({ async = true })
end, bufopts)
... -- 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.

lua

... -- rest of the configurations, omit for simplicity
vim.keymap.set("n", "<space>f", function()
    require("conform").format({ async = true, lsp_fallback = true })
end, bufopts)
... -- rest of the configurations, omit for simplicity

Save the changes and restart Neovim to see if it works.

Now let’s summarize how to set up Neovim for a new programming language. If you also use mason.nvim, mason-lspconfig, conform.nvim, you can follow the instructions to setup:

  1. Read the official pages and install the programming language’s environment on your machine.
  2. 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.
  3. Open the lsp.lua file and configure LSP
  4. If you install the formatter by yourself, you also need to change the configurations for conform.nvim.

For anything not clearly stated in this post, you can check my dotfiles configurations repo for more details.