Skip to content

Commit 66fdbc9

Browse files
committed
util: strip whole CSI sequences per ECMA-48
stripVTControlCharacters() recognises a control sequence by a hand written list of final bytes, inherited from the bundled copy of ansi-regex. The list covers neither parameter bytes such as `<`, used by mouse reports, nor final bytes such as `@`, `X`, `d`, `a` and `b`, nor intermediate bytes at all. A sequence that uses one of them matches only in part, so the rest of it is left in the string rather than removed. getStringWidth() strips before measuring and readline places the cursor from that width, so the leftovers are counted as printable columns. Add an alternative built from the control sequence structure in ECMA-48 5.4: CSI, then any number of parameter bytes (0x30-0x3F), then any number of intermediate bytes (0x20-0x2F), then a single final byte (0x40-0x7E). Both other alternatives are kept, and the new one carries its own introducer rather than sharing the existing prefix, because that prefix also consumes `;` and the two competing for the same run made the match quadratic. Eleven cases, every one well formed and in use by terminals today. The colon sub-parameter form is not among them, since the OSC work already covers it. Signed-off-by: Luan Taraschi <130802253+luantaraschi@users.noreply.github.com>
1 parent d6e67a5 commit 66fdbc9

2 files changed

Lines changed: 31 additions & 0 deletions

File tree

‎lib/internal/util/inspect.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,17 @@ const meta = [
286286
// License: MIT by Sindre Sorhus <sindresorhus@gmail.com>
287287
// Matches all ansi escape code sequences in a string
288288
const ansi = new RegExp(
289+
// Sequences that run until a string terminator, such as OSC 8 hyperlinks.
289290
'(?:\\u001B\\][\\s\\S]*?(?:\\u0007|\\u001B\\u005C|\\u009C))' +
291+
// Control sequences, using the structure ECMA-48 5.4 defines: CSI, then any
292+
// number of parameter bytes (0x30-0x3F), then any number of intermediate
293+
// bytes (0x20-0x2F), then a single final byte (0x40-0x7E). The list of final
294+
// bytes below covers neither parameter bytes such as `<`, used by mouse
295+
// reports, nor final bytes such as `@`, so it stops in the middle of the
296+
// sequence and leaves the rest in the output.
297+
'|(?:\\u001B\\[|\\u009B)[\\u0030-\\u003F]*[\\u0020-\\u002F]*[\\u0040-\\u007E]' +
298+
// Everything else the reference implementation matched, including sequences
299+
// whose last byte is a digit, such as `ESC 7`.
290300
'|[\\u001B\\u009B][[\\]()#;?]*' +
291301
'(?:\\d{1,4}(?:[;:]\\d{0,4})*)?' +
292302
'[\\dA-PR-TZcf-nq-uy=><~]', 'g',

‎test/parallel/test-util-stripvtcontrolcharacters.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ tests.push(
3737
['\u001B[4:3mUnderline\u001B[4:0m', 'Underline'],
3838
);
3939

40+
// Ref: ECMA-48 5.4. A control sequence is CSI, then any number of parameter
41+
// bytes (0x30-0x3F), then any number of intermediate bytes (0x20-0x2F), then a
42+
// single final byte (0x40-0x7E). Every sequence below is well formed and in use
43+
// by terminals today, and every one of them has its tail left behind today.
44+
tests.push(
45+
// Parameter bytes other than digits, `;` and `:`.
46+
['a\u001B[<35;10;20Mb', 'ab'], // SGR mouse report
47+
['a\u001B[>1;2cb', 'ab'], // Secondary device attributes
48+
['a\u001B[=5hb', 'ab'],
49+
['a\u009B<35;10;20Mb', 'ab'], // Same, with the 8 bit CSI introducer
50+
// Final bytes that the reference implementation does not list.
51+
['a\u001B[3@b', 'ab'], // ICH, insert character
52+
['a\u001B[3Xb', 'ab'], // ECH, erase character
53+
['a\u001B[5db', 'ab'], // VPA, line position absolute
54+
['a\u001B[3bb', 'ab'], // REP, repeat preceding character
55+
['a\u001B[2ab', 'ab'], // HPR, character position forward
56+
// Intermediate bytes.
57+
['a\u001B[2 qb', 'ab'], // DECSCUSR, set cursor style
58+
['a\u001B[!pb', 'ab'], // DECSTR, soft terminal reset
59+
);
60+
4061
// Unterminated OSC does not match the OSC alternative; the CSI alternative may
4162
// still consume a short prefix (here ESC ] 8 ;; h), leaving the remainder.
4263
tests.push(

0 commit comments

Comments
 (0)