interface Theme

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

Methods

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 their QUNITX_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 — .imported asks 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.

Usage

import { type Theme } from "lib/repl/theme.ts";