@oh-my-pi/pi-tui 3.15.0 → 3.20.0
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/package.json +1 -1
- package/src/components/editor.ts +271 -79
- package/src/components/settings-list.ts +5 -2
- package/src/index.ts +18 -1
- package/src/keybindings.ts +143 -0
- package/src/keys.ts +626 -402
- package/src/terminal.ts +8 -0
- package/src/tui.ts +69 -0
package/src/keys.ts
CHANGED
|
@@ -1,87 +1,263 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Keyboard input handling for terminal applications.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Supports both legacy terminal sequences and Kitty keyboard protocol.
|
|
5
|
+
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - Super: 8 (value 9)
|
|
12
|
-
* - Hyper: 16
|
|
13
|
-
* - Meta: 32
|
|
14
|
-
* - Caps_Lock: 64
|
|
15
|
-
* - Num_Lock: 128
|
|
7
|
+
* Symbol keys are also supported, however some ctrl+symbol combos
|
|
8
|
+
* overlap with ASCII codes, e.g. ctrl+[ = ESC.
|
|
9
|
+
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-ctrl-mapping-of-ascii-keys
|
|
10
|
+
* Those can still be * used for ctrl+shift combos
|
|
16
11
|
*
|
|
17
|
-
*
|
|
12
|
+
* API:
|
|
13
|
+
* - matchesKey(data, keyId) - Check if input matches a key identifier
|
|
14
|
+
* - parseKey(data) - Parse input and return the key identifier
|
|
15
|
+
* - Key - Helper object for creating typed key identifiers
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Type-Safe Key Identifiers
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
type Letter =
|
|
23
|
+
| "a"
|
|
24
|
+
| "b"
|
|
25
|
+
| "c"
|
|
26
|
+
| "d"
|
|
27
|
+
| "e"
|
|
28
|
+
| "f"
|
|
29
|
+
| "g"
|
|
30
|
+
| "h"
|
|
31
|
+
| "i"
|
|
32
|
+
| "j"
|
|
33
|
+
| "k"
|
|
34
|
+
| "l"
|
|
35
|
+
| "m"
|
|
36
|
+
| "n"
|
|
37
|
+
| "o"
|
|
38
|
+
| "p"
|
|
39
|
+
| "q"
|
|
40
|
+
| "r"
|
|
41
|
+
| "s"
|
|
42
|
+
| "t"
|
|
43
|
+
| "u"
|
|
44
|
+
| "v"
|
|
45
|
+
| "w"
|
|
46
|
+
| "x"
|
|
47
|
+
| "y"
|
|
48
|
+
| "z";
|
|
49
|
+
|
|
50
|
+
type SymbolKey =
|
|
51
|
+
| "`"
|
|
52
|
+
| "-"
|
|
53
|
+
| "="
|
|
54
|
+
| "["
|
|
55
|
+
| "]"
|
|
56
|
+
| "\\"
|
|
57
|
+
| ";"
|
|
58
|
+
| "'"
|
|
59
|
+
| ","
|
|
60
|
+
| "."
|
|
61
|
+
| "/"
|
|
62
|
+
| "!"
|
|
63
|
+
| "@"
|
|
64
|
+
| "#"
|
|
65
|
+
| "$"
|
|
66
|
+
| "%"
|
|
67
|
+
| "^"
|
|
68
|
+
| "&"
|
|
69
|
+
| "*"
|
|
70
|
+
| "("
|
|
71
|
+
| ")"
|
|
72
|
+
| "_"
|
|
73
|
+
| "+"
|
|
74
|
+
| "|"
|
|
75
|
+
| "~"
|
|
76
|
+
| "{"
|
|
77
|
+
| "}"
|
|
78
|
+
| ":"
|
|
79
|
+
| "<"
|
|
80
|
+
| ">"
|
|
81
|
+
| "?";
|
|
82
|
+
|
|
83
|
+
type SpecialKey =
|
|
84
|
+
| "escape"
|
|
85
|
+
| "esc"
|
|
86
|
+
| "enter"
|
|
87
|
+
| "return"
|
|
88
|
+
| "tab"
|
|
89
|
+
| "space"
|
|
90
|
+
| "backspace"
|
|
91
|
+
| "delete"
|
|
92
|
+
| "home"
|
|
93
|
+
| "end"
|
|
94
|
+
| "up"
|
|
95
|
+
| "down"
|
|
96
|
+
| "left"
|
|
97
|
+
| "right";
|
|
98
|
+
|
|
99
|
+
type BaseKey = Letter | SymbolKey | SpecialKey;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Union type of all valid key identifiers.
|
|
103
|
+
* Provides autocomplete and catches typos at compile time.
|
|
104
|
+
*/
|
|
105
|
+
export type KeyId =
|
|
106
|
+
| BaseKey
|
|
107
|
+
| `ctrl+${BaseKey}`
|
|
108
|
+
| `shift+${BaseKey}`
|
|
109
|
+
| `alt+${BaseKey}`
|
|
110
|
+
| `ctrl+shift+${BaseKey}`
|
|
111
|
+
| `shift+ctrl+${BaseKey}`
|
|
112
|
+
| `ctrl+alt+${BaseKey}`
|
|
113
|
+
| `alt+ctrl+${BaseKey}`
|
|
114
|
+
| `shift+alt+${BaseKey}`
|
|
115
|
+
| `alt+shift+${BaseKey}`
|
|
116
|
+
| `ctrl+shift+alt+${BaseKey}`
|
|
117
|
+
| `ctrl+alt+shift+${BaseKey}`
|
|
118
|
+
| `shift+ctrl+alt+${BaseKey}`
|
|
119
|
+
| `shift+alt+ctrl+${BaseKey}`
|
|
120
|
+
| `alt+ctrl+shift+${BaseKey}`
|
|
121
|
+
| `alt+shift+ctrl+${BaseKey}`;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Helper object for creating typed key identifiers with autocomplete.
|
|
18
125
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
126
|
+
* Usage:
|
|
127
|
+
* - Key.escape, Key.enter, Key.tab, etc. for special keys
|
|
128
|
+
* - Key.backtick, Key.comma, Key.period, etc. for symbol keys
|
|
129
|
+
* - Key.ctrl("c"), Key.alt("x") for single modifier
|
|
130
|
+
* - Key.ctrlShift("p"), Key.ctrlAlt("x") for combined modifiers
|
|
22
131
|
*/
|
|
23
|
-
|
|
24
|
-
// Common codepoints
|
|
25
|
-
const CODEPOINTS = {
|
|
26
|
-
// Letters (lowercase ASCII)
|
|
27
|
-
a: 97,
|
|
28
|
-
c: 99,
|
|
29
|
-
d: 100,
|
|
30
|
-
e: 101,
|
|
31
|
-
g: 103,
|
|
32
|
-
k: 107,
|
|
33
|
-
l: 108,
|
|
34
|
-
o: 111,
|
|
35
|
-
p: 112,
|
|
36
|
-
t: 116,
|
|
37
|
-
u: 117,
|
|
38
|
-
v: 118,
|
|
39
|
-
w: 119,
|
|
40
|
-
z: 122,
|
|
41
|
-
|
|
132
|
+
export const Key = {
|
|
42
133
|
// Special keys
|
|
43
|
-
escape:
|
|
44
|
-
|
|
45
|
-
enter:
|
|
46
|
-
|
|
134
|
+
escape: "escape" as const,
|
|
135
|
+
esc: "esc" as const,
|
|
136
|
+
enter: "enter" as const,
|
|
137
|
+
return: "return" as const,
|
|
138
|
+
tab: "tab" as const,
|
|
139
|
+
space: "space" as const,
|
|
140
|
+
backspace: "backspace" as const,
|
|
141
|
+
delete: "delete" as const,
|
|
142
|
+
home: "home" as const,
|
|
143
|
+
end: "end" as const,
|
|
144
|
+
up: "up" as const,
|
|
145
|
+
down: "down" as const,
|
|
146
|
+
left: "left" as const,
|
|
147
|
+
right: "right" as const,
|
|
148
|
+
|
|
149
|
+
// Symbol keys
|
|
150
|
+
backtick: "`" as const,
|
|
151
|
+
hyphen: "-" as const,
|
|
152
|
+
equals: "=" as const,
|
|
153
|
+
leftbracket: "[" as const,
|
|
154
|
+
rightbracket: "]" as const,
|
|
155
|
+
backslash: "\\" as const,
|
|
156
|
+
semicolon: ";" as const,
|
|
157
|
+
quote: "'" as const,
|
|
158
|
+
comma: "," as const,
|
|
159
|
+
period: "." as const,
|
|
160
|
+
slash: "/" as const,
|
|
161
|
+
exclamation: "!" as const,
|
|
162
|
+
at: "@" as const,
|
|
163
|
+
hash: "#" as const,
|
|
164
|
+
dollar: "$" as const,
|
|
165
|
+
percent: "%" as const,
|
|
166
|
+
caret: "^" as const,
|
|
167
|
+
ampersand: "&" as const,
|
|
168
|
+
asterisk: "*" as const,
|
|
169
|
+
leftparen: "(" as const,
|
|
170
|
+
rightparen: ")" as const,
|
|
171
|
+
underscore: "_" as const,
|
|
172
|
+
plus: "+" as const,
|
|
173
|
+
pipe: "|" as const,
|
|
174
|
+
tilde: "~" as const,
|
|
175
|
+
leftbrace: "{" as const,
|
|
176
|
+
rightbrace: "}" as const,
|
|
177
|
+
colon: ":" as const,
|
|
178
|
+
lessthan: "<" as const,
|
|
179
|
+
greaterthan: ">" as const,
|
|
180
|
+
question: "?" as const,
|
|
181
|
+
|
|
182
|
+
// Single modifiers
|
|
183
|
+
ctrl: <K extends BaseKey>(key: K): `ctrl+${K}` => `ctrl+${key}`,
|
|
184
|
+
shift: <K extends BaseKey>(key: K): `shift+${K}` => `shift+${key}`,
|
|
185
|
+
alt: <K extends BaseKey>(key: K): `alt+${K}` => `alt+${key}`,
|
|
186
|
+
|
|
187
|
+
// Combined modifiers
|
|
188
|
+
ctrlShift: <K extends BaseKey>(key: K): `ctrl+shift+${K}` => `ctrl+shift+${key}`,
|
|
189
|
+
shiftCtrl: <K extends BaseKey>(key: K): `shift+ctrl+${K}` => `shift+ctrl+${key}`,
|
|
190
|
+
ctrlAlt: <K extends BaseKey>(key: K): `ctrl+alt+${K}` => `ctrl+alt+${key}`,
|
|
191
|
+
altCtrl: <K extends BaseKey>(key: K): `alt+ctrl+${K}` => `alt+ctrl+${key}`,
|
|
192
|
+
shiftAlt: <K extends BaseKey>(key: K): `shift+alt+${K}` => `shift+alt+${key}`,
|
|
193
|
+
altShift: <K extends BaseKey>(key: K): `alt+shift+${K}` => `alt+shift+${key}`,
|
|
194
|
+
|
|
195
|
+
// Triple modifiers
|
|
196
|
+
ctrlShiftAlt: <K extends BaseKey>(key: K): `ctrl+shift+alt+${K}` => `ctrl+shift+alt+${key}`,
|
|
47
197
|
} as const;
|
|
48
198
|
|
|
49
|
-
//
|
|
50
|
-
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// Constants
|
|
201
|
+
// =============================================================================
|
|
202
|
+
|
|
203
|
+
const SYMBOL_KEYS = new Set([
|
|
204
|
+
"`",
|
|
205
|
+
"-",
|
|
206
|
+
"=",
|
|
207
|
+
"[",
|
|
208
|
+
"]",
|
|
209
|
+
"\\",
|
|
210
|
+
";",
|
|
211
|
+
"'",
|
|
212
|
+
",",
|
|
213
|
+
".",
|
|
214
|
+
"/",
|
|
215
|
+
"!",
|
|
216
|
+
"@",
|
|
217
|
+
"#",
|
|
218
|
+
"$",
|
|
219
|
+
"%",
|
|
220
|
+
"^",
|
|
221
|
+
"&",
|
|
222
|
+
"*",
|
|
223
|
+
"(",
|
|
224
|
+
")",
|
|
225
|
+
"_",
|
|
226
|
+
"+",
|
|
227
|
+
"|",
|
|
228
|
+
"~",
|
|
229
|
+
"{",
|
|
230
|
+
"}",
|
|
231
|
+
":",
|
|
232
|
+
"<",
|
|
233
|
+
">",
|
|
234
|
+
"?",
|
|
235
|
+
]);
|
|
51
236
|
|
|
52
|
-
// Modifier bits (before adding 1)
|
|
53
237
|
const MODIFIERS = {
|
|
54
238
|
shift: 1,
|
|
55
239
|
alt: 2,
|
|
56
240
|
ctrl: 4,
|
|
57
|
-
super: 8,
|
|
58
241
|
} as const;
|
|
59
242
|
|
|
60
|
-
|
|
61
|
-
* Build a Kitty keyboard protocol sequence for a key with modifier.
|
|
62
|
-
*/
|
|
63
|
-
function kittySequence(codepoint: number, modifier: number): string {
|
|
64
|
-
return `\x1b[${codepoint};${modifier + 1}u`;
|
|
65
|
-
}
|
|
243
|
+
const LOCK_MASK = 64 + 128; // Caps Lock + Num Lock
|
|
66
244
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
245
|
+
const CODEPOINTS = {
|
|
246
|
+
escape: 27,
|
|
247
|
+
tab: 9,
|
|
248
|
+
enter: 13,
|
|
249
|
+
space: 32,
|
|
250
|
+
backspace: 127,
|
|
251
|
+
kpEnter: 57414, // Numpad Enter (Kitty protocol)
|
|
252
|
+
} as const;
|
|
253
|
+
|
|
254
|
+
const ARROW_CODEPOINTS = {
|
|
255
|
+
up: -1,
|
|
256
|
+
down: -2,
|
|
257
|
+
right: -3,
|
|
258
|
+
left: -4,
|
|
259
|
+
} as const;
|
|
74
260
|
|
|
75
|
-
/**
|
|
76
|
-
* Parse a Kitty keyboard protocol sequence.
|
|
77
|
-
* Handles formats:
|
|
78
|
-
* - \x1b[<codepoint>u (no modifier)
|
|
79
|
-
* - \x1b[<codepoint>;<modifier>u (with modifier)
|
|
80
|
-
* - \x1b[1;<modifier>A/B/C/D (arrow keys with modifier)
|
|
81
|
-
*
|
|
82
|
-
* Returns null if not a valid Kitty sequence.
|
|
83
|
-
*/
|
|
84
|
-
// Virtual codepoints for functional keys (negative to avoid conflicts)
|
|
85
261
|
const FUNCTIONAL_CODEPOINTS = {
|
|
86
262
|
delete: -10,
|
|
87
263
|
insert: -11,
|
|
@@ -91,8 +267,17 @@ const FUNCTIONAL_CODEPOINTS = {
|
|
|
91
267
|
end: -15,
|
|
92
268
|
} as const;
|
|
93
269
|
|
|
270
|
+
// =============================================================================
|
|
271
|
+
// Kitty Protocol Parsing
|
|
272
|
+
// =============================================================================
|
|
273
|
+
|
|
274
|
+
interface ParsedKittySequence {
|
|
275
|
+
codepoint: number;
|
|
276
|
+
modifier: number;
|
|
277
|
+
}
|
|
278
|
+
|
|
94
279
|
function parseKittySequence(data: string): ParsedKittySequence | null {
|
|
95
|
-
//
|
|
280
|
+
// CSI u format: \x1b[<num>u or \x1b[<num>;<mod>u
|
|
96
281
|
const csiUMatch = data.match(/^\x1b\[(\d+)(?:;(\d+))?u$/);
|
|
97
282
|
if (csiUMatch) {
|
|
98
283
|
const codepoint = parseInt(csiUMatch[1]!, 10);
|
|
@@ -100,30 +285,26 @@ function parseKittySequence(data: string): ParsedKittySequence | null {
|
|
|
100
285
|
return { codepoint, modifier: modValue - 1 };
|
|
101
286
|
}
|
|
102
287
|
|
|
103
|
-
//
|
|
288
|
+
// Arrow keys with modifier: \x1b[1;<mod>A/B/C/D
|
|
104
289
|
const arrowMatch = data.match(/^\x1b\[1;(\d+)([ABCD])$/);
|
|
105
290
|
if (arrowMatch) {
|
|
106
291
|
const modValue = parseInt(arrowMatch[1]!, 10);
|
|
107
|
-
// Map arrow letters to virtual codepoints for easier matching
|
|
108
292
|
const arrowCodes: Record<string, number> = { A: -1, B: -2, C: -3, D: -4 };
|
|
109
|
-
|
|
110
|
-
return { codepoint, modifier: modValue - 1 };
|
|
293
|
+
return { codepoint: arrowCodes[arrowMatch[2]!]!, modifier: modValue - 1 };
|
|
111
294
|
}
|
|
112
295
|
|
|
113
|
-
//
|
|
114
|
-
// DELETE=3, INSERT=2, PAGEUP=5, PAGEDOWN=6, etc.
|
|
296
|
+
// Functional keys: \x1b[<num>~ or \x1b[<num>;<mod>~
|
|
115
297
|
const funcMatch = data.match(/^\x1b\[(\d+)(?:;(\d+))?~$/);
|
|
116
298
|
if (funcMatch) {
|
|
117
299
|
const keyNum = parseInt(funcMatch[1]!, 10);
|
|
118
300
|
const modValue = funcMatch[2] ? parseInt(funcMatch[2], 10) : 1;
|
|
119
|
-
// Map functional key numbers to virtual codepoints
|
|
120
301
|
const funcCodes: Record<number, number> = {
|
|
121
302
|
2: FUNCTIONAL_CODEPOINTS.insert,
|
|
122
303
|
3: FUNCTIONAL_CODEPOINTS.delete,
|
|
123
304
|
5: FUNCTIONAL_CODEPOINTS.pageUp,
|
|
124
305
|
6: FUNCTIONAL_CODEPOINTS.pageDown,
|
|
125
|
-
7: FUNCTIONAL_CODEPOINTS.home,
|
|
126
|
-
8: FUNCTIONAL_CODEPOINTS.end,
|
|
306
|
+
7: FUNCTIONAL_CODEPOINTS.home,
|
|
307
|
+
8: FUNCTIONAL_CODEPOINTS.end,
|
|
127
308
|
};
|
|
128
309
|
const codepoint = funcCodes[keyNum];
|
|
129
310
|
if (codepoint !== undefined) {
|
|
@@ -131,7 +312,7 @@ function parseKittySequence(data: string): ParsedKittySequence | null {
|
|
|
131
312
|
}
|
|
132
313
|
}
|
|
133
314
|
|
|
134
|
-
//
|
|
315
|
+
// Home/End with modifier: \x1b[1;<mod>H/F
|
|
135
316
|
const homeEndMatch = data.match(/^\x1b\[1;(\d+)([HF])$/);
|
|
136
317
|
if (homeEndMatch) {
|
|
137
318
|
const modValue = parseInt(homeEndMatch[1]!, 10);
|
|
@@ -142,419 +323,462 @@ function parseKittySequence(data: string): ParsedKittySequence | null {
|
|
|
142
323
|
return null;
|
|
143
324
|
}
|
|
144
325
|
|
|
145
|
-
/**
|
|
146
|
-
* Check if a Kitty sequence matches the expected codepoint and modifier,
|
|
147
|
-
* ignoring lock key bits (Caps Lock, Num Lock).
|
|
148
|
-
*/
|
|
149
326
|
function matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {
|
|
150
327
|
const parsed = parseKittySequence(data);
|
|
151
328
|
if (!parsed) return false;
|
|
152
|
-
|
|
153
|
-
// Mask out lock bits from both sides for comparison
|
|
154
329
|
const actualMod = parsed.modifier & ~LOCK_MASK;
|
|
155
330
|
const expectedMod = expectedModifier & ~LOCK_MASK;
|
|
156
|
-
|
|
157
331
|
return parsed.codepoint === expectedCodepoint && actualMod === expectedMod;
|
|
158
332
|
}
|
|
159
333
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
CTRL_A: kittySequence(CODEPOINTS.a, MODIFIERS.ctrl),
|
|
164
|
-
CTRL_C: kittySequence(CODEPOINTS.c, MODIFIERS.ctrl),
|
|
165
|
-
CTRL_D: kittySequence(CODEPOINTS.d, MODIFIERS.ctrl),
|
|
166
|
-
CTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl),
|
|
167
|
-
CTRL_G: kittySequence(CODEPOINTS.g, MODIFIERS.ctrl),
|
|
168
|
-
CTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl),
|
|
169
|
-
CTRL_L: kittySequence(CODEPOINTS.l, MODIFIERS.ctrl),
|
|
170
|
-
CTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl),
|
|
171
|
-
CTRL_P: kittySequence(CODEPOINTS.p, MODIFIERS.ctrl),
|
|
172
|
-
CTRL_T: kittySequence(CODEPOINTS.t, MODIFIERS.ctrl),
|
|
173
|
-
CTRL_U: kittySequence(CODEPOINTS.u, MODIFIERS.ctrl),
|
|
174
|
-
CTRL_V: kittySequence(CODEPOINTS.v, MODIFIERS.ctrl),
|
|
175
|
-
CTRL_W: kittySequence(CODEPOINTS.w, MODIFIERS.ctrl),
|
|
176
|
-
CTRL_Z: kittySequence(CODEPOINTS.z, MODIFIERS.ctrl),
|
|
177
|
-
|
|
178
|
-
// Enter combinations
|
|
179
|
-
SHIFT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.shift),
|
|
180
|
-
ALT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.alt),
|
|
181
|
-
CTRL_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.ctrl),
|
|
182
|
-
|
|
183
|
-
// Tab combinations
|
|
184
|
-
SHIFT_TAB: kittySequence(CODEPOINTS.tab, MODIFIERS.shift),
|
|
185
|
-
|
|
186
|
-
// Backspace combinations
|
|
187
|
-
ALT_BACKSPACE: kittySequence(CODEPOINTS.backspace, MODIFIERS.alt),
|
|
188
|
-
} as const;
|
|
334
|
+
// =============================================================================
|
|
335
|
+
// Generic Key Matching
|
|
336
|
+
// =============================================================================
|
|
189
337
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
* @param data - The input data to check
|
|
194
|
-
* @param key - Single lowercase letter (e.g., 'c' for Ctrl+C)
|
|
195
|
-
*/
|
|
196
|
-
export function isKittyCtrl(data: string, key: string): boolean {
|
|
197
|
-
if (key.length !== 1) return false;
|
|
198
|
-
const codepoint = key.charCodeAt(0);
|
|
199
|
-
// Check exact match first (fast path)
|
|
200
|
-
if (data === kittySequence(codepoint, MODIFIERS.ctrl)) return true;
|
|
201
|
-
// Check with lock bits masked out
|
|
202
|
-
return matchesKittySequence(data, codepoint, MODIFIERS.ctrl);
|
|
338
|
+
function rawCtrlChar(letter: string): string {
|
|
339
|
+
const code = letter.toLowerCase().charCodeAt(0) - 96;
|
|
340
|
+
return String.fromCharCode(code);
|
|
203
341
|
}
|
|
204
342
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return matchesKittySequence(data, codepoint, modifier);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Raw control character codes
|
|
220
|
-
const RAW = {
|
|
221
|
-
CTRL_A: "\x01",
|
|
222
|
-
CTRL_C: "\x03",
|
|
223
|
-
CTRL_D: "\x04",
|
|
224
|
-
CTRL_E: "\x05",
|
|
225
|
-
CTRL_G: "\x07",
|
|
226
|
-
CTRL_K: "\x0b",
|
|
227
|
-
CTRL_L: "\x0c",
|
|
228
|
-
CTRL_O: "\x0f",
|
|
229
|
-
CTRL_P: "\x10",
|
|
230
|
-
CTRL_T: "\x14",
|
|
231
|
-
CTRL_U: "\x15",
|
|
232
|
-
CTRL_V: "\x16",
|
|
233
|
-
CTRL_W: "\x17",
|
|
234
|
-
CTRL_Z: "\x1a",
|
|
235
|
-
ALT_BACKSPACE: "\x1b\x7f",
|
|
236
|
-
SHIFT_TAB: "\x1b[Z",
|
|
237
|
-
} as const;
|
|
343
|
+
function parseKeyId(keyId: string): { key: string; ctrl: boolean; shift: boolean; alt: boolean } | null {
|
|
344
|
+
const parts = keyId.toLowerCase().split("+");
|
|
345
|
+
const key = parts[parts.length - 1];
|
|
346
|
+
if (!key) return null;
|
|
347
|
+
return {
|
|
348
|
+
key,
|
|
349
|
+
ctrl: parts.includes("ctrl"),
|
|
350
|
+
shift: parts.includes("shift"),
|
|
351
|
+
alt: parts.includes("alt"),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
238
354
|
|
|
239
355
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
356
|
+
* Match input data against a key identifier string.
|
|
357
|
+
*
|
|
358
|
+
* Supported key identifiers:
|
|
359
|
+
* - Single keys: "escape", "tab", "enter", "backspace", "delete", "home", "end", "space"
|
|
360
|
+
* - Arrow keys: "up", "down", "left", "right"
|
|
361
|
+
* - Ctrl combinations: "ctrl+c", "ctrl+z", etc.
|
|
362
|
+
* - Shift combinations: "shift+tab", "shift+enter"
|
|
363
|
+
* - Alt combinations: "alt+enter", "alt+backspace"
|
|
364
|
+
* - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x"
|
|
365
|
+
*
|
|
366
|
+
* Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p")
|
|
367
|
+
*
|
|
368
|
+
* @param data - Raw input data from terminal
|
|
369
|
+
* @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c"))
|
|
242
370
|
*/
|
|
243
|
-
export function
|
|
244
|
-
|
|
371
|
+
export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
372
|
+
const parsed = parseKeyId(keyId);
|
|
373
|
+
if (!parsed) return false;
|
|
374
|
+
|
|
375
|
+
const { key, ctrl, shift, alt } = parsed;
|
|
376
|
+
let modifier = 0;
|
|
377
|
+
if (shift) modifier |= MODIFIERS.shift;
|
|
378
|
+
if (alt) modifier |= MODIFIERS.alt;
|
|
379
|
+
if (ctrl) modifier |= MODIFIERS.ctrl;
|
|
380
|
+
|
|
381
|
+
switch (key) {
|
|
382
|
+
case "escape":
|
|
383
|
+
case "esc":
|
|
384
|
+
if (modifier !== 0) return false;
|
|
385
|
+
return data === "\x1b" || matchesKittySequence(data, CODEPOINTS.escape, 0);
|
|
386
|
+
|
|
387
|
+
case "space":
|
|
388
|
+
if (modifier === 0) {
|
|
389
|
+
return data === " " || matchesKittySequence(data, CODEPOINTS.space, 0);
|
|
390
|
+
}
|
|
391
|
+
return matchesKittySequence(data, CODEPOINTS.space, modifier);
|
|
392
|
+
|
|
393
|
+
case "tab":
|
|
394
|
+
if (shift && !ctrl && !alt) {
|
|
395
|
+
return data === "\x1b[Z" || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift);
|
|
396
|
+
}
|
|
397
|
+
if (modifier === 0) {
|
|
398
|
+
return data === "\t" || matchesKittySequence(data, CODEPOINTS.tab, 0);
|
|
399
|
+
}
|
|
400
|
+
return matchesKittySequence(data, CODEPOINTS.tab, modifier);
|
|
401
|
+
|
|
402
|
+
case "enter":
|
|
403
|
+
case "return":
|
|
404
|
+
if (shift && !ctrl && !alt) {
|
|
405
|
+
return (
|
|
406
|
+
data === "\x1b\r" || // Legacy: some terminals send ESC+CR for shift+enter
|
|
407
|
+
matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) ||
|
|
408
|
+
matchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.shift)
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
if (alt && !ctrl && !shift) {
|
|
412
|
+
return (
|
|
413
|
+
matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) ||
|
|
414
|
+
matchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.alt)
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
if (modifier === 0) {
|
|
418
|
+
return (
|
|
419
|
+
data === "\r" ||
|
|
420
|
+
data === "\x1bOM" || // SS3 M (numpad enter in some terminals)
|
|
421
|
+
matchesKittySequence(data, CODEPOINTS.enter, 0) ||
|
|
422
|
+
matchesKittySequence(data, CODEPOINTS.kpEnter, 0)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return (
|
|
426
|
+
matchesKittySequence(data, CODEPOINTS.enter, modifier) ||
|
|
427
|
+
matchesKittySequence(data, CODEPOINTS.kpEnter, modifier)
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
case "backspace":
|
|
431
|
+
if (alt && !ctrl && !shift) {
|
|
432
|
+
return data === "\x1b\x7f" || matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt);
|
|
433
|
+
}
|
|
434
|
+
if (modifier === 0) {
|
|
435
|
+
return data === "\x7f" || data === "\x08" || matchesKittySequence(data, CODEPOINTS.backspace, 0);
|
|
436
|
+
}
|
|
437
|
+
return matchesKittySequence(data, CODEPOINTS.backspace, modifier);
|
|
438
|
+
|
|
439
|
+
case "delete":
|
|
440
|
+
if (modifier === 0) {
|
|
441
|
+
return data === "\x1b[3~" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);
|
|
442
|
+
}
|
|
443
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, modifier);
|
|
444
|
+
|
|
445
|
+
case "home":
|
|
446
|
+
if (modifier === 0) {
|
|
447
|
+
return (
|
|
448
|
+
data === "\x1b[H" ||
|
|
449
|
+
data === "\x1b[1~" ||
|
|
450
|
+
data === "\x1b[7~" ||
|
|
451
|
+
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, modifier);
|
|
455
|
+
|
|
456
|
+
case "end":
|
|
457
|
+
if (modifier === 0) {
|
|
458
|
+
return (
|
|
459
|
+
data === "\x1b[F" ||
|
|
460
|
+
data === "\x1b[4~" ||
|
|
461
|
+
data === "\x1b[8~" ||
|
|
462
|
+
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, modifier);
|
|
466
|
+
|
|
467
|
+
case "up":
|
|
468
|
+
if (modifier === 0) {
|
|
469
|
+
return data === "\x1b[A" || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);
|
|
470
|
+
}
|
|
471
|
+
return matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);
|
|
472
|
+
|
|
473
|
+
case "down":
|
|
474
|
+
if (modifier === 0) {
|
|
475
|
+
return data === "\x1b[B" || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);
|
|
476
|
+
}
|
|
477
|
+
return matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);
|
|
478
|
+
|
|
479
|
+
case "left":
|
|
480
|
+
if (alt && !ctrl && !shift) {
|
|
481
|
+
return (
|
|
482
|
+
data === "\x1b[1;3D" ||
|
|
483
|
+
data === "\x1bb" ||
|
|
484
|
+
matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt)
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
if (ctrl && !alt && !shift) {
|
|
488
|
+
return data === "\x1b[1;5D" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);
|
|
489
|
+
}
|
|
490
|
+
if (modifier === 0) {
|
|
491
|
+
return data === "\x1b[D" || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);
|
|
492
|
+
}
|
|
493
|
+
return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);
|
|
494
|
+
|
|
495
|
+
case "right":
|
|
496
|
+
if (alt && !ctrl && !shift) {
|
|
497
|
+
return (
|
|
498
|
+
data === "\x1b[1;3C" ||
|
|
499
|
+
data === "\x1bf" ||
|
|
500
|
+
matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt)
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
if (ctrl && !alt && !shift) {
|
|
504
|
+
return data === "\x1b[1;5C" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);
|
|
505
|
+
}
|
|
506
|
+
if (modifier === 0) {
|
|
507
|
+
return data === "\x1b[C" || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);
|
|
508
|
+
}
|
|
509
|
+
return matchesKittySequence(data, ARROW_CODEPOINTS.right, modifier);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Handle single letter keys (a-z) and some symbols
|
|
513
|
+
if (key.length === 1 && ((key >= "a" && key <= "z") || SYMBOL_KEYS.has(key))) {
|
|
514
|
+
const codepoint = key.charCodeAt(0);
|
|
515
|
+
|
|
516
|
+
if (ctrl && !shift && !alt) {
|
|
517
|
+
const raw = rawCtrlChar(key);
|
|
518
|
+
if (data === raw) return true;
|
|
519
|
+
if (data.length > 0 && data.charCodeAt(0) === raw.charCodeAt(0)) return true;
|
|
520
|
+
return matchesKittySequence(data, codepoint, MODIFIERS.ctrl);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (ctrl && shift && !alt) {
|
|
524
|
+
return matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (shift && !ctrl && !alt) {
|
|
528
|
+
// Legacy: shift+letter produces uppercase
|
|
529
|
+
if (data === key.toUpperCase()) return true;
|
|
530
|
+
return matchesKittySequence(data, codepoint, MODIFIERS.shift);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (modifier !== 0) {
|
|
534
|
+
return matchesKittySequence(data, codepoint, modifier);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return data === key;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return false;
|
|
245
541
|
}
|
|
246
542
|
|
|
247
543
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
544
|
+
* Parse input data and return the key identifier if recognized.
|
|
545
|
+
*
|
|
546
|
+
* @param data - Raw input data from terminal
|
|
547
|
+
* @returns Key identifier string (e.g., "ctrl+c") or undefined
|
|
548
|
+
*/
|
|
549
|
+
export function parseKey(data: string): string | undefined {
|
|
550
|
+
const kitty = parseKittySequence(data);
|
|
551
|
+
if (kitty) {
|
|
552
|
+
const { codepoint, modifier } = kitty;
|
|
553
|
+
const mods: string[] = [];
|
|
554
|
+
const effectiveMod = modifier & ~LOCK_MASK;
|
|
555
|
+
if (effectiveMod & MODIFIERS.shift) mods.push("shift");
|
|
556
|
+
if (effectiveMod & MODIFIERS.ctrl) mods.push("ctrl");
|
|
557
|
+
if (effectiveMod & MODIFIERS.alt) mods.push("alt");
|
|
558
|
+
|
|
559
|
+
let keyName: string | undefined;
|
|
560
|
+
if (codepoint === CODEPOINTS.escape) keyName = "escape";
|
|
561
|
+
else if (codepoint === CODEPOINTS.tab) keyName = "tab";
|
|
562
|
+
else if (codepoint === CODEPOINTS.enter || codepoint === CODEPOINTS.kpEnter) keyName = "enter";
|
|
563
|
+
else if (codepoint === CODEPOINTS.space) keyName = "space";
|
|
564
|
+
else if (codepoint === CODEPOINTS.backspace) keyName = "backspace";
|
|
565
|
+
else if (codepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = "delete";
|
|
566
|
+
else if (codepoint === FUNCTIONAL_CODEPOINTS.home) keyName = "home";
|
|
567
|
+
else if (codepoint === FUNCTIONAL_CODEPOINTS.end) keyName = "end";
|
|
568
|
+
else if (codepoint === ARROW_CODEPOINTS.up) keyName = "up";
|
|
569
|
+
else if (codepoint === ARROW_CODEPOINTS.down) keyName = "down";
|
|
570
|
+
else if (codepoint === ARROW_CODEPOINTS.left) keyName = "left";
|
|
571
|
+
else if (codepoint === ARROW_CODEPOINTS.right) keyName = "right";
|
|
572
|
+
else if (codepoint >= 97 && codepoint <= 122) keyName = String.fromCharCode(codepoint);
|
|
573
|
+
else if (SYMBOL_KEYS.has(String.fromCharCode(codepoint))) keyName = String.fromCharCode(codepoint);
|
|
574
|
+
|
|
575
|
+
if (keyName) {
|
|
576
|
+
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Legacy sequences
|
|
581
|
+
if (data === "\x1b") return "escape";
|
|
582
|
+
if (data === "\t") return "tab";
|
|
583
|
+
if (data === "\r" || data === "\x1bOM") return "enter";
|
|
584
|
+
if (data === " ") return "space";
|
|
585
|
+
if (data === "\x7f" || data === "\x08") return "backspace";
|
|
586
|
+
if (data === "\x1b[Z") return "shift+tab";
|
|
587
|
+
if (data === "\x1b\r") return "shift+enter"; // Legacy: ESC+CR for shift+enter
|
|
588
|
+
if (data === "\x1b\x7f") return "alt+backspace";
|
|
589
|
+
if (data === "\x1b[A") return "up";
|
|
590
|
+
if (data === "\x1b[B") return "down";
|
|
591
|
+
if (data === "\x1b[C") return "right";
|
|
592
|
+
if (data === "\x1b[D") return "left";
|
|
593
|
+
if (data === "\x1b[H") return "home";
|
|
594
|
+
if (data === "\x1b[F") return "end";
|
|
595
|
+
if (data === "\x1b[3~") return "delete";
|
|
596
|
+
|
|
597
|
+
// Raw Ctrl+letter
|
|
598
|
+
if (data.length === 1) {
|
|
599
|
+
const code = data.charCodeAt(0);
|
|
600
|
+
if (code >= 1 && code <= 26) {
|
|
601
|
+
return `ctrl+${String.fromCharCode(code + 96)}`;
|
|
602
|
+
}
|
|
603
|
+
if (code >= 32 && code <= 126) {
|
|
604
|
+
return data;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return undefined;
|
|
253
609
|
}
|
|
254
610
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
export function
|
|
260
|
-
return
|
|
611
|
+
// =============================================================================
|
|
612
|
+
// Legacy helper wrappers (for compatibility)
|
|
613
|
+
// =============================================================================
|
|
614
|
+
|
|
615
|
+
export function isArrowUp(data: string): boolean {
|
|
616
|
+
return matchesKey(data, "up");
|
|
261
617
|
}
|
|
262
618
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
* Ignores lock key bits.
|
|
266
|
-
*/
|
|
267
|
-
export function isCtrlE(data: string): boolean {
|
|
268
|
-
return data === RAW.CTRL_E || data === Keys.CTRL_E || matchesKittySequence(data, CODEPOINTS.e, MODIFIERS.ctrl);
|
|
619
|
+
export function isArrowDown(data: string): boolean {
|
|
620
|
+
return matchesKey(data, "down");
|
|
269
621
|
}
|
|
270
622
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
* Ignores lock key bits.
|
|
274
|
-
*/
|
|
275
|
-
export function isCtrlG(data: string): boolean {
|
|
276
|
-
return data === RAW.CTRL_G || data === Keys.CTRL_G || matchesKittySequence(data, CODEPOINTS.g, MODIFIERS.ctrl);
|
|
623
|
+
export function isArrowLeft(data: string): boolean {
|
|
624
|
+
return matchesKey(data, "left");
|
|
277
625
|
}
|
|
278
626
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
* Ignores lock key bits.
|
|
282
|
-
* Also checks if first byte is 0x0b for compatibility with terminals
|
|
283
|
-
* that may send trailing bytes.
|
|
284
|
-
*/
|
|
285
|
-
export function isCtrlK(data: string): boolean {
|
|
286
|
-
return (
|
|
287
|
-
data === RAW.CTRL_K ||
|
|
288
|
-
(data.length > 0 && data.charCodeAt(0) === 0x0b) ||
|
|
289
|
-
data === Keys.CTRL_K ||
|
|
290
|
-
matchesKittySequence(data, CODEPOINTS.k, MODIFIERS.ctrl)
|
|
291
|
-
);
|
|
627
|
+
export function isArrowRight(data: string): boolean {
|
|
628
|
+
return matchesKey(data, "right");
|
|
292
629
|
}
|
|
293
630
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
* Ignores lock key bits.
|
|
297
|
-
*/
|
|
298
|
-
export function isCtrlL(data: string): boolean {
|
|
299
|
-
return data === RAW.CTRL_L || data === Keys.CTRL_L || matchesKittySequence(data, CODEPOINTS.l, MODIFIERS.ctrl);
|
|
631
|
+
export function isEscape(data: string): boolean {
|
|
632
|
+
return matchesKey(data, "escape") || matchesKey(data, "esc");
|
|
300
633
|
}
|
|
301
634
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
* Ignores lock key bits.
|
|
305
|
-
*/
|
|
306
|
-
export function isCtrlO(data: string): boolean {
|
|
307
|
-
return data === RAW.CTRL_O || data === Keys.CTRL_O || matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.ctrl);
|
|
635
|
+
export function isEnter(data: string): boolean {
|
|
636
|
+
return matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n";
|
|
308
637
|
}
|
|
309
638
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
* Ignores lock key bits.
|
|
313
|
-
*/
|
|
314
|
-
export function isShiftCtrlO(data: string): boolean {
|
|
315
|
-
return matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.shift + MODIFIERS.ctrl);
|
|
639
|
+
export function isTab(data: string): boolean {
|
|
640
|
+
return matchesKey(data, "tab");
|
|
316
641
|
}
|
|
317
642
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
* Ignores lock key bits.
|
|
321
|
-
*/
|
|
322
|
-
export function isCtrlP(data: string): boolean {
|
|
323
|
-
return data === RAW.CTRL_P || data === Keys.CTRL_P || matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.ctrl);
|
|
643
|
+
export function isShiftTab(data: string): boolean {
|
|
644
|
+
return matchesKey(data, "shift+tab");
|
|
324
645
|
}
|
|
325
646
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
* Ignores lock key bits.
|
|
329
|
-
*/
|
|
330
|
-
export function isShiftCtrlP(data: string): boolean {
|
|
331
|
-
return matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.shift + MODIFIERS.ctrl);
|
|
647
|
+
export function isShiftEnter(data: string): boolean {
|
|
648
|
+
return matchesKey(data, "shift+enter");
|
|
332
649
|
}
|
|
333
650
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
* Ignores lock key bits.
|
|
337
|
-
*/
|
|
338
|
-
export function isShiftCtrlD(data: string): boolean {
|
|
339
|
-
return matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.shift + MODIFIERS.ctrl);
|
|
651
|
+
export function isAltEnter(data: string): boolean {
|
|
652
|
+
return matchesKey(data, "alt+enter");
|
|
340
653
|
}
|
|
341
654
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
* Ignores lock key bits.
|
|
345
|
-
*/
|
|
346
|
-
export function isCtrlT(data: string): boolean {
|
|
347
|
-
return data === RAW.CTRL_T || data === Keys.CTRL_T || matchesKittySequence(data, CODEPOINTS.t, MODIFIERS.ctrl);
|
|
655
|
+
export function isAltBackspace(data: string): boolean {
|
|
656
|
+
return matchesKey(data, "alt+backspace");
|
|
348
657
|
}
|
|
349
658
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
* Ignores lock key bits.
|
|
353
|
-
*/
|
|
354
|
-
export function isCtrlU(data: string): boolean {
|
|
355
|
-
return data === RAW.CTRL_U || data === Keys.CTRL_U || matchesKittySequence(data, CODEPOINTS.u, MODIFIERS.ctrl);
|
|
659
|
+
export function isBackspace(data: string): boolean {
|
|
660
|
+
return matchesKey(data, "backspace");
|
|
356
661
|
}
|
|
357
662
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
* Ignores lock key bits.
|
|
361
|
-
* Note: In most terminals, Ctrl+V triggers paste which sends bracketed paste markers,
|
|
362
|
-
* but raw mode can intercept the key before the terminal processes it.
|
|
363
|
-
*/
|
|
364
|
-
export function isCtrlV(data: string): boolean {
|
|
365
|
-
return data === RAW.CTRL_V || data === Keys.CTRL_V || matchesKittySequence(data, CODEPOINTS.v, MODIFIERS.ctrl);
|
|
663
|
+
export function isDelete(data: string): boolean {
|
|
664
|
+
return matchesKey(data, "delete");
|
|
366
665
|
}
|
|
367
666
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
* Ignores lock key bits.
|
|
371
|
-
*/
|
|
372
|
-
export function isCtrlW(data: string): boolean {
|
|
373
|
-
return data === RAW.CTRL_W || data === Keys.CTRL_W || matchesKittySequence(data, CODEPOINTS.w, MODIFIERS.ctrl);
|
|
667
|
+
export function isHome(data: string): boolean {
|
|
668
|
+
return matchesKey(data, "home");
|
|
374
669
|
}
|
|
375
670
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
* Ignores lock key bits.
|
|
379
|
-
*/
|
|
380
|
-
export function isCtrlZ(data: string): boolean {
|
|
381
|
-
return data === RAW.CTRL_Z || data === Keys.CTRL_Z || matchesKittySequence(data, CODEPOINTS.z, MODIFIERS.ctrl);
|
|
671
|
+
export function isEnd(data: string): boolean {
|
|
672
|
+
return matchesKey(data, "end");
|
|
382
673
|
}
|
|
383
674
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
* Ignores lock key bits.
|
|
387
|
-
*/
|
|
388
|
-
export function isAltBackspace(data: string): boolean {
|
|
389
|
-
return (
|
|
390
|
-
data === RAW.ALT_BACKSPACE ||
|
|
391
|
-
data === Keys.ALT_BACKSPACE ||
|
|
392
|
-
matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt)
|
|
393
|
-
);
|
|
675
|
+
export function isCtrlA(data: string): boolean {
|
|
676
|
+
return matchesKey(data, "ctrl+a");
|
|
394
677
|
}
|
|
395
678
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
* Ignores lock key bits.
|
|
399
|
-
*/
|
|
400
|
-
export function isShiftTab(data: string): boolean {
|
|
401
|
-
return (
|
|
402
|
-
data === RAW.SHIFT_TAB || data === Keys.SHIFT_TAB || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift)
|
|
403
|
-
);
|
|
679
|
+
export function isCtrlC(data: string): boolean {
|
|
680
|
+
return matchesKey(data, "ctrl+c");
|
|
404
681
|
}
|
|
405
682
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
* Raw: \x1b (single byte)
|
|
409
|
-
* Kitty: \x1b[27u (codepoint 27 = escape)
|
|
410
|
-
* Ignores lock key bits.
|
|
411
|
-
*/
|
|
412
|
-
export function isEscape(data: string): boolean {
|
|
413
|
-
return data === "\x1b" || data === `\x1b[${CODEPOINTS.escape}u` || matchesKittySequence(data, CODEPOINTS.escape, 0);
|
|
683
|
+
export function isCtrlD(data: string): boolean {
|
|
684
|
+
return matchesKey(data, "ctrl+d");
|
|
414
685
|
}
|
|
415
686
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
down: -2,
|
|
420
|
-
right: -3,
|
|
421
|
-
left: -4,
|
|
422
|
-
} as const;
|
|
687
|
+
export function isCtrlE(data: string): boolean {
|
|
688
|
+
return matchesKey(data, "ctrl+e");
|
|
689
|
+
}
|
|
423
690
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
* Handles both legacy (\x1b[A) and Kitty protocol with modifiers.
|
|
427
|
-
*/
|
|
428
|
-
export function isArrowUp(data: string): boolean {
|
|
429
|
-
return data === "\x1b[A" || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);
|
|
691
|
+
export function isCtrlG(data: string): boolean {
|
|
692
|
+
return matchesKey(data, "ctrl+g");
|
|
430
693
|
}
|
|
431
694
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
* Handles both legacy (\x1b[B) and Kitty protocol with modifiers.
|
|
435
|
-
*/
|
|
436
|
-
export function isArrowDown(data: string): boolean {
|
|
437
|
-
return data === "\x1b[B" || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);
|
|
695
|
+
export function isCtrlK(data: string): boolean {
|
|
696
|
+
return matchesKey(data, "ctrl+k");
|
|
438
697
|
}
|
|
439
698
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
* Handles both legacy (\x1b[C) and Kitty protocol with modifiers.
|
|
443
|
-
*/
|
|
444
|
-
export function isArrowRight(data: string): boolean {
|
|
445
|
-
return data === "\x1b[C" || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);
|
|
699
|
+
export function isCtrlL(data: string): boolean {
|
|
700
|
+
return matchesKey(data, "ctrl+l");
|
|
446
701
|
}
|
|
447
702
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
* Handles both legacy (\x1b[D) and Kitty protocol with modifiers.
|
|
451
|
-
*/
|
|
452
|
-
export function isArrowLeft(data: string): boolean {
|
|
453
|
-
return data === "\x1b[D" || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);
|
|
703
|
+
export function isCtrlO(data: string): boolean {
|
|
704
|
+
return matchesKey(data, "ctrl+o");
|
|
454
705
|
}
|
|
455
706
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
* Handles both legacy (\t) and Kitty protocol.
|
|
459
|
-
*/
|
|
460
|
-
export function isTab(data: string): boolean {
|
|
461
|
-
return data === "\t" || matchesKittySequence(data, CODEPOINTS.tab, 0);
|
|
707
|
+
export function isCtrlP(data: string): boolean {
|
|
708
|
+
return matchesKey(data, "ctrl+p");
|
|
462
709
|
}
|
|
463
710
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
* Handles both legacy (\r) and Kitty protocol.
|
|
467
|
-
*/
|
|
468
|
-
export function isEnter(data: string): boolean {
|
|
469
|
-
return data === "\r" || matchesKittySequence(data, CODEPOINTS.enter, 0);
|
|
711
|
+
export function isCtrlT(data: string): boolean {
|
|
712
|
+
return matchesKey(data, "ctrl+t");
|
|
470
713
|
}
|
|
471
714
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
* Handles both legacy (\x7f, \x08) and Kitty protocol.
|
|
475
|
-
*/
|
|
476
|
-
export function isBackspace(data: string): boolean {
|
|
477
|
-
return data === "\x7f" || data === "\x08" || matchesKittySequence(data, CODEPOINTS.backspace, 0);
|
|
715
|
+
export function isCtrlU(data: string): boolean {
|
|
716
|
+
return matchesKey(data, "ctrl+u");
|
|
478
717
|
}
|
|
479
718
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
* Ignores lock key bits.
|
|
483
|
-
*/
|
|
484
|
-
export function isShiftEnter(data: string): boolean {
|
|
485
|
-
return data === Keys.SHIFT_ENTER || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift);
|
|
719
|
+
export function isCtrlV(data: string): boolean {
|
|
720
|
+
return matchesKey(data, "ctrl+v");
|
|
486
721
|
}
|
|
487
722
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
* Ignores lock key bits.
|
|
491
|
-
*/
|
|
492
|
-
export function isAltEnter(data: string): boolean {
|
|
493
|
-
return data === Keys.ALT_ENTER || data === "\x1b\r" || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt);
|
|
723
|
+
export function isCtrlW(data: string): boolean {
|
|
724
|
+
return matchesKey(data, "ctrl+w");
|
|
494
725
|
}
|
|
495
726
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
* Handles multiple formats including Kitty protocol.
|
|
499
|
-
*/
|
|
500
|
-
export function isAltLeft(data: string): boolean {
|
|
501
|
-
return data === "\x1b[1;3D" || data === "\x1bb" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt);
|
|
727
|
+
export function isCtrlY(data: string): boolean {
|
|
728
|
+
return matchesKey(data, "ctrl+y");
|
|
502
729
|
}
|
|
503
730
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
* Handles multiple formats including Kitty protocol.
|
|
507
|
-
*/
|
|
508
|
-
export function isAltRight(data: string): boolean {
|
|
509
|
-
return data === "\x1b[1;3C" || data === "\x1bf" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt);
|
|
731
|
+
export function isCtrlZ(data: string): boolean {
|
|
732
|
+
return matchesKey(data, "ctrl+z");
|
|
510
733
|
}
|
|
511
734
|
|
|
512
|
-
/**
|
|
513
|
-
* Check if input matches Ctrl+Left (word navigation).
|
|
514
|
-
* Handles multiple formats including Kitty protocol.
|
|
515
|
-
*/
|
|
516
735
|
export function isCtrlLeft(data: string): boolean {
|
|
517
|
-
return
|
|
736
|
+
return matchesKey(data, "ctrl+left");
|
|
518
737
|
}
|
|
519
738
|
|
|
520
|
-
/**
|
|
521
|
-
* Check if input matches Ctrl+Right (word navigation).
|
|
522
|
-
* Handles multiple formats including Kitty protocol.
|
|
523
|
-
*/
|
|
524
739
|
export function isCtrlRight(data: string): boolean {
|
|
525
|
-
return
|
|
740
|
+
return matchesKey(data, "ctrl+right");
|
|
526
741
|
}
|
|
527
742
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
* Handles legacy formats and Kitty protocol with lock key modifiers.
|
|
531
|
-
*/
|
|
532
|
-
export function isHome(data: string): boolean {
|
|
533
|
-
return (
|
|
534
|
-
data === "\x1b[H" ||
|
|
535
|
-
data === "\x1b[1~" ||
|
|
536
|
-
data === "\x1b[7~" ||
|
|
537
|
-
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)
|
|
538
|
-
);
|
|
743
|
+
export function isAltLeft(data: string): boolean {
|
|
744
|
+
return matchesKey(data, "alt+left");
|
|
539
745
|
}
|
|
540
746
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
export function
|
|
546
|
-
return (
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
747
|
+
export function isAltRight(data: string): boolean {
|
|
748
|
+
return matchesKey(data, "alt+right");
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export function isShiftCtrlD(data: string): boolean {
|
|
752
|
+
return matchesKey(data, "shift+ctrl+d") || matchesKey(data, "ctrl+shift+d");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
export function isShiftCtrlO(data: string): boolean {
|
|
756
|
+
return matchesKey(data, "shift+ctrl+o") || matchesKey(data, "ctrl+shift+o");
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export function isShiftCtrlP(data: string): boolean {
|
|
760
|
+
return matchesKey(data, "shift+ctrl+p") || matchesKey(data, "ctrl+shift+p");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export function isShiftBackspace(data: string): boolean {
|
|
764
|
+
return matchesKey(data, "shift+backspace");
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export function isShiftDelete(data: string): boolean {
|
|
768
|
+
return matchesKey(data, "shift+delete");
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export function isShiftSpace(data: string): boolean {
|
|
772
|
+
return matchesKey(data, "shift+space");
|
|
552
773
|
}
|
|
553
774
|
|
|
554
775
|
/**
|
|
555
|
-
* Check if input
|
|
556
|
-
*
|
|
776
|
+
* Check if input indicates Caps Lock state change.
|
|
777
|
+
* Kitty protocol reports Caps Lock via modifier bit 64.
|
|
557
778
|
*/
|
|
558
|
-
export function
|
|
559
|
-
|
|
779
|
+
export function isCapsLock(data: string): boolean {
|
|
780
|
+
const parsed = parseKittySequence(data);
|
|
781
|
+
if (!parsed) return false;
|
|
782
|
+
// Caps Lock is modifier bit 64
|
|
783
|
+
return (parsed.modifier & 64) !== 0;
|
|
560
784
|
}
|