capnprotols
A language server for Cap'n Proto .capnp schema files.
Speaks LSP over stdio. Wraps the official capnp compiler for authoritative
diagnostics and symbol resolution, and uses tree-sitter-capnp
for editor-resilient highlighting.
WARNING: This repository is 100% AI generated code and was intentionally done hackily and quickly to deliver value. It therefore should not be relied on.
Features
- Diagnostics — parse and schema errors from
capnp compile, mapped to LSPDiagnostics with file/line/column ranges. - Go-to-definition for types, enums, annotations, including:
- cross-file via the compiler's
CodeGeneratorRequestsource info, usingalias redirects (local andReceiver.Membercross-file dotted refs),- the path string inside
import "...", - name-based fallback for cases the compiler doesn't track (e.g. type parameters
inside
List(T)).
- cross-file via the compiler's
- Hover — kind + name + the node's doc comment from
cgr.sourceInfo. - Semantic-token highlighting via tree-sitter-capnp's bundled queries, mapped
to standard LSP token types (built-ins get the
defaultLibrarymodifier). - Completion with cursor-context awareness:
- after
:/(/,→ built-in primitives + user types, - after
$→ annotations, - after
Namespace.→ members of the imported file (uses index, falls back to a surface scan when no nodes from that import survived to the CGR), - in unknown contexts → top-level keywords (
struct,enum,interface, …), - after
@→ the next valid field ordinal in the enclosing struct's ID space (scoped correctly across groups, unions, and nested structs).
- after
- Signature help for annotation applications (
$Foo.bar(field = :Type, …)) and generic instantiations (List(T),MyStruct(A, B)). - Formatting (
textDocument/formatting) — conservative whitespace normalisation derived from the KJ style guide and Kenton's canonical schemas:- 2-space indentation, brace-on-same-line,
name @N :Typecolon spacing, - blank line between top-level decls, trailing whitespace stripped, single final newline,
- doc-comment blocks re-indent with their declaration but contents are preserved verbatim (no paragraph reflow),
- hard configurable column limit (default 100): trailing inline comments
get pushed onto a new line when they push past it; long
$Annotation(...)chains break before each$; long generic argument lists break inside(...)one arg per line, - long lines that don't match any wrapper produce a
WARNINGdiagnostic, # capnpfmt: off/# capnpfmt: onmarkers preserve a region verbatim,- bails (returns no edits) on any parse error so broken buffers aren't destructively rewritten.
- 2-space indentation, brace-on-same-line,
- Live-buffer overlay — analysis runs on unsaved edits. The cached symbol index is retained across compile failures so completion and goto stay useful while you have a syntax error mid-edit.
Build Requirements
- Rust toolchain (build only):
cargo,rustc. - A Cap'n Proto installation: the
capnpbinary on$PATHand itscapnp/schema.capnpavailable under one of the install's include directories (Homebrew, MacPorts, apt and most manual installs put it there automatically). Tested with 1.3.0.
build.rs regenerates the Rust bindings from the installed schema.capnp so
the server gets the latest startByte/endByte and FileSourceInfo accessors.
Override the search with CAPNP_SCHEMA=/path/to/schema.capnp if needed.
Install
From crates.io (recommended):
# latest published release
# pin to a specific version
Both binaries (capnprotols and capnpfmt) are installed under ~/.cargo/bin/.
Build from source
# binaries at target/release/{capnprotols,capnpfmt}
Or install the working tree onto $PATH:
capnpfmt
A standalone formatter for .capnp schema files. Installed alongside the
language server.
# Format a file in place
# Format multiple files
# Read stdin, write formatted output to stdout
# Check mode — exit 1 if any file isn't formatted (useful in CI)
# Custom column limit (default: 100)
Bails on parse errors (leaves the file unchanged) so it's safe to run on save or in pre-commit hooks.
Configuration
Settings are passed via initializationOptions on the initialize request.
JSON shape:
{
"compilerPath": "capnp", // path to the capnp binary; default "capnp" on $PATH
"importPaths": ["/abs/dir/one"], // extra -I paths for `import "/..."` resolution
"format": {
"enabled": true, // master switch for textDocument/formatting
"maxWidth": 100, // hard column limit (KJ style guide default)
"warnLongLines": true // diagnose lines we can't auto-wrap
}
}
Standard import roots are auto-discovered on startup. The server probes:
- user-supplied
importPaths, <install_prefix>/includederived from the resolvedcapnpbinary,- capnp's hardcoded paths (
/usr/local/include,/usr/include), - common platform defaults (
/opt/homebrew/include,/opt/local/include).
Each non-user candidate is kept only if it actually contains
capnp/c++.capnp. This covers Homebrew (Apple Silicon and Intel), MacPorts,
apt, and most manual installs without configuration.
Logging
CAPNPROTOLS_LOG=info (or debug, trace) enables tracing-style logs on
stderr. The default is info. Logs go to stderr only — stdout is reserved for
the LSP framing.
Editor setup
The server speaks vanilla LSP over stdio with no custom extensions. Any LSP client works.
YouCompleteMe (ycmd)
Add to your .vimrc:
: +=
VS Code
A minimal extension is included under extension/ — a thin
LSP client that launches the capnprotols binary. See its
README for build/install steps.
Architecture notes
compiler.rsshells out tocapnp compile -o-against an overlay file written alongside the original (so relative imports still resolve), then path-remaps the compiler-reported overlay path back to the real file.index.rsdecodes the CGR into a per-file FSI table (sorted byte-ranges → resolved typeIds) plus a per-node table (kind, displayName, fields, generic parameters, doc comment, source byte range).aliases.rshandlesusing NAME = …and surface-scans top-level declarations for cases where a referenced file isn't represented in the current CGR.ordinals.rsbrace-tracks the buffer to find the enclosing struct and compute the next contiguous@<n>ordinal.semantic_tokens.rsruns tree-sitter-capnp'sHIGHLIGHTS_QUERYand emits LSP semantic tokens.server.rswires everything intotower-lspand detects cursor contexts (type / annotation / member / field-ordinal slots) for completion and signature help.
License
MIT.