console-toolkit 1.1.1 → 1.2.1
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.
- package/README.md +5 -3
- package/package.json +7 -1
- package/src/box.js +2 -2
- package/src/output/updater.js +11 -2
- package/src/panel.js +85 -66
- package/src/strings/clip.js +31 -0
- package/src/strings/parse.js +16 -0
- package/src/strings/split.js +109 -0
- package/src/strings.js +13 -40
package/README.md
CHANGED
|
@@ -89,6 +89,8 @@ BSD 3-Clause License
|
|
|
89
89
|
|
|
90
90
|
## Release history
|
|
91
91
|
|
|
92
|
-
- 1.
|
|
93
|
-
- 1.
|
|
94
|
-
- 1.
|
|
92
|
+
- 1.2.1 *Added support for `Bun.stringWidth()`.*
|
|
93
|
+
- 1.2.0 *Refactored `strings`.*
|
|
94
|
+
- 1.1.1 *Minor bugfixes in `Table`, some improvements, updated deps.*
|
|
95
|
+
- 1.1.0 *Minor improvements, enhanced `Writer` and `Updater`.*
|
|
96
|
+
- 1.0.0 *Initial release.*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "console-toolkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Toolkit to produce a fancy console output (boxes, tables, charts, colors).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
],
|
|
42
42
|
"author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (https://www.lazutkin.com/)",
|
|
43
43
|
"license": "BSD-3-Clause",
|
|
44
|
+
"funding": {
|
|
45
|
+
"type": "github",
|
|
46
|
+
"url": "https://github.com/sponsors/uhop"
|
|
47
|
+
},
|
|
44
48
|
"bugs": {
|
|
45
49
|
"url": "https://github.com/uhop/console-toolkit/issues"
|
|
46
50
|
},
|
|
@@ -54,6 +58,8 @@
|
|
|
54
58
|
]
|
|
55
59
|
},
|
|
56
60
|
"devDependencies": {
|
|
61
|
+
"emoji-regex": "^10.3.0",
|
|
62
|
+
"get-east-asian-width": "^1.2.0",
|
|
57
63
|
"tape-six": "^0.9.6"
|
|
58
64
|
}
|
|
59
65
|
}
|
package/src/box.js
CHANGED
|
@@ -78,8 +78,8 @@ export class Box {
|
|
|
78
78
|
return new Box(this);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
clip(width,
|
|
82
|
-
return
|
|
81
|
+
clip(width, options) {
|
|
82
|
+
return Box.make(clipStrings(this.box, width, options));
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// padding
|
package/src/output/updater.js
CHANGED
|
@@ -28,14 +28,23 @@ export class Updater {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
startRefreshing(ms = 100) {
|
|
31
|
-
if (this.intervalHandle || this.isDone || !this.writer.isTTY) return;
|
|
31
|
+
if (this.intervalHandle || this.isDone || !this.writer.isTTY) return this;
|
|
32
32
|
this.intervalHandle = setInterval(this.update.bind(this), ms);
|
|
33
|
+
return this;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
stopRefreshing() {
|
|
36
|
-
if (!this.intervalHandle) return;
|
|
37
|
+
if (!this.intervalHandle) return this;
|
|
37
38
|
clearInterval(this.intervalHandle);
|
|
38
39
|
this.intervalHandle = null;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
reset() {
|
|
44
|
+
this.stopRefreshing();
|
|
45
|
+
this.isDone = false;
|
|
46
|
+
this.lastHeight = 0;
|
|
47
|
+
return this;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
getFrame(state, ...args) {
|
package/src/panel.js
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
optimize,
|
|
11
11
|
toState
|
|
12
12
|
} from './ansi/sgr-state.js';
|
|
13
|
+
import parse from './strings/parse.js';
|
|
14
|
+
import split, {size} from './strings/split.js';
|
|
13
15
|
import Box from './box.js';
|
|
14
16
|
import {addAliases} from './meta.js';
|
|
15
17
|
|
|
@@ -45,36 +47,15 @@ export class Panel {
|
|
|
45
47
|
break main;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
for (let i = 0, n = s.height; i < n; ++i) {
|
|
52
|
-
const row = s.box[i],
|
|
53
|
-
panelRow = panel.box[i];
|
|
54
|
-
let start = 0,
|
|
55
|
-
pos = 0,
|
|
56
|
-
state = {};
|
|
57
|
-
matchCsi.lastIndex = 0;
|
|
58
|
-
for (const match of row.matchAll(matchCsi)) {
|
|
59
|
-
const str = [...row.substring(start, match.index)];
|
|
60
|
-
for (let j = 0; j < str.length; ++j) {
|
|
61
|
-
panelRow[pos++] = str[j] === emptySymbol ? null : {symbol: str[j], state};
|
|
62
|
-
}
|
|
63
|
-
start = match.index + match[0].length;
|
|
64
|
-
if (match[3] !== 'm') continue;
|
|
65
|
-
state = addCommandsToState(state, match[1].split(';'));
|
|
66
|
-
}
|
|
67
|
-
const str = [...row.substring(start)];
|
|
68
|
-
for (let j = 0; j < str.length; ++j) {
|
|
69
|
-
panelRow[pos++] = str[j] === emptySymbol ? null : {symbol: str[j], state};
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
50
|
+
const panel = new Panel(s.width, s.height);
|
|
51
|
+
panel.put(0, 0, s, options);
|
|
73
52
|
return panel;
|
|
74
53
|
}
|
|
75
54
|
|
|
76
|
-
toStrings(
|
|
55
|
+
toStrings(options = {}) {
|
|
77
56
|
if (!this.height || !this.width) return Box.makeBlank(this.width, this.height);
|
|
57
|
+
|
|
58
|
+
let {emptySymbol = ' ', emptyState = RESET_STATE} = options;
|
|
78
59
|
emptyState = toState(emptyState);
|
|
79
60
|
|
|
80
61
|
const s = new Array(this.height),
|
|
@@ -86,8 +67,9 @@ export class Panel {
|
|
|
86
67
|
initState = {},
|
|
87
68
|
state = initState;
|
|
88
69
|
for (let j = 0; j < this.width; ++j) {
|
|
89
|
-
const cell = panelRow[j] || emptyCell
|
|
90
|
-
|
|
70
|
+
const cell = panelRow[j] || emptyCell;
|
|
71
|
+
if (cell.ignore) continue;
|
|
72
|
+
const newState = combineStates(state, cell.state),
|
|
91
73
|
commands = stateTransition(state, newState);
|
|
92
74
|
row += stringifyCommands(commands) + cell.symbol;
|
|
93
75
|
state = newState;
|
|
@@ -187,7 +169,7 @@ export class Panel {
|
|
|
187
169
|
return this;
|
|
188
170
|
}
|
|
189
171
|
|
|
190
|
-
put(x, y, text,
|
|
172
|
+
put(x, y, text, options = {}) {
|
|
191
173
|
if (text instanceof Panel) return this.copyFrom(x, y, text.width, text.height, text);
|
|
192
174
|
|
|
193
175
|
// normalize arguments
|
|
@@ -203,43 +185,50 @@ export class Panel {
|
|
|
203
185
|
if (y >= this.height) return this;
|
|
204
186
|
if (y + height > this.height) height = this.height - y;
|
|
205
187
|
|
|
188
|
+
const {emptySymbol = '\x07'} = options;
|
|
189
|
+
|
|
206
190
|
// copy characters
|
|
207
191
|
let state = {};
|
|
208
192
|
for (let i = 0; i < height; ++i) {
|
|
209
193
|
const row = this.box[y + i],
|
|
210
194
|
s = box.box[i];
|
|
211
|
-
let
|
|
212
|
-
pos = 0;
|
|
195
|
+
let pos = 0;
|
|
213
196
|
matchCsi.lastIndex = 0;
|
|
214
|
-
for (const match of s
|
|
215
|
-
const
|
|
216
|
-
for (
|
|
197
|
+
for (const {string, match} of parse(s, matchCsi)) {
|
|
198
|
+
const {graphemes} = split(string, options);
|
|
199
|
+
for (const grapheme of graphemes) {
|
|
217
200
|
if (x + pos >= row.length) break;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
201
|
+
if (grapheme.symbol === emptySymbol) {
|
|
202
|
+
row[x + pos] = null;
|
|
203
|
+
} else {
|
|
204
|
+
const cell = row[x + pos];
|
|
205
|
+
row[x + pos] = {
|
|
206
|
+
symbol: grapheme.symbol,
|
|
207
|
+
state: cell && !cell.ignore ? combineStates(cell.state, state) : state
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
++pos;
|
|
211
|
+
if (grapheme.width === 2) {
|
|
212
|
+
if (x + pos < row.length) {
|
|
213
|
+
row[x + pos] = {ignore: true};
|
|
214
|
+
++pos;
|
|
215
|
+
} else {
|
|
216
|
+
row[x + pos - 1] = null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
221
219
|
}
|
|
222
|
-
|
|
223
|
-
if (match[3] !== 'm') continue;
|
|
224
|
-
state = addCommandsToState(state, match[1].split(';'));
|
|
225
|
-
}
|
|
226
|
-
const str = [...s.substring(start)];
|
|
227
|
-
for (let j = 0; j < str.length; ++j, ++pos) {
|
|
228
|
-
if (x + pos >= row.length) break;
|
|
229
|
-
const cell = row[x + pos];
|
|
230
|
-
row[x + pos] =
|
|
231
|
-
str[j] === emptySymbol ? null : {symbol: str[j], state: cell ? combineStates(cell.state, state) : state};
|
|
220
|
+
if (match && match[3] === 'm') state = addCommandsToState(state, match[1].split(';'));
|
|
232
221
|
}
|
|
233
222
|
if (x + pos < row.length) {
|
|
234
223
|
const cell = row[x + pos];
|
|
235
|
-
if (cell) row[x + pos] = {symbol: cell.symbol, state: combineStates(state, cell.state)};
|
|
224
|
+
if (cell && !cell.ignore) row[x + pos] = {symbol: cell.symbol, state: combineStates(state, cell.state)};
|
|
236
225
|
}
|
|
237
226
|
}
|
|
238
227
|
|
|
239
228
|
return this;
|
|
240
229
|
}
|
|
241
230
|
|
|
242
|
-
applyFn(x, y, width, height, fn) {
|
|
231
|
+
applyFn(x, y, width, height, fn, options) {
|
|
243
232
|
// normalize arguments
|
|
244
233
|
|
|
245
234
|
if (typeof x == 'function') {
|
|
@@ -265,16 +254,27 @@ export class Panel {
|
|
|
265
254
|
for (let i = 0; i < height; ++i) {
|
|
266
255
|
const row = this.box[y + i];
|
|
267
256
|
for (let j = 0; j < width; ++j) {
|
|
268
|
-
const cell = row[x + j]
|
|
269
|
-
|
|
270
|
-
|
|
257
|
+
const cell = row[x + j];
|
|
258
|
+
if (cell?.ignore) continue;
|
|
259
|
+
const newCell = fn(x + j, y + i, cell);
|
|
260
|
+
if (newCell !== undefined) {
|
|
261
|
+
if (cell) {
|
|
262
|
+
const symbolWidth = size(cell.symbol, options);
|
|
263
|
+
if (symbolWidth > 1 && x + j + 1 < row.length) row[x + j + 1] = null;
|
|
264
|
+
}
|
|
265
|
+
if (newCell) {
|
|
266
|
+
const symbolWidth = size(newCell.symbol, options);
|
|
267
|
+
if (symbolWidth > 1 && x + j + 1 < row.length) row[x + j + 1] = {ignore: true};
|
|
268
|
+
}
|
|
269
|
+
row[x + j] = newCell;
|
|
270
|
+
}
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
return this;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
fill(x, y, width, height, symbol, state = {}) {
|
|
277
|
+
fill(x, y, width, height, symbol, state = {}, options) {
|
|
278
278
|
if (typeof x === 'string') {
|
|
279
279
|
symbol = x;
|
|
280
280
|
state = y || {};
|
|
@@ -290,7 +290,7 @@ export class Panel {
|
|
|
290
290
|
} else {
|
|
291
291
|
state = toState(state);
|
|
292
292
|
}
|
|
293
|
-
return this.applyFn(x, y, width, height, () => ({symbol, state}));
|
|
293
|
+
return this.applyFn(x, y, width, height, () => ({symbol, state}), options);
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
fillState(x, y, width, height, options) {
|
|
@@ -309,7 +309,14 @@ export class Panel {
|
|
|
309
309
|
} else {
|
|
310
310
|
state = toState(state);
|
|
311
311
|
}
|
|
312
|
-
return this.applyFn(
|
|
312
|
+
return this.applyFn(
|
|
313
|
+
x,
|
|
314
|
+
y,
|
|
315
|
+
width,
|
|
316
|
+
height,
|
|
317
|
+
(x, y, cell) => ({symbol: cell ? cell.symbol : emptySymbol, state}),
|
|
318
|
+
options
|
|
319
|
+
);
|
|
313
320
|
}
|
|
314
321
|
|
|
315
322
|
fillNonEmptyState(x, y, width, height, options) {
|
|
@@ -328,7 +335,7 @@ export class Panel {
|
|
|
328
335
|
} else {
|
|
329
336
|
state = toState(state);
|
|
330
337
|
}
|
|
331
|
-
return this.applyFn(x, y, width, height, (x, y, cell) => cell && {symbol: cell.symbol, state});
|
|
338
|
+
return this.applyFn(x, y, width, height, (x, y, cell) => cell && {symbol: cell.symbol, state}, options);
|
|
332
339
|
}
|
|
333
340
|
|
|
334
341
|
combineStateBefore(x, y, width, height, options) {
|
|
@@ -347,10 +354,16 @@ export class Panel {
|
|
|
347
354
|
} else {
|
|
348
355
|
state = toState(state);
|
|
349
356
|
}
|
|
350
|
-
return this.applyFn(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
357
|
+
return this.applyFn(
|
|
358
|
+
x,
|
|
359
|
+
y,
|
|
360
|
+
width,
|
|
361
|
+
height,
|
|
362
|
+
(x, y, cell) =>
|
|
363
|
+
cell
|
|
364
|
+
? {symbol: cell.symbol, state: combineStates(state, cell.state)}
|
|
365
|
+
: {symbol: emptySymbol, state: combineStates(state, emptyState)},
|
|
366
|
+
options
|
|
354
367
|
);
|
|
355
368
|
}
|
|
356
369
|
|
|
@@ -370,14 +383,20 @@ export class Panel {
|
|
|
370
383
|
} else {
|
|
371
384
|
state = toState(state);
|
|
372
385
|
}
|
|
373
|
-
return this.applyFn(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
386
|
+
return this.applyFn(
|
|
387
|
+
x,
|
|
388
|
+
y,
|
|
389
|
+
width,
|
|
390
|
+
height,
|
|
391
|
+
(x, y, cell) =>
|
|
392
|
+
cell
|
|
393
|
+
? {symbol: cell.symbol, state: combineStates(cell.state, state)}
|
|
394
|
+
: {symbol: emptySymbol, state: combineStates(emptyState, state)},
|
|
395
|
+
options
|
|
377
396
|
);
|
|
378
397
|
}
|
|
379
398
|
|
|
380
|
-
clear(x, y, width, height) {
|
|
399
|
+
clear(x, y, width, height, options) {
|
|
381
400
|
// normalize arguments
|
|
382
401
|
if (typeof x != 'number') {
|
|
383
402
|
x = y = 0;
|
|
@@ -394,7 +413,7 @@ export class Panel {
|
|
|
394
413
|
height = this.height;
|
|
395
414
|
}
|
|
396
415
|
|
|
397
|
-
return this.applyFn(x, y, width, height, () => null);
|
|
416
|
+
return this.applyFn(x, y, width, height, () => null, options);
|
|
398
417
|
}
|
|
399
418
|
|
|
400
419
|
padLeft(n) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import parse, {matchCsiNoGroups} from './parse.js';
|
|
2
|
+
import {split} from './split.js';
|
|
3
|
+
|
|
4
|
+
export const clip = (s, width, options = {}) => {
|
|
5
|
+
const {includeLastCommand = false, matcher = matchCsiNoGroups} = options;
|
|
6
|
+
|
|
7
|
+
let counter = 0;
|
|
8
|
+
for (const {start, string, match} of parse(s, matcher)) {
|
|
9
|
+
if (counter >= width)
|
|
10
|
+
return match ? s.substring(0, match.index + (includeLastCommand ? match[0].length : 0)) : s.substring(0, start);
|
|
11
|
+
const prev = split(string, options),
|
|
12
|
+
newCounter = counter + prev.width;
|
|
13
|
+
if (newCounter === width)
|
|
14
|
+
return match ? s.substring(0, match.index + (includeLastCommand ? match[0].length : 0)) : s;
|
|
15
|
+
if (newCounter < width) {
|
|
16
|
+
counter = newCounter;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
let result = '';
|
|
20
|
+
for (const grapheme of prev.graphemes) {
|
|
21
|
+
if (counter + grapheme.width > width) break;
|
|
22
|
+
result += grapheme.symbol;
|
|
23
|
+
counter += grapheme.width;
|
|
24
|
+
}
|
|
25
|
+
return s.substring(0, start) + result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return s;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default clip;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const matchCsiNoGroups = /\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]/g;
|
|
2
|
+
export const matchCsiNoSgrNoGroups = /\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x6C\x6E-\x7E]/g;
|
|
3
|
+
|
|
4
|
+
export function* parse(s, matcher = matchCsiNoGroups) {
|
|
5
|
+
s = String(s);
|
|
6
|
+
let start = 0;
|
|
7
|
+
matcher.lastIndex = 0;
|
|
8
|
+
for (const match of s.matchAll(matcher)) {
|
|
9
|
+
const string = s.substring(start, match.index);
|
|
10
|
+
yield {start, string, match};
|
|
11
|
+
start = match.index + match[0].length;
|
|
12
|
+
}
|
|
13
|
+
yield {start, string: s.substring(start), match: null};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default parse;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Loosely adapted from https://www.npmjs.com/package/string-width by
|
|
2
|
+
// [Sindre Sorhus](https://www.npmjs.com/~sindresorhus) under the MIT license.
|
|
3
|
+
|
|
4
|
+
let emojiRegex = null,
|
|
5
|
+
eastAsianWidth = null;
|
|
6
|
+
if (!globalThis.Bun) {
|
|
7
|
+
try {
|
|
8
|
+
emojiRegex = (await import('emoji-regex')).default();
|
|
9
|
+
} catch (error) {
|
|
10
|
+
// squelch
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
eastAsianWidth = (await import('get-east-asian-width')).eastAsianWidth;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
// squelch
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const segmenter = new Intl.Segmenter();
|
|
20
|
+
|
|
21
|
+
export const split = (s, options = {}) => {
|
|
22
|
+
s = String(s);
|
|
23
|
+
if (!s) return {graphemes: [], width: 0};
|
|
24
|
+
|
|
25
|
+
const {ignoreControlSymbols = false, ambiguousAsWide = false} = options,
|
|
26
|
+
eastAsianWidthOptions = {ambiguousAsWide},
|
|
27
|
+
bunStringWidthOptions = {ambiguousAsNarrow: !ambiguousAsWide};
|
|
28
|
+
|
|
29
|
+
const graphemes = [];
|
|
30
|
+
let width = 0;
|
|
31
|
+
for (const {segment} of segmenter.segment(s)) {
|
|
32
|
+
const codePoint = segment.codePointAt(0);
|
|
33
|
+
// Control characters: C0, C1
|
|
34
|
+
if (ignoreControlSymbols && (codePoint < 0x20 || (codePoint >= 0x7f && codePoint <= 0x9f))) continue;
|
|
35
|
+
// Combining characters
|
|
36
|
+
if (
|
|
37
|
+
(codePoint >= 0x300 && codePoint <= 0x36f) ||
|
|
38
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
39
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
40
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
41
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f)
|
|
42
|
+
) {
|
|
43
|
+
if (graphemes.length) graphemes[graphemes.length - 1].symbol += segment;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (globalThis.Bun) {
|
|
47
|
+
const w = Bun.stringWidth(segment, bunStringWidthOptions);
|
|
48
|
+
graphemes.push({symbol: segment, width: w});
|
|
49
|
+
width += w;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (emojiRegex && ((emojiRegex.lastIndex = 0), emojiRegex.test(segment))) {
|
|
53
|
+
graphemes.push({symbol: segment, width: 2});
|
|
54
|
+
width += 2;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (eastAsianWidth) {
|
|
58
|
+
const w = eastAsianWidth(codePoint, eastAsianWidthOptions);
|
|
59
|
+
graphemes.push({symbol: segment, width: w});
|
|
60
|
+
width += w;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
graphemes.push({symbol: segment, width: 1});
|
|
64
|
+
++width;
|
|
65
|
+
}
|
|
66
|
+
return {graphemes, width};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const size = (s, options = {}) => {
|
|
70
|
+
s = String(s);
|
|
71
|
+
if (!s) return 0;
|
|
72
|
+
|
|
73
|
+
const {ignoreControlSymbols = false, ambiguousAsWide = false} = options,
|
|
74
|
+
eastAsianWidthOptions = {ambiguousAsWide},
|
|
75
|
+
bunStringWidthOptions = {ambiguousAsNarrow: !ambiguousAsWide};
|
|
76
|
+
|
|
77
|
+
let width = 0;
|
|
78
|
+
for (const {segment} of segmenter.segment(s)) {
|
|
79
|
+
const codePoint = segment.codePointAt(0);
|
|
80
|
+
// Control characters: C0, C1
|
|
81
|
+
if (ignoreControlSymbols && (codePoint < 0x20 || (codePoint >= 0x7f && codePoint <= 0x9f))) continue;
|
|
82
|
+
// Combining characters
|
|
83
|
+
if (
|
|
84
|
+
(codePoint >= 0x300 && codePoint <= 0x36f) ||
|
|
85
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
86
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
87
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
88
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f)
|
|
89
|
+
) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (globalThis.Bun) {
|
|
93
|
+
width += Bun.stringWidth(segment, bunStringWidthOptions);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (emojiRegex && ((emojiRegex.lastIndex = 0), emojiRegex.test(segment))) {
|
|
97
|
+
width += 2;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (eastAsianWidth) {
|
|
101
|
+
width += eastAsianWidth(codePoint, eastAsianWidthOptions);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
++width;
|
|
105
|
+
}
|
|
106
|
+
return width;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default split;
|
package/src/strings.js
CHANGED
|
@@ -1,49 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {size} from './strings/split.js';
|
|
2
|
+
import parse, {matchCsiNoGroups, matchCsiNoSgrNoGroups} from './strings/parse.js';
|
|
3
|
+
import clip from './strings/clip.js';
|
|
3
4
|
|
|
4
|
-
export const getLength = (s, matcher
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
matcher.lastIndex = 0;
|
|
9
|
-
for (const match of s.matchAll(matcher)) {
|
|
10
|
-
counter += [...s.substring(start, match.index)].length;
|
|
11
|
-
start = match.index + match[0].length;
|
|
5
|
+
export const getLength = (s, matcher) => {
|
|
6
|
+
let counter = 0;
|
|
7
|
+
for (const {string} of parse(s, matcher)) {
|
|
8
|
+
counter += size(string);
|
|
12
9
|
}
|
|
13
|
-
counter += [...s.substring(start)].length;
|
|
14
10
|
return counter;
|
|
15
11
|
};
|
|
16
12
|
|
|
17
|
-
export const getMaxLength = (strings, matcher
|
|
18
|
-
|
|
13
|
+
export const getMaxLength = (strings, matcher) =>
|
|
14
|
+
strings.reduce((acc, s) => Math.max(acc, getLength(s, matcher)), 0);
|
|
19
15
|
|
|
20
|
-
export const
|
|
21
|
-
s
|
|
22
|
-
let counter = 0,
|
|
23
|
-
start = 0;
|
|
24
|
-
matcher.lastIndex = 0;
|
|
25
|
-
for (const match of s.matchAll(matcher)) {
|
|
26
|
-
if (counter >= width) return s.substring(0, match.index + (includeLastCommand ? match[0].length : 0));
|
|
27
|
-
const prev = [...s.substring(start, match.index)];
|
|
28
|
-
counter += prev.length;
|
|
29
|
-
if (includeLastCommand ? counter > width : counter >= width) {
|
|
30
|
-
const diff = width - counter,
|
|
31
|
-
end = start + (diff ? prev.slice(0, prev.length + diff).join('').length : prev.length);
|
|
32
|
-
return s.substring(0, end);
|
|
33
|
-
}
|
|
34
|
-
start = match.index + match[0].length;
|
|
35
|
-
}
|
|
36
|
-
if (counter >= width) return s.substring(0, start);
|
|
37
|
-
const prev = [...s.substring(start)];
|
|
38
|
-
if (counter + prev.length > width) {
|
|
39
|
-
const end = start + prev.slice(0, width - counter).join('').length;
|
|
40
|
-
return s.substring(0, end);
|
|
41
|
-
}
|
|
42
|
-
return s; // unchanged
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const clipStrings = (strings, width, includeLastCommand, matcher = matchCsiNoGroups) =>
|
|
46
|
-
strings.map(s => clip(s, width, includeLastCommand, matcher));
|
|
16
|
+
export const clipStrings = (strings, width, options) =>
|
|
17
|
+
strings.map(s => clip(s, width, options));
|
|
47
18
|
|
|
48
19
|
export const toStrings = s => {
|
|
49
20
|
main: for (;;) {
|
|
@@ -70,3 +41,5 @@ export const toStrings = s => {
|
|
|
70
41
|
}
|
|
71
42
|
return [];
|
|
72
43
|
};
|
|
44
|
+
|
|
45
|
+
export {clip, matchCsiNoGroups, matchCsiNoSgrNoGroups};
|