@prometheus-ai/tui 0.5.3 → 0.5.8
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/dist/types/autocomplete.d.ts +3 -1
- package/dist/types/components/box.d.ts +1 -1
- package/dist/types/components/editor.d.ts +35 -2
- package/dist/types/components/image.d.ts +22 -3
- package/dist/types/components/input.d.ts +6 -1
- package/dist/types/components/loader.d.ts +9 -2
- package/dist/types/components/markdown.d.ts +3 -1
- package/dist/types/components/scroll-view.d.ts +23 -1
- package/dist/types/components/select-list.d.ts +19 -1
- package/dist/types/components/settings-list.d.ts +87 -7
- package/dist/types/components/spacer.d.ts +1 -1
- package/dist/types/components/tab-bar.d.ts +37 -4
- package/dist/types/components/text.d.ts +2 -2
- package/dist/types/components/truncated-text.d.ts +1 -1
- package/dist/types/fuzzy.d.ts +10 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/keybindings.d.ts +5 -3
- package/dist/types/keys.d.ts +1 -1
- package/dist/types/kill-ring.d.ts +0 -7
- package/dist/types/kitty-graphics.d.ts +16 -31
- package/dist/types/loop-watchdog.d.ts +39 -0
- package/dist/types/mouse.d.ts +41 -0
- package/dist/types/stdin-buffer.d.ts +17 -0
- package/dist/types/terminal-capabilities.d.ts +74 -18
- package/dist/types/terminal.d.ts +34 -36
- package/dist/types/tui.d.ts +191 -79
- package/dist/types/utils.d.ts +5 -2
- package/package.json +4 -4
- package/src/autocomplete.ts +79 -65
- package/src/components/box.ts +43 -63
- package/src/components/editor.ts +471 -136
- package/src/components/image.ts +85 -9
- package/src/components/input.ts +12 -3
- package/src/components/loader.ts +35 -21
- package/src/components/markdown.ts +174 -53
- package/src/components/scroll-view.ts +63 -2
- package/src/components/select-list.ts +233 -38
- package/src/components/settings-list.ts +626 -64
- package/src/components/spacer.ts +9 -5
- package/src/components/tab-bar.ts +153 -28
- package/src/components/text.ts +6 -2
- package/src/components/truncated-text.ts +10 -2
- package/src/fuzzy.ts +214 -59
- package/src/index.ts +3 -1
- package/src/keybindings.ts +72 -14
- package/src/keys.ts +1 -1
- package/src/kill-ring.ts +5 -0
- package/src/kitty-graphics.ts +2 -101
- package/src/loop-watchdog.ts +106 -0
- package/src/mouse.ts +55 -0
- package/src/stdin-buffer.ts +291 -81
- package/src/terminal-capabilities.ts +206 -168
- package/src/terminal.ts +367 -110
- package/src/tui.ts +2102 -1729
- package/src/utils.ts +92 -60
package/src/utils.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
extractSegments as nativeExtractSegments,
|
|
5
5
|
sliceWithWidth as nativeSliceWithWidth,
|
|
6
6
|
truncateToWidth as nativeTruncateToWidth,
|
|
7
|
-
visibleWidth as nativeVisibleWidth,
|
|
8
7
|
wrapTextWithAnsi as nativeWrapTextWithAnsi,
|
|
9
8
|
type SliceResult,
|
|
10
9
|
} from "@prometheus-ai/natives";
|
|
@@ -145,44 +144,6 @@ export function padding(n: number): string {
|
|
|
145
144
|
// Grapheme segmenter (shared instance)
|
|
146
145
|
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
147
146
|
|
|
148
|
-
const EXTENDED_PICTOGRAPHIC_REGEX = /\p{Extended_Pictographic}/u;
|
|
149
|
-
|
|
150
|
-
// Matches CSI (`\x1b[…`) and OSC (`\x1b]…` terminated by BEL/ST) escape
|
|
151
|
-
// sequences. Mirrors the standard ansi-regex coverage so visible-span
|
|
152
|
-
// segmentation lines up with the native ANSI scanner.
|
|
153
|
-
const ANSI_ESCAPE_REGEX =
|
|
154
|
-
/[\u001b\u009b][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
|
|
155
|
-
|
|
156
|
-
function pictographicSpanWidth(span: string): number {
|
|
157
|
-
let width = 0;
|
|
158
|
-
for (const { segment } of segmenter.segment(span)) {
|
|
159
|
-
width += EXTENDED_PICTOGRAPHIC_REGEX.test(segment) ? 2 : nativeVisibleWidth(segment, getDefaultTabWidth());
|
|
160
|
-
}
|
|
161
|
-
return width;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Width fallback for strings that mix ANSI styling with ZWJ pictographic
|
|
165
|
-
// emoji. `Intl.Segmenter` would split an escape sequence into individual
|
|
166
|
-
// graphemes, so the native scanner (which only skips ANSI when handed the
|
|
167
|
-
// complete sequence) double-counts the printable SGR bytes. Excise the ANSI
|
|
168
|
-
// spans first — they contribute zero cells — and apply the pictographic
|
|
169
|
-
// grapheme override only to the visible spans, then sum.
|
|
170
|
-
function visibleWidthByGrapheme(str: string): number {
|
|
171
|
-
let width = 0;
|
|
172
|
-
let lastIndex = 0;
|
|
173
|
-
ANSI_ESCAPE_REGEX.lastIndex = 0;
|
|
174
|
-
for (let match = ANSI_ESCAPE_REGEX.exec(str); match !== null; match = ANSI_ESCAPE_REGEX.exec(str)) {
|
|
175
|
-
if (match.index > lastIndex) {
|
|
176
|
-
width += pictographicSpanWidth(str.slice(lastIndex, match.index));
|
|
177
|
-
}
|
|
178
|
-
lastIndex = ANSI_ESCAPE_REGEX.lastIndex;
|
|
179
|
-
}
|
|
180
|
-
if (lastIndex < str.length) {
|
|
181
|
-
width += lastIndex === 0 ? pictographicSpanWidth(str) : pictographicSpanWidth(str.slice(lastIndex));
|
|
182
|
-
}
|
|
183
|
-
return width;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
147
|
/**
|
|
187
148
|
* Get the shared grapheme segmenter instance.
|
|
188
149
|
*/
|
|
@@ -190,35 +151,106 @@ export function getSegmenter(): Intl.Segmenter {
|
|
|
190
151
|
return segmenter;
|
|
191
152
|
}
|
|
192
153
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
154
|
+
// Kitty OSC 66 text-sizing spans: `\x1b]66;<meta>;<payload>` terminated by BEL
|
|
155
|
+
// or ST. `Bun.stringWidth` strips the whole span (payload included) to zero
|
|
156
|
+
// cells, but the payload is visible and scales by the `s=` factor, so each is
|
|
157
|
+
// added back so width matches the native truncate/slice/wrap helpers.
|
|
158
|
+
const OSC66_SPAN_REGEX = /\x1b\]66;([^;]*);([\s\S]*?)(?:\x07|\x1b\\)/g;
|
|
159
|
+
const OSC66_PREFIX = "\x1b]66;";
|
|
160
|
+
const ESC = "\x1b";
|
|
161
|
+
const TAB = "\t";
|
|
162
|
+
const LONG_WIDTH_FAST_PATH_MIN = 128;
|
|
163
|
+
|
|
164
|
+
// Pin Bun.stringWidth semantics to the native width engine and guard against Bun
|
|
165
|
+
// default drift: strip ANSI/OSC (don't count escape bytes) and treat
|
|
166
|
+
// ambiguous-width East Asian chars as narrow (1 cell), matching `unicode-width`'s
|
|
167
|
+
// non-CJK tables that back truncate/slice/wrap. Hoisted so no per-call alloc.
|
|
168
|
+
const STRING_WIDTH_OPTS = { countAnsiEscapeCodes: false, ambiguousIsNarrow: true } as const;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Visible width of a string in terminal columns, excluding ANSI/OSC escapes.
|
|
172
|
+
*
|
|
173
|
+
* `Bun.stringWidth` does the heavy lifting (UAX#11 width tables + ANSI/OSC
|
|
174
|
+
* stripping); this adds the two corrections it omits — tabs (expanded to
|
|
175
|
+
* `tabWidth` cells) and OSC 66 text-sizing payloads (scaled by `s=`).
|
|
176
|
+
*/
|
|
177
|
+
export function visibleWidth(str: string): number {
|
|
178
|
+
if (!str) return 0;
|
|
179
|
+
|
|
180
|
+
// Long non-escape text is faster through Bun's native scanner than through
|
|
181
|
+
// a JS printable-ASCII prepass. Escape-bearing strings stay on the scanner
|
|
182
|
+
// below so CSI/OSC-heavy render output can still bail out at the first ESC.
|
|
183
|
+
if (str.length >= LONG_WIDTH_FAST_PATH_MIN && !str.includes(ESC)) {
|
|
184
|
+
let width = Bun.stringWidth(str, STRING_WIDTH_OPTS);
|
|
185
|
+
let tabCount = 0;
|
|
186
|
+
for (let tabIndex = str.indexOf(TAB); tabIndex !== -1; tabIndex = str.indexOf(TAB, tabIndex + 1)) {
|
|
187
|
+
tabCount++;
|
|
188
|
+
}
|
|
189
|
+
if (tabCount > 0) width += tabCount * getDefaultTabWidth();
|
|
190
|
+
return width;
|
|
196
191
|
}
|
|
197
192
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// model instead of mixing Bun.stringWidth quirks with Rust truncation.
|
|
202
|
-
for (let i = 0; i < str.length; i++) {
|
|
193
|
+
let tabCount = 0;
|
|
194
|
+
let i = 0;
|
|
195
|
+
for (; i < str.length; i++) {
|
|
203
196
|
const code = str.charCodeAt(i);
|
|
204
197
|
if (code < 0x20 || code > 0x7e) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
198
|
+
if (code === 0x09) {
|
|
199
|
+
tabCount++;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
208
203
|
}
|
|
209
204
|
}
|
|
210
|
-
|
|
211
|
-
|
|
205
|
+
if (i === str.length) {
|
|
206
|
+
return tabCount === 0 ? str.length : str.length + tabCount * (getDefaultTabWidth() - 1);
|
|
207
|
+
}
|
|
212
208
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
209
|
+
if (tabCount === 0) {
|
|
210
|
+
let tabIndex = str.indexOf(TAB, i + 1);
|
|
211
|
+
if (tabIndex !== -1) {
|
|
212
|
+
tabCount = 1;
|
|
213
|
+
for (tabIndex = str.indexOf(TAB, tabIndex + 1); tabIndex !== -1; tabIndex = str.indexOf(TAB, tabIndex + 1)) {
|
|
214
|
+
tabCount++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
for (let tabIndex = str.indexOf(TAB, i + 1); tabIndex !== -1; tabIndex = str.indexOf(TAB, tabIndex + 1)) {
|
|
219
|
+
tabCount++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// `Bun.stringWidth` is a JSC builtin (no per-call N-API number box, unlike
|
|
224
|
+
// the native scanner that traps under Bun 1.3.x GC/N-API load). It strips
|
|
225
|
+
// CSI/OSC to zero cells and shares the native engine's UAX#11 width tables.
|
|
226
|
+
let width = Bun.stringWidth(str, STRING_WIDTH_OPTS);
|
|
227
|
+
if (tabCount > 0) width += tabCount * getDefaultTabWidth();
|
|
228
|
+
|
|
229
|
+
// OSC 66: add back each stripped span as `scale * (explicit w ?? payload
|
|
230
|
+
// width)`. Matched rather than replaced to avoid reallocating the string.
|
|
231
|
+
if (str.includes(OSC66_PREFIX, i)) {
|
|
232
|
+
OSC66_SPAN_REGEX.lastIndex = 0;
|
|
233
|
+
for (let m = OSC66_SPAN_REGEX.exec(str); m !== null; m = OSC66_SPAN_REGEX.exec(str)) {
|
|
234
|
+
let scale = 1;
|
|
235
|
+
let explicit: number | undefined;
|
|
236
|
+
for (const part of m[1].split(":")) {
|
|
237
|
+
// metadata keys are single chars, e.g. `s=2`, `w=5`
|
|
238
|
+
if (part.indexOf("=") !== 1) continue;
|
|
239
|
+
const value = Number.parseInt(part.slice(2), 10);
|
|
240
|
+
if (!Number.isFinite(value)) continue;
|
|
241
|
+
if (part[0] === "s") {
|
|
242
|
+
if (value >= 1 && value <= 7) scale = value;
|
|
243
|
+
} else if (part[0] === "w" && value > 0) {
|
|
244
|
+
explicit = value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
width += scale * (explicit ?? Bun.stringWidth(m[2], STRING_WIDTH_OPTS));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return width;
|
|
219
252
|
}
|
|
220
253
|
|
|
221
|
-
const THAI_LAO_AM_REGEX = /[\u0e33\u0eb3]/;
|
|
222
254
|
const THAI_LAO_AM_GLOBAL_REGEX = /[\u0e33\u0eb3]/g;
|
|
223
255
|
|
|
224
256
|
/**
|
|
@@ -228,7 +260,7 @@ const THAI_LAO_AM_GLOBAL_REGEX = /[\u0e33\u0eb3]/g;
|
|
|
228
260
|
* width but avoid stale-cell artifacts in terminal renderers.
|
|
229
261
|
*/
|
|
230
262
|
export function normalizeTerminalOutput(str: string): string {
|
|
231
|
-
if (
|
|
263
|
+
if (str.indexOf("\u0e33") === -1 && str.indexOf("\u0eb3") === -1) return str;
|
|
232
264
|
return str.replace(THAI_LAO_AM_GLOBAL_REGEX, char => (char === "\u0e33" ? "\u0e4d\u0e32" : "\u0ecd\u0eb2"));
|
|
233
265
|
}
|
|
234
266
|
|