From 7f31448ec5c65c5039752246d59816b498284521 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 8 Apr 2026 08:58:41 +0200 Subject: [PATCH 1/2] fix(ui): ansi colours --- packages/trace-viewer/src/ui/consoleTab.tsx | 27 ++++++++++++++----- packages/web/src/ansi2html.ts | 7 +++-- .../web/src/components/codeMirrorWrapper.tsx | 2 +- packages/web/src/components/errorMessage.tsx | 2 +- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/trace-viewer/src/ui/consoleTab.tsx b/packages/trace-viewer/src/ui/consoleTab.tsx index 1b88a15b956d5..3e35af381a21d 100644 --- a/packages/trace-viewer/src/ui/consoleTab.tsx +++ b/packages/trace-viewer/src/ui/consoleTab.tsx @@ -47,6 +47,17 @@ type ConsoleTabModel = { const ConsoleListView = ListView; +const ansiColours = { + log: { + bg: 'var(--vscode-editor-background)', fg: 'var(--vscode-editor-foreground)' + }, + warning: { + fg: 'var(--vscode-list-warningForeground)', bg: 'var(--vscode-inputValidation-warningBackground)' + }, + error: { + fg: 'var(--vscode-list-errorForeground)', bg: 'var(--vscode-inputValidation-errorBackground)' + } +}; export function useConsoleTabModel(model: TraceModel | undefined, selectedTime: Boundaries | undefined): ConsoleTabModel { const { entries } = React.useMemo(() => { @@ -76,7 +87,8 @@ export function useConsoleTabModel(model: TraceModel | undefined, selectedTime: }); for (const event of logEvents) { if (event.type === 'console') { - const body = event.args && event.args.length ? format(event.args) : formatAnsi(event.text); + const colours = event.messageType === 'error' ? ansiColours.error : event.messageType === 'warning' ? ansiColours.warning : ansiColours.log; + const body = event.args && event.args.length ? format(event.args, colours) : formatAnsi(event.text, colours); const url = event.location.url; const filename = url ? url.substring(url.lastIndexOf('/') + 1) : ''; const location = `${filename}:${event.location.lineNumber}`; @@ -102,10 +114,11 @@ export function useConsoleTabModel(model: TraceModel | undefined, selectedTime: } if (event.type === 'stderr' || event.type === 'stdout') { let html = ''; + const colours = event.type === 'stderr' ? ansiColours.error : ansiColours.log; if (event.text) - html = ansi2html(event.text.trim()) || ''; + html = ansi2html(event.text.trim(), colours) || ''; if (event.base64) - html = ansi2html(atob(event.base64).trim()) || ''; + html = ansi2html(atob(event.base64).trim(), colours) || ''; addEntry({ nodeMessage: { html }, @@ -188,9 +201,9 @@ export const ConsoleTab: React.FunctionComponent<{ ; }; -function format(args: { preview: string, value: any }[]): React.JSX.Element[] { +function format(args: { preview: string, value: any }[], colours: { fg: string, bg: string }): React.JSX.Element[] { if (args.length === 1) - return formatAnsi(args[0].preview); + return formatAnsi(args[0].preview, colours); const hasMessageFormat = typeof args[0].value === 'string' && args[0].value.includes('%'); const messageFormat = hasMessageFormat ? args[0].value as string : ''; @@ -237,9 +250,9 @@ function format(args: { preview: string, value: any }[]): React.JSX.Element[] { return formatted; } -function formatAnsi(text: string): React.JSX.Element[] { +function formatAnsi(text: string, colours: { fg: string, bg: string }): React.JSX.Element[] { // eslint-disable-next-line react/jsx-key - return []; + return []; } function parseCSSStyle(cssFormat: string): Record { diff --git a/packages/web/src/ansi2html.ts b/packages/web/src/ansi2html.ts index 827e081ae3922..3f684bcc53654 100644 --- a/packages/web/src/ansi2html.ts +++ b/packages/web/src/ansi2html.ts @@ -14,7 +14,7 @@ limitations under the License. */ -export function ansi2html(text: string, defaultColors?: { bg: string, fg: string }): string { +export function ansi2html(text: string, defaultColors: { bg: string, fg: string }): string { const regex = /(\x1b\[(\d+(;\d+)*)m)|([^\x1b]+)/g; const tokens: string[] = []; let match; @@ -109,9 +109,8 @@ export function ansi2html(text: string, defaultColors?: { bg: string, fg: string const color = reverse ? bg : fg; if (color !== undefined) styleCopy['color'] = color; - const backgroundColor = reverse ? fg : bg; - if (backgroundColor !== undefined) - styleCopy['background-color'] = backgroundColor; + if (reverse && fg) + styleCopy['background-color'] = fg; tokens.push(`${escapeHTML(text)}`); } } diff --git a/packages/web/src/components/codeMirrorWrapper.tsx b/packages/web/src/components/codeMirrorWrapper.tsx index 0a8246e1762f0..385a42b60316f 100644 --- a/packages/web/src/components/codeMirrorWrapper.tsx +++ b/packages/web/src/components/codeMirrorWrapper.tsx @@ -170,7 +170,7 @@ export const CodeMirrorWrapper: React.FC = ({ if (h.type === 'error') { const errorWidgetElement = document.createElement('div'); - errorWidgetElement.innerHTML = ansi2html(h.message || ''); + errorWidgetElement.innerHTML = ansi2html(h.message || '', { bg: 'var(--vscode-inputValidation-errorBackground)', fg: 'var(--vscode-editor-foreground)' }); errorWidgetElement.className = 'source-line-error-widget'; widgets.push(codemirror.addLineWidget(h.line, errorWidgetElement, { above: true, coverGutter: false })); } diff --git a/packages/web/src/components/errorMessage.tsx b/packages/web/src/components/errorMessage.tsx index a37f28e2ec2dd..907e9b7ea510e 100644 --- a/packages/web/src/components/errorMessage.tsx +++ b/packages/web/src/components/errorMessage.tsx @@ -21,6 +21,6 @@ import './errorMessage.css'; export const ErrorMessage: React.FC<{ error: string; }> = ({ error }) => { - const html = React.useMemo(() => ansi2html(error), [error]); + const html = React.useMemo(() => ansi2html(error, { bg: 'var(--vscode-editor-background)', fg: 'var(--vscode-editor-foreground)' }), [error]); return
; }; From 1c47bb089d6b032bcc7118ccb6de07dd317e5e55 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 8 Apr 2026 10:11:49 +0200 Subject: [PATCH 2/2] test --- tests/playwright-test/reporter-html.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 01c31d508e6d9..164856c59d6f0 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -1318,7 +1318,6 @@ for (const useIntermediateMergeReport of [true, false] as const) { await expect(page.locator('.test-error-view').getByText('begin ', { exact: true })).toHaveCSS('background-color', 'rgb(205, 49, 49)'); await expect(page.locator('.test-error-view').getByText('inner', { exact: true })).toHaveCSS('color', 'rgb(205, 49, 49)'); - await expect(page.locator('.test-error-view').getByText('inner', { exact: true })).toHaveCSS('background-color', 'rgb(246, 248, 250)'); await expect(page.locator('.test-error-view').getByText('end ', { exact: true })).toHaveCSS('color', 'rgb(246, 248, 250)'); await expect(page.locator('.test-error-view').getByText('end ', { exact: true })).toHaveCSS('background-color', 'rgb(205, 49, 49)');