neocaml Roadmap
June 21, 2026 ยท View on GitHub
This document collects ideas for where neocaml could go next. It's a wish list and a thinking tool, not a set of promises or a strict ordering. Items will move in and out as priorities shift and as the surrounding ecosystem (Emacs, ocaml-lsp-server, the tree-sitter grammars) evolves. Contributions and discussion on any of these are very welcome.
Guiding principles
A few things that shape what belongs here and what doesn't:
- tree-sitter first. Every mode is built on a grammar and on the built-in
treesitmachinery. We prefer fixing things at that layer over bolting on heuristics. - Lean on the LSP stack, don't reinvent it. For
.ml/.mli,ocaml-lsp-serverplusocaml-eglotalready provide completion, type-on-hover, navigation, refactoring, and diagnostics. neocaml's job is to be a great editing substrate underneath that, not to duplicate it. - The auxiliary modes are ours to own. dune, opam, ocamllex, menhir, cram, and the REPL have no language server. That's where neocaml-specific intelligence pays off the most.
- Stay consistent across modes. Whatever we add (a menu, flymake, a face naming scheme) should look and behave the same everywhere.
Language support
1. Finish OCaml 5.x coverage (in flight)
PR #62 moves the grammars to tree-sitter-ocaml v0.25.0, which brings the OCaml 5.5
syntax. Once that lands (currently parked on the upstream ABI-14 tag), audit
font-lock for the newer constructs that arrived across 5.x: effect handlers, the
effect/perform surface, binding operators, and the 5.5 additions (external
types, modular explicits, polymorphic parameters). Make sure each gets a sensible
face and that indentation handles them. Priority: high (mostly in progress).
2. OxCaml support (blocked on grammar)
Jane Street's OxCaml adds a substantial set of extensions (modes, kinds, unboxed and layout-annotated types, locality, and more). Supporting it well needs the upstream grammar to grow those constructs first. Track the grammar work, and once it's available, extend font-lock and indentation, ideally gated so the extra rules only kick in when the OxCaml-aware grammar is installed. Priority: later (blocked upstream).
Diagnostics and editing intelligence
3. flymake backends for the tool modes
Today only neocaml-opam-mode has a flymake backend (it shells out to opam lint).
Extend that same pattern to the other modes that have no language server behind them:
- dune: validate via
dune buildoutput or a dune-file check. - ocamllex / menhir: run the respective tool and surface parse/grammar errors.
- cram: basic structural checks on
.tfiles.
There's good precedent for shipping a built-in backend rather than leaving it to the
user: rust-ts-mode defines rust-ts-flymake-command (defaulting to
clippy-driver). For .ml/.mli, diagnostics already come from eglot + ocamllsp,
so the only thing worth considering there is an optional non-LSP fallback (for
example via ocamlc -i or merlin) for users who don't run a language server.
Priority: high.
4. ocamlformat integration
There's a neocaml-dune-format-buffer for dune files, but nothing for OCaml source.
A neocaml-format-buffer that drives ocamlformat (respecting .ocamlformat), with
an opt-in format-on-save mirroring the dune mode, would be one of the highest-value
practical additions. Following haskell-ts-mode's C-c C-f (which formats the
active region or the current function with ormolu), we should support formatting a
region or single definition, not just the whole buffer. It should work whether or
not a language server is running, and play nicely with apheleia/format-all for
people who prefer those. Priority: high.
5. completion-at-point for the auxiliary modes
The LSP stack handles completion for OCaml source. What it doesn't cover is the
surrounding files: dune stanza and field names, opam fields, and findlib package
names. A small set of capf functions for neocaml-dune-mode, neocaml-opam-mode,
and the dune (libraries ...) field would make those modes feel much more alive,
and they'd plug straight into completion-preview-mode (see item 17).
Priority: medium.
6. First-class ocaml-eglot interop
ocaml-eglot is becoming the standard way to drive ocamllsp from Emacs, and it
exposes OCaml-specific niceties (type-enclosing sessions, construct, jump-to-*). We
already register the modes with eglot-server-programs; the next step is to make
sure neocaml and ocaml-eglot compose cleanly out of the box, document the pairing,
and avoid keybinding or menu clashes. Priority: medium.
Font-lock and syntax correctness
7. A syntax-propertize-function for OCaml's tricky lexemes
OCaml has a few constructs that a static syntax table can't classify correctly,
most notably the clash between a character literal ('a') and a type variable
('a), plus quoted string literals ({id|...|id}). The grammar parses these
fine, but everything keyed on the syntax table (sexp motion, electric-pair,
delete-pair, string/comment detection, forward-word) can still trip over them.
rust-ts-mode solves the analogous char-vs-lifetime and raw-string problem with a
syntax-propertize-function; neocaml should do the same so the lower-level
machinery agrees with the parse tree. Priority: medium-high (correctness).
8. Prettify and font-lock refinements
Two smaller polish items inspired by sibling modes:
- Add a
prettify-symbols-compose-predicateso symbols aren't composed inside strings, comments, or partial tokens (asrust-ts-modedoes). - Consider
haskell-ts-mode's approach of highlighting only the pattern variables that are actually bindable/usable, rather than every name in a pattern.
Priority: medium / low.
REPL
9. A smarter REPL
The REPL (neocaml-repl-mode) covers the basics today: send region/buffer/phrase/
definition, #use a file, history persistence, prompt and error fontification, and
error navigation back to source. Room to grow:
- Completion in the REPL buffer (via the toplevel's own completion or merlin).
- Restart command, and a cleaner story for switching between the plain toplevel,
utop, and
dune utop. - Send-and-step (evaluate the current phrase and advance to the next).
- Result handling: optional pretty-printing, folding long output, and quick jump from a REPL error straight to the offending source location.
- Helpers for
#require/#use/findlib package loading.
Priority: medium-high.
Tooling and project integration
10. project.el backend
The dune interaction mode already finds the project root with
locate-dominating-file, but neocaml doesn't teach project.el about OCaml
projects. A small backend keyed on dune-project would give users project-wide
commands (find file, search, compile, test) for free, and a sensible
compile-command per project. Priority: medium.
11. dune-interaction polish
neocaml-dune-interaction-mode already wraps build/test/clean/promote/fmt/exec with
watch-mode support. Natural follow-ups: a transient menu, a test runner with
rerun-last and failure navigation, and tighter integration with the compilation
buffer's error parsing. Priority: medium.
Editing ergonomics
12. Skeletons and templates for common forms
tuareg ships insert-helpers for match, let ... in, module, try ... with, and
friends. neocaml has none. A small set of skeletons (or first-class snippet
definitions) for the common OCaml forms would smooth out everyday editing, and they
compose well with the structural commands we already document. Priority: medium.
13. Structural editing helpers
We lean hard on the built-in structural commands (see the navigation docs), but
there's room for OCaml-aware editing built on the same tree-sitter framework, in the
spirit of combobulate: wrap a region in begin ... end, toggle a module between
struct and sig, move a function argument left/right, or transpose match arms.
These are exactly the operations the parse tree makes reliable. Priority: medium,
exploratory.
14. Selectable indentation styles
c-ts-mode exposes c-ts-mode-indent-style (a defcustom of named styles plus a
command to switch them live). OCaml indentation is famously contentious (ocp-indent
vs ocamlformat vs personal taste), so a small set of named neocaml indent presets,
switchable at runtime, could let people get closer to their preferred look without
hand-tuning rules. Worth prototyping before committing to it, since each style is a
maintenance surface. Priority: later, exploratory.
Adopting newer Emacs features (29 / 30 / 31)
Emacs keeps moving capabilities into core. Several map onto things neocaml either hand-rolls or doesn't expose yet. None of these are tree-sitter specific.
15. Grammar install via the Emacs 31 built-ins
Emacs 31 adds treesit-auto-install-grammar (offer to fetch and build a missing
grammar instead of erroring) and treesit-enabled-modes. We can lean on these to
slim down neocaml-install-grammars and the "grammar not installed" prompt on 31+,
while keeping the current path for 29/30. Priority: medium.
16. Evaluate define-treesit-generic-mode and .scm font-lock
Emacs 31 introduces define-treesit-generic-mode and the ability to drive
font-locking from .scm query files. Worth evaluating whether the simpler auxiliary
modes (cram, objinfo) could be expressed more compactly this way, and whether parts
of our font-lock could move closer to the upstream highlights.scm.
Priority: later, evaluate.
17. Opt into the newer built-in minor modes
A cluster of smaller, non-tree-sitter improvements worth wiring up or recommending:
completion-preview-mode(Emacs 30): inline preview of the top completion. It'scapf-driven, so it benefits directly from item 5 and from eglot.flymake-show-diagnostics-at-end-of-line(Emacs 30): nothing to implement, but worth recommending in the docs alongside the flymake work.bug-reference-prog-modeandgoto-address-prog-mode: linkify issue references and URLs in comments across all our modes. Cheap, broadly useful.- Multiple eldoc backends via
eldoc-documentation-strategy: if we ever add a neocaml-owned eldoc source (say, doc-comment or REPL-backed), it should compose with eglot's rather than replace it.
Priority: medium / low, mostly low-effort.
18. Track upstream tree-sitter fixes and injection improvements
Retire neocaml's treesit-forward-comment off-by-one workaround once the upstream
fix is in a release we depend on, and revisit the language-injection setup (used by
ocamllex and menhir, and by the pending odoc mode) against the newer multi-language
support, including the markdown-ts-mode approach of injecting real language modes
into code blocks. Priority: ongoing.
Smaller items and nice-to-haves
- odoc mode (PR #46): land
.mldsupport once the open review points are resolved (notably finishing or dropping the sh/bash injection). - ocamldoc rendering: prettier handling of doc comments in source, and applying the odoc grammar to code comments and LSP hovers.
- Face audit: make sure every mode exposes customizable, per-feature faces with
reasonable
font-lock-*defaults (this came up in the odoc review). - More file coverage: a mode or sensible defaults for
METAfiles and other stragglers in the ecosystem. - Performance and robustness: behavior on very large files, and good defaults
for
treesit-font-lock-level.
Lessons from sibling tree-sitter modes
A few patterns worth stealing, noted next to the items they informed:
rust-ts-mode: ships its own flymake backend (clippy-driver, item 3), uses asyntax-propertize-functionfor char/string/lifetime edge cases (item 7), and aprettify-symbols-compose-predicate(item 8).c-ts-mode: offers selectable indentation styles via a defcustom and a live switch command (item 14), and factors comment filling/indentation into the sharedc-ts-commonlibrary.haskell-ts-mode: formats the active region or current function with one key (item 4), and highlights only the pattern variables that are actually usable (item 8).