@oh-my-pi/pi-tui 17.3.8 → 17.4.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.
- package/CHANGELOG.md +7 -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/utils.d.ts +0 -1
- package/package.json +3 -3
- 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/utils.ts +53 -58
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
|
*/
|