@oh-my-pi/pi-tui 15.1.7 → 15.1.9
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 +6 -0
- package/package.json +3 -3
- package/src/terminal.ts +45 -0
- package/src/utils.ts +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.1.9] - 2026-05-21
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed terminal probe responses (DA1, kitty keyboard, Mode 2031) leaking into the prompt as keystrokes when the response is split across stdin reads. `ProcessTerminal` now reassembles `\x1b[?<digits>...` private CSI fragments and dispatches the complete response through the existing pattern handlers. ([#1238](https://github.com/can1357/oh-my-pi/issues/1238))
|
|
10
|
+
|
|
5
11
|
## [15.1.4] - 2026-05-19
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "15.1.
|
|
4
|
+
"version": "15.1.9",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "15.1.
|
|
41
|
-
"@oh-my-pi/pi-utils": "15.1.
|
|
40
|
+
"@oh-my-pi/pi-natives": "15.1.9",
|
|
41
|
+
"@oh-my-pi/pi-utils": "15.1.9",
|
|
42
42
|
"lru-cache": "11.3.6",
|
|
43
43
|
"marked": "^18.0.3"
|
|
44
44
|
},
|
package/src/terminal.ts
CHANGED
|
@@ -127,6 +127,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
127
127
|
#osc11Pending = false;
|
|
128
128
|
#osc11QueryQueued = false;
|
|
129
129
|
#osc11ResponseBuffer = "";
|
|
130
|
+
#privateCsiResponseBuffer = "";
|
|
130
131
|
#pendingDa1Sentinels = 0;
|
|
131
132
|
#osc11PollTimer?: Timer;
|
|
132
133
|
#mode2031DebounceTimer?: Timer;
|
|
@@ -278,8 +279,51 @@ export class ProcessTerminal implements Terminal {
|
|
|
278
279
|
// DA1 (Primary Device Attributes) response: \x1b[?...c
|
|
279
280
|
const da1ResponsePattern = /^\x1b\[\?[\d;]*c$/;
|
|
280
281
|
|
|
282
|
+
// Private CSI partial: \x1b[?<digits/semicolons>... — incomplete probe response
|
|
283
|
+
// that the StdinBuffer flushed before the terminator arrived (split across
|
|
284
|
+
// stdin reads). Used to reassemble DA1, kitty, and Mode 2031 replies.
|
|
285
|
+
const privateCsiPartialPattern = /^\x1b\[\?[\d;]*$/;
|
|
286
|
+
|
|
281
287
|
// Forward individual sequences to the input handler
|
|
282
288
|
this.#stdinBuffer.on("data", (sequence: string) => {
|
|
289
|
+
// Reassemble split private CSI responses (DA1, kitty keyboard, Mode 2031).
|
|
290
|
+
// When the terminal writes the response slowly enough that the StdinBuffer's
|
|
291
|
+
// flush timeout elapses mid-sequence, the prefix `\x1b[?<digits>` arrives as
|
|
292
|
+
// one event and the tail `;...<terminator>` arrives as individual character
|
|
293
|
+
// events that would otherwise leak into the prompt as keystrokes. See #1238.
|
|
294
|
+
if (
|
|
295
|
+
this.#privateCsiResponseBuffer ||
|
|
296
|
+
(privateCsiPartialPattern.test(sequence) && this.#pendingDa1Sentinels > 0)
|
|
297
|
+
) {
|
|
298
|
+
if (this.#privateCsiResponseBuffer && sequence.startsWith("\x1b")) {
|
|
299
|
+
// New escape arrived mid-reassembly — abandon partial and re-process the new sequence.
|
|
300
|
+
this.#privateCsiResponseBuffer = "";
|
|
301
|
+
} else {
|
|
302
|
+
this.#privateCsiResponseBuffer += sequence;
|
|
303
|
+
// Cap accumulator to defend against runaway partials if the terminator never arrives.
|
|
304
|
+
if (this.#privateCsiResponseBuffer.length > 256) {
|
|
305
|
+
this.#privateCsiResponseBuffer = "";
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const lastChar = this.#privateCsiResponseBuffer.at(-1)!;
|
|
309
|
+
const lastCode = lastChar.charCodeAt(0);
|
|
310
|
+
if (lastCode >= 0x40 && lastCode <= 0x7e) {
|
|
311
|
+
// Terminator byte arrived. Fall through to the pattern checks with the
|
|
312
|
+
// reassembled sequence so the existing DA1/kitty/Mode 2031 handlers run.
|
|
313
|
+
sequence = this.#privateCsiResponseBuffer;
|
|
314
|
+
this.#privateCsiResponseBuffer = "";
|
|
315
|
+
} else if (!privateCsiPartialPattern.test(this.#privateCsiResponseBuffer)) {
|
|
316
|
+
// Diverged from a valid private CSI prefix (unexpected byte). Drop the
|
|
317
|
+
// probe noise we ate; do not forward to the input handler.
|
|
318
|
+
this.#privateCsiResponseBuffer = "";
|
|
319
|
+
return;
|
|
320
|
+
} else {
|
|
321
|
+
// Still accumulating.
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
283
327
|
// Check for Kitty protocol response (only if not already enabled)
|
|
284
328
|
if (!this.#kittyProtocolActive) {
|
|
285
329
|
const match = sequence.match(kittyResponsePattern);
|
|
@@ -531,6 +575,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
531
575
|
this.#osc11Pending = false;
|
|
532
576
|
this.#osc11QueryQueued = false;
|
|
533
577
|
this.#osc11ResponseBuffer = "";
|
|
578
|
+
this.#privateCsiResponseBuffer = "";
|
|
534
579
|
this.#pendingDa1Sentinels = 0;
|
|
535
580
|
|
|
536
581
|
// Disable Kitty keyboard protocol if not already done by drainInput()
|
package/src/utils.ts
CHANGED
|
@@ -28,7 +28,17 @@ export function truncateToWidth(
|
|
|
28
28
|
// and `maxWidth` is a required `u32` that throws on `null`/`undefined`
|
|
29
29
|
// everywhere. Pass concrete defaults that mirror the Rust `unwrap_or`s.
|
|
30
30
|
const safeWidth = Number.isFinite(maxWidth) ? Math.max(0, Math.trunc(maxWidth)) : 0;
|
|
31
|
-
|
|
31
|
+
let resolvedEllipsis: Ellipsis | null | undefined | string = ellipsisKind;
|
|
32
|
+
if (typeof resolvedEllipsis === "string") {
|
|
33
|
+
resolvedEllipsis = resolvedEllipsis === "" ? Ellipsis.Omit : Ellipsis.Unicode;
|
|
34
|
+
}
|
|
35
|
+
return nativeTruncateToWidth(
|
|
36
|
+
text,
|
|
37
|
+
safeWidth,
|
|
38
|
+
resolvedEllipsis ?? Ellipsis.Unicode,
|
|
39
|
+
pad ?? false,
|
|
40
|
+
getDefaultTabWidth(),
|
|
41
|
+
);
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
export function wrapTextWithAnsi(text: string, width: number): string[] {
|