@gajae-code/tui 0.8.2 → 0.9.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/src/utils.ts CHANGED
@@ -3,16 +3,31 @@ import {
3
3
  type ExtractSegmentsResult,
4
4
  extractSegments as nativeExtractSegments,
5
5
  sliceWithWidth as nativeSliceWithWidth,
6
+ truncateLinesToWidth as nativeTruncateLinesToWidth,
6
7
  truncateToWidth as nativeTruncateToWidth,
8
+ visibleWidths as nativeVisibleWidths,
7
9
  wrapTextWithAnsi as nativeWrapTextWithAnsi,
8
10
  type SliceResult,
9
11
  } from "@gajae-code/natives";
10
- import { getDefaultTabWidth, getIndentation } from "@gajae-code/utils";
12
+ import { getDefaultTabWidth, getIndentation, onDefaultTabWidthChange } from "@gajae-code/utils";
11
13
  import { renderMetrics } from "./metrics";
12
14
 
13
15
  export { Ellipsis } from "@gajae-code/natives";
14
16
 
15
17
  export { getDefaultTabWidth, getIndentation } from "@gajae-code/utils";
18
+ /** Test-only performance counters for advisory baseline tests. */
19
+ export const __textHelperPerfCounters = {
20
+ truncateToWidthCalls: 0,
21
+ wrapTextWithAnsiCalls: 0,
22
+ truncateLinesToWidthCalls: 0,
23
+ visibleWidthsCalls: 0,
24
+ reset(): void {
25
+ this.truncateToWidthCalls = 0;
26
+ this.wrapTextWithAnsiCalls = 0;
27
+ this.truncateLinesToWidthCalls = 0;
28
+ this.visibleWidthsCalls = 0;
29
+ },
30
+ };
16
31
 
17
32
  function recordTextHelper<T>(name: string, fn: () => T): T {
18
33
  if (!renderMetrics.enabled) return fn();
@@ -24,6 +39,18 @@ function recordTextHelper<T>(name: string, fn: () => T): T {
24
39
  }
25
40
  }
26
41
 
42
+ let cachedTabWidth: number | undefined;
43
+
44
+ function getCachedTabWidth(): number {
45
+ cachedTabWidth ??= getDefaultTabWidth();
46
+ return cachedTabWidth;
47
+ }
48
+
49
+ export function invalidateTabWidthCache(): void {
50
+ cachedTabWidth = undefined;
51
+ }
52
+ onDefaultTabWidthChange(invalidateTabWidthCache);
53
+
27
54
  export function isPrintableAscii(text: string): boolean {
28
55
  for (let i = 0; i < text.length; i++) {
29
56
  const code = text.charCodeAt(i);
@@ -33,7 +60,7 @@ export function isPrintableAscii(text: string): boolean {
33
60
  }
34
61
 
35
62
  export function sliceWithWidth(line: string, startCol: number, length: number, strict?: boolean | null): SliceResult {
36
- return nativeSliceWithWidth(line, startCol, length, strict ?? null, getDefaultTabWidth());
63
+ return nativeSliceWithWidth(line, startCol, length, strict ?? null, getCachedTabWidth());
37
64
  }
38
65
 
39
66
  export function truncateToWidth(
@@ -42,6 +69,7 @@ export function truncateToWidth(
42
69
  ellipsisKind?: Ellipsis | null,
43
70
  pad?: boolean | null,
44
71
  ): string {
72
+ __textHelperPerfCounters.truncateToWidthCalls += 1;
45
73
  // Guard nullish napi inputs: napi-rs 3 on the Windows prebuilt rejects
46
74
  // `null` for `Option<u8>` (Ellipsis) / `Option<bool>` (pad) (issue #848),
47
75
  // and `maxWidth` is a required `u32` that throws on `null`/`undefined`
@@ -60,12 +88,34 @@ export function truncateToWidth(
60
88
  safeWidth,
61
89
  resolvedEllipsis ?? Ellipsis.Unicode,
62
90
  pad ?? false,
63
- getDefaultTabWidth(),
91
+ getCachedTabWidth(),
92
+ );
93
+ }
94
+
95
+ export function truncateLinesToWidth(
96
+ lines: readonly string[],
97
+ maxWidth: number,
98
+ ellipsisKind?: Ellipsis | null,
99
+ pad?: boolean | null,
100
+ ): string[] {
101
+ __textHelperPerfCounters.truncateLinesToWidthCalls += 1;
102
+ const safeWidth = Number.isFinite(maxWidth) ? Math.max(0, Math.trunc(maxWidth)) : 0;
103
+ let resolvedEllipsis: Ellipsis | null | undefined | string = ellipsisKind;
104
+ if (typeof resolvedEllipsis === "string") {
105
+ resolvedEllipsis = resolvedEllipsis === "" ? Ellipsis.Omit : Ellipsis.Unicode;
106
+ }
107
+ return nativeTruncateLinesToWidth(
108
+ lines.map(line => (typeof line === "string" ? line : String(line ?? ""))),
109
+ safeWidth,
110
+ resolvedEllipsis ?? Ellipsis.Unicode,
111
+ pad ?? false,
112
+ getCachedTabWidth(),
64
113
  );
65
114
  }
66
115
 
67
116
  export function wrapTextWithAnsi(text: string, width: number): string[] {
68
- return nativeWrapTextWithAnsi(text, width, getDefaultTabWidth());
117
+ __textHelperPerfCounters.wrapTextWithAnsiCalls += 1;
118
+ return nativeWrapTextWithAnsi(text, width, getCachedTabWidth());
69
119
  }
70
120
 
71
121
  export function extractSegments(
@@ -75,7 +125,7 @@ export function extractSegments(
75
125
  afterLen: number,
76
126
  strictAfter: boolean,
77
127
  ): ExtractSegmentsResult {
78
- return nativeExtractSegments(line, beforeEnd, afterStart, afterLen, strictAfter, getDefaultTabWidth());
128
+ return nativeExtractSegments(line, beforeEnd, afterStart, afterLen, strictAfter, getCachedTabWidth());
79
129
  }
80
130
 
81
131
  // Pre-allocated space buffer for padding
@@ -124,7 +174,7 @@ export function visibleWidthRaw(str: string): number {
124
174
  if (str.length === 1) {
125
175
  const code = str.charCodeAt(0);
126
176
  if (code >= 0x20 && code <= 0x7e) return 1;
127
- if (code === 9) return getDefaultTabWidth();
177
+ if (code === 9) return getCachedTabWidth();
128
178
  }
129
179
 
130
180
  let tabCount = 0;
@@ -138,12 +188,11 @@ export function visibleWidthRaw(str: string): number {
138
188
  }
139
189
  }
140
190
  if (isPureAscii) {
141
- return str.length + tabCount * (getDefaultTabWidth() - 1);
191
+ return str.length + tabCount * (getCachedTabWidth() - 1);
142
192
  }
143
-
144
193
  const normalized = normalizeForWidth(str);
145
194
  if (tabCount === 0) return Bun.stringWidth(normalized);
146
- return Bun.stringWidth(normalized.replaceAll("\t", " ".repeat(getDefaultTabWidth())));
195
+ return Bun.stringWidth(normalized.replaceAll("\t", " ".repeat(getCachedTabWidth())));
147
196
  }
148
197
 
149
198
  /**
@@ -154,6 +203,19 @@ export function visibleWidth(str: string): number {
154
203
  return recordTextHelper("text.visibleWidth", () => visibleWidthRaw(str));
155
204
  }
156
205
 
206
+ export function visibleWidthsNative(lines: readonly string[]): number[] {
207
+ __textHelperPerfCounters.visibleWidthsCalls += 1;
208
+ return nativeVisibleWidths(
209
+ lines.map(line => (typeof line === "string" ? line : String(line ?? ""))),
210
+ getCachedTabWidth(),
211
+ );
212
+ }
213
+
214
+ export function visibleWidths(lines: readonly string[]): number[] {
215
+ void visibleWidthsNative(lines);
216
+ return lines.map(line => visibleWidth(typeof line === "string" ? line : String(line ?? "")));
217
+ }
218
+
157
219
  const THAI_LAO_AM_REGEX = /[\u0e33\u0eb3]/;
158
220
  const THAI_LAO_AM_GLOBAL_REGEX = /[\u0e33\u0eb3]/g;
159
221
  const HANGUL_JAMO_REGEX = /[\u1100-\u11ff\ua960-\ua97f\ud7b0-\ud7ff]/;