Contributing to Coq LSP
September 16, 2025 · View on GitHub
Thank you very much for willing to contribute to coq-lsp!
The coq-lsp repository contains several tightly coupled components
in a single repository, also known as a monorepo, in particular:
- base libraries:
lang/,lsp/which define common language constructs - Rocq API: in
coq/, an abstraction layer over Rocq's API - Flèche: an incremental document engine for Rocq supporting
literate programming and programability, in
fleche/. fcc: an extensible command line compiler built on top of Flèche, incompiler/petanque: direct access to Coq's proof engine, inpetanque/coq-lspa LSP server for the Rocq Prover, incontroller/- Binaries for different platforms are in
lsp-server/$platform coq-lsp/VSCode: a VSCode extension written in TypeScript and React, undereditor/code
Read coq-lsp FAQ to learn more about LSP and server/client roles. See more about code organization below.
It is possible to hack only in the server, on the client, or on both at the same time. We have thus structured this guide in 3 sections: general guidelines, server, and VS Code client.
General guidelines
coq-lsp is developed in an open-source manner using GitHub.
Zulip chat
Our main development channel is hosted at Zulip . Please don't hesitate to stop by if you have any questions.
Code of conduct
All contributors of coq-lsp must agree to our code of
conduct
License
coq-lsp uses the LGPL 2.1 license, which is compatible with Coq's
license. We also allow licensing of our code under GPL 3+ and LGPL 2.1+.
Submitting a contribution, opening an issue.
Please use the github standard interfaces to do so. When you submit a
pull request, you agree to share your code under coq-lsp license.
We have a set of tags to classify pull requests and issues, we try to use them as much as possible. As of today, GitHub requires some permissions for regular users to be able to manipulate this meta-data, let us know if you need access.
Changelog
We require an entry in CHANGES.md for all changes of relevance; note that as
of today, CHANGES.md is the canonical changelog for both the server and the
client.
The client changelog that is used by the VS Code marketplace is at
editor/code/CHANGELOG.md, but you should not modify it, as of today we will
generate it from the relevant entries in CHANGES.md at release time.
Server development guide
Compilation
The server project uses a standard OCaml development setup based on Opam and Dune.
The default development environment for coq-lsp is a "composed"
build that includes git submodules for coq and coq-stdlib in the
vendor/ directory.
This allows to easily work with experimental Rocq branches, and some other advantages like a better CI build cache and easier bisects.
This will however install a local Coq version which may not be
convenient for all use cases; we thus detail below an alternative
install method for those that would like to install a coq-lsp
development branch into an OPAM switch.
Default development setup, local Coq / coq-lsp
This setup will build Coq and coq-lsp locally; it is the recommended
way to develop coq-lsp itself.
-
Install the dependencies (the complete updated list of dependencies can be found in
coq-lsp.opam).opam install --deps-only . -
Initialize submodules (the
mainbranch uses some submodules, which we plan to get rid of soon. Branchesv8.xcan already skip this step.)make submodules-init -
Compile the server (the
coq-lspbinary can be found in_build/install/default/bin/coq-lsp).make
Alternatively, you can also use the regular dune build @check etc... targets.
Now, binaries for Coq and coq-lsp can be found under
_build/install/default and used via dune exec -- fcc or dune exec -- $your_editor.
Global OPAM install
This setup will build Coq and coq-lsp and install them to the
current OPAM switch. This is a good setup for people looking to try
out coq-lsp development versions with other OPAM packages.
You can just do:
make submodules-init # Only needed if submodules not initialized
make opam-update-and-reinstall
or alternatively, do it step by step
-
Be sure submodules and
coq-lspare up to date:agit pull --recurse-submodulesalternatively you can use
make submodules-initto refresh the submodules. -
Install vendored Coq
opam install vendor/coq/coq{-core,-stdlib,ide-server,}.opam -
Install
coq-lsp:opam install .
Then, you should get a working OPAM switch with Coq and coq-lsp from
your chosen coq-lsp branch.
Nix
We have a Nix flake that you can use.
-
Dependencies: for development it suffices to run
nix developto spawn a shell with the corresponding dependencies.With the following line you can save the configuration in a Nix profile which will prevent the
nix store gcfrom deleting the entries:nix develop --profile nix/profiles/devYou can then use the following line to reuse the previous profile:
nix develop nix/profiles/dev -
Initialize submodules (the
mainbranch uses some submodules, which we plan to get rid of soon. Branchesv8.xcan already skip this step.)make submodules-init -
Compile the server (the
coq-lspbinary can be found in_build/install/default/bin/coq-lsp).make
You can view the list of packages and devShells that are exposed
by running nix flake show.
If you wish to do nix build, you will need to use the .?submodules=1 trick,
since we use submodules here for vendoring. For example, building requires:
nix build .?submodules=1
This currently only applies to building the default package (coq-lsp), which is
the server. Clients don't have specific submodules as of yet.
When the flake is out-of-date, for instance when a new version of ocamlformat is out, you can update the flake inputs with:
nix flake update
You can also add the dev version build to your flake as:
inputs.coq-lsp = { type = "git"; url = "https://github.com/ejgallego/coq-lsp.git"; submodules = true; };
...
coq-lsp.packages.${system}.default
Code organization
The coq-lsp server consists of several components, we present them bottom-up
serlib: utility functions to handle Coq ASTcoq: Utility library / abstracted Coq API. This is the main entry point for communication with Coq, and it reifies Coq calls as to present a purely functional interface to Coq.lang: base language definitions for Flèchefleche: incremental document processing backend. Exposes a generic API, but closely modelled to match LSPlsp: small generic LSP utilities, at some point to be replaced by a generic librarypetanque: low-level access to Coq's APIcontroller: platform-agnostic LSP controller library, a thin layer translating LSP transport layer toflèchesurface API.lsp-server: LSP server binaries for different platforms, usingcontroller:native: Native OCaml support, good for Windows, MacOS, and Linux.jsoo: Javascript LSP server using js_of_ocaml
Some tips:
- we much prefer not to address Coq API directly, but always use the
coqlibrary to do it. flechehas carefully controlled dependencies and code structure due to a) having to run in JS, b) targeting other systems in addition to Coq.- We use ocamlformat to automatically format our codebase.
make fmtwill take care of it if your editor is not configured to so automatically.
Unicode setup
Flèche stores locations in the protocol-native format. This has the advantage that conversion is needed only at range creation point (where we usually have access to the document to perform the conversion).
This way, we can send ranges to the client without conversion.
Request that work on the raw Contents.t buffer must do the inverse
conversion, but we handle this via a proper text API that is aware of
the conversion.
For now, the setup for Coq is:
- protocol-level (and Flèche) encoding: UTF-16 (due to LSP)
Contents.t: UTF-8 (sent to Coq)
It would be very easy to set this parameters at initialization time, ideal would be for LSP clients to catch up and allow UTF-8 encodings (so no conversion is needed, at least for Coq), but it seems that we will take a while to get to this point.
Worker version (and debugging tips)
See https://github.com/ocsigen/js_of_ocaml/issues/410 for debugging
tips with js_of_ocaml.
Client guide (VS Code Extension)
The VS Code extension is setup as a standard npm Typescript + React
package using esbuild as the bundler.
Setup
- Navigate to the editor folder
cd editor/code - Install dependencies:
npm i
Then there are two ways to work with the VS Code extension: you can let VS Code itself take care of building it (preferred setup), or you can build it manually.
Let VS Code handle building the project
There is nothing to be done, VS Code will build the project automatically when launching the extension. You can skip to launching the extension.
Manual build
- Navigate to the editor folder
cd editor/code
You can now run package.json scripts the usual way:
- Typecheck the project
npm run typecheck - Fast dev-transpile (no typecheck)
npm run compile
Launch the extension
From the toplevel directory launch VS Code using dune exec -- code -n editor/code, which will setup the right environment variables such as
PATH and OCAMLPATH, so the coq-lsp binary can be found by VS
Code. If you have installed coq-lsp globally, you don't need dune exec, and can just run code -n editor/code.
Once in VS Code, you can launch the extension normally using the left "Run and Debug" panel in Visual Studio Code, or the F5 keybinding.
You can of course install the extension in your ~/.vscode/ folder if so you
desire, although this is not recommended.
Nix
In the case of the client we expose a separate shell, client-vscode,
which can be spawned with the following line (this can be done on top
of the original nix develop).
nix develop .#client-vscode
The steps to setup the client are similar to the manual build:
- Spawn
developshellnix develop - Inside
develop, spawn theclient-vscodeshellnix develop .#client-vscode - Navigate to the editor folder
cd editor/code - Install dependencies:
npm i - Follow the steps in manual build.
You are now ready to launch the extension.
Code organization
The extension is divided into two main folders:
editor/code/src/: contains the main components,editor/code/views: contains some webviews components written using React.
Testing the Web Extension
coq-lsp has preliminary support to run as a "Web Extension", to test
that, you want to use the web extension profile in the launch setup.
Miscellaneous info
- The "Restart Coq LSP server" command will be of great help while developing with the server.
- We use prettier to automatically format files in
editor/code.make ts-fmtwill do this in batch mode.
Debugging
The default build target will allow you to debug the extension by providing the right sourcemaps.
Nix:
Nix can be configured to use the version of the VS Code extension from
our git in the following way:
inputs.coq-lsp = { type = "git"; url = "https://github.com/ejgallego/coq-lsp.git"; submodules = true; };
...
programs.vscode = {
enable = true;
extensions = with pkgs.vscode-extensions; [
...
inputs.coq-lsp.packages.${pkgs.system}.vscode-extension
...
];
};
Test-suite
coq-lsp has a test-suite in the test directory, see the
README there for more details.
make testwill perform the LSP testsmake test-compilerwill perform the compiler tests
Releasing
coq-lsp is released using dune-release tag + dune-release.
The checklist for the release as of today is the following:
Prepare a release commit
- update the client changelog at
editor/code/CHANGELOG.md, commit - update the version number at
lsp-server/wasm/package.json - update the version number at
editor/code/package.json - do
make extensionto update thepackage-lock.jsonfiles - update the version number at
fleche/version.ml - add release notes in
etc/release_notes/if needed
Tag and test release commit
export COQLSPV=0.2.5
git checkout main && opam exec --switch=dev-coq-lsp -- make && opam exec --switch=dev-coq-lsp -- make test test-compiler && dune-release tag ${COQLSPV}
git checkout v9.1 && git merge main && opam exec --switch=rocq-v9.1 -- make && opam exec --switch=rocq-v9.1 -- make test test-compiler && dune-release tag ${COQLSPV}+9.1
git checkout v9.0 && git merge v9.1 && opam exec --switch=rocq-v9.0 -- make && opam exec --switch=rocq-v9.0 -- make test test-compiler && dune-release tag ${COQLSPV}+9.0
git checkout v8.20 && git merge v9.0 && opam exec --switch=coq-v8.20 -- make && opam exec --switch=coq-v8.20 -- make test test-compiler && dune-release tag ${COQLSPV}+8.20
git checkout v8.19 && git merge v8.20 && opam exec --switch=coq-v8.19 -- make && opam exec --switch=coq-v8.19 -- make test test-compiler && dune-release tag ${COQLSPV}+8.19
git checkout v8.18 && git merge v8.19 && opam exec --switch=coq-v8.18 -- make && opam exec --switch=coq-v8.18 -- make test test-compiler && dune-release tag ${COQLSPV}+8.18
git checkout v8.17 && git merge v8.18 && opam exec --switch=coq-v8.17 -- make && opam exec --switch=coq-v8.17 -- make test test-compiler && dune-release tag ${COQLSPV}+8.17
Client release:
- build the JavaScript version
make js - build the extension with
npm run vscode:prepublish - check with
vsce lsthat the client contents are OK - upload to official VSCode marketplace:
vsce publish - upload vsix to OpenVSX marketplace
- todo: check the wasm / js stuff is there, needs to improve
Server:
dune release for each version that should to the main opam repos:
export COQLSPV=0.2.5
git checkout v9.1 && opam exec --switch=rocq-v9.1 -- dune-release
git checkout v9.0 && opam exec --switch=rocq-v9.0 -- dune-release
git checkout v8.20 && opam exec --switch=coq-v8.20 -- dune-release
git checkout v8.19 && opam exec --switch=coq-v8.19 -- dune-release
git checkout v8.18 && opam exec --switch=coq-v8.18 -- dune-release
git checkout v8.17 && opam exec --switch=coq-v8.17 -- dune-release
[important] After release commit
- bump
version.mlandeditor/code/package.jsonversion string to a$version+1-dev