Skip to main content

Module translate

Module translate 

Source
Expand description

Translates a parsed RawGrammar into syntect’s own SyntaxDefinition.

§Why this goes through .sublime-syntax YAML text

The obvious approach — build syntect::parsing::syntax_definition::{ Context, MatchPattern, ContextReference, ...} values directly — turned out not to be possible from outside the syntect crate: syntax_definition.rs says “Everything here is public”, but ContextReference (and each of its variants) is #[non_exhaustive] with no public constructor function, so external code cannot construct a ContextReference::Named(..) or ::ByScope { .. } value at all (confirmed against syntect 5.3.0; see TODO.canvas.md, “Correction: ContextReference isn’t externally constructible”).

syntect’s own actually-public, stable construction path is syntect::parsing::SyntaxDefinition::load_from_str — the same entry point .sublime-syntax files go through. So this crate instead emits .sublime-syntax-shaped YAML (via yaml-rust, the exact YAML crate syntect itself parses with) and hands that text to syntect’s own loader, which builds the real tree for us. This is arguably a better fit for the project’s Goal item 3 anyway (“works offline: convert once, ship a generated .sublime-syntax”) — see to_sublime_syntax_yaml, which exposes that intermediate text directly, valid to save as a .sublime-syntax file as-is.

§Mapping from TextMate’s model to sublime-syntax’s

  • Top-level patterns become the main context (the conventional entry point syntect looks up by name).
  • Each repository entry becomes its own named context, addressable via include: "#name".
  • include: "$self" / "$base" become include: main; any other include string is treated as an external scope name (include: "scope:<name>").
  • A begin/end rule becomes a push match on begin into a freshly named body context, whose own pop: true match on end is ordered relative to the body’s own patterns per applyEndPatternLast (end first when absent/false — TextMate’s default — last when true). name/contentName become that body context’s meta_scope/meta_content_scope.

with_prototype is not part of the .tmLanguage.json spec at all (it’s a sublime-syntax-only concept for prototype injection without nesting under patterns) — nothing to translate.

begin/while rules are real — VS Code’s own markdown.tmLanguage.json uses them extensively (blockquotes, most fenced-code-block variants) — but syntect has no engine-level equivalent whatsoever: no YAML while: key, no MatchOperation variant for it, checked by grepping its source. A begin/while rule fails translation with Error::WhileNotSupported rather than being silently mistranslated; this means real-world grammars built around while (markdown chief among them) can’t be fully translated today. See TODO.canvas.md for the analysis (this was originally — wrongly — listed as “not part of the spec”; VS Code’s markdown grammar disproved that).

Injections (the grammar’s injections key, VS Code’s injectionSelector) genuinely aren’t handled either: structural embedding (a fixed-boundary begin/end region whose body includes another grammar’s scope) already works through the mapping above and is covered by a real end-to-end test, but true selector-based injection isn’t something syntect itself supports at all (checked by grepping its source for “injection”: zero matches) — see TODO.canvas.md for the full analysis.

\G anchors are rejected outright (Error::GAnchorNotSupported), checked on every match/begin/end regex: confirmed empirically (examples/g_anchor_never_matches.rs) that \G never matches at all through syntect’s onig backend, in any position, because syntect runs each pattern as an independent search rather than the iterative same-string session real \G semantics depend on. This isn’t a rare corner case — VS Code’s real shell-unix-bash.tmLanguage.json and html.tmLanguage.json both hit it in commonly-exercised paths (adjacent quoted command names; <script>/<style> content detection), so both fail translation entirely today. Silently letting \G through would be worse than rejecting it: the affected context just never closes, corrupting everything parsed afterward, with no error at all.

Capture indexes that syntect’s own YAML loader can’t line up with a “consuming” group in the regex (via its get_consuming_capture_indexes, backed by the regex-syntax crate rather than true Oniguruma semantics) are silently dropped by syntect itself, not by this crate.

Enums§

Error

Functions§

load
Parses and translates a .tmLanguage.json grammar into a syntect SyntaxDefinition in one step.
load_file
Reads and translates a .tmLanguage.json grammar from an arbitrary local file at runtime — the “load a custom grammar the user dropped in, no bundling required” case from the project’s Goal.
to_sublime_syntax_yaml
Renders grammar as .sublime-syntax-shaped YAML text — the same intermediate translate feeds to syntect’s own loader, exposed directly because it’s also valid to write out as a .sublime-syntax file for offline/build-time use.
translate