OCaml-LSP

repository·master·Indexed 21 days ago

https://github.com/ocaml/ocaml-lsp

A language server implementation for the OCaml programming language that provides IDE-like features such as auto-completion, go-to-definition, and diagnostics via the Language Server Protocol (LSP). It supports advanced features like typed holes, value destruction via code actions, semantic highlighting, and integration with Dune's RPC system for enhanced diagnostics.

Tokens
13.5K
Snippets
47
Records
66
Agent score
74%

What's inside OCaml-LSP

  1. Overview of Lev OCaml bindings

    master

    Lev provides OCaml bindings to libev, a minimal and portable event loop library. The repository is split into two distinct packages depending on your concurrency needs:

    1. lev: Low-level, minimal, and low-overhead bindings. The API is callback-based, meaning you must provide your own concurrency model (BYOC).
    2. lev-fiber: A higher-level API built on top of lev. It utilizes Dune's fiber library to provide structured concurrency.
  2. Use Typed Holes for development

    master

    A typed hole is a syntactic _ (underscore) used as a temporary substitute for an expression. OCaml-LSP treats these as valid, well-typed programs during development, allowing you to write code that doesn't yet compile but has the correct type structure.

    Examples:

    • let foo : int = _ (the hole has type int)
    • let bar = _ 10 (the hole has type int -> 'a)

    Warning: Files containing typed holes are not valid OCaml and cannot be compiled by the standard OCaml compiler until the holes are replaced with actual expressions.

  3. Use Dune RPC for enhanced diagnostics and file promotion

    master

    Since version 1.11.0, OCaml-LSP can automatically communicate with Dune's RPC system if it is running. OCaml-LSP does not launch the RPC itself; you must launch it by running Dune in watch mode.

    Key benefits:

    • Advanced Diagnostics: Shows warnings/errors like interface and implementation mismatches. Note that you must save the file to refresh these diagnostics, as Dune only sees saved files.
    • File Promotion: If using ppx_expect and tests fail, you will see a diagnostic. You can use the Promote code action to promote the file.

    Recommendation: Always run dune build --watch to ensure OCaml-LSP has the most up-to-date information.

    dune build --watch
  4. Understand the Lev API structure

    master

    Lev provides OCaml bindings to the libev event loop library. It is split into two distinct packages depending on your concurrency needs:

    1. lev: Provides low-level, minimal, and low-overhead bindings. The API is callback-based, meaning it does not manage concurrency for you; you must "bring your own concurrency" (BYOC).
    2. lev-fiber: Provides a higher-level, more familiar API built on top of Dune's fiber library for structured concurrency.
  5. Install OCaml-LSP from source

    master

    You can build OCaml-LSP from source. This repository uses submodules to manage dependencies to avoid dependency conflicts in your sandbox. Ensure you use the --recurse-submodules flag when cloning.

    $ git clone --recurse-submodules http://github.com/ocaml/ocaml-lsp.git
    $ cd ocaml-lsp
    $ make install
  6. Use the low-level Lev API

    master

    The lev package is a thin wrapper around libev. Because it is a direct binding, you should refer to the official libev documentation for core logic, using Lev's documentation only to identify deviations from libev conventions. The API uses a callback-based pattern for event handling.

    open Lev
    
    let () =
      let loop = Loop.default () in
      (* ... setup pipes and processes ... *) 
      let child = 
        match Child.create with
        | Error `Unimplemented -> assert false
        | Ok create -> 
            create (fun t ~pid status -> 
              Child.stop t loop; 
              (* handle status *) 
            ) (Pid pid) Terminate
      in
      Child.start child loop;
      Loop.run_until_done loop;
      Child.destroy child
  7. Install OCaml-LSP via Opam

    master

    To install the language server in your current opam switch, use the opam install command. Note that you must install ocaml-lsp-server in every switch where you intend to use it. The installed binary is named ocamllsp.

    $ opam install ocaml-lsp-server
  8. Install OCaml-LSP locally with Dune

    master

    If you are using Dune (version 3.24 or later, or the latest nightly build), you can install ocamllsp locally within your project. This ensures ocamllsp is compiled with the same OCaml compiler as your project, which is required for accurate code analysis.

    To ensure your editor uses this local instance, run eval $(dune tools env) in your shell before launching the editor. You can automate this using direnv with a .envrc file.

    For VSCode, use the OCaml Platform extension. For Emacs, use the ocaml-eglot package.

    $ dune tools install ocamllsp
    $ eval $(dune tools env)
  9. Configure source file formatting with ocamlformat

    master

    To enable source file formatting support, you must install the ocamlformat package. Additionally, an .ocamlformat file must be present in your project's root directory.

    OCaml-LSP also uses ocamlformat-rpc (included with ocamlformat version > 0.21.0) to format code displayed in hover tooltips.

  10. Configure ocamllsp settings

    master

    The ocamllsp server supports various configuration options sent via the didChangeConfiguration LSP notification. These settings allow you to control features like hover behavior, CodeLens, inlay hints, and diagnostics. Configuration is typically managed through your IDE's LSP client settings (e.g., VS Code's settings.json).

    {
      "extendedHover": { "enable": boolean },
      "standardHover": { "enable": boolean },
      "codelens": {
        "enable": boolean,
        "forNestedBindings": boolean
      },
      "duneDiagnostics": { "enable": boolean },
      "inlayHints": {
        "hintPatternVariables": boolean,
        "hintLetBindings": boolean,
        "hintFunctionParams": boolean
      },
      "syntaxDocumentation": { "enable": boolean },
      "merlinJumpCodeActions": { "enable": boolean },
      "shortenMerlinDiagnostics": { "enable": boolean }
    }