slice-ansi 5.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +136 -74
  2. package/package.json +9 -9
  3. package/readme.md +5 -17
package/index.js CHANGED
@@ -1,105 +1,167 @@
1
- import isFullwidthCodePoint from 'is-fullwidth-code-point';
2
1
  import ansiStyles from 'ansi-styles';
2
+ import isFullwidthCodePoint from 'is-fullwidth-code-point';
3
3
 
4
- const astralRegex = /^[\uD800-\uDBFF][\uDC00-\uDFFF]$/;
4
+ // \x1b and \x9b
5
+ const ESCAPES = new Set([27, 155]);
5
6
 
6
- const ESCAPES = [
7
- '\u001B',
8
- '\u009B'
9
- ];
7
+ const CODE_POINT_0 = '0'.codePointAt(0);
8
+ const CODE_POINT_9 = '9'.codePointAt(0);
10
9
 
11
- const wrapAnsi = code => `${ESCAPES[0]}[${code}m`;
10
+ const endCodesSet = new Set();
11
+ const endCodesMap = new Map();
12
+ for (const [start, end] of ansiStyles.codes) {
13
+ endCodesSet.add(ansiStyles.color.ansi(end));
14
+ endCodesMap.set(ansiStyles.color.ansi(start), ansiStyles.color.ansi(end));
15
+ }
12
16
 
13
- const checkAnsi = (ansiCodes, isEscapes, endAnsiCode) => {
14
- let output = [];
15
- ansiCodes = [...ansiCodes];
17
+ function getEndCode(code) {
18
+ if (endCodesSet.has(code)) {
19
+ return code;
20
+ }
16
21
 
17
- for (let ansiCode of ansiCodes) {
18
- const ansiCodeOrigin = ansiCode;
19
- if (ansiCode.includes(';')) {
20
- ansiCode = ansiCode.split(';')[0][0] + '0';
21
- }
22
+ if (endCodesMap.has(code)) {
23
+ return endCodesMap.get(code);
24
+ }
22
25
 
23
- const item = ansiStyles.codes.get(Number.parseInt(ansiCode, 10));
24
- if (item) {
25
- const indexEscape = ansiCodes.indexOf(item.toString());
26
- if (indexEscape === -1) {
27
- output.push(wrapAnsi(isEscapes ? item : ansiCodeOrigin));
28
- } else {
29
- ansiCodes.splice(indexEscape, 1);
30
- }
31
- } else if (isEscapes) {
32
- output.push(wrapAnsi(0));
33
- break;
34
- } else {
35
- output.push(wrapAnsi(ansiCodeOrigin));
26
+ code = code.slice(2);
27
+ if (code.includes(';')) {
28
+ code = code[0] + '0';
29
+ }
30
+
31
+ const returnValue = ansiStyles.codes.get(Number.parseInt(code, 10));
32
+ if (returnValue) {
33
+ return ansiStyles.color.ansi(returnValue);
34
+ }
35
+
36
+ return ansiStyles.reset.open;
37
+ }
38
+
39
+ function findNumberIndex(string) {
40
+ for (let index = 0; index < string.length; index++) {
41
+ const codePoint = string.codePointAt(index);
42
+ if (codePoint >= CODE_POINT_0 && codePoint <= CODE_POINT_9) {
43
+ return index;
36
44
  }
37
45
  }
38
46
 
39
- if (isEscapes) {
40
- output = output.filter((element, index) => output.indexOf(element) === index);
47
+ return -1;
48
+ }
41
49
 
42
- if (endAnsiCode !== undefined) {
43
- const fistEscapeCode = wrapAnsi(ansiStyles.codes.get(Number.parseInt(endAnsiCode, 10)));
44
- // TODO: Remove the use of `.reduce` here.
45
- // eslint-disable-next-line unicorn/no-array-reduce
46
- output = output.reduce((current, next) => next === fistEscapeCode ? [next, ...current] : [...current, next], []);
50
+ function parseAnsiCode(string, offset) {
51
+ string = string.slice(offset, offset + 19);
52
+ const startIndex = findNumberIndex(string);
53
+ if (startIndex !== -1) {
54
+ let endIndex = string.indexOf('m', startIndex);
55
+ if (endIndex === -1) {
56
+ endIndex = string.length;
47
57
  }
58
+
59
+ return string.slice(0, endIndex + 1);
48
60
  }
61
+ }
49
62
 
50
- return output.join('');
51
- };
63
+ function tokenize(string, endCharacter = Number.POSITIVE_INFINITY) {
64
+ const returnValue = [];
65
+
66
+ let index = 0;
67
+ let visibleCount = 0;
68
+ while (index < string.length) {
69
+ const codePoint = string.codePointAt(index);
70
+
71
+ if (ESCAPES.has(codePoint)) {
72
+ const code = parseAnsiCode(string, index);
73
+ if (code) {
74
+ returnValue.push({
75
+ type: 'ansi',
76
+ code,
77
+ endCode: getEndCode(code),
78
+ });
79
+ index += code.length;
80
+ continue;
81
+ }
82
+ }
52
83
 
53
- export default function sliceAnsi(string, begin, end) {
54
- const characters = [...string];
55
- const ansiCodes = [];
84
+ const isFullWidth = isFullwidthCodePoint(codePoint);
85
+ const character = String.fromCodePoint(codePoint);
56
86
 
57
- let stringEnd = typeof end === 'number' ? end : characters.length;
58
- let isInsideEscape = false;
59
- let ansiCode;
60
- let visible = 0;
61
- let output = '';
87
+ returnValue.push({
88
+ type: 'character',
89
+ value: character,
90
+ isFullWidth,
91
+ });
62
92
 
63
- for (const [index, character] of characters.entries()) {
64
- let leftEscape = false;
93
+ index += character.length;
94
+ visibleCount += isFullWidth ? 2 : character.length;
65
95
 
66
- if (ESCAPES.includes(character)) {
67
- const code = /\d[^m]*/.exec(string.slice(index, index + 18));
68
- ansiCode = code && code.length > 0 ? code[0] : undefined;
96
+ if (visibleCount >= endCharacter) {
97
+ break;
98
+ }
99
+ }
69
100
 
70
- if (visible < stringEnd) {
71
- isInsideEscape = true;
101
+ return returnValue;
102
+ }
72
103
 
73
- if (ansiCode !== undefined) {
74
- ansiCodes.push(ansiCode);
75
- }
76
- }
77
- } else if (isInsideEscape && character === 'm') {
78
- isInsideEscape = false;
79
- leftEscape = true;
104
+ function reduceAnsiCodes(codes) {
105
+ let returnValue = [];
106
+
107
+ for (const code of codes) {
108
+ if (code.code === ansiStyles.reset.open) {
109
+ // Reset code, disable all codes
110
+ returnValue = [];
111
+ } else if (endCodesSet.has(code.code)) {
112
+ // This is an end code, disable all matching start codes
113
+ returnValue = returnValue.filter(returnValueCode => returnValueCode.endCode !== code.code);
114
+ } else {
115
+ // This is a start code. Disable all styles this "overrides", then enable it
116
+ returnValue = returnValue.filter(returnValueCode => returnValueCode.endCode !== code.endCode);
117
+ returnValue.push(code);
80
118
  }
119
+ }
120
+
121
+ return returnValue;
122
+ }
81
123
 
82
- if (!isInsideEscape && !leftEscape) {
83
- visible++;
124
+ function undoAnsiCodes(codes) {
125
+ const reduced = reduceAnsiCodes(codes);
126
+ const endCodes = reduced.map(({endCode}) => endCode);
127
+ return endCodes.reverse().join('');
128
+ }
129
+
130
+ export default function sliceAnsi(string, start, end) {
131
+ const tokens = tokenize(string, end);
132
+ let activeCodes = [];
133
+ let position = 0;
134
+ let returnValue = '';
135
+ let include = false;
136
+
137
+ for (const token of tokens) {
138
+ if (end !== undefined && position >= end) {
139
+ break;
84
140
  }
85
141
 
86
- if (!astralRegex.test(character) && isFullwidthCodePoint(character.codePointAt())) {
87
- visible++;
142
+ if (token.type === 'ansi') {
143
+ activeCodes.push(token);
144
+ if (include) {
145
+ returnValue += token.code;
146
+ }
147
+ } else {
148
+ // Character
149
+ if (!include && position >= start) {
150
+ include = true;
151
+ // Simplify active codes
152
+ activeCodes = reduceAnsiCodes(activeCodes);
153
+ returnValue = activeCodes.map(({code}) => code).join('');
154
+ }
88
155
 
89
- if (typeof end !== 'number') {
90
- stringEnd++;
156
+ if (include) {
157
+ returnValue += token.value;
91
158
  }
92
- }
93
159
 
94
- if (visible > begin && visible <= stringEnd) {
95
- output += character;
96
- } else if (visible === begin && !isInsideEscape && ansiCode !== undefined) {
97
- output = checkAnsi(ansiCodes);
98
- } else if (visible >= stringEnd) {
99
- output += checkAnsi(ansiCodes, true, ansiCode);
100
- break;
160
+ position += token.isFullWidth ? 2 : token.value.length;
101
161
  }
102
162
  }
103
163
 
104
- return output;
164
+ // Disable active codes at the end
165
+ returnValue += undoAnsiCodes(activeCodes);
166
+ return returnValue;
105
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slice-ansi",
3
- "version": "5.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "Slice a string with ANSI escape codes",
5
5
  "license": "MIT",
6
6
  "repository": "chalk/slice-ansi",
@@ -8,7 +8,7 @@
8
8
  "type": "module",
9
9
  "exports": "./index.js",
10
10
  "engines": {
11
- "node": ">=12"
11
+ "node": ">=14.16"
12
12
  },
13
13
  "scripts": {
14
14
  "test": "xo && ava"
@@ -40,14 +40,14 @@
40
40
  "text"
41
41
  ],
42
42
  "dependencies": {
43
- "ansi-styles": "^6.0.0",
44
- "is-fullwidth-code-point": "^4.0.0"
43
+ "ansi-styles": "^6.2.1",
44
+ "is-fullwidth-code-point": "^5.0.0"
45
45
  },
46
46
  "devDependencies": {
47
- "ava": "^3.15.0",
48
- "chalk": "^4.1.0",
49
- "random-item": "^4.0.0",
50
- "strip-ansi": "^7.0.0",
51
- "xo": "^0.38.2"
47
+ "ava": "^5.3.1",
48
+ "chalk": "^5.3.0",
49
+ "random-item": "^4.0.1",
50
+ "strip-ansi": "^7.1.0",
51
+ "xo": "^0.56.0"
52
52
  }
53
53
  }
package/readme.md CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  ## Install
6
6
 
7
- ```
8
- $ npm install slice-ansi
7
+ ```sh
8
+ npm install slice-ansi
9
9
  ```
10
10
 
11
11
  ## Usage
@@ -22,7 +22,7 @@ console.log(sliceAnsi(string, 20, 30));
22
22
 
23
23
  ## API
24
24
 
25
- ### sliceAnsi(string, beginSlice, endSlice?)
25
+ ### sliceAnsi(string, startSlice, endSlice?)
26
26
 
27
27
  #### string
28
28
 
@@ -30,11 +30,11 @@ Type: `string`
30
30
 
31
31
  String with ANSI escape codes. Like one styled by [`chalk`](https://github.com/chalk/chalk).
32
32
 
33
- #### beginSlice
33
+ #### startSlice
34
34
 
35
35
  Type: `number`
36
36
 
37
- Zero-based index at which to begin the slice.
37
+ Zero-based index at which to start the slice.
38
38
 
39
39
  #### endSlice
40
40
 
@@ -52,15 +52,3 @@ Zero-based index at which to end the slice.
52
52
 
53
53
  - [Sindre Sorhus](https://github.com/sindresorhus)
54
54
  - [Josh Junon](https://github.com/qix-)
55
-
56
- ---
57
-
58
- <div align="center">
59
- <b>
60
- <a href="https://tidelift.com/subscription/pkg/npm-slice_ansi?utm_source=npm-slice-ansi&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
61
- </b>
62
- <br>
63
- <sub>
64
- Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
65
- </sub>
66
- </div>