diff --git a/src/client/ansi.ts b/src/client/ansi.ts index bae5c38..8a770c2 100644 --- a/src/client/ansi.ts +++ b/src/client/ansi.ts @@ -1,26 +1,36 @@ -const COLORS: Record = { - 30: '#666', // black (brightened for dark bg) - 31: '#f87171', // red - 32: '#4ade80', // green - 33: '#facc15', // yellow - 34: '#60a5fa', // blue - 35: '#c084fc', // magenta - 36: '#22d3ee', // cyan - 37: '#e5e5e5', // white - 90: '#999', // bright black - 91: '#fca5a5', // bright red - 92: '#86efac', // bright green - 93: '#fde047', // bright yellow - 94: '#93c5fd', // bright blue - 95: '#d8b4fe', // bright magenta - 96: '#67e8f9', // bright cyan - 97: '#fff', // bright white +const ESC = /\x1b\[([0-9;]*)m/g + +const STYLES: Record = { + 1: 'font-weight:bold', + 2: 'opacity:0.7', + 30: 'color:#666', + 31: 'color:#f87171', + 32: 'color:#4ade80', + 33: 'color:#facc15', + 34: 'color:#60a5fa', + 35: 'color:#c084fc', + 36: 'color:#22d3ee', + 37: 'color:#e5e5e5', + 90: 'color:#999', + 91: 'color:#fca5a5', + 92: 'color:#86efac', + 93: 'color:#fde047', + 94: 'color:#93c5fd', + 95: 'color:#d8b4fe', + 96: 'color:#67e8f9', + 97: 'color:#fff', } +const escape = (s: string) => + s.replace(/&/g, '&').replace(//g, '>') + +export const stripAnsi = (s: string) => + s.replace(/\x1b\[[0-9;]*m/g, '') + export function ansiToHtml(text: string): string { if (!text.includes('\x1b')) return escape(text) - const ESC = /\x1b\[([0-9;]*)m/g + ESC.lastIndex = 0 let result = '' let last = 0 let open = false @@ -34,15 +44,16 @@ export function ansiToHtml(text: string): string { const styles: string[] = [] for (const code of codes) { - if (code === 0 || code === 39) { + if (code === 0) { styles.length = 0 if (open) { result += ''; open = false } - } else if (COLORS[code]) { - styles.push(`color:${COLORS[code]}`) - } else if (code === 1) { - styles.push('font-weight:bold') - } else if (code === 2) { - styles.push('opacity:0.7') + } else if (code === 39) { + const filtered = styles.filter(s => !s.startsWith('color:')) + styles.length = 0 + styles.push(...filtered) + if (open) { result += ''; open = false } + } else if (STYLES[code]) { + styles.push(STYLES[code]) } } @@ -58,6 +69,3 @@ export function ansiToHtml(text: string): string { return result } - -const escape = (s: string) => - s.replace(/&/g, '&').replace(//g, '>') diff --git a/src/client/components/UnifiedLogs.tsx b/src/client/components/UnifiedLogs.tsx index 930147b..be4b052 100644 --- a/src/client/components/UnifiedLogs.tsx +++ b/src/client/components/UnifiedLogs.tsx @@ -1,4 +1,4 @@ -import { ansiToHtml } from '../ansi' +import { ansiToHtml, stripAnsi } from '../ansi' import { isNarrow } from '../state' import { LogApp, @@ -59,7 +59,7 @@ function parseLogText(text: string): { method?: string, path?: string, status?: } function LogLineEntry({ log }: { log: UnifiedLogLine }) { - const parsed = parseLogText(log.text) + const parsed = parseLogText(stripAnsi(log.text)) const statusColor = getStatusColor(parsed.status) const narrow = isNarrow || undefined return (