diff options
| author | Taylor Mullen <[email protected]> | 2025-04-22 18:37:58 -0700 |
|---|---|---|
| committer | N. Taylor Mullen <[email protected]> | 2025-04-22 18:57:27 -0700 |
| commit | e163e024996ff8bb1152284322831c4f35696801 (patch) | |
| tree | e8799f87ba6b234136e56a1581ddd106962aa196 /packages/cli/src/ui/themes/theme.ts | |
| parent | ffe368afed853f43c9ab8851c1edc86160db8486 (diff) | |
Colorize code blocks.
- This changeset uses lowlight.js to parse the code in codeblocks to derive an AST, it then translates that into CSS themes that are widely known via highlight.js (things that GitHub use), finally I translate those css.color attributes into Ink colors and effectivel do <Text color={the color}>the text</Text>.
- To do this I needed to build color mappings from css -> Ink
- I introduced a new `Theme` type that will be used to represent many different color themes. It also enabled the color mappings to be seamless.
- Added a theme manager that only has one theme for now (VS2015). The theme works very well with our colorization.
- Some other bits was removal of borders around our codeblocks since they now have richer rendering.
- Most complex bits of code in this PR is in the `CodeColorizer.tsx`
Fixes https://b.corp.google.com/issues/412433479
Diffstat (limited to 'packages/cli/src/ui/themes/theme.ts')
| -rw-r--r-- | packages/cli/src/ui/themes/theme.ts | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/packages/cli/src/ui/themes/theme.ts b/packages/cli/src/ui/themes/theme.ts new file mode 100644 index 00000000..f7bd1cd0 --- /dev/null +++ b/packages/cli/src/ui/themes/theme.ts @@ -0,0 +1,278 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { CSSProperties } from 'react'; + +export class Theme { + /** + * The user-facing name of the theme. + */ + readonly name: string; + + /** + * 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<Record<string, string>>; + + // --- 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<Record<string, string>> = { + 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(name: string, rawMappings: Record<string, CSSProperties>) { + this.name = name; + 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<string, string>). + */ + protected _buildColorMap( + hljsTheme: Record<string, CSSProperties>, + ): Record<string, string> { + const inkTheme: Record<string, string> = {}; + 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; + } +} |
