When developing OCaml projects in Neovim, the OCaml language server (ocamllsp) can be of great help. It should just work out of the box, however there are few tweaks you can do in order to improve your developer experience even more.
First things first: Setting everything up
I'm assuming you have a working OCaml and opam environment. If not, follow the steps on the official "Getting Started" page first.
After that, let's make sure all the necessary packages are installed:
$ opam install ocaml-lsp-server ocamlformat dune
This will install the following packages globally
ocaml-lsp-server
: the language server itself (the executable is calledocamllsp
)ocamlformat
: the file formatting tool which ocamllsp usesdune
: the newest OCaml build system.
In order to simplify the server configuration, we're using nvim-lspconfig.
Make sure to follow your package manager's instructions on how to add lspconfig to your Neovim installation. Once this is
done, add a basic configuration for ocamllsp
somewhere in your Lua config files:
local lsp_config = require('lspconfig')
lsp_config['ocamllsp'].setup({})
Once you've reloaded your configuration, you should have a working LSP integration while editing OCaml source files.
Bonus: LSP keybindings
Last but not least, if you have not configured your LSP keybindings so far, feel free to use those as your inspiration:
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('UserLspConfig', {}),
callback = function(event)
local opts = { buffer = event.buf }
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
vim.keymap.set('n', 'gt', vim.lsp.buf.type_definition, opts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, opts)
vim.keymap.set('n', '<leader>wa', vim.lsp.buf.add_workspace_folder, opts)
vim.keymap.set('n', '<leader>wr', vim.lsp.buf.remove_workspace_folder, opts)
vim.keymap.set('n', '<leader>wl', function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end, opts)
vim.keymap.set('n', '<leader>H', vim.lsp.buf.typehierarchy, opts)
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
vim.keymap.set({ 'n', 'v' }, '<leader>ca', vim.lsp.buf.code_action, opts)
vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
vim.keymap.set('n', '<leader>fo', function()
vim.lsp.buf.format { async = true }
end, opts)
end,
})
Dune RPC: keeping LSP information up to date
Once you started using the LSP server, you may notice that from time to time diagnostic information are not up to
date. This often happens when new files or modules are added the project. One way to solve those issues is to build the
project with dune build
followed by :LspRestart
in Neovim. This will ensure that the latest build
information are available to ocamllsp.
However, there is a much better way by utilizing the RPC mechanism of
dune. Newer versions of dune
will start an (experimental) RPC server when a "watch" build is started. ocamllsp can
utilize this RPC mechanism to get notifications about new build information and update the editor diagnostics
accordingly.
There is configuration for ocamllsp needed, since it should recognize the running dune
process and automatically
establish the RPC connection. The only thing to do is to start a continuous build of your project via the -w
flag
before the LSP server is started (e.g. dune build -w
or dune exec -w
).
Running ocamllsp in a separate build directory
While the continuous dune build gives up-to-date diagnostics of the project, there is one annoying downside - it will
lock your build directory for other dune processes. I.e. you won't be able to execute dune exec
or dune runtest
while the build process is running. You could obviously stop the build, run your other commands and then restart the
build, but this gets annoying fast.
Another way is to use a different build directory for ocamllsp. dune supports this via the --build-dir=<path>
option.
E.g. to start the continuous build in the _build_lsp
folder, run
$ dune build -w --build-dir=_build_lsp
This will ensure that your main _build
folder is not locked and can be used for one-off dune commands. The beauty of
it is that ocamllsp will automatically detect the new build directory with the running dune instance and uses it
for live diagnostics.
Help! My files are not formatting
If the LSP format command is not doing anything, the most likely reason is a missing .ocamlformat
file. Without it,
the ocamlformat
tool will not change any files. To solve the issue, simply add a .ocamlformat
file at the root
of your project (even an empty one should do). For more details on the formatting options available via this file, run
man ocamlformat
or check the online documentation.