# capnprotols
A language server for [Cap'n Proto](https://capnproto.org/) `.capnp` schema files.
Speaks LSP over stdio. Wraps the official `capnp` compiler for authoritative
diagnostics and symbol resolution, and uses [tree-sitter-capnp](https://github.com/amaanq/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 LSP
`Diagnostic`s with file/line/column ranges.
- **Go-to-definition** for types, enums, annotations, including:
- cross-file via the compiler's `CodeGeneratorRequest` source info,
- `using` alias redirects (local *and* `Receiver.Member` cross-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)`).
- **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 `defaultLibrary` modifier).
- **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).
- **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](https://github.com/capnproto/capnproto/blob/master/style-guide.md)
and Kenton's canonical schemas:
- 2-space indentation, brace-on-same-line, `name @N :Type` colon 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 `WARNING` diagnostic,
- `# capnpfmt: off` / `# capnpfmt: on` markers preserve a region verbatim,
- bails (returns no edits) on any parse error so broken buffers aren't
destructively rewritten.
- **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 `capnp` binary on `$PATH` and its
`capnp/schema.capnp` available 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](https://crates.io/crates/capnprotols) (recommended):
```sh
# latest published release
cargo install capnprotols --locked
# pin to a specific version
cargo install capnprotols --locked --version 0.1.3
```
Both binaries (`capnprotols` and `capnpfmt`) are installed under `~/.cargo/bin/`.
### Build from source
```sh
cargo build --release
# binaries at target/release/{capnprotols,capnpfmt}
```
Or install the working tree onto `$PATH`:
```sh
cargo install --path .
```
## capnpfmt
A standalone formatter for `.capnp` schema files. Installed alongside the
language server.
```sh
# Format a file in place
capnpfmt schema.capnp
# Format multiple files
capnpfmt *.capnp
# Read stdin, write formatted output to stdout
capnpfmt < schema.capnp > formatted.capnp
# Check mode — exit 1 if any file isn't formatted (useful in CI)
capnpfmt --check *.capnp
# Custom column limit (default: 100)
capnpfmt --width 80 schema.capnp
```
Bails on parse errors (leaves the file unchanged) so it's safe to run on
save or in pre-commit hooks.
### Ignoring files (`.capnpfmtignore`)
`capnpfmt` honours `.capnpfmtignore` files (gitignore syntax) so you can
run it against a recursive glob without rewriting vendored or generated
schemas:
```sh
capnpfmt $(find . -name '*.capnp')
```
For each input file, `capnpfmt` walks up its ancestor directories
collecting any `.capnpfmtignore` files until it hits a `.git` directory
(or the filesystem root). The collected rules are applied in the usual
gitignore order — deeper files can override shallower ones with `!`
whitelist patterns. Matched files are skipped silently.
Example `.capnpfmtignore`:
```gitignore
# Don't touch vendored schemas.
vendor/
# But do format our local fork.
!vendor/local-fork/
```
Pass `--no-ignore` to bypass all `.capnpfmtignore` files for a single
invocation.
## Configuration
Settings are passed via `initializationOptions` on the `initialize` request.
JSON shape:
```jsonc
{
"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:
1. user-supplied `importPaths`,
2. `<install_prefix>/include` derived from the resolved `capnp` binary,
3. capnp's hardcoded paths (`/usr/local/include`, `/usr/include`),
4. 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`:
```python
let g:ycm_langauge_server += [
\ {
\ 'name': 'capnprotols',
\ 'cmdline': [ '/path/to/capnprotols' ],
\ 'filetypes': [ 'capnp' ],
\ },
]
```
### VS Code
A minimal extension is included under [`extension/`](extension/) — a thin
LSP client that launches the `capnprotols` binary. See its
[README](extension/README.md) for build/install steps.
## Architecture notes
- `compiler.rs` shells out to `capnp 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.rs` decodes 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.rs` handles `using NAME = …` and surface-scans top-level declarations
for cases where a referenced file isn't represented in the current CGR.
- `ordinals.rs` brace-tracks the buffer to find the enclosing struct and compute
the next contiguous `@<n>` ordinal.
- `semantic_tokens.rs` runs tree-sitter-capnp's `HIGHLIGHTS_QUERY` and emits LSP
semantic tokens.
- `server.rs` wires everything into `tower-lsp` and detects cursor contexts
(type / annotation / member / field-ordinal slots) for completion and
signature help.
## License
MIT.