diff --git a/package-lock.json b/package-lock.json
index 1c01f7dc6e..77356e2d6d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6624,7 +6624,6 @@
"packages/web": {
"version": "0.0.0",
"dependencies": {
- "ansi-to-html": "^0.7.2",
"codemirror": "^5.65.9",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0"
@@ -10630,7 +10629,6 @@
"web": {
"version": "file:packages/web",
"requires": {
- "ansi-to-html": "^0.7.2",
"codemirror": "^5.65.9",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0"
diff --git a/packages/trace-viewer/src/ui/consoleTab.tsx b/packages/trace-viewer/src/ui/consoleTab.tsx
index 22b96d83cf..7ec3238197 100644
--- a/packages/trace-viewer/src/ui/consoleTab.tsx
+++ b/packages/trace-viewer/src/ui/consoleTab.tsx
@@ -19,9 +19,9 @@ import * as React from 'react';
import './consoleTab.css';
import * as modelUtil from './modelUtil';
import { ListView } from '@web/components/listView';
-import { ansi2htmlMarkup } from '@web/components/errorMessage';
import type { Boundaries } from '../geometry';
import { msToString } from '@web/uiUtils';
+import { ansi2html } from '@web/ansi2html';
import type * as trace from '@trace/trace';
type ConsoleEntry = {
@@ -124,10 +124,10 @@ export const ConsoleTab: React.FunctionComponent<{
}
if (nodeMessage?.text)
- messageInnerHTML = ansi2htmlMarkup(nodeMessage.text.trim()) || '';
+ messageInnerHTML = ansi2html(nodeMessage.text.trim()) || '';
if (nodeMessage?.base64)
- messageInnerHTML = ansi2htmlMarkup(atob(nodeMessage.base64).trim()) || '';
+ messageInnerHTML = ansi2html(atob(nodeMessage.base64).trim()) || '';
return
{timestampElement}
diff --git a/packages/web/package.json b/packages/web/package.json
index fd68fcb78d..2b21846455 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -4,7 +4,6 @@
"version": "0.0.0",
"scripts": {},
"dependencies": {
- "ansi-to-html": "^0.7.2",
"codemirror": "^5.65.9",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0"
diff --git a/packages/web/src/ansi2html.ts b/packages/web/src/ansi2html.ts
new file mode 100644
index 0000000000..210d1210bc
--- /dev/null
+++ b/packages/web/src/ansi2html.ts
@@ -0,0 +1,107 @@
+/*
+ Copyright (c) Microsoft Corporation.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+export function ansi2html(text: string): string {
+ const regex = /(\x1b\[(\d+(;\d+)*)m)|([^\x1b]+)/g;
+ const tokens: string[] = [];
+ let match;
+ let style: any = {};
+ while ((match = regex.exec(text)) !== null) {
+ const [, , codeStr, , text] = match;
+ if (codeStr) {
+ const code = +codeStr;
+ switch (code) {
+ case 0: style = {}; break;
+ case 1: style['font-weight'] = 'bold'; break;
+ case 3: style['font-style'] = 'italic'; break;
+ case 4: style['text-decoration'] = 'underline'; break;
+ case 8: style.display = 'none'; break;
+ case 9: style['text-decoration'] = 'line-through'; break;
+ case 22: style = { ...style, 'font-weight': undefined, 'font-style': undefined, 'text-decoration': undefined }; break;
+ case 23: style = { ...style, 'font-weight': undefined, 'font-style': undefined }; break;
+ case 24: style = { ...style, 'text-decoration': undefined }; break;
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37: style.color = ansiColors[code - 30]; break;
+ case 39: style = { ...style, color: undefined }; break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47: style['background-color'] = ansiColors[code - 40]; break;
+ case 49: style = { ...style, 'background-color': undefined }; break;
+ case 53: style['text-decoration'] = 'overline'; break;
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96:
+ case 97: style.color = brightAnsiColors[code - 90]; break;
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ case 106:
+ case 107: style['background-color'] = brightAnsiColors[code - 100]; break;
+ }
+ } else if (text) {
+ tokens.push(`
${escapeHTML(text)}`);
+ }
+ }
+ return tokens.join('');
+}
+
+const ansiColors: Record
= {
+ 0: 'var(--vscode-terminal-ansiBlack)',
+ 1: 'var(--vscode-terminal-ansiRed)',
+ 2: 'var(--vscode-terminal-ansiGreen)',
+ 3: 'var(--vscode-terminal-ansiYellow)',
+ 4: 'var(--vscode-terminal-ansiBlue)',
+ 5: 'var(--vscode-terminal-ansiMagenta)',
+ 6: 'var(--vscode-terminal-ansiCyan)',
+ 7: 'var(--vscode-terminal-ansiWhite)',
+};
+
+const brightAnsiColors: Record = {
+ 0: 'var(--vscode-terminal-ansiBrightBlack)',
+ 1: 'var(--vscode-terminal-ansiBrightRed)',
+ 2: 'var(--vscode-terminal-ansiBrightGreen)',
+ 3: 'var(--vscode-terminal-ansiBrightYellow)',
+ 4: 'var(--vscode-terminal-ansiBrightBlue)',
+ 5: 'var(--vscode-terminal-ansiBrightMagenta)',
+ 6: 'var(--vscode-terminal-ansiBrightCyan)',
+ 7: 'var(--vscode-terminal-ansiBrightWhite)',
+};
+
+function escapeHTML(text: string): string {
+ return text.replace(/[&"<>]/g, c => ({ '&': '&', '"': '"', '<': '<', '>': '>' }[c]!));
+}
+
+function styleBody(style: any): string {
+ return Object.entries(style).map(([name, value]) => `${name}: ${value}`).join('; ');
+}
diff --git a/packages/web/src/components/DEPS.list b/packages/web/src/components/DEPS.list
index 69cd00dc55..42e031fcc4 100644
--- a/packages/web/src/components/DEPS.list
+++ b/packages/web/src/components/DEPS.list
@@ -2,6 +2,7 @@
../theme.ts
../third_party/vscode/codicon.css
../uiUtils.ts
+../ansi2html.ts
[expandable.spec.tsx]
***
diff --git a/packages/web/src/components/codeMirrorWrapper.tsx b/packages/web/src/components/codeMirrorWrapper.tsx
index 1f8e0c0ac0..3f64a1d944 100644
--- a/packages/web/src/components/codeMirrorWrapper.tsx
+++ b/packages/web/src/components/codeMirrorWrapper.tsx
@@ -17,7 +17,7 @@
import './codeMirrorWrapper.css';
import * as React from 'react';
import type { CodeMirror } from './codeMirrorModule';
-import { ansi2htmlMarkup } from './errorMessage';
+import { ansi2html } from '../ansi2html';
import { useMeasure } from '../uiUtils';
export type SourceHighlight = {
@@ -152,7 +152,7 @@ export const CodeMirrorWrapper: React.FC = ({
}
const errorWidgetElement = document.createElement('div');
- errorWidgetElement.innerHTML = ansi2htmlMarkup(h.message || '');
+ errorWidgetElement.innerHTML = ansi2html(h.message || '');
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 b2d0452c59..c9f4500ece 100644
--- a/packages/web/src/components/errorMessage.tsx
+++ b/packages/web/src/components/errorMessage.tsx
@@ -14,45 +14,13 @@
* limitations under the License.
*/
-import ansi2html from 'ansi-to-html';
+import { ansi2html } from '@web/ansi2html';
import * as React from 'react';
import './errorMessage.css';
export const ErrorMessage: React.FC<{
error: string;
}> = ({ error }) => {
- const html = React.useMemo(() => ansi2htmlMarkup(error), [error]);
+ const html = React.useMemo(() => ansi2html(error), [error]);
return ;
};
-
-export function ansi2htmlMarkup(text: string) {
- const config: any = {
- bg: 'var(--vscode-panel-background)',
- fg: 'var(--vscode-foreground)',
- };
- config.colors = ansiColors;
- return new ansi2html(config).toHtml(escapeHTML(text));
-}
-
-const ansiColors = {
- 0: '#000',
- 1: '#C00',
- 2: '#0C0',
- 3: '#C50',
- 4: '#00C',
- 5: '#C0C',
- 6: '#0CC',
- 7: '#CCC',
- 8: '#555',
- 9: '#F55',
- 10: '#5F5',
- 11: '#FF5',
- 12: '#55F',
- 13: '#F5F',
- 14: '#5FF',
- 15: '#FFF'
-};
-
-function escapeHTML(text: string): string {
- return text.replace(/[&"<>]/g, c => ({ '&': '&', '"': '"', '<': '<', '>': '>' }[c]!));
-}
diff --git a/tests/playwright-test/ui-mode-test-output.spec.ts b/tests/playwright-test/ui-mode-test-output.spec.ts
index 436c849a6d..d82e3b8f40 100644
--- a/tests/playwright-test/ui-mode-test-output.spec.ts
+++ b/tests/playwright-test/ui-mode-test-output.spec.ts
@@ -108,8 +108,8 @@ test('should show console messages for test', async ({ runUITest }, testInfo) =>
'codicon codicon-file status-none',
]);
- await expect(page.getByText('RED', { exact: true })).toHaveCSS('color', 'rgb(204, 0, 0)');
- await expect(page.getByText('GREEN', { exact: true })).toHaveCSS('color', 'rgb(0, 204, 0)');
+ await expect.soft(page.getByText('RED', { exact: true })).toHaveCSS('color', 'rgb(205, 49, 49)');
+ await expect.soft(page.getByText('GREEN', { exact: true })).toHaveCSS('color', 'rgb(0, 188, 0)');
});
test('should format console messages in page', async ({ runUITest }, testInfo) => {