Skip to content

Commit 89d3ea3

Browse files
committed
ansiline implementation, new ansi tailwind colors
1 parent 119b1ec commit 89d3ea3

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

frontend/app/element/ansiline.tsx

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
export const ANSI_TAILWIND_MAP = {
2+
// Reset and modifiers
3+
0: "reset", // special: clear state
4+
1: "font-bold",
5+
2: "opacity-75",
6+
3: "italic",
7+
4: "underline",
8+
8: "invisible",
9+
9: "line-through",
10+
11+
// Foreground standard colors
12+
30: "text-ansi-black",
13+
31: "text-ansi-red",
14+
32: "text-ansi-green",
15+
33: "text-ansi-yellow",
16+
34: "text-ansi-blue",
17+
35: "text-ansi-magenta",
18+
36: "text-ansi-cyan",
19+
37: "text-ansi-white",
20+
21+
// Foreground bright colors
22+
90: "text-ansi-brightblack",
23+
91: "text-ansi-brightred",
24+
92: "text-ansi-brightgreen",
25+
93: "text-ansi-brightyellow",
26+
94: "text-ansi-brightblue",
27+
95: "text-ansi-brightmagenta",
28+
96: "text-ansi-brightcyan",
29+
97: "text-ansi-brightwhite",
30+
31+
// Background standard colors
32+
40: "bg-ansi-black",
33+
41: "bg-ansi-red",
34+
42: "bg-ansi-green",
35+
43: "bg-ansi-yellow",
36+
44: "bg-ansi-blue",
37+
45: "bg-ansi-magenta",
38+
46: "bg-ansi-cyan",
39+
47: "bg-ansi-white",
40+
41+
// Background bright colors
42+
100: "bg-ansi-brightblack",
43+
101: "bg-ansi-brightred",
44+
102: "bg-ansi-brightgreen",
45+
103: "bg-ansi-brightyellow",
46+
104: "bg-ansi-brightblue",
47+
105: "bg-ansi-brightmagenta",
48+
106: "bg-ansi-brightcyan",
49+
107: "bg-ansi-brightwhite",
50+
};
51+
52+
type InternalStateType = {
53+
modifiers: Set<string>;
54+
textColor: string | null;
55+
bgColor: string | null;
56+
reverse: boolean;
57+
};
58+
59+
type SegmentType = {
60+
text: string;
61+
classes: string;
62+
};
63+
64+
const makeInitialState: () => InternalStateType = () => ({
65+
modifiers: new Set<string>(),
66+
textColor: null,
67+
bgColor: null,
68+
reverse: false,
69+
});
70+
71+
const updateStateWithCodes = (state, codes) => {
72+
codes.forEach((code) => {
73+
if (code === 0) {
74+
// Reset state
75+
state.modifiers.clear();
76+
state.textColor = null;
77+
state.bgColor = null;
78+
state.reverse = false;
79+
return;
80+
}
81+
// Instead of swapping immediately, we set a flag
82+
if (code === 7) {
83+
state.reverse = true;
84+
return;
85+
}
86+
const tailwindClass = ANSI_TAILWIND_MAP[code];
87+
if (tailwindClass && tailwindClass !== "reset") {
88+
if (tailwindClass.startsWith("text-")) {
89+
state.textColor = tailwindClass;
90+
} else if (tailwindClass.startsWith("bg-")) {
91+
state.bgColor = tailwindClass;
92+
} else {
93+
state.modifiers.add(tailwindClass);
94+
}
95+
}
96+
});
97+
return state;
98+
};
99+
100+
const stateToClasses = (state: InternalStateType) => {
101+
const classes = [];
102+
classes.push(...Array.from(state.modifiers));
103+
104+
// Apply reverse: swap text and background colors if flag is set.
105+
let textColor = state.textColor;
106+
let bgColor = state.bgColor;
107+
if (state.reverse) {
108+
[textColor, bgColor] = [bgColor, textColor];
109+
}
110+
if (textColor) classes.push(textColor);
111+
if (bgColor) classes.push(bgColor);
112+
113+
return classes.join(" ");
114+
};
115+
116+
const ansiRegex = /\x1b\[([0-9;]+)m/g;
117+
118+
const AnsiLine = ({ line }) => {
119+
const segments: SegmentType[] = [];
120+
let lastIndex = 0;
121+
let currentState = makeInitialState();
122+
123+
let match: RegExpExecArray;
124+
while ((match = ansiRegex.exec(line)) !== null) {
125+
if (match.index > lastIndex) {
126+
segments.push({
127+
text: line.substring(lastIndex, match.index),
128+
classes: stateToClasses(currentState),
129+
});
130+
}
131+
const codes = match[1].split(";").map(Number);
132+
updateStateWithCodes(currentState, codes);
133+
lastIndex = ansiRegex.lastIndex;
134+
}
135+
136+
if (lastIndex < line.length) {
137+
segments.push({
138+
text: line.substring(lastIndex),
139+
classes: stateToClasses(currentState),
140+
});
141+
}
142+
143+
return (
144+
<div>
145+
{segments.map((seg, idx) => (
146+
<span key={idx} className={seg.classes}>
147+
{seg.text}
148+
</span>
149+
))}
150+
</div>
151+
);
152+
};
153+
154+
export default AnsiLine;

frontend/tailwindsetup.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,22 @@
4141
--text-default: 14px;
4242

4343
--radius: 8px;
44+
45+
/* ANSI Colors (Default Dark Palette) */
46+
--ansi-black: #757575;
47+
--ansi-red: #cc685c;
48+
--ansi-green: #76c266;
49+
--ansi-yellow: #cbca9b;
50+
--ansi-blue: #85aacb;
51+
--ansi-magenta: #cc72ca;
52+
--ansi-cyan: #74a7cb;
53+
--ansi-white: #c1c1c1;
54+
--ansi-brightblack: #727272;
55+
--ansi-brightred: #cc9d97;
56+
--ansi-brightgreen: #a3dd97;
57+
--ansi-brightyellow: #cbcaaa;
58+
--ansi-brightblue: #9ab6cb;
59+
--ansi-brightmagenta: #cc8ecb;
60+
--ansi-brightcyan: #b7b8cb;
61+
--ansi-brightwhite: #f0f0f0;
4462
}

0 commit comments

Comments
 (0)