Colors in logs

This commit is contained in:
2026-01-03 13:06:16 +01:00
parent 5e84c05bc7
commit 7e5289ae22
4 changed files with 138 additions and 2 deletions
+50
View File
@@ -0,0 +1,50 @@
import { tokenizeAnsi } from "./tokenize";
type AnsiTextProps = {
text: string;
className?: string;
};
const ANSI_TW_CLASSES: Record<string, string> = {
black: "text-black",
red: "text-red-500",
green: "text-emerald-500",
yellow: "text-yellow-400",
blue: "text-blue-400",
magenta: "text-purple-400",
cyan: "text-cyan-400",
white: "text-gray-100",
brightBlack: "text-gray-500",
brightRed: "text-red-400",
brightGreen: "text-emerald-400",
brightYellow: "text-yellow-300",
brightBlue: "text-blue-300",
brightMagenta: "text-purple-300",
brightCyan: "text-cyan-300",
brightWhite: "text-white",
};
export function AnsiText({ text, className }: AnsiTextProps) {
const tokens = tokenizeAnsi(text);
return (
<span className={`whitespace-pre-wrap font-mono ${className ?? ""}`}>
{tokens.map((token, i) => {
const classes = [
token.style.color && ANSI_TW_CLASSES[token.style.color],
token.style.bold && "font-semibold",
token.style.underline && "underline",
]
.filter(Boolean)
.join(" ");
return (
<span key={i} className={classes}>
{token.text}
</span>
);
})}
</span>
);
}
+45
View File
@@ -0,0 +1,45 @@
export type StyleState = {
color?: string;
bold?: boolean;
underline?: boolean;
};
export const ANSI_REGEX = /\u001b\[([0-9;]*)m/g;
const COLOR_CODES: Record<number, string> = {
30: "black",
31: "red",
32: "green",
33: "yellow",
34: "blue",
35: "magenta",
36: "cyan",
37: "white",
90: "brightBlack",
91: "brightRed",
92: "brightGreen",
93: "brightYellow",
94: "brightBlue",
95: "brightMagenta",
96: "brightCyan",
97: "brightWhite",
};
export function applyAnsiCodes(codes: number[], state: StyleState): StyleState {
let next = { ...state };
for (const code of codes) {
if (code === 0) {
next = {};
} else if (code === 1) {
next.bold = true;
} else if (code === 4) {
next.underline = true;
} else if (COLOR_CODES[code]) {
next.color = COLOR_CODES[code];
}
}
return next;
}
+39
View File
@@ -0,0 +1,39 @@
import { ANSI_REGEX, applyAnsiCodes, StyleState } from "./parser";
export type Token = {
text: string;
style: StyleState;
};
export function tokenizeAnsi(input: string): Token[] {
const tokens: Token[] = [];
let lastIndex = 0;
let match: RegExpExecArray | null;
let style: StyleState = {};
while ((match = ANSI_REGEX.exec(input))) {
const index = match.index;
if (index > lastIndex) {
tokens.push({
text: input.slice(lastIndex, index),
style,
});
}
const codes = match[1].split(";").map(Number);
style = applyAnsiCodes(codes, style);
lastIndex = ANSI_REGEX.lastIndex;
}
if (lastIndex < input.length) {
tokens.push({
text: input.slice(lastIndex),
style,
});
}
return tokens;
}