import type { AnsiTermColor, AnsiColorVal, AnsiRgb } from '@/types/grid'; import type { NumericRange } from '@/types/num'; import type { CSSProperties } from 'react'; const byteToAnsiValRatio = 255 / 5; export const ansiRgbToHex = ({ r, g, b }: AnsiRgb): string => { // Scale ANSI colors (0-5) to RGB range (0-255) const scaleToRgb = (val: number) => Math.round(val * byteToAnsiValRatio); const rHex = scaleToRgb(r).toString(16).padStart(2, '0'); const gHex = scaleToRgb(g).toString(16).padStart(2, '0'); const bHex = scaleToRgb(b).toString(16).padStart(2, '0'); return `#${rHex}${gHex}${bHex}`; }; export const hexToAnsi = (_hex: string): AnsiRgb => { const hex = _hex.split('#').at(-1)!; const rgb = [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map( (val) => parseInt(val, 16) || 0, ); const [r, g, b] = rgb.map( (value) => Math.floor( Math.min(value, 255) * (1 / byteToAnsiValRatio), ) as AnsiColorVal, ); return { r, g, b }; }; export const getAnsiColorEscape = (ansi: AnsiTermColor) => { const [fg, bg] = [ansi.foreground, ansi.background].map((color, i) => { if (color === null) return `\x1b[39;49m`; const { r, g, b } = color; const id = (16 + 36 * r + 6 * g + b) as NumericRange<16, 231>; return `\x1b[${3 + i}8;5;${id}m`; }); return { fg, bg }; }; export const getAnsiEscapeCodeFromDiff = ( currentColor: AnsiTermColor, newColor: AnsiTermColor, ): string => { const { fg: currentFg, bg: currentBg } = getAnsiColorEscape(currentColor); const { fg: newFg, bg: newBg } = getAnsiColorEscape(newColor); const fg = currentFg === newFg ? '' : newFg; const bg = currentBg === newBg ? '' : newBg; return `${fg}${bg}`; }; export const ansiColorsEqual = ( color1: AnsiTermColor, color2: AnsiTermColor, ): boolean => { // eh. return JSON.stringify(color1) === JSON.stringify(color2); }; export const getStyleForAnsiColor = (color: AnsiTermColor): CSSProperties => { const { background, foreground } = color; return { backgroundColor: background ? ansiRgbToHex(background) : undefined, color: foreground ? ansiRgbToHex(foreground) : undefined, }; }; export const parseAnsiColorCode = (code: number): AnsiRgb | null => { // Only handle 216 color cube (16-231) if (code < 16 || code > 231) return null; const adjusted = code - 16; const r = Math.floor(adjusted / 36) as AnsiColorVal; const g = Math.floor((adjusted % 36) / 6) as AnsiColorVal; const b = (adjusted % 6) as AnsiColorVal; return { r, g, b }; };