Syntax highlighting, bracket matching, indentation, folding, document outline,
test run buttons, and diagnostics (via the cf lsp language server) for
C! in the Zed editor.
extension.toml Zed manifest — grammar + language + language server
src/lib.rs the wasm shim handing Zed the `cf lsp` command
languages/cflang/config.toml file suffix (.cf), comments (#), brackets, tabs
languages/cflang/*.scm tree-sitter queries: highlights, brackets,
indents, folds, outline, runnables
The grammar lives in its own repo,
tree-sitter-cflang, vendored
beside this one in the cf tree at usr/tree-sitter-cflang.
languages/cflang/runnables.scm marks every case!/todo! call, so Zed shows
a run button in the gutter next to each test case. The button fires whatever
task carries the cflang-case tag — define one in your project's
.zed/tasks.json (the cf repo ships one that uses ./var/cf):
[
{
"label": "cf test $ZED_RELATIVE_FILE",
"command": "cf",
"args": ["test", "$ZED_RELATIVE_FILE"],
"tags": ["cflang-case"]
}
]cf test runs a whole file's cases — there is no per-case filter.
The server is the compiler itself: cf lsp speaks LSP over stdio and publishes
located diagnostics on open and save (positions negotiated as utf-8 byte
columns). The extension resolves cf from the worktree's PATH; point it
elsewhere with Zed settings:
{ "lsp": { "cf-lsp": { "binary": { "path": "/path/to/cf" } } } }Building the wasm shim needs the wasm32-wasip1 Rust target
(rustup target add wasm32-wasip1) — Zed compiles it automatically when the
dev extension is (re)installed.
Zed language support is built entirely on Tree-sitter: every query file runs
against a parse tree, so the grammar in tree-sitter-cflang/ is the foundation.
There is no separate TextMate/regex grammar.
- Open Zed → command palette →
zed: install dev extension. - Pick this directory (
usr/zed). Open any.cffile to see it applied.
Zed loads the language config and queries live from this directory, but
fetches the grammar from the tree-sitter-cflang GitHub repo at the commit
pinned by rev in extension.toml.
-
Queries /
config.toml— edit freely, thenzed: reload extensions. No commit needed; Zed reads them live. -
grammar.js— edit inusr/tree-sitter-cflang, regenerate, commit, push the subtree upstream, and pin the new rev:cd ../tree-sitter-cflang npx tree-sitter generate # regenerate src/ npx tree-sitter parse FILE.cf # sanity-check (look for ERROR nodes) cd ../.. && git commit … # the subtree split hash is what gets pushed git subtree split --prefix=usr/tree-sitter-cflang # → SHA for `rev` git subtree push --prefix=usr/tree-sitter-cflang git@github.com:orlowdev/tree-sitter-cflang.git master
Then
zed: reload extensions.
The grammar parses lib/, boot/src, and the valid boot/tests corpus with
zero error nodes (sole corner: n!= 0, whose bang the lexer cannot split
without lookahead).
The grammar serves editor tooling, so a few choices favour resilient highlighting over strict conformance:
- Newlines are insignificant. The language is newline-terminated (one
statement per line — see
root/code_style/indentation.md), but the compact cf0 test files put several statements on a line. The grammar stays permissive so it highlights both styles without flagging the existing files. break/continuecarry no label. The EBNF gives them an optional loop label; with insignificant newlines an optional trailing name would swallow the next statement's leading identifier. Abreak outerstill highlights fine.- Index / type-application overlap (
xs[8]vsf[8]) is resolved by the receiver's type in the real language — unknown to a syntax grammar — so the grammar biases toward indexing, the common case. - Types and UPPER_SNAKE consts share one token.
STDOUTandStrare lexically inseparable without lookahead, sotype_namecovers both and the highlight query recolors the all-caps shape as a constant via#match?.