@oh-my-pi/pi-tui 18.4.8 → 18.4.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/types/apps/debug/log-viewer.d.ts +17 -0
  3. package/dist/types/apps/debug/raw-sse.d.ts +14 -0
  4. package/dist/types/native/backend.d.ts +8 -0
  5. package/dist/types/native/encode.d.ts +5 -1
  6. package/dist/types/native/node.d.ts +14 -1
  7. package/dist/types/native/reconcile.d.ts +5 -0
  8. package/dist/types/overlays/extensions/extension-dashboard.d.ts +31 -0
  9. package/dist/types/overlays/extensions/extension-list.d.ts +37 -0
  10. package/dist/types/overlays/extensions/inspector-panel.d.ts +8 -0
  11. package/dist/types/overlays/jobs-panel.d.ts +35 -9
  12. package/dist/types/prompt/word-completion.d.ts +12 -0
  13. package/dist/types/terminal.d.ts +7 -0
  14. package/dist/types/tools/bash-interactive.d.ts +15 -0
  15. package/dist/types/tools/output-meta.d.ts +8 -2
  16. package/dist/types/tools/streaming-output.d.ts +15 -10
  17. package/dist/types/tui.d.ts +5 -0
  18. package/dist/types/windows-input-mode.d.ts +26 -0
  19. package/package.json +9 -9
  20. package/src/apps/debug/log-viewer.ts +236 -2
  21. package/src/apps/debug/protocol-probe.ts +10 -1
  22. package/src/apps/debug/raw-sse.ts +210 -28
  23. package/src/native/apply.ts +2 -1
  24. package/src/native/backend.ts +22 -0
  25. package/src/native/encode.ts +7 -2
  26. package/src/native/node.ts +15 -1
  27. package/src/native/reconcile.ts +28 -1
  28. package/src/overlays/extensions/extension-dashboard.ts +275 -23
  29. package/src/overlays/extensions/extension-list.ts +274 -14
  30. package/src/overlays/extensions/inspector-panel.ts +368 -0
  31. package/src/overlays/hook-selector.ts +1 -1
  32. package/src/overlays/jobs-panel.ts +222 -49
  33. package/src/prompt/composer-cache.ts +26 -3
  34. package/src/prompt/word-completion.ts +31 -13
  35. package/src/terminal.ts +37 -1
  36. package/src/tools/bash-interactive.ts +85 -16
  37. package/src/tools/output-meta.ts +13 -5
  38. package/src/tools/streaming-output.ts +48 -23
  39. package/src/tui.ts +52 -14
  40. package/src/windows-input-mode.ts +206 -0
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Decode Windows console win32-input-mode key records into the key sequences
3
+ * the rest of the TUI already understands.
4
+ *
5
+ * Hosts without the kitty keyboard protocol (Windows Terminal before 1.25)
6
+ * deliver Shift+Enter as a bare `\r` (indistinguishable from Enter) and
7
+ * Ctrl+Enter as `\n`. Enabling `CSI ? 9001 h` makes the console host serving
8
+ * this process report every key as `CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _`, which
9
+ * carries the full modifier state. Paste text, mouse reports and terminal
10
+ * replies keep arriving as plain VT, so only key records need translating.
11
+ *
12
+ * Unmodified and legacy-expressible keys translate to the bytes a legacy
13
+ * terminal sends; chords legacy encoding loses (modified Enter/Tab/Escape,
14
+ * Ctrl+Shift+letter, …) translate to kitty CSI-u, which key matching already
15
+ * parses.
16
+ */
17
+
18
+ const W32IM_PATTERN = /^\x1b\[([\d;]*)_$/;
19
+
20
+ const RIGHT_ALT_PRESSED = 0x0001;
21
+ const LEFT_ALT_PRESSED = 0x0002;
22
+ const RIGHT_CTRL_PRESSED = 0x0004;
23
+ const LEFT_CTRL_PRESSED = 0x0008;
24
+ const SHIFT_PRESSED = 0x0010;
25
+
26
+ const VK_BACK = 0x08;
27
+ const VK_TAB = 0x09;
28
+ const VK_RETURN = 0x0d;
29
+ const VK_MENU = 0x12;
30
+ const VK_ESCAPE = 0x1b;
31
+ const VK_SPACE = 0x20;
32
+ const VK_PACKET = 0xe7;
33
+
34
+ /** Modifier and lock keys: their own presses produce no input. */
35
+ const MODIFIER_VKS = new Set([
36
+ 0x10, // VK_SHIFT
37
+ 0x11, // VK_CONTROL
38
+ 0x12, // VK_MENU
39
+ 0x14, // VK_CAPITAL
40
+ 0x5b, // VK_LWIN
41
+ 0x5c, // VK_RWIN
42
+ 0x90, // VK_NUMLOCK
43
+ 0x91, // VK_SCROLL
44
+ 0xa0, // VK_LSHIFT
45
+ 0xa1, // VK_RSHIFT
46
+ 0xa2, // VK_LCONTROL
47
+ 0xa3, // VK_RCONTROL
48
+ 0xa4, // VK_LMENU
49
+ 0xa5, // VK_RMENU
50
+ ]);
51
+
52
+ /** Cursor keys: legacy `CSI <final>`, modified `CSI 1 ; <mod> <final>`. */
53
+ const CURSOR_FINALS = new Map<number, string>([
54
+ [0x23, "F"], // VK_END
55
+ [0x24, "H"], // VK_HOME
56
+ [0x25, "D"], // VK_LEFT
57
+ [0x26, "A"], // VK_UP
58
+ [0x27, "C"], // VK_RIGHT
59
+ [0x28, "B"], // VK_DOWN
60
+ [0x70, "P"], // VK_F1 (legacy SS3)
61
+ [0x71, "Q"], // VK_F2
62
+ [0x72, "R"], // VK_F3
63
+ [0x73, "S"], // VK_F4
64
+ ]);
65
+ const SS3_VKS = new Set([0x70, 0x71, 0x72, 0x73]);
66
+
67
+ /** Tilde keys: `CSI <n> ~`, modified `CSI <n> ; <mod> ~`. */
68
+ const TILDE_CODES = new Map<number, number>([
69
+ [0x21, 5], // VK_PRIOR
70
+ [0x22, 6], // VK_NEXT
71
+ [0x2d, 2], // VK_INSERT
72
+ [0x2e, 3], // VK_DELETE
73
+ [0x74, 15], // VK_F5
74
+ [0x75, 17], // VK_F6
75
+ [0x76, 18], // VK_F7
76
+ [0x77, 19], // VK_F8
77
+ [0x78, 20], // VK_F9
78
+ [0x79, 21], // VK_F10
79
+ [0x7a, 23], // VK_F11
80
+ [0x7b, 24], // VK_F12
81
+ ]);
82
+
83
+ interface KeyRecord {
84
+ vk: number;
85
+ uc: number;
86
+ down: boolean;
87
+ state: number;
88
+ repeat: number;
89
+ }
90
+
91
+ function parseRecord(data: string): KeyRecord | undefined {
92
+ const match = W32IM_PATTERN.exec(data);
93
+ if (!match) return undefined;
94
+ const params = match[1]!.split(";");
95
+ if (params.length > 6) return undefined;
96
+ const at = (index: number, fallback: number): number => {
97
+ const raw = params[index];
98
+ return raw ? Number.parseInt(raw, 10) : fallback;
99
+ };
100
+ return { vk: at(0, 0), uc: at(2, 0), down: at(3, 0) === 1, state: at(4, 0), repeat: Math.max(1, at(5, 1)) };
101
+ }
102
+
103
+ /** Lowercase codepoint a letter/digit virtual key stands for, or 0. */
104
+ function baseCodepointForVk(vk: number): number {
105
+ if (vk >= 0x41 && vk <= 0x5a) return vk + 0x20;
106
+ if (vk >= 0x30 && vk <= 0x39) return vk;
107
+ return 0;
108
+ }
109
+
110
+ function encodeKey(record: KeyRecord): string | null {
111
+ const { vk, uc, state } = record;
112
+ const shift = (state & SHIFT_PRESSED) !== 0;
113
+ const alt = (state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) !== 0;
114
+ const ctrl = (state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) !== 0;
115
+ const mod = 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0);
116
+ const text = uc === 0 ? "" : String.fromCharCode(uc);
117
+
118
+ switch (vk) {
119
+ case VK_RETURN:
120
+ return mod === 1 ? "\r" : `\x1b[13;${mod}u`;
121
+ case VK_TAB:
122
+ if (mod === 1) return "\t";
123
+ return mod === 2 ? "\x1b[Z" : `\x1b[9;${mod}u`;
124
+ case VK_BACK:
125
+ if (mod === 1) return "\x7f";
126
+ return mod === 3 ? "\x1b\x7f" : `\x1b[127;${mod}u`;
127
+ case VK_ESCAPE:
128
+ return mod === 1 ? "\x1b" : `\x1b[27;${mod}u`;
129
+ case VK_SPACE:
130
+ if (!ctrl && !alt) return " ";
131
+ return `\x1b[32;${mod}u`;
132
+ }
133
+
134
+ const cursorFinal = CURSOR_FINALS.get(vk);
135
+ if (cursorFinal !== undefined) {
136
+ if (mod !== 1) return `\x1b[1;${mod}${cursorFinal}`;
137
+ return SS3_VKS.has(vk) ? `\x1bO${cursorFinal}` : `\x1b[${cursorFinal}`;
138
+ }
139
+ const tildeCode = TILDE_CODES.get(vk);
140
+ if (tildeCode !== undefined) {
141
+ return mod === 1 ? `\x1b[${tildeCode}~` : `\x1b[${tildeCode};${mod}~`;
142
+ }
143
+
144
+ // AltGr arrives as Right Alt + Left Ctrl; when it composed printable text,
145
+ // that text is the input.
146
+ const altGr = (state & RIGHT_ALT_PRESSED) !== 0 && (state & LEFT_CTRL_PRESSED) !== 0;
147
+ if (altGr && uc >= 0x20 && uc !== 0x7f) return text;
148
+
149
+ const base = baseCodepointForVk(vk);
150
+ if (ctrl) {
151
+ // Ctrl+Shift+letter collapses onto Ctrl+letter in legacy bytes; keep it distinct.
152
+ if ((shift || uc === 0) && base !== 0) return `\x1b[${base};${mod}u`;
153
+ if (uc === 0) return null;
154
+ return alt ? `\x1b${text}` : text;
155
+ }
156
+ if (alt) {
157
+ if (uc !== 0) return `\x1b${text}`;
158
+ return base !== 0 ? `\x1b${String.fromCharCode(base)}` : null;
159
+ }
160
+ // Plain or shifted key; Uc 0 is a dead key whose composed text arrives with the next key.
161
+ return uc === 0 ? null : text;
162
+ }
163
+
164
+ /** Stateful decoder: joins UTF-16 surrogate halves that arrive as separate records. */
165
+ export class Win32InputModeDecoder {
166
+ #pendingHighSurrogate = 0;
167
+
168
+ /**
169
+ * Translate one stdin sequence. Returns `undefined` when `data` is not a
170
+ * win32-input-mode record (pass it through unchanged), otherwise the key
171
+ * events it produces — empty for key releases and bare modifier presses.
172
+ */
173
+ decode(data: string): string[] | undefined {
174
+ const record = parseRecord(data);
175
+ if (!record) return undefined;
176
+
177
+ if (!record.down) {
178
+ // Alt+Numpad composition delivers its character on the Alt release.
179
+ if (record.vk === VK_MENU && record.uc !== 0) return this.#emitText(record.uc);
180
+ return [];
181
+ }
182
+ if (MODIFIER_VKS.has(record.vk)) return [];
183
+ if (record.vk === 0 || record.vk === VK_PACKET) return this.#emitText(record.uc, record.repeat);
184
+ if (record.uc >= 0xd800 && record.uc <= 0xdfff) return this.#emitText(record.uc);
185
+
186
+ this.#pendingHighSurrogate = 0;
187
+ const encoded = encodeKey(record);
188
+ if (encoded === null) return [];
189
+ return Array.from({ length: record.repeat }, () => encoded);
190
+ }
191
+
192
+ #emitText(codeUnit: number, repeat = 1): string[] {
193
+ if (codeUnit === 0) return [];
194
+ if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
195
+ this.#pendingHighSurrogate = codeUnit;
196
+ return [];
197
+ }
198
+ let text = String.fromCharCode(codeUnit);
199
+ if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
200
+ if (this.#pendingHighSurrogate === 0) return [];
201
+ text = String.fromCharCode(this.#pendingHighSurrogate, codeUnit);
202
+ }
203
+ this.#pendingHighSurrogate = 0;
204
+ return Array.from({ length: repeat }, () => text);
205
+ }
206
+ }