/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type { CSSProperties } from 'react'; export type ThemeType = 'light' | 'dark' | 'ansi'; export interface ColorsTheme { type: ThemeType; Background: string; Foreground: string; LightBlue: string; AccentBlue: string; AccentPurple: string; AccentCyan: string; AccentGreen: string; AccentYellow: string; AccentRed: string; Comment: string; Gray: string; GradientColors?: string[]; } export const lightTheme: ColorsTheme = { type: 'light', Background: '#FAFAFA', Foreground: '#3C3C43', LightBlue: '#89BDCD', AccentBlue: '#3B82F6', AccentPurple: '#8B5CF6', AccentCyan: '#06B6D4', AccentGreen: '#3CA84B', AccentYellow: '#D5A40A', AccentRed: '#DD4C4C', Comment: '#008000', Gray: '#B7BECC', GradientColors: ['#4796E4', '#847ACE', '#C3677F'], }; export const darkTheme: ColorsTheme = { type: 'dark', Background: '#1E1E2E', Foreground: '#CDD6F4', LightBlue: '#ADD8E6', AccentBlue: '#89B4FA', AccentPurple: '#CBA6F7', AccentCyan: '#89DCEB', AccentGreen: '#A6E3A1', AccentYellow: '#F9E2AF', AccentRed: '#F38BA8', Comment: '#6C7086', Gray: '#6C7086', GradientColors: ['#4796E4', '#847ACE', '#C3677F'], }; export const ansiTheme: ColorsTheme = { type: 'ansi', Background: 'black', Foreground: 'white', LightBlue: 'blue', AccentBlue: 'blue', AccentPurple: 'magenta', AccentCyan: 'cyan', AccentGreen: 'green', AccentYellow: 'yellow', AccentRed: 'red', Comment: 'gray', Gray: 'gray', }; export class Theme { /** * The default foreground color for text when no specific highlight rule applies. * This is an Ink-compatible color string (hex or name). */ readonly defaultColor: string; /** * Stores the mapping from highlight.js class names (e.g., 'hljs-keyword') * to Ink-compatible color strings (hex or name). */ protected readonly _colorMap: Readonly>; // --- Static Helper Data --- // Mapping from common CSS color names (lowercase) to hex codes (lowercase) // Excludes names directly supported by Ink private static readonly cssNameToHexMap: Readonly> = { aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', blanchedalmond: '#ffebcd', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgrey: '#a9a9a9', darkgreen: '#006400', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkslategrey: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', fuchsia: '#ff00ff', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', greenyellow: '#adff2f', honeydew: '#f0fff0', hotpink: '#ff69b4', indianred: '#cd5c5c', indigo: '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', lavender: '#e6e6fa', lavenderblush: '#fff0f5', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrodyellow: '#fafad2', lightgray: '#d3d3d3', lightgrey: '#d3d3d3', lightgreen: '#90ee90', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', lime: '#00ff00', limegreen: '#32cd32', linen: '#faf0e6', maroon: '#800000', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370db', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', navy: '#000080', oldlace: '#fdf5e6', olive: '#808000', olivedrab: '#6b8e23', orange: '#ffa500', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#db7093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', purple: '#800080', rebeccapurple: '#663399', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', silver: '#c0c0c0', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', slategrey: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', teal: '#008080', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', whitesmoke: '#f5f5f5', yellowgreen: '#9acd32', }; // Define the set of Ink's named colors for quick lookup private static readonly inkSupportedNames = new Set([ 'black', 'red', 'green', 'yellow', 'blue', 'cyan', 'magenta', 'white', 'gray', 'grey', 'blackbright', 'redbright', 'greenbright', 'yellowbright', 'bluebright', 'cyanbright', 'magentabright', 'whitebright', ]); /** * Creates a new Theme instance. * @param name The name of the theme. * @param rawMappings The raw CSSProperties mappings from a react-syntax-highlighter theme object. */ constructor( readonly name: string, readonly type: ThemeType, rawMappings: Record, readonly colors: ColorsTheme, ) { this._colorMap = Object.freeze(this._buildColorMap(rawMappings)); // Build and freeze the map // Determine the default foreground color const rawDefaultColor = rawMappings['hljs']?.color; this.defaultColor = (rawDefaultColor ? Theme._resolveColor(rawDefaultColor) : undefined) ?? ''; // Default to empty string if not found or resolvable } /** * Gets the Ink-compatible color string for a given highlight.js class name. * @param hljsClass The highlight.js class name (e.g., 'hljs-keyword', 'hljs-string'). * @returns The corresponding Ink color string (hex or name) if it exists. */ getInkColor(hljsClass: string): string | undefined { return this._colorMap[hljsClass]; } /** * Resolves a CSS color value (name or hex) into an Ink-compatible color string. * @param colorValue The raw color string (e.g., 'blue', '#ff0000', 'darkkhaki'). * @returns An Ink-compatible color string (hex or name), or undefined if not resolvable. */ private static _resolveColor(colorValue: string): string | undefined { const lowerColor = colorValue.toLowerCase(); // 1. Check if it's already a hex code if (lowerColor.startsWith('#')) { return lowerColor; // Use hex directly } // 2. Check if it's an Ink supported name (lowercase) else if (Theme.inkSupportedNames.has(lowerColor)) { return lowerColor; // Use Ink name directly } // 3. Check if it's a known CSS name we can map to hex else if (Theme.cssNameToHexMap[lowerColor]) { return Theme.cssNameToHexMap[lowerColor]; // Use mapped hex } // 4. Could not resolve console.warn( `[Theme] Could not resolve color "${colorValue}" to an Ink-compatible format.`, ); return undefined; } /** * Builds the internal map from highlight.js class names to Ink-compatible color strings. * This method is protected and primarily intended for use by the constructor. * @param hljsTheme The raw CSSProperties mappings from a react-syntax-highlighter theme object. * @returns An Ink-compatible theme map (Record). */ protected _buildColorMap( hljsTheme: Record, ): Record { const inkTheme: Record = {}; for (const key in hljsTheme) { // Ensure the key starts with 'hljs-' or is 'hljs' for the base style if (!key.startsWith('hljs-') && key !== 'hljs') { continue; // Skip keys not related to highlighting classes } const style = hljsTheme[key]; if (style?.color) { const resolvedColor = Theme._resolveColor(style.color); if (resolvedColor !== undefined) { // Use the original key from the hljsTheme (e.g., 'hljs-keyword') inkTheme[key] = resolvedColor; } // If color is not resolvable, it's omitted from the map, // allowing fallback to the default foreground color. } // We currently only care about the 'color' property for Ink rendering. // Other properties like background, fontStyle, etc., are ignored. } return inkTheme; } }