@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.
- package/CHANGELOG.md +18 -0
- package/dist/types/apps/debug/log-viewer.d.ts +17 -0
- package/dist/types/apps/debug/raw-sse.d.ts +14 -0
- package/dist/types/native/backend.d.ts +8 -0
- package/dist/types/native/encode.d.ts +5 -1
- package/dist/types/native/node.d.ts +14 -1
- package/dist/types/native/reconcile.d.ts +5 -0
- package/dist/types/overlays/extensions/extension-dashboard.d.ts +31 -0
- package/dist/types/overlays/extensions/extension-list.d.ts +37 -0
- package/dist/types/overlays/extensions/inspector-panel.d.ts +8 -0
- package/dist/types/overlays/jobs-panel.d.ts +35 -9
- package/dist/types/prompt/word-completion.d.ts +12 -0
- package/dist/types/terminal.d.ts +7 -0
- package/dist/types/tools/bash-interactive.d.ts +15 -0
- package/dist/types/tools/output-meta.d.ts +8 -2
- package/dist/types/tools/streaming-output.d.ts +15 -10
- package/dist/types/tui.d.ts +5 -0
- package/dist/types/windows-input-mode.d.ts +26 -0
- package/package.json +9 -9
- package/src/apps/debug/log-viewer.ts +236 -2
- package/src/apps/debug/protocol-probe.ts +10 -1
- package/src/apps/debug/raw-sse.ts +210 -28
- package/src/native/apply.ts +2 -1
- package/src/native/backend.ts +22 -0
- package/src/native/encode.ts +7 -2
- package/src/native/node.ts +15 -1
- package/src/native/reconcile.ts +28 -1
- package/src/overlays/extensions/extension-dashboard.ts +275 -23
- package/src/overlays/extensions/extension-list.ts +274 -14
- package/src/overlays/extensions/inspector-panel.ts +368 -0
- package/src/overlays/hook-selector.ts +1 -1
- package/src/overlays/jobs-panel.ts +222 -49
- package/src/prompt/composer-cache.ts +26 -3
- package/src/prompt/word-completion.ts +31 -13
- package/src/terminal.ts +37 -1
- package/src/tools/bash-interactive.ts +85 -16
- package/src/tools/output-meta.ts +13 -5
- package/src/tools/streaming-output.ts +48 -23
- package/src/tui.ts +52 -14
- 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
|
+
}
|