@xterm/xterm 6.1.0-beta.98 → 6.1.0-beta.99
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/lib/xterm.js +1 -1
- package/lib/xterm.js.map +1 -1
- package/lib/xterm.mjs +16 -16
- package/lib/xterm.mjs.map +4 -4
- package/package.json +2 -2
- package/src/browser/CoreBrowserTerminal.ts +17 -7
- package/src/browser/services/KeyboardService.ts +41 -0
- package/src/browser/services/Services.ts +9 -1
- package/src/common/InputHandler.ts +121 -3
- package/src/common/Types.ts +17 -0
- package/src/common/Version.ts +1 -1
- package/src/common/input/KittyKeyboard.ts +513 -0
- package/src/common/services/CoreService.ts +12 -1
- package/src/common/services/OptionsService.ts +2 -1
- package/src/common/services/Services.ts +8 -1
- package/typings/xterm.d.ts +35 -6
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*
|
|
5
|
+
* Kitty keyboard protocol implementation.
|
|
6
|
+
* @see https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { IKeyboardEvent, IKeyboardResult, KeyboardResultType } from 'common/Types';
|
|
10
|
+
import { C0 } from 'common/data/EscapeSequences';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Kitty keyboard protocol enhancement flags (bitfield).
|
|
14
|
+
*/
|
|
15
|
+
export const enum KittyKeyboardFlags {
|
|
16
|
+
NONE = 0b00000,
|
|
17
|
+
/** Disambiguate escape codes - fixes ambiguous legacy encodings */
|
|
18
|
+
DISAMBIGUATE_ESCAPE_CODES = 0b00001,
|
|
19
|
+
/** Report event types - press/repeat/release */
|
|
20
|
+
REPORT_EVENT_TYPES = 0b00010,
|
|
21
|
+
/** Report alternate keys - shifted key and base layout key */
|
|
22
|
+
REPORT_ALTERNATE_KEYS = 0b00100,
|
|
23
|
+
/** Report all keys as escape codes - text-producing keys as CSI u */
|
|
24
|
+
REPORT_ALL_KEYS_AS_ESCAPE_CODES = 0b01000,
|
|
25
|
+
/** Report associated text - includes text codepoints in escape code */
|
|
26
|
+
REPORT_ASSOCIATED_TEXT = 0b10000,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Kitty keyboard event types.
|
|
31
|
+
*/
|
|
32
|
+
export const enum KittyKeyboardEventType {
|
|
33
|
+
PRESS = 1,
|
|
34
|
+
REPEAT = 2,
|
|
35
|
+
RELEASE = 3,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Kitty modifier bits (different from xterm modifier encoding).
|
|
40
|
+
* Value sent = 1 + modifier_bits
|
|
41
|
+
*/
|
|
42
|
+
export const enum KittyKeyboardModifiers {
|
|
43
|
+
SHIFT = 0b00000001,
|
|
44
|
+
ALT = 0b00000010,
|
|
45
|
+
CTRL = 0b00000100,
|
|
46
|
+
SUPER = 0b00001000,
|
|
47
|
+
HYPER = 0b00010000,
|
|
48
|
+
META = 0b00100000,
|
|
49
|
+
CAPS_LOCK = 0b01000000,
|
|
50
|
+
NUM_LOCK = 0b10000000,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Functional key codes for Kitty protocol.
|
|
55
|
+
* Keys that don't produce text have specific unicode codepoint mappings.
|
|
56
|
+
*/
|
|
57
|
+
const FUNCTIONAL_KEY_CODES: { [key: string]: number } = {
|
|
58
|
+
'Escape': 27,
|
|
59
|
+
'Enter': 13,
|
|
60
|
+
'Tab': 9,
|
|
61
|
+
'Backspace': 127,
|
|
62
|
+
'CapsLock': 57358,
|
|
63
|
+
'ScrollLock': 57359,
|
|
64
|
+
'NumLock': 57360,
|
|
65
|
+
'PrintScreen': 57361,
|
|
66
|
+
'Pause': 57362,
|
|
67
|
+
'ContextMenu': 57363,
|
|
68
|
+
// F13-F35 (F1-F12 use legacy encoding)
|
|
69
|
+
'F13': 57376,
|
|
70
|
+
'F14': 57377,
|
|
71
|
+
'F15': 57378,
|
|
72
|
+
'F16': 57379,
|
|
73
|
+
'F17': 57380,
|
|
74
|
+
'F18': 57381,
|
|
75
|
+
'F19': 57382,
|
|
76
|
+
'F20': 57383,
|
|
77
|
+
'F21': 57384,
|
|
78
|
+
'F22': 57385,
|
|
79
|
+
'F23': 57386,
|
|
80
|
+
'F24': 57387,
|
|
81
|
+
'F25': 57388,
|
|
82
|
+
// Keypad keys
|
|
83
|
+
'KP_0': 57399,
|
|
84
|
+
'KP_1': 57400,
|
|
85
|
+
'KP_2': 57401,
|
|
86
|
+
'KP_3': 57402,
|
|
87
|
+
'KP_4': 57403,
|
|
88
|
+
'KP_5': 57404,
|
|
89
|
+
'KP_6': 57405,
|
|
90
|
+
'KP_7': 57406,
|
|
91
|
+
'KP_8': 57407,
|
|
92
|
+
'KP_9': 57408,
|
|
93
|
+
'KP_Decimal': 57409,
|
|
94
|
+
'KP_Divide': 57410,
|
|
95
|
+
'KP_Multiply': 57411,
|
|
96
|
+
'KP_Subtract': 57412,
|
|
97
|
+
'KP_Add': 57413,
|
|
98
|
+
'KP_Enter': 57414,
|
|
99
|
+
'KP_Equal': 57415,
|
|
100
|
+
// Modifier keys
|
|
101
|
+
'ShiftLeft': 57441,
|
|
102
|
+
'ShiftRight': 57447,
|
|
103
|
+
'ControlLeft': 57442,
|
|
104
|
+
'ControlRight': 57448,
|
|
105
|
+
'AltLeft': 57443,
|
|
106
|
+
'AltRight': 57449,
|
|
107
|
+
'MetaLeft': 57444,
|
|
108
|
+
'MetaRight': 57450,
|
|
109
|
+
// Media keys
|
|
110
|
+
'MediaPlayPause': 57430,
|
|
111
|
+
'MediaStop': 57432,
|
|
112
|
+
'MediaTrackNext': 57435,
|
|
113
|
+
'MediaTrackPrevious': 57436,
|
|
114
|
+
'AudioVolumeDown': 57438,
|
|
115
|
+
'AudioVolumeUp': 57439,
|
|
116
|
+
'AudioVolumeMute': 57440
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Keys that use CSI ~ encoding with a number parameter.
|
|
121
|
+
*/
|
|
122
|
+
const CSI_TILDE_KEYS: { [key: string]: number } = {
|
|
123
|
+
'Insert': 2,
|
|
124
|
+
'Delete': 3,
|
|
125
|
+
'PageUp': 5,
|
|
126
|
+
'PageDown': 6,
|
|
127
|
+
'F5': 15,
|
|
128
|
+
'F6': 17,
|
|
129
|
+
'F7': 18,
|
|
130
|
+
'F8': 19,
|
|
131
|
+
'F9': 20,
|
|
132
|
+
'F10': 21,
|
|
133
|
+
'F11': 23,
|
|
134
|
+
'F12': 24
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Keys that use CSI letter encoding (arrows, Home, End).
|
|
139
|
+
*/
|
|
140
|
+
const CSI_LETTER_KEYS: { [key: string]: string } = {
|
|
141
|
+
'ArrowUp': 'A',
|
|
142
|
+
'ArrowDown': 'B',
|
|
143
|
+
'ArrowRight': 'C',
|
|
144
|
+
'ArrowLeft': 'D',
|
|
145
|
+
'Home': 'H',
|
|
146
|
+
'End': 'F'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Function keys F1-F4 use SS3 encoding without modifiers.
|
|
151
|
+
*/
|
|
152
|
+
const SS3_FUNCTION_KEYS: { [key: string]: string } = {
|
|
153
|
+
'F1': 'P',
|
|
154
|
+
'F2': 'Q',
|
|
155
|
+
'F3': 'R',
|
|
156
|
+
'F4': 'S'
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Map browser key codes to Kitty numpad codes.
|
|
161
|
+
*/
|
|
162
|
+
function getNumpadKeyCode(ev: IKeyboardEvent): number | undefined {
|
|
163
|
+
// Detect numpad via code property
|
|
164
|
+
if (ev.code.startsWith('Numpad')) {
|
|
165
|
+
const suffix = ev.code.slice(6);
|
|
166
|
+
if (suffix >= '0' && suffix <= '9') {
|
|
167
|
+
return 57399 + parseInt(suffix, 10);
|
|
168
|
+
}
|
|
169
|
+
switch (suffix) {
|
|
170
|
+
case 'Decimal': return 57409;
|
|
171
|
+
case 'Divide': return 57410;
|
|
172
|
+
case 'Multiply': return 57411;
|
|
173
|
+
case 'Subtract': return 57412;
|
|
174
|
+
case 'Add': return 57413;
|
|
175
|
+
case 'Enter': return 57414;
|
|
176
|
+
case 'Equal': return 57415;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get modifier key code from code property.
|
|
184
|
+
*/
|
|
185
|
+
function getModifierKeyCode(ev: IKeyboardEvent): number | undefined {
|
|
186
|
+
switch (ev.code) {
|
|
187
|
+
case 'ShiftLeft': return 57441;
|
|
188
|
+
case 'ShiftRight': return 57447;
|
|
189
|
+
case 'ControlLeft': return 57442;
|
|
190
|
+
case 'ControlRight': return 57448;
|
|
191
|
+
case 'AltLeft': return 57443;
|
|
192
|
+
case 'AltRight': return 57449;
|
|
193
|
+
case 'MetaLeft': return 57444;
|
|
194
|
+
case 'MetaRight': return 57450;
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Encode modifiers for Kitty protocol.
|
|
201
|
+
* Returns 1 + modifier bits, or 0 if no modifiers.
|
|
202
|
+
*/
|
|
203
|
+
function encodeModifiers(ev: IKeyboardEvent): number {
|
|
204
|
+
let mods = 0;
|
|
205
|
+
if (ev.shiftKey) mods |= KittyKeyboardModifiers.SHIFT;
|
|
206
|
+
if (ev.altKey) mods |= KittyKeyboardModifiers.ALT;
|
|
207
|
+
if (ev.ctrlKey) mods |= KittyKeyboardModifiers.CTRL;
|
|
208
|
+
if (ev.metaKey) mods |= KittyKeyboardModifiers.SUPER;
|
|
209
|
+
return mods > 0 ? mods + 1 : 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get the unicode key code for a keyboard event.
|
|
214
|
+
* Returns the lowercase codepoint for letters.
|
|
215
|
+
* For shifted keys, uses the code property to get the base key.
|
|
216
|
+
*/
|
|
217
|
+
function getKeyCode(ev: IKeyboardEvent): number | undefined {
|
|
218
|
+
// Check for numpad first
|
|
219
|
+
const numpadCode = getNumpadKeyCode(ev);
|
|
220
|
+
if (numpadCode !== undefined) {
|
|
221
|
+
return numpadCode;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check for modifier keys
|
|
225
|
+
const modifierCode = getModifierKeyCode(ev);
|
|
226
|
+
if (modifierCode !== undefined) {
|
|
227
|
+
return modifierCode;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check functional keys
|
|
231
|
+
const funcCode = FUNCTIONAL_KEY_CODES[ev.key];
|
|
232
|
+
if (funcCode !== undefined) {
|
|
233
|
+
return funcCode;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// For shifted keys, use code property to get base key
|
|
237
|
+
if (ev.shiftKey && ev.code) {
|
|
238
|
+
// Handle Digit0-Digit9
|
|
239
|
+
if (ev.code.startsWith('Digit') && ev.code.length === 6) {
|
|
240
|
+
const digit = ev.code.charAt(5);
|
|
241
|
+
if (digit >= '0' && digit <= '9') {
|
|
242
|
+
return digit.charCodeAt(0);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Handle KeyA-KeyZ
|
|
246
|
+
if (ev.code.startsWith('Key') && ev.code.length === 4) {
|
|
247
|
+
const letter = ev.code.charAt(3).toLowerCase();
|
|
248
|
+
return letter.charCodeAt(0);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// For regular keys, use the key character's codepoint
|
|
253
|
+
// Always use lowercase for letters (per spec)
|
|
254
|
+
if (ev.key.length === 1) {
|
|
255
|
+
const code = ev.key.codePointAt(0)!;
|
|
256
|
+
// Convert uppercase A-Z to lowercase a-z
|
|
257
|
+
if (code >= 65 && code <= 90) {
|
|
258
|
+
return code + 32;
|
|
259
|
+
}
|
|
260
|
+
return code;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if a key is a modifier key.
|
|
268
|
+
*/
|
|
269
|
+
function isModifierKey(ev: IKeyboardEvent): boolean {
|
|
270
|
+
return ev.key === 'Shift' || ev.key === 'Control' || ev.key === 'Alt' || ev.key === 'Meta';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Evaluate a keyboard event using Kitty keyboard protocol.
|
|
275
|
+
*
|
|
276
|
+
* @param ev The keyboard event.
|
|
277
|
+
* @param flags The active Kitty keyboard enhancement flags.
|
|
278
|
+
* @param eventType The event type (press, repeat, release).
|
|
279
|
+
* @returns The keyboard result with the encoded key sequence.
|
|
280
|
+
*/
|
|
281
|
+
export function evaluateKeyboardEventKitty(
|
|
282
|
+
ev: IKeyboardEvent,
|
|
283
|
+
flags: number,
|
|
284
|
+
eventType: KittyKeyboardEventType = KittyKeyboardEventType.PRESS
|
|
285
|
+
): IKeyboardResult {
|
|
286
|
+
const result: IKeyboardResult = {
|
|
287
|
+
type: KeyboardResultType.SEND_KEY,
|
|
288
|
+
cancel: false,
|
|
289
|
+
key: undefined
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const modifiers = encodeModifiers(ev);
|
|
293
|
+
const isMod = isModifierKey(ev);
|
|
294
|
+
const reportEventTypes = !!(flags & KittyKeyboardFlags.REPORT_EVENT_TYPES);
|
|
295
|
+
|
|
296
|
+
// Don't report release events unless flag is set
|
|
297
|
+
if (!reportEventTypes && eventType === KittyKeyboardEventType.RELEASE) {
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Modifier-only keys require REPORT_ALL_KEYS_AS_ESCAPE_CODES or REPORT_EVENT_TYPES
|
|
302
|
+
if (isMod && !(flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) && !reportEventTypes) {
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Check for CSI letter keys (arrows, Home, End)
|
|
307
|
+
const csiLetter = CSI_LETTER_KEYS[ev.key];
|
|
308
|
+
if (csiLetter) {
|
|
309
|
+
result.key = buildCsiLetterSequence(csiLetter, modifiers, eventType, reportEventTypes);
|
|
310
|
+
result.cancel = true;
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check for SS3/CSI function keys (F1-F4)
|
|
315
|
+
const ss3Letter = SS3_FUNCTION_KEYS[ev.key];
|
|
316
|
+
if (ss3Letter) {
|
|
317
|
+
result.key = buildSs3Sequence(ss3Letter, modifiers, eventType, reportEventTypes);
|
|
318
|
+
result.cancel = true;
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Check for CSI ~ keys (Insert, Delete, PageUp/Down, F5-F12)
|
|
323
|
+
const tildeCode = CSI_TILDE_KEYS[ev.key];
|
|
324
|
+
if (tildeCode !== undefined) {
|
|
325
|
+
result.key = buildCsiTildeSequence(tildeCode, modifiers, eventType, reportEventTypes);
|
|
326
|
+
result.cancel = true;
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Get the key code for CSI u encoding
|
|
331
|
+
const keyCode = getKeyCode(ev);
|
|
332
|
+
if (keyCode === undefined) {
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const isFunc = FUNCTIONAL_KEY_CODES[ev.key] !== undefined || getNumpadKeyCode(ev) !== undefined;
|
|
337
|
+
|
|
338
|
+
// Determine if we should use CSI u encoding
|
|
339
|
+
let useCsiU = false;
|
|
340
|
+
|
|
341
|
+
if (flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) {
|
|
342
|
+
useCsiU = true;
|
|
343
|
+
} else if (reportEventTypes) {
|
|
344
|
+
useCsiU = true;
|
|
345
|
+
} else if (flags & KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES) {
|
|
346
|
+
// Modifier-only keys already handled above
|
|
347
|
+
// Use CSI u for keys that would be ambiguous in legacy encoding
|
|
348
|
+
if (keyCode === 27 || keyCode === 127 || keyCode === 13 || keyCode === 9 || keyCode === 32) {
|
|
349
|
+
// Escape, Backspace, Enter, Tab, Space
|
|
350
|
+
useCsiU = true;
|
|
351
|
+
} else if (isFunc) {
|
|
352
|
+
useCsiU = true;
|
|
353
|
+
} else if (modifiers > 0) {
|
|
354
|
+
// Any modified key
|
|
355
|
+
useCsiU = true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (useCsiU) {
|
|
360
|
+
result.key = buildCsiUSequence(ev, keyCode, modifiers, eventType, flags, isFunc, isMod);
|
|
361
|
+
result.cancel = true;
|
|
362
|
+
} else {
|
|
363
|
+
// Legacy-compatible encoding for text keys without modifiers
|
|
364
|
+
if (ev.key.length === 1 && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
|
365
|
+
result.key = ev.key;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Build CSI letter sequence for arrow keys, Home, End.
|
|
374
|
+
* Format: CSI [1;mod] letter
|
|
375
|
+
*/
|
|
376
|
+
function buildCsiLetterSequence(
|
|
377
|
+
letter: string,
|
|
378
|
+
modifiers: number,
|
|
379
|
+
eventType: KittyKeyboardEventType,
|
|
380
|
+
reportEventTypes: boolean
|
|
381
|
+
): string {
|
|
382
|
+
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
|
383
|
+
|
|
384
|
+
if (modifiers > 0 || needsEventType) {
|
|
385
|
+
let seq = C0.ESC + '[1;' + (modifiers > 0 ? modifiers : '1');
|
|
386
|
+
if (needsEventType) {
|
|
387
|
+
seq += ':' + eventType;
|
|
388
|
+
}
|
|
389
|
+
seq += letter;
|
|
390
|
+
return seq;
|
|
391
|
+
}
|
|
392
|
+
return C0.ESC + '[' + letter;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Build SS3 sequence for F1-F4.
|
|
397
|
+
* Without modifiers: SS3 letter
|
|
398
|
+
* With modifiers: CSI 1;mod letter
|
|
399
|
+
*/
|
|
400
|
+
function buildSs3Sequence(
|
|
401
|
+
letter: string,
|
|
402
|
+
modifiers: number,
|
|
403
|
+
eventType: KittyKeyboardEventType,
|
|
404
|
+
reportEventTypes: boolean
|
|
405
|
+
): string {
|
|
406
|
+
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
|
407
|
+
|
|
408
|
+
if (modifiers > 0 || needsEventType) {
|
|
409
|
+
let seq = C0.ESC + '[1;' + (modifiers > 0 ? modifiers : '1');
|
|
410
|
+
if (needsEventType) {
|
|
411
|
+
seq += ':' + eventType;
|
|
412
|
+
}
|
|
413
|
+
seq += letter;
|
|
414
|
+
return seq;
|
|
415
|
+
}
|
|
416
|
+
return C0.ESC + 'O' + letter;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Build CSI ~ sequence for Insert, Delete, PageUp/Down, F5-F12.
|
|
421
|
+
* Format: CSI number [;mod[:event]] ~
|
|
422
|
+
*/
|
|
423
|
+
function buildCsiTildeSequence(
|
|
424
|
+
number: number,
|
|
425
|
+
modifiers: number,
|
|
426
|
+
eventType: KittyKeyboardEventType,
|
|
427
|
+
reportEventTypes: boolean
|
|
428
|
+
): string {
|
|
429
|
+
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
|
430
|
+
|
|
431
|
+
let seq = C0.ESC + '[' + number;
|
|
432
|
+
if (modifiers > 0 || needsEventType) {
|
|
433
|
+
seq += ';' + (modifiers > 0 ? modifiers : '1');
|
|
434
|
+
if (needsEventType) {
|
|
435
|
+
seq += ':' + eventType;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
seq += '~';
|
|
439
|
+
return seq;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Build CSI u sequence.
|
|
444
|
+
* Format: CSI keycode[:shifted[:base]] [;mod[:event][;text]] u
|
|
445
|
+
*/
|
|
446
|
+
function buildCsiUSequence(
|
|
447
|
+
ev: IKeyboardEvent,
|
|
448
|
+
keyCode: number,
|
|
449
|
+
modifiers: number,
|
|
450
|
+
eventType: KittyKeyboardEventType,
|
|
451
|
+
flags: number,
|
|
452
|
+
isFunc: boolean,
|
|
453
|
+
isMod: boolean
|
|
454
|
+
): string {
|
|
455
|
+
const reportEventTypes = !!(flags & KittyKeyboardFlags.REPORT_EVENT_TYPES);
|
|
456
|
+
const reportAlternateKeys = !!(flags & KittyKeyboardFlags.REPORT_ALTERNATE_KEYS);
|
|
457
|
+
|
|
458
|
+
let seq = C0.ESC + '[' + keyCode;
|
|
459
|
+
|
|
460
|
+
// Add shifted key alternate if REPORT_ALTERNATE_KEYS is set and shift is pressed
|
|
461
|
+
// Only for text-producing keys (not functional or modifier keys)
|
|
462
|
+
let shiftedKey: number | undefined;
|
|
463
|
+
if (reportAlternateKeys && ev.shiftKey && ev.key.length === 1 && !isFunc && !isMod) {
|
|
464
|
+
shiftedKey = ev.key.codePointAt(0);
|
|
465
|
+
seq += ':' + shiftedKey;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check if we need associated text (press and repeat events, not release)
|
|
469
|
+
// Only for text-producing keys (not functional or modifier keys)
|
|
470
|
+
// Also don't include text when ctrl is pressed (produces control code)
|
|
471
|
+
const reportAssociatedText = !!(flags & KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT) &&
|
|
472
|
+
eventType !== KittyKeyboardEventType.RELEASE &&
|
|
473
|
+
ev.key.length === 1 &&
|
|
474
|
+
!isFunc &&
|
|
475
|
+
!isMod &&
|
|
476
|
+
!ev.ctrlKey;
|
|
477
|
+
const textCode = reportAssociatedText ? ev.key.codePointAt(0) : undefined;
|
|
478
|
+
|
|
479
|
+
// Determine if we need event type suffix
|
|
480
|
+
// For repeat: only include :2 when there's no text (text implies it's still useful input)
|
|
481
|
+
// For release: always include :3
|
|
482
|
+
const needsEventType = reportEventTypes &&
|
|
483
|
+
eventType !== KittyKeyboardEventType.PRESS &&
|
|
484
|
+
(eventType === KittyKeyboardEventType.RELEASE || textCode === undefined);
|
|
485
|
+
|
|
486
|
+
if (modifiers > 0 || needsEventType || textCode !== undefined) {
|
|
487
|
+
seq += ';';
|
|
488
|
+
if (modifiers > 0) {
|
|
489
|
+
seq += modifiers;
|
|
490
|
+
} else if (needsEventType) {
|
|
491
|
+
seq += '1';
|
|
492
|
+
}
|
|
493
|
+
if (needsEventType) {
|
|
494
|
+
seq += ':' + eventType;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Add associated text if requested
|
|
499
|
+
if (textCode !== undefined) {
|
|
500
|
+
seq += ';' + textCode;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
seq += 'u';
|
|
504
|
+
return seq;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Check if a keyboard event should be handled by Kitty protocol.
|
|
509
|
+
* Returns true if Kitty flags are active and the event should use Kitty encoding.
|
|
510
|
+
*/
|
|
511
|
+
export function shouldUseKittyProtocol(flags: number): boolean {
|
|
512
|
+
return flags > 0;
|
|
513
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { clone } from 'common/Clone';
|
|
7
7
|
import { Disposable } from 'vs/base/common/lifecycle';
|
|
8
|
-
import { IDecPrivateModes, IModes } from 'common/Types';
|
|
8
|
+
import { IDecPrivateModes, IKittyKeyboardState, IModes } from 'common/Types';
|
|
9
9
|
import { IBufferService, ICoreService, ILogService, IOptionsService } from 'common/services/Services';
|
|
10
10
|
import { Emitter } from 'vs/base/common/event';
|
|
11
11
|
|
|
@@ -26,6 +26,14 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
|
|
|
26
26
|
wraparound: true // defaults: xterm - true, vt100 - false
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
const DEFAULT_KITTY_KEYBOARD_STATE = (): IKittyKeyboardState => ({
|
|
30
|
+
flags: 0,
|
|
31
|
+
mainFlags: 0,
|
|
32
|
+
altFlags: 0,
|
|
33
|
+
mainStack: [],
|
|
34
|
+
altStack: []
|
|
35
|
+
});
|
|
36
|
+
|
|
29
37
|
export class CoreService extends Disposable implements ICoreService {
|
|
30
38
|
public serviceBrand: any;
|
|
31
39
|
|
|
@@ -33,6 +41,7 @@ export class CoreService extends Disposable implements ICoreService {
|
|
|
33
41
|
public isCursorHidden: boolean = false;
|
|
34
42
|
public modes: IModes;
|
|
35
43
|
public decPrivateModes: IDecPrivateModes;
|
|
44
|
+
public kittyKeyboard: IKittyKeyboardState;
|
|
36
45
|
|
|
37
46
|
private readonly _onData = this._register(new Emitter<string>());
|
|
38
47
|
public readonly onData = this._onData.event;
|
|
@@ -51,11 +60,13 @@ export class CoreService extends Disposable implements ICoreService {
|
|
|
51
60
|
super();
|
|
52
61
|
this.modes = clone(DEFAULT_MODES);
|
|
53
62
|
this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES);
|
|
63
|
+
this.kittyKeyboard = DEFAULT_KITTY_KEYBOARD_STATE();
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
public reset(): void {
|
|
57
67
|
this.modes = clone(DEFAULT_MODES);
|
|
58
68
|
this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES);
|
|
69
|
+
this.kittyKeyboard = DEFAULT_KITTY_KEYBOARD_STATE();
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
public triggerDataEvent(data: string, wasUserInput: boolean = false): void {
|
|
@@ -54,7 +54,8 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
|
|
|
54
54
|
termName: 'xterm',
|
|
55
55
|
cancelEvents: false,
|
|
56
56
|
overviewRuler: {},
|
|
57
|
-
quirks: {}
|
|
57
|
+
quirks: {},
|
|
58
|
+
vtExtensions: {}
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IDecoration, IDecorationOptions, ILinkHandler, ILogger, IWindowsPty, type IOverviewRulerOptions } from '@xterm/xterm';
|
|
7
|
-
import { CoreMouseEncoding, CoreMouseEventType, CursorInactiveStyle, CursorStyle, IAttributeData, ICharset, IColor, ICoreMouseEvent, ICoreMouseProtocol, IDecPrivateModes, IDisposable, IModes, IOscLinkData, IWindowOptions } from 'common/Types';
|
|
7
|
+
import { CoreMouseEncoding, CoreMouseEventType, CursorInactiveStyle, CursorStyle, IAttributeData, ICharset, IColor, ICoreMouseEvent, ICoreMouseProtocol, IDecPrivateModes, IDisposable, IKittyKeyboardState, IModes, IOscLinkData, IWindowOptions } from 'common/Types';
|
|
8
8
|
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
|
9
9
|
import { createDecorator } from 'common/services/ServiceRegistry';
|
|
10
10
|
import type { Emitter, Event } from 'vs/base/common/event';
|
|
@@ -85,6 +85,7 @@ export interface ICoreService {
|
|
|
85
85
|
|
|
86
86
|
readonly modes: IModes;
|
|
87
87
|
readonly decPrivateModes: IDecPrivateModes;
|
|
88
|
+
readonly kittyKeyboard: IKittyKeyboardState;
|
|
88
89
|
|
|
89
90
|
readonly onData: Event<string>;
|
|
90
91
|
readonly onUserInput: Event<void>;
|
|
@@ -265,6 +266,7 @@ export interface ITerminalOptions {
|
|
|
265
266
|
overviewRuler?: IOverviewRulerOptions;
|
|
266
267
|
quirks?: ITerminalQuirks;
|
|
267
268
|
scrollOnEraseInDisplay?: boolean;
|
|
269
|
+
vtExtensions?: IVtExtensions;
|
|
268
270
|
|
|
269
271
|
[key: string]: any;
|
|
270
272
|
cancelEvents: boolean;
|
|
@@ -306,6 +308,11 @@ export interface ITerminalQuirks {
|
|
|
306
308
|
allowSetCursorBlink?: boolean;
|
|
307
309
|
}
|
|
308
310
|
|
|
311
|
+
export interface IVtExtensions {
|
|
312
|
+
kittyKeyboard?: boolean;
|
|
313
|
+
kittySgrBoldFaintControl?: boolean;
|
|
314
|
+
}
|
|
315
|
+
|
|
309
316
|
export const IOscLinkService = createDecorator<IOscLinkService>('OscLinkService');
|
|
310
317
|
export interface IOscLinkService {
|
|
311
318
|
serviceBrand: undefined;
|
package/typings/xterm.d.ts
CHANGED
|
@@ -199,6 +199,12 @@ declare module '@xterm/xterm' {
|
|
|
199
199
|
*/
|
|
200
200
|
minimumContrastRatio?: number;
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Controls the visibility and style of the overview ruler which visualizes
|
|
204
|
+
* decorations underneath the scroll bar.
|
|
205
|
+
*/
|
|
206
|
+
overviewRuler?: IOverviewRulerOptions;
|
|
207
|
+
|
|
202
208
|
/**
|
|
203
209
|
* Control various quirks features that are either non-standard or standard
|
|
204
210
|
* in but generally rejected in modern terminals.
|
|
@@ -284,6 +290,11 @@ declare module '@xterm/xterm' {
|
|
|
284
290
|
*/
|
|
285
291
|
theme?: ITheme;
|
|
286
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Enable various VT extensions. All extensions are disabled by default.
|
|
295
|
+
*/
|
|
296
|
+
vtExtensions?: IVtExtensions;
|
|
297
|
+
|
|
287
298
|
/**
|
|
288
299
|
* Compatibility information when the pty is known to be hosted on Windows.
|
|
289
300
|
* Setting this will turn on certain heuristics/workarounds depending on the
|
|
@@ -313,12 +324,6 @@ declare module '@xterm/xterm' {
|
|
|
313
324
|
* All features are disabled by default for security reasons.
|
|
314
325
|
*/
|
|
315
326
|
windowOptions?: IWindowOptions;
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Controls the visibility and style of the overview ruler which visualizes
|
|
319
|
-
* decorations underneath the scroll bar.
|
|
320
|
-
*/
|
|
321
|
-
overviewRuler?: IOverviewRulerOptions;
|
|
322
327
|
}
|
|
323
328
|
|
|
324
329
|
/**
|
|
@@ -430,6 +435,30 @@ declare module '@xterm/xterm' {
|
|
|
430
435
|
allowSetCursorBlink?: boolean;
|
|
431
436
|
}
|
|
432
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Enable certain optional VT extensions.
|
|
440
|
+
*/
|
|
441
|
+
export interface IVtExtensions {
|
|
442
|
+
/**
|
|
443
|
+
* Whether the [kitty keyboard protocol][0] (`CSI =|?|>|< u`) is enabled.
|
|
444
|
+
* When enabled, the terminal will respond to keyboard protocol queries and
|
|
445
|
+
* allow programs to enable enhanced keyboard reporting. The default is
|
|
446
|
+
* false.
|
|
447
|
+
*
|
|
448
|
+
* [0]: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
449
|
+
*/
|
|
450
|
+
kittyKeyboard?: boolean;
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Whether [SGR 221 (not bold) and SGR 222 (not faint) are enabled][0].
|
|
454
|
+
* These are kitty extensions that allow resetting bold and faint
|
|
455
|
+
* independently. The default is true.
|
|
456
|
+
*
|
|
457
|
+
* [0]: https://sw.kovidgoyal.net/kitty/misc-protocol/
|
|
458
|
+
*/
|
|
459
|
+
kittySgrBoldFaintControl?: boolean;
|
|
460
|
+
}
|
|
461
|
+
|
|
433
462
|
/**
|
|
434
463
|
* Pty information for Windows.
|
|
435
464
|
*/
|