ReasonML + BuckleScript is now Rescript.
As the ecosystem has changed around those tools, this blog post is not accurate anymore.

Vim is my favorite editor, and I prefer it over VS Code.

(Neo)Vim offers a light-weight, fast experience where I can quickly navigate via keyboard shortcuts and use the terminal to its fullest.

Reason offers superb editor support for Vim.

From the vim-reason-plus README:

To have the complete Vim/Neovim Reason experience, there are two plugins to install: [vim-reason-plus], and the language-server.

[vim-reason-plus] provides syntax highlight, snippets for Reason and allows related features to recognize the Reason syntax.

Language-server provides all the others (autocompletion, type hint, jump-to-definition, etc.).

Install Reason and BuckleScript

The easiest way is to install the packages globally:

$ npm install -g bs-platform reason-cli@latest-linux

// or `reason-cli@latest-macos`

(Windows users should check out these instructions.)

VIM Setup

The official instructions are very helpful, and you should look them up.

You’ll need Vim with Python 3 support, or NeoVim (I prefer NeoVim).

In your Vim configuration file (.vimrc or similar):

" in `.vimrc` or `~/.config/nvim/init.vim`

syntax on
filetype plugin indent on

set laststatus=2
set wildmenu
set hidden

You’ll need vim-reason-plus.

I use minpac as my package manager, but vim-plug seems to be another popular choice.

Add this to .vimrc (or ~/.config/nvim/init.vim for NeoVim):

" in `.vimrc` or `~/.config/nvim/init.vim`

" Using minpac
call minpac#add('reasonml-editor/vim-reason-plus')

" If using Vim-Plug
Plug 'reasonml-editor/vim-reason-plus'

Language Server & Language Client

For optimal language support with auto-completion, type hints, etc., you’ll need a language server plugin.

If you’re interested in the Language Server Protocol, you should check out this Reddit post: A guide to LSP in VIM.

LanguageClient-neovim suits my needs: it provides documentation to help you with your setup, and it works fine after some minor configuration.

LanguageClient-neovim Installation

The language client plugin works with NeoVim and Vim8.

" in `.vimrc` or `~/.config/nvim/init.vim`

" Using minpac
call minpac#add('autozimu/LanguageClient-neovim', {'rev': 'next', 'do': '!bash'})

" Vim-Plug
Plug 'autozimu/LanguageClient-neovim', {'branch': 'next', 'do': '!bash'}

You have to install a protocol for each language. (That’s a bit of a hassle, I admit.)

For Reason, you should use Reason Language Server.

Unfortunately, you have to manually download the latest release. Then point your language client plugin to the location of the executable.

" in `.vimrc` or `~/.config/nvim/init.vim`

let g:LanguageClient_serverCommands = {
    \ 'reason': ['/absolute/path/to/reason-language-server.exe']
    \ }

Some example key bindings:

" in `.vimrc` or `~/.config/nvim/init.vim`

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>

Optional: ALE

ALE is another favorite module which helps with linting (syntax checking and semantic errors) and fixing/formatting files.

For me, the tool lessens the cognitive overhead of having to format my files correctly.

ALE also comes with language server protocol features. But those are not as fleshed out as with dedicated LSP plugins.

" in `.vimrc` or `~/.config/nvim/init.vim`

" Using minpac
call minpac#add('dense-analysis/ale')

" Using vim-plug
Plug 'dense-analysis/ale'

Add the following to your configuration file:

" in `.vimrc` or `~/.config/nvim/init.vim`

let g:ale_reason_ls_executable = <path-to-your-reason-language-server.exe>
let g:ale_linters = {
\   'reason': ['reason-language-server'],
let g:ale_fixers = {
\   'reason': ['refmt'],

" Optional (but useful) configuration
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

" Example key bindings
nmap <leader>ag <plug>(ale_go_to_definition)
nmap <leader>at <plug>(ale_go_to_type_definition)
nmap <leader>ah <plug>(ale_hover)
nmap <leader>ad <plug>(ale_documentation)
nmap <leader>ap <plug>(ale_detail)
nmap <leader>af <plug>(ale_fix)
nmap <leader>al <plug>(ale_lint)
nmap <leader>ar <plug>(ale_find_references)
imap <c-c> <plug>(ale_complete)
"Move between linting errors
nmap ]r <plug>(ale_next_wrap)
nmap [r <plug>(ale_previous_wrap)

Further Reading