Skip to content

Reference — the core

The engine-agnostic middle of sparkles:syntax: what engines produce, what themes resolve, and what renderers consume. Everything here is pure D over sparkles:base; nothing depends on tree-sitter.

Events (sparkles.syntax.event)

SymbolWhat it is
LabelIdushort index into a LabelSet; LabelId.none = unlabeled
HighlightEventflat POD: kind ∈ {source, push, pop}, byte offsets start/end (source), label (push)
isHighlightEventRange!Rthe concept both engines target and both renderers accept
StyledSpana maximal run with one resolved label (innermost wins)
byStyledSpan(events)lazy flatten of the event stream into StyledSpans

The stream contract: events are ordered; source ranges ascend without overlapping; push/pop are balanced and never split a source span; the stream is infallible — engine errors surface around the stream, never in it, so renderers are total.

byStyledSpan, StyledSpan, and ResolvedTheme are the third-backend contract: a consumer that wants styled runs as data (a GPU text renderer, a paginator) folds these instead of parsing markup.

Labels (sparkles.syntax.label)

SymbolWhat it is
standardLabelsthe canonical vocabulary: 72 dotted names, sorted
LabelSet.standard()wraps standardLabels, allocation-free
LabelSet.fromNames(names)custom vocabulary (sorted + deduplicated)
find(name)exact lookup, binary search
resolve(name)longest-dot-prefix: function.builtin.staticfunction.builtinfunction

Resolution is Helix's rule, deliberately not the reference crate's part-subset match — one predictable algorithm shared by capture names and theme selectors. The whole configure-time path is CTFE-able.

Colors (sparkles.syntax.color)

SymbolWhat it is
Colorsum type: unset / default_ / palette(index) / rgb
parseHexColor#RGB, #RRGGBB, #RRGGBBAA (bat's alpha conventions: 00 = palette index, 01 = terminal default)
ColorDepthnone / ansi16 / ansi256 / trueColor (re-exported from sparkles.base.term_color)
ansi256FromRgb / ansi16FromRgbnearest-match tier folds (6×6×6 cube + gray ramp; xterm classic 16)
xterm256ToRgbpalette index → RGB (the concretization step for non-terminal backends)
classifyColorDepth(colorterm, term)pure tier classifier (in sparkles.base.term_color, re-exported); detectColorDepth() reads the environment

Themes (sparkles.syntax.theme, .themes)

SymbolWhat it is
FontStylebackend-neutral flags: bold, dim, italic, underline, strikethrough
StyleSpec{ fg, bg, font }; Color.Kind.unset = unspecified
Themeplain data: name, defaults, ordered ThemeRule[] (selectorStyleSpec)
resolveTheme(theme, labels)folds rules into a flat ResolvedTheme table — longest-dot-prefix, whole spec wins, last rule wins among equals
ResolvedThemelabelId → StyleSpec in O(1); theme[LabelId.none] = the defaults
builtinDark / builtinLightCatppuccin-Mocha- / Solarized-Light-derived data themes

Theme files (TOML/JSON) are a recorded seam: only a parser producing ThemeRule[] is missing.

Renderers (sparkles.syntax.render.*)

Both are attribute-inferring template folds over any char output range — @safe pure nothrow @nogc given @nogc inputs — and both guarantee per-line validity: at every newline, active styling closes and re-opens, so each output line stands alone.

ANSI — renderAnsi(source, events, theme, writer, AnsiOptions)

  • AnsiOptions: depth (default ansi256; none = verbatim passthrough), emitBackground (default off — respect the terminal), italics (default off — bat's defensive gate).
  • Minimal SGR diffs between adjacent runs (writeStyleTransition); palette colors stay palette-native so the user's terminal scheme applies.

HTML — renderHtml(source, events, theme, writer, HtmlOptions)

  • HtmlOptions.mode: inlineStyles (self-contained style="…") or cssClasses (class="syn-…", dots → dashes); classPrefix default "syn-".
  • writeThemeStylesheet(theme, writer, prefix) emits one rule per styled label plus a syn-root rule for the theme defaults.
  • Source text is escaped via sparkles.base.text.html.writeHtmlEscaped.
  • Output is content-only — wrap it in <pre><code> yourself.

See also