summaryrefslogtreecommitdiff
path: root/src/utils/ansi.ts
blob: e57c075e022070296a02de423c70f652212ff764 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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 };
};