git-watchtower 2.3.10 → 2.3.11
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/package.json +1 -1
- package/src/ui/ansi.js +128 -9
package/package.json
CHANGED
package/src/ui/ansi.js
CHANGED
|
@@ -358,12 +358,106 @@ function sanitizeForRender(str) {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
361
|
+
* Display width of a single Unicode code point in terminal columns.
|
|
362
|
+
* Returns 0 for combining marks / variation selectors / ZWJ, 2 for East
|
|
363
|
+
* Asian Wide / Fullwidth and emoji-presentation code points, 1 otherwise.
|
|
364
|
+
* Used by visibleLength's grapheme iterator.
|
|
365
|
+
* @param {number} cp
|
|
366
|
+
* @returns {number}
|
|
367
|
+
* @private
|
|
368
|
+
*/
|
|
369
|
+
function _codePointWidth(cp) {
|
|
370
|
+
if (cp == null) return 0;
|
|
371
|
+
// C0 / DEL — defensively zero-width (stripAnsi already drops them).
|
|
372
|
+
if (cp < 0x20 || cp === 0x7f) return 0;
|
|
373
|
+
|
|
374
|
+
// Combining marks, variation selectors, ZWJ — zero-width.
|
|
375
|
+
if (
|
|
376
|
+
(cp >= 0x0300 && cp <= 0x036F) || // Combining Diacritical Marks
|
|
377
|
+
(cp >= 0x1AB0 && cp <= 0x1AFF) || // Combining Diacritical Marks Extended
|
|
378
|
+
(cp >= 0x1DC0 && cp <= 0x1DFF) || // Combining Diacritical Marks Supplement
|
|
379
|
+
(cp >= 0x20D0 && cp <= 0x20FF) || // Combining Diacritical Marks for Symbols
|
|
380
|
+
(cp >= 0xFE00 && cp <= 0xFE0F) || // Variation Selectors (incl. emoji presentation)
|
|
381
|
+
(cp >= 0xFE20 && cp <= 0xFE2F) || // Combining Half Marks
|
|
382
|
+
cp === 0x200D // ZWJ
|
|
383
|
+
) return 0;
|
|
384
|
+
|
|
385
|
+
// East Asian Wide / Fullwidth / emoji — two columns.
|
|
386
|
+
if (
|
|
387
|
+
(cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
|
|
388
|
+
(cp >= 0x2E80 && cp <= 0x303E) || // CJK Radicals etc
|
|
389
|
+
(cp >= 0x3041 && cp <= 0x33FF) || // CJK Symbols / Hiragana / Katakana / Bopomofo / Hangul / CJK
|
|
390
|
+
(cp >= 0x3400 && cp <= 0x4DBF) || // CJK Unified Ideographs Ext A
|
|
391
|
+
(cp >= 0x4E00 && cp <= 0x9FFF) || // CJK Unified Ideographs
|
|
392
|
+
(cp >= 0xA000 && cp <= 0xA4CF) || // Yi Syllables
|
|
393
|
+
(cp >= 0xAC00 && cp <= 0xD7A3) || // Hangul Syllables
|
|
394
|
+
(cp >= 0xF900 && cp <= 0xFAFF) || // CJK Compatibility Ideographs
|
|
395
|
+
(cp >= 0xFE30 && cp <= 0xFE4F) || // CJK Compatibility Forms
|
|
396
|
+
(cp >= 0xFF00 && cp <= 0xFF60) || // Fullwidth Forms
|
|
397
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) || // Fullwidth Signs
|
|
398
|
+
(cp >= 0x1F300 && cp <= 0x1F5FF) || // Misc Symbols and Pictographs
|
|
399
|
+
(cp >= 0x1F600 && cp <= 0x1F64F) || // Emoticons
|
|
400
|
+
(cp >= 0x1F680 && cp <= 0x1F6FF) || // Transport
|
|
401
|
+
(cp >= 0x1F900 && cp <= 0x1F9FF) || // Supplemental Symbols and Pictographs
|
|
402
|
+
(cp >= 0x1FA00 && cp <= 0x1FAFF) || // Symbols and Pictographs Extended-A
|
|
403
|
+
(cp >= 0x1F1E6 && cp <= 0x1F1FF) || // Regional indicators (flags)
|
|
404
|
+
(cp >= 0x1F3FB && cp <= 0x1F3FF) || // Emoji modifiers (skin tone)
|
|
405
|
+
(cp >= 0x20000 && cp <= 0x2FFFD) || // CJK Ideographs Extension B-F
|
|
406
|
+
(cp >= 0x30000 && cp <= 0x3FFFD) // CJK Ideographs Extension G-H
|
|
407
|
+
) return 2;
|
|
408
|
+
|
|
409
|
+
return 1;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Reused grapheme segmenter — instantiating per-call would be wasteful for
|
|
413
|
+
// strings hit on every render. Default locale is fine; we only care about
|
|
414
|
+
// boundaries, not script-aware ordering.
|
|
415
|
+
const _graphemeSegmenter = (typeof Intl !== 'undefined' && Intl.Segmenter)
|
|
416
|
+
? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
|
|
417
|
+
: null;
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get the visible width of a string in terminal columns, excluding ANSI
|
|
421
|
+
* codes and counting wide characters (CJK ideographs, emoji, fullwidth)
|
|
422
|
+
* as 2 columns. Combining marks, variation selectors, and ZWJ are 0-width.
|
|
423
|
+
* Grapheme clusters that contain at least one wide code point clamp to 2
|
|
424
|
+
* — so a ZWJ family (e.g. 👨👩👧, which is 5 code points = 13 UTF-16 units)
|
|
425
|
+
* counts as 2, matching how terminals render it.
|
|
426
|
+
*
|
|
427
|
+
* Without this, branch names with emoji/CJK misalign sparklines, badges
|
|
428
|
+
* and box borders because the renderer's `padRight`/truncation math drifts
|
|
429
|
+
* by one or more columns per non-ASCII character.
|
|
430
|
+
*
|
|
431
|
+
* @param {string} str - String potentially containing ANSI codes and unicode
|
|
432
|
+
* @returns {number} Visible width in terminal columns
|
|
364
433
|
*/
|
|
365
434
|
function visibleLength(str) {
|
|
366
|
-
|
|
435
|
+
const stripped = stripAnsi(str);
|
|
436
|
+
// ASCII fast path — every char is 1 column, regex avoids the Segmenter
|
|
437
|
+
// setup cost on the renderer's hot path.
|
|
438
|
+
if (/^[\x20-\x7e]*$/.test(stripped)) return stripped.length;
|
|
439
|
+
if (!_graphemeSegmenter) {
|
|
440
|
+
// Pre-Intl.Segmenter fallback (shouldn't hit on Node 16+).
|
|
441
|
+
let width = 0;
|
|
442
|
+
for (const ch of stripped) width += _codePointWidth(ch.codePointAt(0));
|
|
443
|
+
return width;
|
|
444
|
+
}
|
|
445
|
+
let total = 0;
|
|
446
|
+
for (const { segment } of _graphemeSegmenter.segment(stripped)) {
|
|
447
|
+
let clusterTotal = 0;
|
|
448
|
+
let clusterHasWide = false;
|
|
449
|
+
for (const ch of segment) {
|
|
450
|
+
const w = _codePointWidth(ch.codePointAt(0));
|
|
451
|
+
if (w === 2) clusterHasWide = true;
|
|
452
|
+
clusterTotal += w;
|
|
453
|
+
}
|
|
454
|
+
// ZWJ-joined emoji clusters (e.g. family / flag / skin-tone) sum to
|
|
455
|
+
// more than 2 by code point, but render as a single 2-column glyph.
|
|
456
|
+
// Cap them at 2; non-wide clusters (combining marks on a base char)
|
|
457
|
+
// still take at least 1 column.
|
|
458
|
+
total += clusterHasWide ? 2 : Math.max(1, clusterTotal);
|
|
459
|
+
}
|
|
460
|
+
return total;
|
|
367
461
|
}
|
|
368
462
|
|
|
369
463
|
/**
|
|
@@ -382,14 +476,39 @@ function truncate(str, maxLen, suffix = '…') {
|
|
|
382
476
|
// raw input with cursor/screen-control sequences in it. SGR survives.
|
|
383
477
|
const safeStr = sanitizeForRender(str);
|
|
384
478
|
const visible = stripAnsi(safeStr);
|
|
385
|
-
|
|
479
|
+
|
|
480
|
+
// Compare in display columns, not UTF-16 code units, so a string of CJK
|
|
481
|
+
// ideographs (e.g. "中文测试" — 4 code units, 8 columns) is correctly
|
|
482
|
+
// recognised as needing truncation in a 4-column slot.
|
|
483
|
+
if (visibleLength(visible) <= maxLen) {
|
|
386
484
|
return safeStr;
|
|
387
485
|
}
|
|
388
486
|
|
|
389
|
-
// Long-string path: drop ALL escapes (including SGR),
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
|
|
487
|
+
// Long-string path: drop ALL escapes (including SGR), accumulate
|
|
488
|
+
// graphemes up to the column budget, append ellipsis + reset. Walking
|
|
489
|
+
// graphemes (not code units) keeps wide characters from being split
|
|
490
|
+
// mid-glyph and keeps the truncated width <= maxLen even for emoji
|
|
491
|
+
// and CJK input.
|
|
492
|
+
const suffixWidth = visibleLength(suffix);
|
|
493
|
+
const budget = Math.max(0, maxLen - suffixWidth);
|
|
494
|
+
let acc = '';
|
|
495
|
+
let used = 0;
|
|
496
|
+
if (_graphemeSegmenter) {
|
|
497
|
+
for (const { segment } of _graphemeSegmenter.segment(visible)) {
|
|
498
|
+
const w = visibleLength(segment);
|
|
499
|
+
if (used + w > budget) break;
|
|
500
|
+
acc += segment;
|
|
501
|
+
used += w;
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
for (const ch of visible) {
|
|
505
|
+
const w = _codePointWidth(ch.codePointAt(0));
|
|
506
|
+
if (used + w > budget) break;
|
|
507
|
+
acc += ch;
|
|
508
|
+
used += w;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return acc + suffix + ansi.reset;
|
|
393
512
|
}
|
|
394
513
|
|
|
395
514
|
/**
|