chore: implement faster ansi2html format for console (#26826)
This commit is contained in:
parent
583964f8dd
commit
a339bead09
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -6624,7 +6624,6 @@
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-to-html": "^0.7.2",
|
|
||||||
"codemirror": "^5.65.9",
|
"codemirror": "^5.65.9",
|
||||||
"xterm": "^5.1.0",
|
"xterm": "^5.1.0",
|
||||||
"xterm-addon-fit": "^0.7.0"
|
"xterm-addon-fit": "^0.7.0"
|
||||||
|
|
@ -10630,7 +10629,6 @@
|
||||||
"web": {
|
"web": {
|
||||||
"version": "file:packages/web",
|
"version": "file:packages/web",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-to-html": "^0.7.2",
|
|
||||||
"codemirror": "^5.65.9",
|
"codemirror": "^5.65.9",
|
||||||
"xterm": "^5.1.0",
|
"xterm": "^5.1.0",
|
||||||
"xterm-addon-fit": "^0.7.0"
|
"xterm-addon-fit": "^0.7.0"
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ import * as React from 'react';
|
||||||
import './consoleTab.css';
|
import './consoleTab.css';
|
||||||
import * as modelUtil from './modelUtil';
|
import * as modelUtil from './modelUtil';
|
||||||
import { ListView } from '@web/components/listView';
|
import { ListView } from '@web/components/listView';
|
||||||
import { ansi2htmlMarkup } from '@web/components/errorMessage';
|
|
||||||
import type { Boundaries } from '../geometry';
|
import type { Boundaries } from '../geometry';
|
||||||
import { msToString } from '@web/uiUtils';
|
import { msToString } from '@web/uiUtils';
|
||||||
|
import { ansi2html } from '@web/ansi2html';
|
||||||
import type * as trace from '@trace/trace';
|
import type * as trace from '@trace/trace';
|
||||||
|
|
||||||
type ConsoleEntry = {
|
type ConsoleEntry = {
|
||||||
|
|
@ -124,10 +124,10 @@ export const ConsoleTab: React.FunctionComponent<{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeMessage?.text)
|
if (nodeMessage?.text)
|
||||||
messageInnerHTML = ansi2htmlMarkup(nodeMessage.text.trim()) || '';
|
messageInnerHTML = ansi2html(nodeMessage.text.trim()) || '';
|
||||||
|
|
||||||
if (nodeMessage?.base64)
|
if (nodeMessage?.base64)
|
||||||
messageInnerHTML = ansi2htmlMarkup(atob(nodeMessage.base64).trim()) || '';
|
messageInnerHTML = ansi2html(atob(nodeMessage.base64).trim()) || '';
|
||||||
|
|
||||||
return <div className='console-line'>
|
return <div className='console-line'>
|
||||||
{timestampElement}
|
{timestampElement}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-to-html": "^0.7.2",
|
|
||||||
"codemirror": "^5.65.9",
|
"codemirror": "^5.65.9",
|
||||||
"xterm": "^5.1.0",
|
"xterm": "^5.1.0",
|
||||||
"xterm-addon-fit": "^0.7.0"
|
"xterm-addon-fit": "^0.7.0"
|
||||||
|
|
|
||||||
107
packages/web/src/ansi2html.ts
Normal file
107
packages/web/src/ansi2html.ts
Normal file
|
|
@ -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(`<span style="${styleBody(style)}">${escapeHTML(text)}</span>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ansiColors: Record<number, string> = {
|
||||||
|
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<number, string> = {
|
||||||
|
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('; ');
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
../theme.ts
|
../theme.ts
|
||||||
../third_party/vscode/codicon.css
|
../third_party/vscode/codicon.css
|
||||||
../uiUtils.ts
|
../uiUtils.ts
|
||||||
|
../ansi2html.ts
|
||||||
|
|
||||||
[expandable.spec.tsx]
|
[expandable.spec.tsx]
|
||||||
***
|
***
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import './codeMirrorWrapper.css';
|
import './codeMirrorWrapper.css';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { CodeMirror } from './codeMirrorModule';
|
import type { CodeMirror } from './codeMirrorModule';
|
||||||
import { ansi2htmlMarkup } from './errorMessage';
|
import { ansi2html } from '../ansi2html';
|
||||||
import { useMeasure } from '../uiUtils';
|
import { useMeasure } from '../uiUtils';
|
||||||
|
|
||||||
export type SourceHighlight = {
|
export type SourceHighlight = {
|
||||||
|
|
@ -152,7 +152,7 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorWidgetElement = document.createElement('div');
|
const errorWidgetElement = document.createElement('div');
|
||||||
errorWidgetElement.innerHTML = ansi2htmlMarkup(h.message || '');
|
errorWidgetElement.innerHTML = ansi2html(h.message || '');
|
||||||
errorWidgetElement.className = 'source-line-error-widget';
|
errorWidgetElement.className = 'source-line-error-widget';
|
||||||
widgets.push(codemirror.addLineWidget(h.line, errorWidgetElement, { above: true, coverGutter: false }));
|
widgets.push(codemirror.addLineWidget(h.line, errorWidgetElement, { above: true, coverGutter: false }));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,45 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ansi2html from 'ansi-to-html';
|
import { ansi2html } from '@web/ansi2html';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './errorMessage.css';
|
import './errorMessage.css';
|
||||||
|
|
||||||
export const ErrorMessage: React.FC<{
|
export const ErrorMessage: React.FC<{
|
||||||
error: string;
|
error: string;
|
||||||
}> = ({ error }) => {
|
}> = ({ error }) => {
|
||||||
const html = React.useMemo(() => ansi2htmlMarkup(error), [error]);
|
const html = React.useMemo(() => ansi2html(error), [error]);
|
||||||
return <div className='error-message' dangerouslySetInnerHTML={{ __html: html || '' }}></div>;
|
return <div className='error-message' dangerouslySetInnerHTML={{ __html: html || '' }}></div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
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]!));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,8 @@ test('should show console messages for test', async ({ runUITest }, testInfo) =>
|
||||||
'codicon codicon-file status-none',
|
'codicon codicon-file status-none',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await expect(page.getByText('RED', { exact: true })).toHaveCSS('color', 'rgb(204, 0, 0)');
|
await expect.soft(page.getByText('RED', { exact: true })).toHaveCSS('color', 'rgb(205, 49, 49)');
|
||||||
await expect(page.getByText('GREEN', { exact: true })).toHaveCSS('color', 'rgb(0, 204, 0)');
|
await expect.soft(page.getByText('GREEN', { exact: true })).toHaveCSS('color', 'rgb(0, 188, 0)');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should format console messages in page', async ({ runUITest }, testInfo) => {
|
test('should format console messages in page', async ({ runUITest }, testInfo) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue