mystral

A fast, index-driven Crystal language server.

Mystral

CI License: MIT Crystal

A Crystal language server that stays out of your way.

Mystral is an experiment in how far the parser alone can take an editor. Hover, go-to, completion and friends answer from a parser-built index — no compiler on the request path, so they're instant (sub-millisecond). Types are AST-shaped: exact where the syntax spells them out (x = Foo.new, a param's : T, a declared ivar), best-effort otherwise, and nothing rather than a guess. The compiler runs only in the background — for diagnostics and to reap richer facts — never on a keystroke.

How fast

Measured on real projects (release build, warm index — what a hover actually hits):

project files lines indexes in hover p50 / p99
kemal 72 7.2k 12 ms 0.10 / 0.30 ms
lune 166 19.5k 45 ms 0.03 / 0.10 ms
mint 754 34k 62 ms 0.06 / 0.90 ms

The one slow thing — a full crystal build --no-codegen (~3 s) — runs debounced in a subprocess and is skipped when reachable content hasn't changed. Memory is ~60–70 MB resident, almost all the symbol index (workspace + stdlib + shards), bounded — light for an LSP. (Developed on a fanless 2020 M1 Air, 8 GB.)

What works

  • Hover, go-to-definition, completion, references, signature help, document/workspace symbols, document highlight, formatting.
  • Hover shows doc comments, param types, instance/class vars, block args, locals, and annotations (@[JSON::Field], …).
  • Indexes your workspace plus everything on crystal env CRYSTAL_PATH (stdlib, lib/ shards) at startup — hovering into a dependency is as fast as your own code.
  • Platform-split files: the require-graph walk evaluates flag?(:darwin) against the host, so stdlib's 23 per-platform LibC.open signatures collapse to yours.

Not yet: rename, inlay hints, code actions.

Diagnostics

  • Syntax — instant, on every keystroke, no shellout.
  • Semantic — on a settled edit (and at startup), a debounced crystal build --no-codegen subprocess catches undefined methods, type mismatches, wrong arity. Content-hash cached (an unchanged program is never recompiled); one compile refreshes every open file. Never a stale or false squiggle — if requires don't resolve (deps missing), you get a red line on the offending shard.yml dependency and a "run shards install?" toast, not noise on require lines.

A different bet

The established tools — crystalline and vscode-crystal-lang — drive navigation through the real compiler. That's the exact, correct path; Mystral isn't trying to replace it. Mystral bets that most editing doesn't need type inference, and keeps the compiler off the request path for parser-speed answers.

The cost, named honestly — what genuinely needs the type system, Mystral approximates or sits out:

  • generic instantiation and is_a? union narrowing,
  • cross-include / cross-module dispatch by type,
  • macro expansion across files.

Shrinking that list is the work. A background side-index reaps crystal tool output off the hot path — content-hash keyed, served on the next hover at parser speed. Two slices are in: inheritance through a generic superclass (class B < A(Int32)) resolves via reaped type hierarchy, and an untyped local (arr = [Foo.new]) fills in from an on-demand crystal tool context run.

Mystral compiler-backed
Approach AST + parser index real compiler, per request
Type accuracy AST-shaped exact
Hover (warm) sub-millisecond seconds
Hover detail + docs, params, ivars, block args, locals, annotations compiler types

Install

make deploy   # specs + release build + install to /usr/local/bin/mystral

(macOS: the Makefile rm -fs before cp — adhoc-signed binaries get SIGKILLed if overwritten in place; don't swap it for a plain cp.)

  • VSCodecd editors/vscode && npm install && npx vsce package && code --install-extension mystral-vscode-*.vsix. Prefers /usr/local/bin/mystral, then $PATH. Logs at /tmp/mystral.log (MYSTRAL_DEBUG=1 for verbose).
  • Helix / Neovim — merge editors/helix/languages.toml or drop in editors/neovim/mystral.lua. Any editor with a generic LSP client works — mystral with no subcommand serves over stdio.
  • CLImystral check FILE.cr prints every symbol the indexer sees.

Tests: make test (unit) and make test-integration (shells out to crystal).

Acknowledgements

The VSCode extension ships the Crystal TextMate grammar from vscode-crystal-lang (license). Thanks to it and crystalline for charting the compiler-backed path.

License

MIT — see LICENSE.

Repository

mystral

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 1
  • about 2 hours ago
  • May 28, 2026
License

MIT License

Links
Synced at

Thu, 28 May 2026 19:22:33 GMT

Languages