A palette, keyed by the capture names nvim's treesitter highlighting uses.
@keyword.return, @string.escape, @punctuation.bracket — the same names, so a theme can be
transcribed from an nvim config without translating anything, and so the fallback works the way
nvim's does: a capture with no style of its own inherits its parent's, @keyword.return from
@keyword, all the way up.
import { theme } from './theme.ts'; const palette = theme(); palette.painter('@keyword.return') === palette.painter('@keyword'); // true — inherited palette.painter('@variable')('x'); // 'x' — no style is a style: the terminal's own colour
painter(capture: string): (text: string) => string
The function that paints text in a capture's colour — kleur's shape, one function per colour.
palette.painter('@string')('hi') rather than a prefix you then have to wrap text in
yourself. It used to hand back the raw SGR sequence, and every single caller did the same
thing with it, which is the definition of an implementation detail that leaked.
Two colour vocabularies live in this codebase and this is the themed one:
red('No such frame')—lib/utils/color.ts, a FIXED colour chosen at the call site. The tool speaking in its own voice: errors, warnings. Not themeable, because an error is red in every terminal and nobody wants to configure that.palette.painter('@function')(name)— a colour chosen by the DEVELOPER in theirQUNITX_REPL_THEME. The page's own content shown back: a value, a path, an identifier.red()cannot do this job, because the capture arrives as DATA —.importedasks the page what kind each export is and gets back@function,@string,@type, and there is no fixed set of functions to name when the set is whatever somebody wrote in an env var.
An unstyled capture returns the identity function, so it costs no bytes: half the default
theme is deliberately unstyled, and a piped or NO_COLOR session makes all of it so. What a
painter writes comes back out through plain, plainLength and truncate, which is how a
line laid out in columns is measured without its escapes.
The same function comes back for the same capture, so the highlighter can ask on every keystroke without allocating.