Skip to content

Commit 617d649

Browse files
Get all ansi escape sequences (#25)
Co-Authored-By: Sindre Sorhus <[email protected]>
1 parent e33482c commit 617d649

File tree

2 files changed

+69
-19
lines changed

2 files changed

+69
-19
lines changed

index.js

+49-11
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,68 @@ const ESCAPES = [
88
'\u009B'
99
];
1010

11-
const END_CODE = 39;
12-
1311
const wrapAnsi = code => `${ESCAPES[0]}[${code}m`;
1412

13+
const checkAnsi = (ansiCodes, isEscapes, endAnsiCode) => {
14+
let output = [];
15+
ansiCodes = [...ansiCodes];
16+
17+
for (let ansiCode of ansiCodes) {
18+
const ansiCodeOrigin = ansiCode;
19+
if (ansiCode.match(';')) {
20+
ansiCode = ansiCode.split(';')[0][0] + '0';
21+
}
22+
23+
const item = ansiStyles.codes.get(parseInt(ansiCode, 10));
24+
if (item) {
25+
const indexEscape = ansiCodes.indexOf(item.toString());
26+
if (indexEscape >= 0) {
27+
ansiCodes.splice(indexEscape, 1);
28+
} else {
29+
output.push(wrapAnsi(isEscapes ? item : ansiCodeOrigin));
30+
}
31+
} else if (isEscapes) {
32+
output.push(wrapAnsi(0));
33+
break;
34+
} else {
35+
output.push(wrapAnsi(ansiCodeOrigin));
36+
}
37+
}
38+
39+
if (isEscapes) {
40+
output = output.filter((element, index) => output.indexOf(element) === index);
41+
if (endAnsiCode !== undefined) {
42+
const fistEscapeCode = wrapAnsi(ansiStyles.codes.get(parseInt(endAnsiCode, 10)));
43+
output = output.reduce((current, next) => next === fistEscapeCode ? [next, ...current] : [...current, next], []);
44+
}
45+
}
46+
47+
return output.join('');
48+
};
49+
1550
module.exports = (string, begin, end) => {
1651
const characters = [...string.normalize()];
52+
const ansiCodes = [];
1753

1854
end = typeof end === 'number' ? end : characters.length;
1955

2056
let isInsideEscape = false;
21-
let escapeCode;
57+
let ansiCode;
2258
let visible = 0;
2359
let output = '';
2460

2561
for (const [index, character] of characters.entries()) {
2662
let leftEscape = false;
2763

2864
if (ESCAPES.includes(character)) {
29-
isInsideEscape = true;
3065
const code = /\d[^m]*/.exec(string.slice(index, index + 18));
31-
escapeCode = code === END_CODE ? undefined : code;
66+
ansiCode = code && code.length > 0 ? code[0] : undefined;
67+
if (visible < end) {
68+
isInsideEscape = true;
69+
if (ansiCode !== undefined) {
70+
ansiCodes.push(ansiCode);
71+
}
72+
}
3273
} else if (isInsideEscape && character === 'm') {
3374
isInsideEscape = false;
3475
leftEscape = true;
@@ -44,13 +85,10 @@ module.exports = (string, begin, end) => {
4485

4586
if (visible > begin && visible <= end) {
4687
output += character;
47-
} else if (visible === begin && !isInsideEscape && escapeCode !== undefined && escapeCode !== END_CODE) {
48-
output += wrapAnsi(escapeCode);
88+
} else if (visible === begin && !isInsideEscape && ansiCode !== undefined) {
89+
output = checkAnsi(ansiCodes);
4990
} else if (visible >= end) {
50-
if (escapeCode !== undefined) {
51-
output += wrapAnsi(ansiStyles.codes.get(parseInt(escapeCode, 10)) || END_CODE);
52-
}
53-
91+
output += checkAnsi(ansiCodes, true, ansiCode);
5492
break;
5593
}
5694
}

test.js

+20-8
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ test('main', t => {
2424
}
2525
}
2626

27-
const a = util.inspect('\u001B[31mthe \u001B[39m\u001B[32mquick \u001B[39m\u001B[34m\u001B[39m');
28-
const b = util.inspect('\u001B[32m\u001B[39m\u001B[34mbrown \u001B[39m\u001B[36mfox \u001B[39m\u001B[33m\u001B[39m');
29-
const c = util.inspect('\u001B[31m \u001B[39m\u001B[32mquick \u001B[39m\u001B[34mbrown \u001B[39m\u001B[36mfox \u001B[39m\u001B[33m\u001B[39m');
27+
const a = util.inspect('\u001B[31mthe \u001B[39m\u001B[32mquick \u001B[39m');
28+
const b = util.inspect('\u001B[34mbrown \u001B[39m\u001B[36mfox \u001B[39m');
29+
const c = util.inspect('\u001B[31m \u001B[39m\u001B[32mquick \u001B[39m\u001B[34mbrown \u001B[39m\u001B[36mfox \u001B[39m');
3030

3131
t.is(util.inspect(sliceAnsi(fixture, 0, 10)), a);
3232
t.is(util.inspect(sliceAnsi(fixture, 10, 20)), b);
@@ -50,33 +50,45 @@ test('doesn\'t add unnecessary escape codes', t => {
5050
t.is(sliceAnsi('\u001B[31municorn\u001B[39m', 0, 3), '\u001B[31muni\u001B[39m');
5151
});
5252

53-
test.failing('can slice a normal character before a colored character', t => {
53+
test('can slice a normal character before a colored character', t => {
5454
t.is(sliceAnsi('a\u001B[31mb\u001B[39m', 0, 1), 'a');
5555
});
5656

57-
test.failing('can slice a normal character after a colored character', t => {
57+
test('can slice a normal character after a colored character', t => {
5858
t.is(sliceAnsi('\u001B[31ma\u001B[39mb', 1, 2), 'b');
5959
});
6060

6161
// See https://github.com/chalk/slice-ansi/issues/22
62-
test.failing('can slice a string styled with both background and foreground', t => {
62+
test('can slice a string styled with both background and foreground', t => {
6363
// Test string: `chalk.bgGreen.black('test');`
6464
t.is(sliceAnsi('\u001B[42m\u001B[30mtest\u001B[39m\u001B[49m', 0, 1), '\u001B[42m\u001B[30mt\u001B[39m\u001B[49m');
6565
});
6666

67+
test('can slice a string styled with modifier', t => {
68+
// Test string: `chalk.underline('test');`
69+
t.is(sliceAnsi('\u001B[4mtest\u001B[24m', 0, 1), '\u001B[4mt\u001B[24m');
70+
});
71+
72+
test('can slice a string with unknown ANSI color', t => {
73+
t.is(sliceAnsi('\u001B[20mTEST\u001B[49m', 0, 4), '\u001B[20mTEST\u001B[0m');
74+
t.is(sliceAnsi('\u001B[1001mTEST\u001B[49m', 0, 3), '\u001B[1001mTES\u001B[0m');
75+
t.is(sliceAnsi('\u001B[1001mTEST\u001B[49m', 0, 2), '\u001B[1001mTE\u001B[0m');
76+
});
77+
6778
test('weird null issue', t => {
6879
const s = '\u001B[1mautotune.flipCoin("easy as") ? 🎂 : 🍰 \u001B[33m★\u001B[39m\u001B[22m';
6980
const result = sliceAnsi(s, 38);
7081
t.false(result.includes('null'));
7182
});
7283

7384
test('support true color escape sequences', t => {
74-
t.is(sliceAnsi('\u001B[[1m\u001B[[48;2;255;255;255m\u001B[[38;2;255;0;0municorn\u001B[[39m\u001B[[49m\u001B[[22m', 0, 3), '\u001B[1m\u001B[48;2;255;255;25m\u001B[38;2;255;0;0muni\u001B[39m');
85+
t.is(sliceAnsi('\u001B[1m\u001B[48;2;255;255;255m\u001B[38;2;255;0;0municorn\u001B[39m\u001B[49m\u001B[22m', 0, 3), '\u001B[1m\u001B[48;2;255;255;255m\u001B[38;2;255;0;0muni\u001B[22m\u001B[49m\u001B[39m');
7586
});
7687

7788
// See https://github.com/chalk/slice-ansi/issues/24
78-
test.failing('doesn\'t add extra escapes', t => {
89+
test('doesn\'t add extra escapes', t => {
7990
const output = `${chalk.black.bgYellow(' RUNS ')} ${chalk.green('test')}`;
8091
t.is(sliceAnsi(output, 0, 7), `${chalk.black.bgYellow(' RUNS ')} `);
8192
t.is(sliceAnsi(output, 0, 8), `${chalk.black.bgYellow(' RUNS ')} `);
93+
t.is(sliceAnsi('\u001B[31m' + output, 0, 4), `\u001B[31m${chalk.black.bgYellow(' RUN')}`);
8294
});

0 commit comments

Comments
 (0)