Warning
This plugin is in early development and may not work as expected. Breaking changes may occur without notice.
A comprehensive Neovim plugin for OCaml development with intelligent sandbox detection, LSP integration, and comprehensive filetype support.
- π― Smart LSP Integration: Automatic detection and setup of
ocamllspwith sandbox support - π¦ Sandbox Detection: Supports esy projects with automatic command resolution
- π¨ Multiple Filetypes: Support for OCaml, Reason, and various OCaml file formats
- π§ Automatic Formatting: Integration with
ocamlformatandocamlformat-mlxvia conform.nvim - π³ TreeSitter Integration: Enhanced syntax highlighting and code understanding
- β‘ Performance: Efficient client reuse and project detection
| Extension | Language | Description |
|---|---|---|
.ml |
ocaml |
OCaml implementation files |
.mli |
ocaml.interface |
OCaml interface files |
.mll |
ocaml.ocamllex |
OCaml lexer files |
.mly |
ocaml.menhir |
Menhir parser files |
.mlx |
ocaml.mlx |
OCaml JSX files |
.t |
ocaml.cram |
Cram test files |
.re |
reason |
Reason implementation files |
.rei |
reason |
Reason interface files |
- Neovim 0.11+
ocamllsplanguage server installed- TreeSitter OCaml parsers
lazy.nvim (Recommended)
{
"ocaml/ocaml.nvim",
dependencies = {
"nvim-treesitter/nvim-treesitter",
-- Optional: for enhanced UI pickers (recommended)
-- "ibhagwan/fzf-lua", -- or
-- "nvim-telescope/telescope.nvim",
},
}use {
"ocaml/ocaml.nvim",
requires = {
"nvim-treesitter/nvim-treesitter",
-- Optional: for enhanced UI pickers (recommended)
-- "ibhagwan/fzf-lua", -- or
-- "nvim-telescope/telescope.nvim",
},
}Plug 'nvim-treesitter/nvim-treesitter'
" Optional: for enhanced UI pickers (recommended)
" Plug 'ibhagwan/fzf-lua' " or
" Plug 'nvim-telescope/telescope.nvim'
Plug 'ocaml/ocaml.nvim'- fzf-lua or telescope.nvim: Provides enhanced fuzzy pickers with live
search for commands like
:OCaml type-search. Falls back tovim.ui.selectif neither is installed.
This plugin manages the OCaml LSP server automatically. If you're using
nvim-lspconfig, you MUST
disable the ocamllsp server to avoid conflicts:
-- β DO NOT enable ocamllsp in lspconfig when using this plugin
require("lspconfig").ocamllsp.setup({
autostart = false, -- Set this to false!
})
-- β
OR better yet, don't configure it at all in lspconfigIf you're using LazyVim, completely disable
the ocamllsp server in your LSP configuration to prevent conflicts:
{
"neovim/nvim-lspconfig",
opts = {
servers = {
-- Completely disable ocamllsp to prevent conflicts
ocamllsp = false,
},
},
}Why this is important: Even with autostart = false, incomplete LSP configurations
can cause errors when Neovim tries to resolve LSP commands. Setting the server to
false completely removes it from the global LSP configuration.
This plugin provides intelligent sandbox detection (esy, opam, global) and will automatically start the appropriate LSP server with the correct configuration.
The plugin works out of the box with sensible defaults. For advanced configuration:
---@type ocaml.Opts
vim.g.ocamlnvim = {
lsp = {
-- Enable/disable automatic LSP attachment
auto_attach = true,
-- Custom on_attach function
on_attach = function(client_id, bufnr)
-- Set up keymaps, autocommands, etc.
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, { buffer = bufnr })
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr })
-- ... more LSP keymaps
end,
-- OCaml LSP server settings
settings = {
duneDiagnostics = true, -- Dune-specific diagnostics
syntaxDocumentation = true, -- Syntax documentation
},
-- Experimental OCaml LSP features
experimental = {
switchImplIntf = true, -- Switch between .ml/.mli files
inferIntf = true, -- Interface inference
typedHoles = true, -- Typed holes support
typeEnclosing = true, -- Type enclosing
construct = true, -- Construct handling
destruct = true, -- Destruct handling
jumpToNextHole = true, -- Jump to next hole
},
},
}This plugin supports all the advanced features from the VSCode OCaml Platform:
- Dune Diagnostics: Build system integration for better error reporting
- Syntax Documentation: Documentation extraction from comments
- Switch Implementation/Interface: Quick navigation between
.mland.mlifiles - Interface Inference: Automatic interface generation from implementation
- Typed Holes: Support for
_placeholders with type information - Type Enclosing: Show types of expressions under cursor
- Construct/Destruct: Advanced code manipulation features
- Jump to Next Hole: Navigate between typed holes in your code
All features are disabled by default but can be enabled individually through configuration.
The plugin automatically detects your project type and starts the appropriate LSP server:
- Esy Projects: Detects
esy.jsonorpackage.jsonwithesyfield β usesesy -P ocamllsp - Opam Projects: Detects
*.opamfiles β uses local opam sandbox if present, otherwise falls back to globalocamllsp - Dune Projects: Detects
dune-project/dune-workspaceβ usesocamllsp - Global: Falls back to global
ocamllsp
All plugin commands are available as subcommands under :OCaml:
:OCaml lsp start " Start LSP server
:OCaml lsp stop " Stop LSP server
:OCaml lsp restart " Restart LSP server" Jump to definition (fun, let, module, module-type, match,
" match-next-case, match-prev-case)
:OCaml jump [target]
:OCaml jump-hole [next|prev] " Jump to next/previous typed hole
" Jump to next/previous phrase (top-level definition)
:OCaml phrase [next|prev]:OCaml expand-ppx " Expand PPX at cursor position
" Search functions by type signature and insert selected result
:OCaml type-search [query]
" Show type of expression under cursor with enclosing navigation
:OCaml type-enclosingType Search allows you to search for functions by their type signature
using the ocamllsp/typeSearch LSP method:
:OCaml type-search " Prompt for query, then show results
:OCaml type-search int -> string " Search for functions matching the typeAfter entering a query, you'll get an interactive picker (fzf-lua or telescope if available) showing all matching functions with their types, file locations, and documentation previews. You can then use the picker's fuzzy matching to further refine results. Selecting a result inserts the constructible form at the cursor position and displays full documentation in a floating window.
Type Enclosing shows the type of the expression under your cursor and allows you to navigate through progressively larger enclosing expressions:
:OCaml type-enclosingOnce the floating window appears, you can:
βorK: Show type of larger enclosing expression (zoom out)βorJ: Show type of smaller enclosing expression (zoom in)d: Delete the currently highlighted expressiony: Yank (copy) the currently highlighted expressionc: Change the currently highlighted expression (delete and enter insert mode)qorEsc: Close the window
This is useful for understanding complex expressions and debugging type errors by seeing how OCaml infers types at different levels of your code. The operator support (d/y/c) allows you to quickly manipulate expressions based on their type boundaries.
:OCaml select-ast " Select the wrapping AST node at cursorAST Selection allows you to select code based on OCaml's AST structure:
:OCaml select-ast- Selects the smallest AST node containing the cursor (usesocamllsp/wrappingAstNode)
This is useful for quickly selecting logical code blocks (expressions, statements, modules) based on the OCaml AST.
For expanding/shrinking selections and applying operators (delete, yank, change),
use :OCaml type-enclosing instead, which provides interactive navigation and
operator support.
Suggested keybinding:
vim.keymap.set('n', '<leader>v', ':OCaml select-ast<CR>',
{ desc = 'Select AST node' }):OCaml dune promote " Promote current file changesDune Promote applies file promotions for the current file. This is useful when:
- Updating test expectations (cram tests, expect tests)
- Promoting generated files to source tree
- Accepting diff-based changes from build rules
The command requires the file to be saved and uses LSP code actions to trigger the promotion.
The plugin automatically registers custom TreeSitter parsers for various OCaml file types. Install the parsers you need using nvim-treesitter:
:TSInstall ocaml " OCaml implementation files (.ml)
:TSInstall ocaml_interface " OCaml interface files (.mli)
:TSInstall menhir " Menhir parser files (.mly)
:TSInstall ocamllex " OCamllex lexer files (.mll)
:TSInstall ocaml_mlx " OCaml JSX files (.mlx)
:TSInstall reason " Reason files (.re, .rei)
:TSInstall cram " Cram test files (.t)Filetype to TreeSitter parser mappings (automatically registered):
ocaml.interfaceβocaml_interfaceocaml.menhirβmenhirocaml.ocamllexβocamllexocaml.mlxβocaml_mlxocaml.cramβcramreason/reason.interfaceβreason
You only need to install the parsers for the file types you use.
The plugin automatically configures formatters for conform.nvim:
- OCaml files β
ocamlformat - MLX files β
ocamlformat-mlx
The plugin searches for these files to determine project root and type:
π Project Root Detection (in order of precedence):
βββ dune-project # Dune project
βββ dune-workspace # Dune workspace
βββ package.json # Esy project (with esy field)
βββ esy.json # Esy project
βββ *.opam # Opam package
βββ _build/ # Build directory
βββ .git/ # Git repository
If you see errors like "cannot start ocamllsp due to config error" in your
LSP logs:
- Check for conflicts: Make sure you've disabled
ocamllspin your LSP configuration - LazyVim users: Set
ocamllsp = falsein your server configuration - Check LSP logs:
:lua print(vim.fn.stdpath("log") .. "/lsp.log")to find the log file - Debug configuration: Run
:lua print(vim.inspect(vim.lsp.config))to check for incomplete configurations
If the plugin can't find ocamllsp or other tools:
- Esy projects: Ensure dependencies are installed with
esy install - Opam projects: Make sure you're in the correct opam switch with tools installed
- Global installation: Install
ocamllspglobally via opam or your package manager
If the plugin doesn't detect your project type correctly:
- Check project markers: Ensure you have the appropriate files
(
dune-project,package.json, etc.) - Manual LSP control: Use
:OCaml lsp restartto force re-detection - Debug sandbox: Check what command is being used by looking at LSP logs
# Run all tests
busted
# Run with coverage
busted --coverage
# Lint code
luacheck .
# Format code
stylua .# Enter development shell
nix develop
# Run all checks
nix flake check
# Build plugin
nix buildThis plugin is in early development. Contributions are welcome!
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Run tests:
busted - Submit a pull request
MIT License - see LICENSE for details.
- Inspired by haskell-tools.nvim LSP architecture
- Built for the OCaml community with β€οΈ