Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions data/tutorials/getting-started/2_00_editor_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
1. Go to `File` > `Preferences` > `Settings` view (or press `Ctrl ,`)
2. Select `User` > `Extensions` > `OCaml Platform`
3. Uncheck `OCaml: Use OCaml Env`. That's it!

Check failure on line 52 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces

data/tutorials/getting-started/2_00_editor_setup.md:52:1 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 4] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md009.md
## Emacs

Using Emacs to work with OCaml requires at least two modes:
Expand All @@ -73,7 +73,7 @@
:mode (("\\.ocamlinit\\'" . tuareg-mode)))
```


Check failure on line 76 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Multiple consecutive blank lines

data/tutorials/getting-started/2_00_editor_setup.md:76 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md012.md
#### Melpa and `use-package`

If your version of Emacs does not support the `use-package` macro (or is not set up to take MELPA packages into account), please update it and follow these instructions to install [`use-package`](https://github.com/jwiegley/use-package) and [MELPA](https://melpa.org/#/getting-started).
Expand Down Expand Up @@ -110,7 +110,7 @@

OCaml-eglot can be finely configured, the project [README](https://github.com/tarides/ocaml-eglot/blob/main/README.md) gives several configuration paths to adapt perfectly to your workflow. You will also find there an exhaustive presentation of the different functions offered by the mode.


Check failure on line 113 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Multiple consecutive blank lines

data/tutorials/getting-started/2_00_editor_setup.md:113 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md012.md
#### Getting Type Information

Opening an OCaml file should launch an `ocaml-lsp` server, and you can convince yourself that it's working by using, for example, the `ocaml-eglot-type-enclosing` command (or using the `C-c C-t` binding) on an expression of your choice:
Expand All @@ -119,7 +119,7 @@

OCaml-eglot [README](https://github.com/tarides/ocaml-eglot/blob/main/README.md) provides a comprehensive overview of all the functions available in this mode!


Check failure on line 122 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Multiple consecutive blank lines

data/tutorials/getting-started/2_00_editor_setup.md:122 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md012.md
## Vim

For Vim, we won't use the LSP server but rather directly talk to Merlin.
Expand All @@ -136,7 +136,7 @@

### Talking to Merlin

#### Getting Type Information

Check failure on line 139 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

data/tutorials/getting-started/2_00_editor_setup.md:139 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Getting Type Information"] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md024.md

![Vim Type information](/media/tutorials/vim-type-info.gif)

Expand All @@ -145,3 +145,110 @@
- Type `:MerlinTypeOf` and press <kbd>Enter</kbd>.
- The type information will be displayed in the command bar.
Other Merlin commands for Vim are available and you can checkout their usage on the [Merlin official documentation for Vim](https://ocaml.github.io/merlin/editor/vim/).

## Neovim

Neovim comes with an LSP client.

One note here is that is that `ocaml-lsp-server` is sensitive to versioning, and often does not play well with the sometimes outdated sources in Mason, a popular package manager for language services. We recommend you install the LSP server directly in the switch, and pointing your Neovim config to use that.

To install the LSP server and the formatter, run the following.
```shell

Check failure on line 156 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Fenced code blocks should be surrounded by blank lines

data/tutorials/getting-started/2_00_editor_setup.md:156 MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```shell"] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md031.md
opam install ocaml-lsp-server ocamlformat
```

There are two main ways to install and manage LSP servers.
- A newer, more recommended way is to use the new Neovim LSP API for versions newer than v0.11.0 via `vim.lsp`.

Check failure on line 161 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Lists should be surrounded by blank lines

data/tutorials/getting-started/2_00_editor_setup.md:161 MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "- A newer, more recommended wa..."] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md032.md
- A more traditional way is to use `nvim-lspconfig`. For more info, `kickstart.nvim` has a great example setup.

### Using vim.lsp:

Check failure on line 164 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Trailing punctuation in heading

data/tutorials/getting-started/2_00_editor_setup.md:164:18 MD026/no-trailing-punctuation Trailing punctuation in heading [Punctuation: ':'] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md026.md

Add this to your toplevel `init.lua`.
```lua

Check failure on line 167 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Fenced code blocks should be surrounded by blank lines

data/tutorials/getting-started/2_00_editor_setup.md:167 MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```lua"] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md031.md
vim.lsp.config['ocamllsp'] = {
cmd = { 'ocamllsp' },
filetypes = {
'ocaml',
'ocaml.interface',
'ocaml.menhir',
'ocaml.ocamllex',
'dune',
'reason'
},
root_markers = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

I proposed this simplification to the upstream nvim-lspconfig, which was using a root_dir function and lspconfig.util instead (which appears deprecated).

{ 'dune-project', 'dune-workspace' },
{ "*.opam", "esy.json", "package.json" },
'.git'
},
settings = {},
}

vim.lsp.enable 'ocamllsp'
```

See `:h lsp-config` for more detail on configuration options.

#### Using vim.lsp With runtimepath

You can also move your LSP config to a separate file via `runtimepath` if you'd like to keep your `init.lua` minimal. Putting your config table inside `lsp/<some_name>.lua` or `after/lsp/<some_name>.lua` will allow Neovim to search for them automatically.

See `:h runtimepath` for more detail.

Run the following at the root of your config.
```text

Check failure on line 198 in data/tutorials/getting-started/2_00_editor_setup.md

View workflow job for this annotation

GitHub Actions / lint

Fenced code blocks should be surrounded by blank lines

data/tutorials/getting-started/2_00_editor_setup.md:198 MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```text"] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md031.md
mkdir lsp
touch lsp/ocamllsp.lua
```

Your Neovim config should have the following structure now.
```text
.
├── init.lua
├── lsp
│ └── ocamllsp.lua
└── ...
```

Add your LSP config to `lsp/ocamllsp.lua`.
```lua
return {
cmd = { 'ocamllsp' },
filetypes = {
'ocaml',
'ocaml.interface',
'ocaml.menhir',
'ocaml.ocamllex',
Copy link
Contributor

@edwintorok edwintorok Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one doesn't actually seem to work on NeoVim 0.11.4 and ocamllsp 1.23.0.
ocamllsp says 'unsupported file extension'.
But removing it from this list doesn't work either, because then it falls back to the ocaml filetype on the .mll file, and calls ocamllsp anyway.

The rest seems to work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ocaml.ocamllex is to future-proof it for ocaml/vim-ocaml#61 (which isn't merged yet)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#3322 (comment) workaround, but ocaml.ocamllex is fine as it is in the suggested doc, even though it doesn't work yet.

'dune',
'reason'
},
root_markers = {
{ 'dune-project', 'dune-workspace' },
{ "*.opam", "esy.json", "package.json" },
'.git'
},
settings = {},
}
```

Then enable them in the toplevel `init.lua`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another nit: Can we explain what vim.lsp.enable() do here? It will help users understand the difference between configuration and enabling.

Then enable them in the toplevel init.lua. This will make Neovim to automatically start the ocamllsp for associated filetypes.
..codeblock..
To start ocamllsp manually, you can use vim.lsp.start. (see :h vim.lsp.start)

:lua vim.lsp.start(vim.lsp.config['ocamllsp'])

Not sure if we want to mention vim.lsp.start here. I think it can help users to understand what vim.lsp.enable actually does, but it can be yet another code snippet they should read and understand.

Copy link
Contributor Author

@jpoly1219 jpoly1219 Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is a bit out of scope for the purpose of this doc. I think the curious readers will go out and research this further on their own.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of calling lsp.enable with just ocamllsp we could show that you only need to call this once, with a list of languages (OCaml being just one of them), but it is fine as it is too.
Maybe add a link to the official NeoVim LSP docs? https://neovim.io/doc/user/lsp.html

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are already mentioning it. :h lsp-config should be enough.

```lua
vim.lsp.enable 'ocamllsp'
```

### Using nvim-lspconfig

Add this to your `nvim-lspconfig` setup.
```lua
{
'neovim/nvim-lspconfig',
config = function()
-- rest of config...

-- add this line specifically for OCaml
require('lspconfig').ocamllsp.setup {}

This comment was marked as resolved.

end,
},
```

There is no need to pass more settings to `setup` because `nvim-lspconfig` provides reasonable defaults. See [here](https://github.com/neovim/nvim-lspconfig/blob/master/lsp/ocamllsp.lua) for more info.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we explain about tree-sitter? (with nvim-treesitter)?

local treesitter_langs = {
 'menhir', 'ocaml', 'ocaml_interface', 'ocamllex'
}

require('nvim-treesitter').install(treesitter_langs)

It isn't strictly required, although might help with performance on large files, before the LSP finishes parsing it.
Maybe leave it out for now, I'll do some experiments with using just the LSP, and see how well that works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Let me know if you have more updates 👍

Copy link
Contributor

@edwintorok edwintorok Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured out how to configure tree-sitter for ocamllex, but it is quite complicated: ocaml/vim-ocaml#61 (comment).

For now I'd leave out tree-sitter from the official docs, until that PR is merged (and an equivalent PR is merged into NeoVim).

The workaround is (fixes both picking the right tree-sitter syntax and the LSP errors):

vim.filetype.add({
    extension = {
        mll = 'ocamllex',
        mly = 'menhir',
        mli = 'ocamlinterface'
    }
})

followed by the usual tree-sitter config:

local treesitter_langs = {
 'menhir', 'ocaml', 'ocaml_interface', 'ocamllex'
}

require('nvim-treesitter').install(treesitter_langs)

(This doesn't require a new release of NeoVim to work, but if your tree-sitter grammar fails to install you'll be without syntax highlighting, so I wouldn't propose it in the official docs, even though I'll start using it myself).

Loading