style
js/tty/style.ts
fino:tty/style — terminal text appearance as data.
A Style is a fully-resolved cell appearance: colors and attributes, with
every absent field meaning "terminal default". Styles are plain data, never
escape sequences — SGR bytes exist only at the wire edge, produced by
styleToSgr() as a minimal transition between two styles.
Styles are interned: internStyle() returns a canonical object per distinct
appearance, so equality on interned styles is a pointer compare. The layout
and paint pipeline in fino:tty/tui relies on this when compacting adjacent
cells into styled runs.
SGR encoding and decoding implement the color and text-attribute subset of ECMA-48 used by modern terminals. Other control functions remain outside this style data model.
import { internStyle, styleToSgr, EMPTY_STYLE } from 'fino:tty/style';
const accent = internStyle({ fg: 'cyan', bold: true });
const open = styleToSgr(EMPTY_STYLE, accent); // '\x1b[1;36m'
const close = styleToSgr(accent, EMPTY_STYLE); // '\x1b[0m'Types
type NamedColor =
| 'black'
| 'red'
| 'green'
| 'yellow'
| 'blue'
| 'magenta'
| 'cyan'
| 'white'
| 'brightBlack'
| 'brightRed'
| 'brightGreen'
| 'brightYellow'
| 'brightBlue'
| 'brightMagenta'
| 'brightCyan'
| 'brightWhite'
| 'default'
The sixteen standard palette colors plus default, the terminal's own
foreground or background.
type Color =
| NamedColor
| { readonly ansi256: number }
| { readonly rgb: readonly [number, number, number] }
A terminal color: a named palette entry, a 256-color index, or a truecolor triple.
Interfaces
interface Style {
A fully-resolved cell appearance. Absent fields mean "terminal default";
an explicit false means the attribute is off (which matters when merging).
Readonly Properties
readonly fg?: Color
readonly bg?: Color
readonly bold?: boolean
readonly dim?: boolean
readonly italic?: boolean
readonly underline?: boolean
readonly inverse?: boolean
readonly strike?: boolean
Constants
const EMPTY_STYLE: Style
The default appearance: every field absent.
Functions
function internStyle(style: Style): Style
Return the canonical object for a style, so that two styles describing the
same appearance are ===. Attributes set to false normalize to absent.
The intern table is bounded; a program generating unbounded distinct styles (say, per-pixel truecolor) resets it rather than growing without limit, which only costs the pointer-equality fast path, never correctness.
function styleEquals(a: Style, b: Style): boolean
Whether two styles describe the same appearance.
function colorEquals(a: Color | undefined, b: Color | undefined): boolean
Whether two colors are the same.
function mergeStyle(under: Style, over: Style): Style
Layer over on top of under: absent fields inherit, explicit false
turns an attribute off, and a set color replaces. Returns an interned style.
function styleToSgr(from: Style, to: Style): string
The minimal SGR sequence that changes a cell painted in from to paint in
to. Returns '' when the styles are equal. Transitioning to the default
appearance emits a bare reset.
function supportsTruecolor(colorterm: string | undefined): boolean
Whether a COLORTERM value declares 24-bit RGB support.
Keeping detection pure lets callers pass an injected environment value and reuse the same policy without coupling rendering code to process state.
import { supportsTruecolor } from 'fino:tty/style';
supportsTruecolor('truecolor'); // true
supportsTruecolor(undefined); // falsefunction nearestAnsi256(r: number, g: number, b: number): number
Return the nearest xterm 256-color palette index for an RGB color.
Both the 6×6×6 color cube and grayscale ramp are considered. Centralizing this fallback avoids each color-producing component implementing its own approximation.
import { nearestAnsi256 } from 'fino:tty/style';
nearestAnsi256(255, 0, 0); // 196
nearestAnsi256(128, 128, 128); // 244function applySgr(style: Style, params: readonly number[]): Style
Apply one SGR parameter list (the numbers of a CSI ... m sequence) to a
style, returning the interned result. Unknown parameters are ignored.