CrateStack Editor Tooling
This document records the current state of CrateStack editor support, how to use it locally, and the most useful follow-up work.Scope
CrateStack has two editor surfaces:- Rust files that consume one of the role-specific schema macros —
cratestack::include_server_schema!(...),cratestack::include_embedded_schema!(...), orcratestack::include_client_schema!(...) .cstackschema files authored directly
.cstack support is intentionally split out into a standalone language server so basic schema authoring does not require a full host project checkout.
Current State
Implemented in this repo today:crates/cratestack-lspprovides a standalone language server for.cstackfiles (built on the actively-maintainedtower-lsp-server0.23 fork as of 0.3.0; the previoustower-lsp0.20 dep had been unmaintained since 2024)packages/cratestack-vscodeprovides the VS Code extension wrapper that launchescratestack-lspcratestack-cli check --format jsonprovides machine-readable diagnostics for CI or editor fallback integrations- parser and semantic structures now preserve schema docs and source spans needed for editor features and generated Rust docs
- the schema macros now emit Rust
#[doc = "..."]attributes from schema-authored comments
.cstack editor features:
- diagnostics — all independent errors at once, not one per save
- hover
- completion
- go-to-definition, including enums and mixins
- find-all-references (
textDocument/references) - rename (F2), with
prepareRenameso the editor can refuse an invalid rename before showing the input box - semantic tokens (
textDocument/semanticTokens/full) - document symbols
- document highlight
- basic syntax highlighting through the bundled TextMate grammar
- relation-aware definition lookup inside
@relation(fields:[...],references:[...]) - narrower relation diagnostics that point at the bad relation token instead of only the whole declaration line
Semantic tokens
The TextMate grammar cannot tellString (a builtin scalar), User (a
model), Role (an enum) and Timestamps (a mixin) apart — to a regex
they are four bare capitalised words. The server re-colours identifiers
by what they actually resolve to:
Tokens supplement the grammar rather than replace it. VS Code has no
tree-sitter API for third-party languages, so the grammar keeps handling
keywords, strings and comments — available instantly, before the server
starts — and the server layers resolved identifier colouring on top.
Only an attribute’s
@name head is a decorator, so columns named inside
@relation(fields: [...], references: [...]) keep colouring as the
properties they are.
Features survive a syntax error
A failed parse used to drop the schema entirely, so every feature needing one flickered off keystroke by keystroke while typing — a file spends most of its editing life invalid. The server now retains the last schema that parsed together with the exact text it was parsed from, because spans index into the text that produced them: resolving a retained span against the current buffer would land in the wrong place while still looking like working navigation. Two limits are deliberate. Diagnostics always describe the current text, so a retained schema never suppresses a live error. And a document that has never parsed keeps nothing. Because a result can now legitimately predate what is on screen, hover marks a stale popup with a one-line note rather than presenting it as current. Implemented Rust-side editor improvements:- schema
///docs now flow into generated Rust docs and rust-analyzer hovers when proc-macro expansion is enabled - procedure
/// @param name ...docs now flow into generated procedure argument types
- the parser still validates an initial schema subset rather than the full target grammar described across the broader docs
- Rust-side support is still project-dependent and requires real Cargo context
- the LSP does not yet implement formatting or code actions
- parsing has no error recovery, so a syntax error still yields exactly one diagnostic — the multi-error reporting above applies to semantic validation, where declarations are independent
- the VS Code extension prefers a bundled server binary when one is staged, but it does not yet auto-download release binaries
Rust Setup In VS Code
For Rust consumers of the CrateStack schema macros, userust-analyzer and point it at the workspace or workspaces that actually build the schema consumer.
Recommended workspace settings for this repo:
- this repo root is not a single Cargo workspace
- generated Rust APIs come from proc-macro expansion
- the generated
cratestack_schemamodule only exists when rust-analyzer can build the real consumer crate
.cstack Setup In VS Code
The intended path for .cstack files is the cratestack-vscode extension plus cratestack-lsp.
Local development flow:
- From
cratestack/, build the language server withcargo build -p cratestack-lsp. - From
cratestack/packages/cratestack-vscode, runpnpm installif needed. - Install or run the extension.
- If the server binary is not on
PATHand not bundled into the extension package, setcratestack.lsp.pathto the built binary.
cratestack.lsp.path: path to thecratestack-lspbinarycratestack.lsp.args: extra args passed through to the server
- configured
cratestack.lsp.path, if set to something other than the defaultcratestack-lsp - bundled binary under
server/<platform>/cratestack-lsp cratestack-lsponPATH
CLI Fallback And CI
For machine-readable schema validation outside the editor:- CI validation
- fallback editor integrations outside VS Code
- smoke-testing parser and semantic diagnostics without starting the LSP
Schema Docs And Generated Rust Docs
Schema-authored comments now serve both schema authors and Rust consumers. Supported today:- leading
///comments on declarations and fields /// @param name ...docs for procedure arguments- proc-macro emission of Rust
#[doc = "..."]attributes for generated models, fields, inputs, and procedure modules
.cstackauthors reading schemas- Rust users reading generated API docs and hovers
- future richer hover content in the
.cstacklanguage server
Packaging And Release Flow
The current extension packaging model is intentionally thin.cratestack-vscode contributes the language registration and launches cratestack-lsp; the heavy logic stays in the Rust binary.
Current release flow:
- Build the release server with
cargo build --release -p cratestack-lsp. - Stage the binary into
packages/cratestack-vscode/server/<platform>/withpnpm run stage-server. - Package the extension with
pnpm run package:vsix.
vsce --no-dependencies because the extension ships a small JavaScript wrapper plus the staged server binary rather than relying on npm dependency scanning to decide runtime contents.
Listing metadata is in place: alongside .vscodeignore, license, and repository, package.json declares an icon (packages/cratestack-vscode/icon.png, a 256×256 PNG) and a matching galleryBanner. Neither the Marketplace nor Open VSX requires an icon to accept a publish, so without one both listings — and the in-editor Extensions sidebar after a manual VSIX install — fall back to a generic placeholder. The field is platform-independent, so every per-target VSIX carries it without extra work.
Marketplace And Open VSX
The published extension’s identity, for anyone searching for it or scripting an install:
The display name is
CrateStack Schema, not CrateStack. The Marketplace rejects the shorter name
as already taken — independently of the extension ID, which was itself accepted. Nothing public holds
the shorter name: an extensionquery for CrateStack across the entire gallery, not just VS Code
extensions, returns zero results, so whatever reserves it is unlisted, removed, or internal. Gallery
search is therefore not a valid way to check a candidate display name in advance; only a real publish
attempt answers the question.
Open VSX has no such constraint and accepted CrateStack at v0.10.1, so the two registries genuinely
disagree about this name’s availability — v0.10.1 is live on Open VSX under the old display name.
Verifying a publish
The Marketplace item page lags a successful publish. After a publish succeeds, the listing page can return 404 for several minutes — in both publisher casings — while the extension is already fully published with every target platform. That is the same write-path/read-path split that makesnpm view
unreliable immediately after publishing, and it reads exactly like a failed publish.
Query the gallery API instead. It is consistent with the write path, and it returns targetPlatform
per version, so it also confirms every per-platform vsix landed — which the item page does not show
directly:
itemName is case-insensitive — both cratestack. and Cratestack. resolve. Worth knowing, because
the gallery API reports the publisher as Cratestack while package.json declares cratestack,
which is enough of a mismatch to look like a problem when it isn’t.
Probing a publish without cutting a release
release-vscode.yml normally runs on a vX.Y.Z tag push. It also accepts a manual
workflow_dispatch, which builds every target and publishes to the Marketplace only — the
attach-github-release and publish-openvsx jobs are gated if: github.event_name == 'push', so a
manual run can neither create a GitHub Release nor reach Open VSX.
That is a deliberate exception to the tag-push-only rule, narrowed so the rule still holds absolutely
for Open VSX (where a publish cannot be cleanly deleted and retried). It exists because several
Marketplace rejections are discoverable only at publish time, and some cost a version number to retry
— a displayName collision worst of all, since the field is baked into the vsix at package time and a
failed release is bumped past rather than re-run. A failed Marketplace publish consumes nothing: the
version is taken only on success, so probing the same version repeatedly is safe.
The trade is that a successful probe is a real publish, and can put a version on the Marketplace
whose artifact differs from the one attached to that tag’s GitHub Release — which is exactly what
happens when the probe is what fixed the artifact. Cosmetic, and resolved at the next real tag.
Verification In Repo
Covered today:- parser tests for docs, spans, and related regressions
- LSP tests for hover, definitions, symbols, and relation diagnostics
- extension package tests for server path resolution
- VS Code extension-host smoke tests for activation and bundled server launch
- Rust workspace tests for the underlying crates
Future Improvements
Highest-value follow-up work:- Add code actions for common relation mistakes, especially missing
fields/referencestargets and simple typo recovery. - Add formatting (
textDocument/formatting) for.cstackfiles. - Add stronger extension-host end-to-end tests that assert definition, hover, and diagnostics through the actual VS Code APIs.
- Extend the parser and semantic model toward the full target grammar described in the broader PRD and ADR docs.
- Add richer relation-aware validation so relation diagnostics can reason about more mismatches before code generation.
- Expose more stable editor-oriented library surfaces from parser and semantic crates instead of keeping some logic narrowly embedded in the current LSP layer.
- Improve multi-platform release packaging so extension artifacts can be produced and verified more systematically across supported targets.
- Add non-VS-Code editor integration paths using the standalone
cratestack-lspbinary.
- Formatting support for
.cstackonce the schema grammar and style expectations stabilize. - Auto-download or release-channel discovery for
cratestack-lspbinaries instead of requiring either a bundled server or manual path setup. - More workspace-aware Rust and schema cross-navigation if future architecture needs symbol links between generated Rust surfaces and original schema declarations.