@oh-my-pi/pi-tui 17.3.8 → 17.4.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/CHANGELOG.md +13 -0
- package/LICENSE +23 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/components/composer/borderless.d.ts +8 -0
- package/dist/types/components/composer/box.d.ts +2 -0
- package/dist/types/components/composer/claude.d.ts +8 -0
- package/dist/types/components/composer/field.d.ts +3 -0
- package/dist/types/components/composer/index.d.ts +9 -0
- package/dist/types/components/composer/pi.d.ts +2 -0
- package/dist/types/components/composer/rail.d.ts +3 -0
- package/dist/types/components/composer/registry.d.ts +12 -0
- package/dist/types/components/composer/rule.d.ts +7 -0
- package/dist/types/components/composer/types.d.ts +90 -0
- package/dist/types/components/editor.d.ts +10 -9
- package/dist/types/components/settings-list.d.ts +7 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/tui.d.ts +7 -0
- package/dist/types/utils.d.ts +0 -1
- package/package.json +6 -4
- package/src/components/composer/borderless.ts +37 -0
- package/src/components/composer/box.ts +85 -0
- package/src/components/composer/claude.ts +39 -0
- package/src/components/composer/field.ts +47 -0
- package/src/components/composer/index.ts +9 -0
- package/src/components/composer/pi.ts +37 -0
- package/src/components/composer/rail.ts +48 -0
- package/src/components/composer/registry.ts +46 -0
- package/src/components/composer/rule.ts +55 -0
- package/src/components/composer/types.ts +98 -0
- package/src/components/editor.ts +103 -92
- package/src/components/settings-list.ts +43 -11
- package/src/index.ts +1 -0
- package/src/tui.ts +84 -6
- package/src/utils.ts +53 -58
package/src/tui.ts
CHANGED
|
@@ -216,6 +216,13 @@ export interface NativeScrollbackLiveRegion {
|
|
|
216
216
|
getNativeScrollbackLiveRegionStart(): number | undefined;
|
|
217
217
|
/** Keeps the mutable suffix viewport-local instead of recording frozen snapshots. */
|
|
218
218
|
isNativeScrollbackLiveRegionPinned?(): boolean;
|
|
219
|
+
/**
|
|
220
|
+
* Local row where viewport pinning begins. When omitted, pinning (if
|
|
221
|
+
* reported) starts at {@link getNativeScrollbackLiveRegionStart}. A nested
|
|
222
|
+
* transcript uses this to keep an earlier unpinned live seam while still
|
|
223
|
+
* capping commits at a later pinned dashboard (hub wait, todo snapshot).
|
|
224
|
+
*/
|
|
225
|
+
getNativeScrollbackLiveRegionPinnedStart?(): number | undefined;
|
|
219
226
|
}
|
|
220
227
|
|
|
221
228
|
export interface NativeScrollbackCommittedRows {
|
|
@@ -278,6 +285,13 @@ function getNativeScrollbackLiveRegionStart(component: Component): number | unde
|
|
|
278
285
|
return (component as Component & Partial<NativeScrollbackLiveRegion>).getNativeScrollbackLiveRegionStart?.();
|
|
279
286
|
}
|
|
280
287
|
|
|
288
|
+
function getNativeScrollbackLiveRegionPinnedStart(component: Component): number | undefined {
|
|
289
|
+
const start = (
|
|
290
|
+
component as Component & Partial<NativeScrollbackLiveRegion>
|
|
291
|
+
).getNativeScrollbackLiveRegionPinnedStart?.();
|
|
292
|
+
return start === undefined || !Number.isFinite(start) ? undefined : start;
|
|
293
|
+
}
|
|
294
|
+
|
|
281
295
|
/**
|
|
282
296
|
* Opt-in stability report for components that mutate their returned render
|
|
283
297
|
* array in place across frames (instead of returning a fresh array per
|
|
@@ -901,6 +915,8 @@ interface FrameSegment {
|
|
|
901
915
|
widthEpochRevision?: number;
|
|
902
916
|
liveLocalStart?: number;
|
|
903
917
|
liveRegionPinned: boolean;
|
|
918
|
+
/** Local pin start; when omitted, pinning begins at `liveLocalStart`. */
|
|
919
|
+
liveRegionPinnedStart?: number;
|
|
904
920
|
}
|
|
905
921
|
|
|
906
922
|
/** Depth-first identity search through `Container`-shaped children. */
|
|
@@ -1372,6 +1388,13 @@ export class TUI extends Container {
|
|
|
1372
1388
|
#multiplexerResizeTimer: RenderTimer | undefined;
|
|
1373
1389
|
#deferredForcedClearScrollback = false;
|
|
1374
1390
|
#multiplexerResizeHasPendingRender = false;
|
|
1391
|
+
// Rows a mux pane pushed into its own scrollback on a height-only shrink
|
|
1392
|
+
// without the engine committing them, plus the commit seam at push time.
|
|
1393
|
+
// A later height grow pulls the pane's scrollback tail back into the grid:
|
|
1394
|
+
// the pushed rows come out first — but only while no commit has buried
|
|
1395
|
+
// them — and only the remainder of the pull removes committed rows.
|
|
1396
|
+
#muxPushedRows = 0;
|
|
1397
|
+
#muxPushSeam = 0;
|
|
1375
1398
|
// True from the first SIGWINCH of a non-multiplexer drag until the settle
|
|
1376
1399
|
// timer fires. While set, every `#doRender` short-circuits to the viewport
|
|
1377
1400
|
// fast path (`#renderResizeViewport`) instead of an authoritative full
|
|
@@ -1661,12 +1684,14 @@ export class TUI extends Container {
|
|
|
1661
1684
|
let childLines: readonly string[];
|
|
1662
1685
|
let liveLocalStart: number | undefined;
|
|
1663
1686
|
let liveRegionPinned = false;
|
|
1687
|
+
let liveRegionPinnedStart: number | undefined;
|
|
1664
1688
|
let widthEpochRevision: number | undefined;
|
|
1665
1689
|
let reported: number | undefined;
|
|
1666
1690
|
if (reuse) {
|
|
1667
1691
|
childLines = previous.lines;
|
|
1668
1692
|
liveLocalStart = previous.liveLocalStart;
|
|
1669
1693
|
liveRegionPinned = previous.liveRegionPinned;
|
|
1694
|
+
liveRegionPinnedStart = previous.liveRegionPinnedStart;
|
|
1670
1695
|
widthEpochRevision = previous.widthEpochRevision;
|
|
1671
1696
|
} else {
|
|
1672
1697
|
// Feed the engine's committed-row claim (from the previous frame's
|
|
@@ -1697,6 +1722,15 @@ export class TUI extends Container {
|
|
|
1697
1722
|
liveRegionPinned =
|
|
1698
1723
|
(child as Component & Partial<NativeScrollbackLiveRegion>).isNativeScrollbackLiveRegionPinned?.() ===
|
|
1699
1724
|
true;
|
|
1725
|
+
if (liveRegionPinned) {
|
|
1726
|
+
const pinStart = getNativeScrollbackLiveRegionPinnedStart(child);
|
|
1727
|
+
if (pinStart !== undefined) {
|
|
1728
|
+
liveRegionPinnedStart = Math.max(
|
|
1729
|
+
liveLocalStart,
|
|
1730
|
+
Math.min(childLines.length, Math.trunc(pinStart)),
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1700
1734
|
}
|
|
1701
1735
|
// Consume the stability report unconditionally for implementers:
|
|
1702
1736
|
// reading re-bases the component's baseline to the state this
|
|
@@ -1722,7 +1756,7 @@ export class TUI extends Container {
|
|
|
1722
1756
|
// even when an earlier unpinned seam won the topmost merge above:
|
|
1723
1757
|
// its rows (a growing anchored panel) must never reach scrollback.
|
|
1724
1758
|
if (liveRegionPinned && this.#nativeScrollbackPinnedBoundary === undefined) {
|
|
1725
|
-
this.#nativeScrollbackPinnedBoundary =
|
|
1759
|
+
this.#nativeScrollbackPinnedBoundary = offset + (liveRegionPinnedStart ?? liveLocalStart);
|
|
1726
1760
|
}
|
|
1727
1761
|
}
|
|
1728
1762
|
if (chainStable) {
|
|
@@ -1754,6 +1788,7 @@ export class TUI extends Container {
|
|
|
1754
1788
|
widthEpochRevision,
|
|
1755
1789
|
liveLocalStart,
|
|
1756
1790
|
liveRegionPinned,
|
|
1791
|
+
liveRegionPinnedStart,
|
|
1757
1792
|
};
|
|
1758
1793
|
offset += childLines.length;
|
|
1759
1794
|
}
|
|
@@ -3761,6 +3796,37 @@ export class TUI extends Container {
|
|
|
3761
3796
|
!isMultiplexerSession() &&
|
|
3762
3797
|
(committedRowsResynced || frameLength <= this.#committedRows);
|
|
3763
3798
|
const fullPaint = firstPaint || replaceRequested || geometryRebuild || divergenceRebuild;
|
|
3799
|
+
// Height-only mux resizes move rows between the pane's scrollback and its
|
|
3800
|
+
// grid. A shrink with a full grid pushes the grid-top rows into pane
|
|
3801
|
+
// scrollback without a commit; a grow pulls the scrollback tail back into
|
|
3802
|
+
// the grid, where the forced in-place window rewrite overwrites it — gone
|
|
3803
|
+
// from history. The scrollback is a stack: pushed rows sit at the tail
|
|
3804
|
+
// only until the next commit buries them, so they shield a pull only
|
|
3805
|
+
// while the commit seam has not moved since the push. The unshielded
|
|
3806
|
+
// remainder removes committed rows — drop the commit seam by it and let
|
|
3807
|
+
// the next chunk re-commit them. A short frame gains blank rows instead
|
|
3808
|
+
// of pulling.
|
|
3809
|
+
if (fullPaint || widthChanged) {
|
|
3810
|
+
this.#muxPushedRows = 0;
|
|
3811
|
+
} else if (
|
|
3812
|
+
geometryChanged &&
|
|
3813
|
+
this.#previousHeight > 0 &&
|
|
3814
|
+
this.#previousFrameLength - this.#windowTopRow >= this.#previousHeight
|
|
3815
|
+
) {
|
|
3816
|
+
if (this.#committedRows !== this.#muxPushSeam) this.#muxPushedRows = 0;
|
|
3817
|
+
if (height < this.#previousHeight) {
|
|
3818
|
+
this.#muxPushedRows += this.#previousHeight - height;
|
|
3819
|
+
this.#muxPushSeam = this.#committedRows;
|
|
3820
|
+
} else if (height > this.#previousHeight) {
|
|
3821
|
+
const pull = height - this.#previousHeight;
|
|
3822
|
+
const fromPushed = Math.min(pull, this.#muxPushedRows);
|
|
3823
|
+
this.#muxPushedRows -= fromPushed;
|
|
3824
|
+
const fromCommitted = Math.min(pull - fromPushed, this.#committedRows);
|
|
3825
|
+
this.#committedRows -= fromCommitted;
|
|
3826
|
+
this.#committedPrefix.length = this.#committedRows;
|
|
3827
|
+
this.#muxPushSeam = this.#committedRows;
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3764
3830
|
let windowTop: number;
|
|
3765
3831
|
let chunkTo: number;
|
|
3766
3832
|
let widthEpochAppendFrom = 0;
|
|
@@ -3828,11 +3894,23 @@ export class TUI extends Container {
|
|
|
3828
3894
|
// the viewport. Rebase the commit seam to that exposed frame tail before
|
|
3829
3895
|
// the forced rewrite; flooring at the old seam would paint only the live
|
|
3830
3896
|
// suffix followed by blanks, then preserve that gap on every stream tick.
|
|
3831
|
-
committedPrefixResliced = true;
|
|
3832
3897
|
windowTop = Math.max(0, frameLength - height);
|
|
3833
3898
|
chunkTo = windowTop;
|
|
3834
3899
|
this.#committedRows = windowTop;
|
|
3835
|
-
|
|
3900
|
+
if (widthChanged) {
|
|
3901
|
+
// A rewrap invalidated the recorded bytes: re-base the audit prefix
|
|
3902
|
+
// at the new width so the accepted wrap drift does not read as a
|
|
3903
|
+
// violation on the next ordinary frame.
|
|
3904
|
+
committedPrefixResliced = true;
|
|
3905
|
+
this.#committedPrefix = rawFrame.slice(0, windowTop);
|
|
3906
|
+
} else {
|
|
3907
|
+
// Height-only reflow: the pane did not rewrap, so the recorded
|
|
3908
|
+
// bytes are still the true tape record. Truncate instead of
|
|
3909
|
+
// reslicing from the current frame — replacing the record with
|
|
3910
|
+
// live content would bless drifted frozen snapshots as verified
|
|
3911
|
+
// and skip their finalize-time recommit (rows lost from history).
|
|
3912
|
+
this.#committedPrefix.length = Math.min(this.#committedPrefix.length, windowTop);
|
|
3913
|
+
}
|
|
3836
3914
|
} else {
|
|
3837
3915
|
// Re-anchor to the frame tail, floored at the committed boundary: a
|
|
3838
3916
|
// shrink (or overlay close) pulls the window back down, but never
|
|
@@ -3845,15 +3923,15 @@ export class TUI extends Container {
|
|
|
3845
3923
|
// record; nothing that was painted may vanish. Overlays freeze
|
|
3846
3924
|
// commits: composited rows must never enter history, and the hidden
|
|
3847
3925
|
// gap backfills via the chunk once the overlay closes. A multiplexer
|
|
3848
|
-
// resize also commits nothing — the pane keeps its own
|
|
3849
|
-
//
|
|
3926
|
+
// resize also commits nothing — the pane keeps its own history — and
|
|
3927
|
+
// a width rewrap re-bases the audit prefix at the new width so the
|
|
3850
3928
|
// accepted wrap drift does not read as a violation on the next
|
|
3851
3929
|
// ordinary frame.
|
|
3852
3930
|
chunkTo =
|
|
3853
3931
|
hasVisibleOverlay || geometryChanged
|
|
3854
3932
|
? this.#committedRows
|
|
3855
3933
|
: Math.min(windowTop, Math.max(this.#committedRows, commitCeiling));
|
|
3856
|
-
if (
|
|
3934
|
+
if (widthChanged) {
|
|
3857
3935
|
committedPrefixResliced = true;
|
|
3858
3936
|
this.#committedPrefix = rawFrame.slice(0, this.#committedRows);
|
|
3859
3937
|
}
|
package/src/utils.ts
CHANGED
|
@@ -225,9 +225,7 @@ export function getSegmenter(): Intl.Segmenter {
|
|
|
225
225
|
// added back so width matches the native truncate/slice/wrap helpers.
|
|
226
226
|
const OSC66_SPAN_REGEX = /\x1b\]66;([^;]*);([\s\S]*?)(?:\x07|\x1b\\)/g;
|
|
227
227
|
const OSC66_PREFIX = "\x1b]66;";
|
|
228
|
-
const
|
|
229
|
-
const TAB = "\t";
|
|
230
|
-
const LONG_WIDTH_FAST_PATH_MIN = 128;
|
|
228
|
+
const PRINTABLE_ASCII_REGEX = /^[\u0020-\u007e]*$/;
|
|
231
229
|
|
|
232
230
|
// Pin Bun.stringWidth semantics to the native width engine and guard against Bun
|
|
233
231
|
// default drift: strip ANSI/OSC (don't count escape bytes) and treat
|
|
@@ -243,8 +241,6 @@ const STRING_WIDTH_OPTS = { countAnsiEscapeCodes: false, ambiguousIsNarrow: true
|
|
|
243
241
|
// `setHangulCompatibilityJamoWidth`; mirror the same correction here so the TS
|
|
244
242
|
// width stays in parity with the native truncate/slice/wrap model — and so the
|
|
245
243
|
// hardware cursor column lands on the actual glyph during Korean IME input.
|
|
246
|
-
const HANGUL_COMPAT_JAMO_REGEX = /[\u3131-\u318e]/;
|
|
247
|
-
const HANGUL_COMPAT_JAMO_GLOBAL_REGEX = /[\u3131-\u318e]/g;
|
|
248
244
|
const HANGUL_FILLER_CODE_POINT = 0x3164;
|
|
249
245
|
// `Bun.stringWidth` counts every code point in the Compatibility Jamo block as
|
|
250
246
|
// 2 cells (even the U+3164 filler that `unicode-width` treats as zero-width).
|
|
@@ -274,19 +270,28 @@ function hangulCompatibilityJamoTargetWidth(): 1 | 2 | null {
|
|
|
274
270
|
// crates/pi-natives/src/text.rs, including the rule that the zero-width filler
|
|
275
271
|
// (U+3164) is never widened past the narrow correction (a wide terminal still
|
|
276
272
|
// renders it at its Unicode width of 0).
|
|
277
|
-
function correctHangulCompatibilityJamoWidth(
|
|
278
|
-
|
|
273
|
+
function correctHangulCompatibilityJamoWidth(
|
|
274
|
+
width: number,
|
|
275
|
+
compatibilityJamoCount: number,
|
|
276
|
+
fillerCount: number,
|
|
277
|
+
): number {
|
|
278
|
+
if (compatibilityJamoCount === 0) return width;
|
|
279
279
|
const target = hangulCompatibilityJamoTargetWidth();
|
|
280
|
-
|
|
281
|
-
HANGUL_COMPAT_JAMO_GLOBAL_REGEX.lastIndex = 0;
|
|
282
|
-
for (let m = HANGUL_COMPAT_JAMO_GLOBAL_REGEX.exec(str); m !== null; m = HANGUL_COMPAT_JAMO_GLOBAL_REGEX.exec(str)) {
|
|
283
|
-
const unicodeWidth = m[0].codePointAt(0) === HANGUL_FILLER_CODE_POINT ? 0 : 2;
|
|
284
|
-
const finalWidth = target === null || (unicodeWidth === 0 && target > 1) ? unicodeWidth : target;
|
|
285
|
-
corrected += finalWidth - HANGUL_COMPAT_JAMO_BUN_WIDTH;
|
|
286
|
-
}
|
|
287
|
-
return corrected;
|
|
280
|
+
return target === 1 ? width - compatibilityJamoCount : width - fillerCount * HANGUL_COMPAT_JAMO_BUN_WIDTH;
|
|
288
281
|
}
|
|
289
282
|
|
|
283
|
+
// Terminal redraws re-measure the same visible lines every frame, usually as
|
|
284
|
+
// the same string objects (JSC caches their hashes, so repeat lookups are
|
|
285
|
+
// O(1) — cheaper than even the ASCII fast scan). Strings longer than the
|
|
286
|
+
// length gate skip the cache entirely: hashing them costs as much as measuring
|
|
287
|
+
// them, and retaining them would pin large render buffers. Worst-case
|
|
288
|
+
// retention is MAX * MAX_LEN UTF-16 units (~2 MiB); cleared when the width
|
|
289
|
+
// configuration epoch changes.
|
|
290
|
+
const VISIBLE_WIDTH_CACHE_MAX = 2048;
|
|
291
|
+
const VISIBLE_WIDTH_CACHE_MAX_LEN = 512;
|
|
292
|
+
const visibleWidthCache = new Map<string, number>();
|
|
293
|
+
let visibleWidthCacheEpoch = widthConfigEpoch;
|
|
294
|
+
|
|
290
295
|
/**
|
|
291
296
|
* Visible width of a string in terminal columns, excluding ANSI/OSC escapes.
|
|
292
297
|
*
|
|
@@ -296,65 +301,51 @@ function correctHangulCompatibilityJamoWidth(width: number, str: string): number
|
|
|
296
301
|
*/
|
|
297
302
|
export function visibleWidth(str: string): number {
|
|
298
303
|
if (!str) return 0;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
let width = Bun.stringWidth(str, STRING_WIDTH_OPTS);
|
|
305
|
-
let tabCount = 0;
|
|
306
|
-
for (let tabIndex = str.indexOf(TAB); tabIndex !== -1; tabIndex = str.indexOf(TAB, tabIndex + 1)) {
|
|
307
|
-
tabCount++;
|
|
304
|
+
const cacheable = str.length <= VISIBLE_WIDTH_CACHE_MAX_LEN;
|
|
305
|
+
if (cacheable) {
|
|
306
|
+
if (visibleWidthCacheEpoch !== widthConfigEpoch) {
|
|
307
|
+
visibleWidthCache.clear();
|
|
308
|
+
visibleWidthCacheEpoch = widthConfigEpoch;
|
|
308
309
|
}
|
|
309
|
-
|
|
310
|
-
|
|
310
|
+
const cached = visibleWidthCache.get(str);
|
|
311
|
+
if (cached !== undefined) return cached;
|
|
311
312
|
}
|
|
312
313
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
tabCount++;
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
break;
|
|
314
|
+
// This regex compiles to a native ASCII scan, cheaper than Bun's width
|
|
315
|
+
// scanner for the overwhelmingly common source-code path.
|
|
316
|
+
if (PRINTABLE_ASCII_REGEX.test(str)) {
|
|
317
|
+
if (cacheable) {
|
|
318
|
+
if (visibleWidthCache.size >= VISIBLE_WIDTH_CACHE_MAX) visibleWidthCache.clear();
|
|
319
|
+
visibleWidthCache.set(str, str.length);
|
|
323
320
|
}
|
|
324
|
-
|
|
325
|
-
if (i === str.length) {
|
|
326
|
-
return tabCount === 0 ? str.length : str.length + tabCount * (DEFAULT_TAB_WIDTH - 1);
|
|
321
|
+
return str.length;
|
|
327
322
|
}
|
|
328
323
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
} else {
|
|
338
|
-
for (let tabIndex = str.indexOf(TAB, i + 1); tabIndex !== -1; tabIndex = str.indexOf(TAB, tabIndex + 1)) {
|
|
324
|
+
let tabCount = 0;
|
|
325
|
+
let compatibilityJamoCount = 0;
|
|
326
|
+
let fillerCount = 0;
|
|
327
|
+
let hasEsc = false;
|
|
328
|
+
for (let i = 0; i < str.length; i++) {
|
|
329
|
+
const code = str.charCodeAt(i);
|
|
330
|
+
if (code === 0x09) {
|
|
339
331
|
tabCount++;
|
|
332
|
+
} else if (code === 0x1b) {
|
|
333
|
+
hasEsc = true;
|
|
334
|
+
} else if (code >= 0x3131 && code <= 0x318e) {
|
|
335
|
+
compatibilityJamoCount++;
|
|
336
|
+
if (code === HANGUL_FILLER_CODE_POINT) fillerCount++;
|
|
340
337
|
}
|
|
341
338
|
}
|
|
342
339
|
|
|
343
|
-
// `Bun.stringWidth` is a JSC builtin (no per-call N-API number box, unlike
|
|
344
|
-
// the native scanner that traps under Bun 1.3.x GC/N-API load). It strips
|
|
345
|
-
// CSI/OSC to zero cells and shares the native engine's UAX#11 width tables.
|
|
346
340
|
let width = Bun.stringWidth(str, STRING_WIDTH_OPTS);
|
|
347
341
|
if (tabCount > 0) width += tabCount * DEFAULT_TAB_WIDTH;
|
|
348
342
|
|
|
349
|
-
|
|
350
|
-
// width)`. Matched rather than replaced to avoid reallocating the string.
|
|
351
|
-
if (str.includes(OSC66_PREFIX, i)) {
|
|
343
|
+
if (hasEsc && str.includes(OSC66_PREFIX)) {
|
|
352
344
|
OSC66_SPAN_REGEX.lastIndex = 0;
|
|
353
345
|
for (let m = OSC66_SPAN_REGEX.exec(str); m !== null; m = OSC66_SPAN_REGEX.exec(str)) {
|
|
354
346
|
let scale = 1;
|
|
355
347
|
let explicit: number | undefined;
|
|
356
348
|
for (const part of m[1].split(":")) {
|
|
357
|
-
// metadata keys are single chars, e.g. `s=2`, `w=5`
|
|
358
349
|
if (part.indexOf("=") !== 1) continue;
|
|
359
350
|
const value = Number.parseInt(part.slice(2), 10);
|
|
360
351
|
if (!Number.isFinite(value)) continue;
|
|
@@ -368,11 +359,15 @@ export function visibleWidth(str: string): number {
|
|
|
368
359
|
}
|
|
369
360
|
}
|
|
370
361
|
|
|
371
|
-
|
|
362
|
+
width = correctHangulCompatibilityJamoWidth(width, compatibilityJamoCount, fillerCount);
|
|
363
|
+
if (cacheable) {
|
|
364
|
+
if (visibleWidthCache.size >= VISIBLE_WIDTH_CACHE_MAX) visibleWidthCache.clear();
|
|
365
|
+
visibleWidthCache.set(str, width);
|
|
366
|
+
}
|
|
367
|
+
return width;
|
|
372
368
|
}
|
|
373
369
|
|
|
374
370
|
/**
|
|
375
|
-
* True when a row carries a Kitty OSC 66 text-sizing span (`\x1b]66;…`).
|
|
376
371
|
* Scaled spans must bypass wrapping/padding and, when scaled up, reserve the
|
|
377
372
|
* terminal rows their multicell glyphs flow into.
|
|
378
373
|
*/
|