DEV Community

loading...
Cover image for Setup OCaml With NeoVim

Setup OCaml With NeoVim

Sophia Brandt
former tax officer turned programmer
Originally published at rockyourcode.com on ・5 min read

How to use the NeoVim text editor as your Ocaml IDE

Why OCaml?

I've always been interested in learning an ML language. But Haskell, the poster child of functional programming, has a high learning curve.

OCaml and ReasonML (an alternative syntax for OCaml) are much more beginner-friendly.

I started a free MOOC on functional programming with OCaml a few days ago. Thus, it's the perfect time to set up my editor for OCaml development.

If you want to know more about OCaml, read the short article Why OCaml?.

In this article, I will show you how to integrate OCaml with NeoVim.

What is NeoVim?

NeoVim is a fork of the terminal-based Vim text editor.

It's fully compatible with Vim, but adds more features with a slimmer code base.

Plugin Manager and Basic Setup

NeoVim comes with very sensible defaults. See :h nvim-defaults for more information.

If you'd like some suggestions for further customization, you can check out neovim-sensible by Jeff Kreeftmeijer.

Install a plugin loader for convenience, for example, minpac.

There are two alternatives for code support in NeoVim: Merlin (standalone) or esy.

You'll install Merlin with opam, the package manager for OCaml. esy is the package manager for Reason and OCaml and works with the npm ecosystem (Node.js/JavaScript).

You can use Merlin and its Vim integration, or you can use esy's Vim integration.

(esy only works if you compile your programs to native binaries.)

Merlin

Let's start with Merlin. Skip this section section if you want to try out esy.

Basic Requirements

Merlin is the de-facto code helper for OCaml. If you'd like to use Merlin with Neovim, you need Python and pynvim.

pip3 install --user pynvim
Enter fullscreen mode Exit fullscreen mode

Make sure that your Python integration for NeoVim works correctly.

If you encounter problems, use :checkhealth provider for trouble-shooting.

For Merlin, you can follow the vim from scratch guide:

opam install merlin
Enter fullscreen mode Exit fullscreen mode

Then add this line to your init.vim:

let g:opamshare = substitute(system('opam config var share'),'\n$','','''')
execute "set rtp+=" . g:opamshare . "/merlin/vim"
Enter fullscreen mode Exit fullscreen mode

That not only adds merlin to your runtime path, but will always pick the version corresponding to your opam switch without you needing to modify your config (assuming you install merlin on each of your switches).

In NeoVim, run the following command:

:execute "helptags " . substitute(system('opam config var share'),'\n$','','''') .  "/merlin/vim/doc"
Enter fullscreen mode Exit fullscreen mode

Merlin comes with default key mappings. Be aware that those might not work.

You might want to set your own key bindings. I find :MerlinTypeOf in normal mode especially useful.

See :h merlin for more info.

(Tab) Completion

Merlin supports several completion plugins out of the box, for example, Deoplete or SuperTab.

I prefer VimCompletesMe, a minimal plugin that does everything I need:

call minpac#add('ajh17/VimCompletesMe')
Enter fullscreen mode Exit fullscreen mode

Merlin will now suggest completions when you press <Tab>.

Language Server

Merlin offers the most important features, like showing type signatures.

If you desire more functionality, for example, hover information about different modules, you can use a language server.

You'll need a Language Server client. I recommed LanguageClient-neovim, as it works with every language server I've tried.

Install with your Vim plugin manager, e.g.:

call minpac#add('autozimu/LanguageClient-neovim', {'branch': 'next', 'do': 'bash install.sh'})
Enter fullscreen mode Exit fullscreen mode

You also have to configure the plugin. Modify init.vim (or ~/.vimrc or similar):

set hidden

 let g:LanguageClient_serverCommands = {
 \   'ocaml':           ['ocamllsp'],
 \}

nnoremap <F5> :call LanguageClient_contextMenu()<CR>
" Or map each action separately
nnoremap <silent> K :call LanguageClient#textDocument_hover()<CR>
nnoremap <silent> gd :call LanguageClient#textDocument_definition()<CR>
nnoremap <silent> <F2> :call LanguageClient#textDocument_rename()<CR>
Enter fullscreen mode Exit fullscreen mode

Now install the language server. There are several alternatives. ocaml-lsp is a community project written in OCaml that's in active development.

See the installation instructions for further details.

I use opam:

opam pin add ocaml-lsp-server https://github.com/ocaml/ocaml-lsp.git
Enter fullscreen mode Exit fullscreen mode

Open an OCaml file. Press K while the cursor is on a definition. The language server will give you information about the function or file.

If you use a different language server than ocaml-lsp, you have to change the setup for LanguageClient-neovim accordingly.

esy

If you're willing to work with the Node ecosystem, esy is a good choice that works both with OCaml and ReasonML.

Basic Requirements

Install esy with npm, the Node.js package manager:

npm install -g esy
Enter fullscreen mode Exit fullscreen mode

Now, you'll need a Vim plugin: vim-reasonml. Install it with your plugin manager. For example, with minpac:

call minpac#add('jordwalke/vim-reasonml')
Enter fullscreen mode Exit fullscreen mode

The plugin supports Merlin out of the box. For example, if you have a .merlin file in your project, vim-reasonml will pull merlin from your dev dependencies.

That means that you don't have to install Merlin separately.

(Tab) Completion

See above.

ALE And ocamlformat

ALE is a linting and fixing tool for Vim. I heavily rely on it for avoiding syntax errors and tidying up my code.

ALE adds some bloat to your NeoVim setup. Still, I find the trade-off worthwhile.

Install it with your package manager (or find alternative instructions on GitHub):

call minpac#add('dense-analysis/ale')
Enter fullscreen mode Exit fullscreen mode

Example setup in your init.vim (or similar):

let g:ale_sign_error                  = '✘'
let g:ale_sign_warning                = '⚠'
highlight ALEErrorSign ctermbg        =NONE ctermfg=red
highlight ALEWarningSign ctermbg      =NONE ctermfg=yellow
let g:ale_linters_explicit            = 1
let g:ale_lint_on_text_changed        = 'never'
let g:ale_lint_on_enter               = 0
let g:ale_lint_on_save                = 1
let g:ale_fix_on_save                 = 1

let g:ale_linters = {
\   'ocaml':      ['merlin'],
\}

let g:ale_fixers = {
\   'ocaml':      ['ocamlformat'],
\   '*':          ['remove_trailing_lines', 'trim_whitespace'],
\}
Enter fullscreen mode Exit fullscreen mode

Improve ALE's performance by setting the linters you need and don't lint every time you type in something.

Fix a file when you save or call :ALEFix manually.

See :h ale for more information.

We can use ocamlformat for parsing our code.

opam install ocamlformat
Enter fullscreen mode Exit fullscreen mode

Thoughts

It took me some time to properly setup OCaml and NeoVim. I hope my article helped you to get there faster.

After following this guide, you should have a fully-fledged OCaml IDE with syntax highlighting, hover information, auto-completion and more.

Links

Discussion (7)

Collapse
yawaramin profile image
Yawar Amin

Good guide. Btw for the esy setup you don't need opam and opam install merlin because as you mentioned if you start the editor with esy it will automatically set up the merlin that's in your project's devDeps.

Collapse
sophiabrandt profile image
Sophia Brandt Author

Thanks. That's good to know. I will update the guide.

Collapse
ryskajakub profile image
Jakub Ryška

Can you enumerate the features it does have? Last time I tried to use merlin with native ocaml it was hard to jump to source, for example to core, it was all red with errors and it only worked when the source was compilable, as soon as the source file didn't compile, there were no type inspections, no go to source etc. I'm eager to see experience of others in this area

Collapse
sophiabrandt profile image
Sophia Brandt Author • Edited

Jump to definition/source works for me. Also auto-completion.
But I had problems with Reason code where Merlin tends to interfere.
I've switched to the reason language server for now. But for ocaml it doesn't work that great.
Esy is an alternative.

Collapse
rohansawant profile image
Rohan Sawant

Going in I had no idea what any of the words in your title meant, now...I am enlightened ☺

🔥!

Collapse
sophiabrandt profile image
Sophia Brandt Author • Edited

Thanks Rohan.
Good point. I didn't mention that NeoVim is an editor (a Vim spin-off). I'll add it.

Collapse
avelino profile image
Avelino