@gajae-code/tui 0.11.11 → 0.12.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 +25 -1
- package/README.md +8 -0
- package/dist/types/metrics.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +6 -0
- package/dist/types/terminal-capabilities.d.ts +9 -0
- package/dist/types/terminal.d.ts +16 -0
- package/dist/types/tui.d.ts +32 -0
- package/package.json +3 -3
- package/src/autocomplete.ts +4 -0
- package/src/metrics.ts +15 -0
- package/src/stdin-buffer.ts +99 -11
- package/src/terminal-capabilities.ts +63 -1
- package/src/terminal.ts +98 -11
- package/src/tui.ts +1042 -166
package/src/terminal.ts
CHANGED
|
@@ -8,6 +8,33 @@ const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
|
|
|
8
8
|
const TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1b]9;4;3\x07";
|
|
9
9
|
const TERMINAL_PROGRESS_CLEAR_SEQUENCE = "\x1b]9;4;0;\x07";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Capability-probe reply shapes that only this layer solicits (OSC 11 background
|
|
13
|
+
* color, the Mode 2031 appearance DSR, and the Kitty keyboard-flags report).
|
|
14
|
+
* These are terminal-to-host replies and are NEVER legitimate user input, so a
|
|
15
|
+
* reply that arrives outside its pending-query window is dropped defensively.
|
|
16
|
+
*
|
|
17
|
+
* DA1 is deliberately absent: `Tui` issues its own DA1 request for the sixel
|
|
18
|
+
* probe and consumes that reply downstream.
|
|
19
|
+
*/
|
|
20
|
+
export const PROBE_REPLY_PATTERNS: ReadonlyArray<{ name: string; issuedProbe: string; pattern: RegExp }> = [
|
|
21
|
+
{
|
|
22
|
+
name: "osc11-background",
|
|
23
|
+
issuedProbe: "\x1b]11;?\x07",
|
|
24
|
+
pattern: /^\x1b\]11;rgba?:[0-9a-fA-F]{1,4}\/[0-9a-fA-F]{1,4}\/[0-9a-fA-F]{1,4}(?:\x07|\x1b\\)$/,
|
|
25
|
+
},
|
|
26
|
+
{ name: "mode2031-dsr", issuedProbe: "\x1b[?2031h", pattern: /^\x1b\[\?997;[12]n$/ },
|
|
27
|
+
{ name: "kitty-flags", issuedProbe: "\x1b[?u", pattern: /^\x1b\[\?\d+u$/ },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/** True when `sequence` is one of the probe replies above. */
|
|
31
|
+
export function isUnsolicitedProbeReply(sequence: string): boolean {
|
|
32
|
+
for (const entry of PROBE_REPLY_PATTERNS) {
|
|
33
|
+
if (entry.pattern.test(sequence)) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
11
38
|
/**
|
|
12
39
|
* Whether GJC may reprogram the keyboard with enhanced input protocols
|
|
13
40
|
* (the Kitty keyboard protocol and the xterm modifyOtherKeys fallback).
|
|
@@ -222,6 +249,10 @@ export class ProcessTerminal implements Terminal {
|
|
|
222
249
|
#privateCsiResponseBuffer = "";
|
|
223
250
|
#pendingDa1Sentinels = 0;
|
|
224
251
|
#osc11PollTimer?: Timer;
|
|
252
|
+
// Bounds the OSC 11 / DA1 pending-query window so a dropped or mangled reply
|
|
253
|
+
// (multiplexer, TERM=dumb host) cannot latch #osc11Pending forever and freeze
|
|
254
|
+
// stdin.
|
|
255
|
+
#osc11QueryWatchdog?: Timer;
|
|
225
256
|
#mode2031DebounceTimer?: Timer;
|
|
226
257
|
#progressTimer?: ReturnType<typeof setInterval>;
|
|
227
258
|
#mouseEnabled = false;
|
|
@@ -318,6 +349,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
318
349
|
// When the terminal reports a change, we re-query OSC 11 to get the
|
|
319
350
|
// actual background color (following Neovim convention) with 100ms debounce.
|
|
320
351
|
this.#safeWrite("\x1b[?2031h");
|
|
352
|
+
this.#stdinBuffer?.noteProbeIssued();
|
|
321
353
|
|
|
322
354
|
// Start periodic OSC 11 re-query for terminals without Mode 2031
|
|
323
355
|
// (Warp, Alacritty, WezTerm, iTerm2). Self-disables once Mode 2031 fires.
|
|
@@ -415,10 +447,11 @@ export class ProcessTerminal implements Terminal {
|
|
|
415
447
|
// flush timeout elapses mid-sequence, the prefix `\x1b[?<digits>` arrives as
|
|
416
448
|
// one event and the tail `;...<terminator>` arrives as individual character
|
|
417
449
|
// events that would otherwise leak into the prompt as keystrokes. See #1238.
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
450
|
+
// Reassembly is keyed on the reply's shape, not on `#pendingDa1Sentinels`:
|
|
451
|
+
// replies the terminal still owed after a counter reset (stop()/start()
|
|
452
|
+
// around a foreground command) otherwise leaked into the editor one
|
|
453
|
+
// character at a time. No keystroke can produce this prefix.
|
|
454
|
+
if (this.#privateCsiResponseBuffer || privateCsiPartialPattern.test(sequence)) {
|
|
422
455
|
if (this.#privateCsiResponseBuffer && sequence.startsWith("\x1b")) {
|
|
423
456
|
// New escape arrived mid-reassembly — abandon partial and re-process the new sequence.
|
|
424
457
|
this.#privateCsiResponseBuffer = "";
|
|
@@ -491,7 +524,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
491
524
|
// Accumulate fragments until the BEL/ST terminator arrives, then parse once.
|
|
492
525
|
// If a new escape sequence arrives (not the ST terminator), abort buffering
|
|
493
526
|
// and forward it as normal input so user keystrokes are never swallowed.
|
|
494
|
-
if (this.#
|
|
527
|
+
if (this.#osc11ResponseBuffer || sequence.startsWith("\x1b]11;")) {
|
|
495
528
|
if (this.#osc11ResponseBuffer && sequence.startsWith("\x1b") && sequence !== "\x1b\\") {
|
|
496
529
|
// New escape sequence arrived mid-buffer — not an OSC 11 continuation.
|
|
497
530
|
this.#osc11ResponseBuffer = "";
|
|
@@ -499,12 +532,25 @@ export class ProcessTerminal implements Terminal {
|
|
|
499
532
|
} else {
|
|
500
533
|
this.#osc11ResponseBuffer += sequence;
|
|
501
534
|
const osc11Match = this.#osc11ResponseBuffer.match(osc11ResponsePattern);
|
|
502
|
-
if (
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
535
|
+
if (osc11Match) {
|
|
536
|
+
const [, rHex, gHex, bHex] = osc11Match;
|
|
537
|
+
this.#osc11Pending = false;
|
|
538
|
+
this.#osc11ResponseBuffer = "";
|
|
539
|
+
this.#clearOsc11QueryWatchdog();
|
|
540
|
+
this.#handleOsc11Response(rHex!, gHex!, bHex!);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
// Bound the reassembly buffer. A real reply is <= ~25 bytes; if the
|
|
544
|
+
// terminator is dropped or mangled (multiplexer, TERM=dumb) an unbounded
|
|
545
|
+
// buffer swallows every following keystroke and freezes input. Past the
|
|
546
|
+
// cap, abandon reassembly and let the sequence fall through as input.
|
|
547
|
+
if (this.#osc11ResponseBuffer.length > 64) {
|
|
548
|
+
this.#osc11Pending = false;
|
|
549
|
+
this.#osc11ResponseBuffer = "";
|
|
550
|
+
this.#clearOsc11QueryWatchdog();
|
|
551
|
+
} else {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
508
554
|
}
|
|
509
555
|
}
|
|
510
556
|
|
|
@@ -520,6 +566,13 @@ export class ProcessTerminal implements Terminal {
|
|
|
520
566
|
}, 100);
|
|
521
567
|
return;
|
|
522
568
|
}
|
|
569
|
+
// Defensive backstop. A capability-probe reply reaching this point arrived
|
|
570
|
+
// outside its pending-query window, so none of the handlers above consumed
|
|
571
|
+
// it. These shapes are never user input, and paste content never reaches
|
|
572
|
+
// this handler, so dropping is always safe.
|
|
573
|
+
if (isUnsolicitedProbeReply(sequence)) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
523
576
|
if (this.#inputHandler) {
|
|
524
577
|
this.#inputHandler(sequence);
|
|
525
578
|
}
|
|
@@ -562,6 +615,39 @@ export class ProcessTerminal implements Terminal {
|
|
|
562
615
|
this.#pendingDa1Sentinels++;
|
|
563
616
|
this.#safeWrite("\x1b]11;?\x07"); // OSC 11 query (BEL terminated)
|
|
564
617
|
this.#safeWrite("\x1b[c"); // DA1 sentinel
|
|
618
|
+
this.#stdinBuffer?.noteProbeIssued();
|
|
619
|
+
this.#armOsc11QueryWatchdog();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* OSC 11 pending-query watchdog. If neither the OSC 11 reply nor its DA1
|
|
624
|
+
* sentinel comes back (dropped by a multiplexer or a TERM=dumb host),
|
|
625
|
+
* #osc11Pending / #pendingDa1Sentinels latch forever: #queryBackgroundColor
|
|
626
|
+
* stops re-querying and the reassembly branch swallows keystrokes.
|
|
627
|
+
* Force-resolve the cycle after a bounded wait so the state machine self-heals.
|
|
628
|
+
*/
|
|
629
|
+
#armOsc11QueryWatchdog(): void {
|
|
630
|
+
this.#clearOsc11QueryWatchdog();
|
|
631
|
+
this.#osc11QueryWatchdog = setTimeout(() => {
|
|
632
|
+
this.#osc11QueryWatchdog = undefined;
|
|
633
|
+
if (this.#dead) return;
|
|
634
|
+
if (!this.#osc11Pending && this.#pendingDa1Sentinels === 0) return;
|
|
635
|
+
this.#osc11Pending = false;
|
|
636
|
+
this.#osc11ResponseBuffer = "";
|
|
637
|
+
this.#pendingDa1Sentinels = 0;
|
|
638
|
+
if (this.#osc11QueryQueued && !this.#dead) {
|
|
639
|
+
this.#osc11QueryQueued = false;
|
|
640
|
+
this.#startOsc11Query();
|
|
641
|
+
}
|
|
642
|
+
}, 1000);
|
|
643
|
+
this.#osc11QueryWatchdog.unref?.();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
#clearOsc11QueryWatchdog(): void {
|
|
647
|
+
if (this.#osc11QueryWatchdog) {
|
|
648
|
+
clearTimeout(this.#osc11QueryWatchdog);
|
|
649
|
+
this.#osc11QueryWatchdog = undefined;
|
|
650
|
+
}
|
|
565
651
|
}
|
|
566
652
|
/**
|
|
567
653
|
* Parse an OSC 11 background color response and compute BT.601 luminance.
|
|
@@ -631,6 +717,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
631
717
|
return;
|
|
632
718
|
}
|
|
633
719
|
this.#safeWrite("\x1b[?u");
|
|
720
|
+
this.#stdinBuffer?.noteProbeIssued();
|
|
634
721
|
// Windows Terminal and conhost do not implement the Kitty keyboard
|
|
635
722
|
// protocol, so the query above never activates it there. They do honor the
|
|
636
723
|
// modifyOtherKeys fallback below — but that mode breaks Windows CJK/Hangul
|