/** * @file terminalTheme.ts * @description The xterm colour theme for lane terminals. The terminal is * ALWAYS dark — Claude Code's TUI picks its own colours for a dark background, * so a light terminal would make its text unreadable — and is painted with the * palette of the DARK theme the user picked (`themeFor.dark`). Colours are read * from the CSS variables in `index.css` on a hidden probe carrying * `class="dark" data-theme=`, so no hex is duplicated here. * @author Nguyễn Ngọc Trí Vĩ */ import type { ITheme } from "@xterm/xterm"; /** Reads one CSS variable (an RGB triplet like `17 17 19`) by name. */ export type VarReader = (name: string) => string; function toHex(triplet: string): string | undefined { const parts = triplet .trim() .split(/[\s,]+/) .map(Number); if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) return undefined; return "#" + parts.map((n) => Math.round(n).toString(16).padStart(2, "0")).join(""); } /** Mix a hex colour toward white by `amount` (0..1) — the ANSI "bright" twin. */ function lighten(hex: string | undefined, amount: number): string | undefined { if (!hex) return undefined; const rgb = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)); return ( "#" + rgb .map((c) => Math.round(c + (255 - c) * amount) .toString(16) .padStart(2, "0") ) .join("") ); } /** * Build an xterm theme from a theme's CSS variables. red/green/yellow come from * `--status-*`, blue from `--accent`, black/white from the surface/fg ramp. * Magenta and cyan stay the classic ANSI hues: no token keeps that hue in every * theme (darcula's keyword is orange), and a program printing "magenta" should * get magenta. */ export function buildTerminalTheme(read: VarReader): ITheme { const v = (name: string) => toHex(read(name)); const red = v("--status-danger"); const green = v("--status-success"); const yellow = v("--status-warning"); const blue = v("--accent"); const magenta = "#c678dd"; const cyan = "#56b6c2"; return { // The page's colour: the console sits on the page, with no card or frame. background: v("--surface-0"), foreground: v("--fg-primary"), cursor: v("--fg-primary"), cursorAccent: v("--surface-0"), selectionBackground: v("--border-light"), black: v("--border-light"), red, green, yellow, blue, magenta, cyan, white: v("--fg-secondary"), brightBlack: v("--fg-muted"), brightRed: lighten(red, 0.25), brightGreen: lighten(green, 0.25), brightYellow: lighten(yellow, 0.25), brightBlue: lighten(blue, 0.25), brightMagenta: lighten(magenta, 0.25), brightCyan: lighten(cyan, 0.25), brightWhite: v("--fg-primary"), }; } /** Resolve the dark theme `id`'s variables on a hidden probe and build the theme. */ export function terminalThemeFor(id: string): ITheme { const probe = document.createElement("div"); probe.className = "dark"; if (id !== "default") probe.dataset.theme = id; probe.style.display = "none"; document.body.appendChild(probe); try { const style = getComputedStyle(probe); return buildTerminalTheme((name) => style.getPropertyValue(name)); } finally { probe.remove(); } }