strings: use more comprehensive ansi escape code regex (#173362)

* strings: use more comprehensive ansi escape code regex

The ansi escape remover only recognized a few codes, and only a
subset of their syntax. Be more comprehensive, and add tests.

Fixes #153648

* address pr comment
This commit is contained in:
Connor Peet 2023-02-03 12:37:20 -08:00 committed by GitHub
parent 06c5eb5ead
commit dc6fd239e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 128 additions and 16 deletions

View file

@ -21,11 +21,8 @@ interface ServerReadyAction {
killOnServerStop?: boolean;
}
// Escape codes
// http://en.wikipedia.org/wiki/ANSI_escape_code
const EL = /\x1B\x5B[12]?K/g; // Erase in line
const COLOR_START = /\x1b\[\d+m/g; // Color
const COLOR_END = /\x1b\[0?m/g; // Color
// Escape codes, compiled from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
const CSI_SEQUENCE = /(:?\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~]/g;
/**
* Froms vs/base/common/strings.ts in core
@ -33,9 +30,7 @@ const COLOR_END = /\x1b\[0?m/g; // Color
*/
function removeAnsiEscapeCodes(str: string): string {
if (str) {
str = str.replace(EL, '');
str = str.replace(COLOR_START, '');
str = str.replace(COLOR_END, '');
str = str.replace(CSI_SEQUENCE, '');
}
return str;

View file

@ -727,17 +727,12 @@ export function lcut(text: string, n: number) {
return text.substring(i).replace(/^\s/, '');
}
// Escape codes
// http://en.wikipedia.org/wiki/ANSI_escape_code
const EL = /\x1B\x5B[12]?K/g; // Erase in line
const COLOR_START = /\x1b\[\d+m/g; // Color
const COLOR_END = /\x1b\[0?m/g; // Color
// Escape codes, compiled from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
const CSI_SEQUENCE = /(:?\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~]/g;
export function removeAnsiEscapeCodes(str: string): string {
if (str) {
str = str.replace(EL, '');
str = str.replace(COLOR_START, '');
str = str.replace(COLOR_END, '');
str = str.replace(CSI_SEQUENCE, '');
}
return str;

View file

@ -395,4 +395,126 @@ suite('Strings', () => {
return `${i++}${after}`;
}), 'a0ca1ca2ca3c');
});
test('removeAnsiEscapeCodes', () => {
const CSI = '\x1b\[';
const sequences = [
// Base cases from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
`${CSI}42@`,
`${CSI}42 @`,
`${CSI}42A`,
`${CSI}42 A`,
`${CSI}42B`,
`${CSI}42C`,
`${CSI}42D`,
`${CSI}42E`,
`${CSI}42F`,
`${CSI}42G`,
`${CSI}42;42H`,
`${CSI}42I`,
`${CSI}42J`,
`${CSI}?42J`,
`${CSI}42K`,
`${CSI}?42K`,
`${CSI}42L`,
`${CSI}42M`,
`${CSI}42P`,
`${CSI}#P`,
`${CSI}3#P`,
`${CSI}#Q`,
`${CSI}3#Q`,
`${CSI}#R`,
`${CSI}42S`,
`${CSI}?1;2;3S`,
`${CSI}42T`,
`${CSI}42;42;42;42;42T`,
`${CSI}>3T`,
`${CSI}42X`,
`${CSI}42Z`,
`${CSI}42^`,
`${CSI}42\``,
`${CSI}42a`,
`${CSI}42b`,
`${CSI}42c`,
`${CSI}=42c`,
`${CSI}>42c`,
`${CSI}42d`,
`${CSI}42e`,
`${CSI}42;42f`,
`${CSI}42g`,
`${CSI}3h`,
`${CSI}?3h`,
`${CSI}42i`,
`${CSI}?42i`,
`${CSI}3l`,
`${CSI}?3l`,
`${CSI}3m`,
`${CSI}>0;0m`,
`${CSI}>0m`,
`${CSI}?0m`,
`${CSI}42n`,
`${CSI}>42n`,
`${CSI}?42n`,
`${CSI}>42p`,
`${CSI}!p`,
`${CSI}0;0"p`,
`${CSI}42$p`,
`${CSI}?42$p`,
`${CSI}#p`,
`${CSI}3#p`,
`${CSI}>42q`,
`${CSI}42q`,
`${CSI}42 q`,
`${CSI}42"q`,
`${CSI}#q`,
`${CSI}42;42r`,
`${CSI}?3r`,
`${CSI}0;0;0;0;3$r`,
`${CSI}s`,
`${CSI}0;0s`,
`${CSI}>42s`,
`${CSI}?3s`,
`${CSI}42;42;42t`,
`${CSI}>3t`,
`${CSI}42 t`,
`${CSI}0;0;0;0;3$t`,
`${CSI}u`,
`${CSI}42 u`,
`${CSI}0;0;0;0;0;0;0;0$v`,
`${CSI}42$w`,
`${CSI}0;0;0;0'w`,
`${CSI}42x`,
`${CSI}42*x`,
`${CSI}0;0;0;0;0$x`,
`${CSI}42#y`,
`${CSI}0;0;0;0;0;0*y`,
`${CSI}42;0'z`,
`${CSI}0;1;2;4$z`,
`${CSI}3'{`,
`${CSI}#{`,
`${CSI}3#{`,
`${CSI}0;0;0;0\${`,
`${CSI}0;0;0;0#|`,
`${CSI}42$|`,
`${CSI}42'|`,
`${CSI}42*|`,
`${CSI}#}`,
`${CSI}42'}`,
`${CSI}42$}`,
`${CSI}42'~`,
`${CSI}42$~`,
// Common SGR cases:
`${CSI}1;31m`, // multiple attrs
`${CSI}105m`, // bright background
`${CSI}48:5:128m`, // 256 indexed color
`${CSI}48;5;128m`, // 256 indexed color alt
`${CSI}38:2:0:255:255:255m`, // truecolor
`${CSI}38;2;255;255;255m`, // truecolor alt
];
for (const sequence of sequences) {
assert.strictEqual(strings.removeAnsiEscapeCodes(`hello${sequence}world`), 'helloworld', `expect to remove ${JSON.stringify(sequence)}`);
}
});
});