Merge branch 'master' into nullish
This commit is contained in:
@@ -12,7 +12,7 @@ interface IAttachOptions {
|
||||
bidirectional?: boolean;
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon , IAttachApi {
|
||||
export class AttachAddon implements ITerminalAddon, IAttachApi {
|
||||
private _socket: WebSocket;
|
||||
private _bidirectional: boolean;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
@@ -33,7 +33,7 @@ function _getComputedStyle(el: HTMLElement): CSSStyleDeclaration {
|
||||
return getWindow(el).getComputedStyle(el, null);
|
||||
}
|
||||
|
||||
export class FitAddon implements ITerminalAddon , IFitApi {
|
||||
export class FitAddon implements ITerminalAddon, IFitApi {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
|
||||
@@ -48,7 +48,7 @@ const enum GaStatus {
|
||||
}
|
||||
|
||||
|
||||
export class ImageAddon implements ITerminalAddon , IImageApi {
|
||||
export class ImageAddon implements ITerminalAddon, IImageApi {
|
||||
private _opts: IImageAddonOptions;
|
||||
private _defaultOpts: IImageAddonOptions;
|
||||
private _storage: ImageStorage | undefined;
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface ITerminalAddon {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class LigaturesAddon implements ITerminalAddon , ILigaturesApi {
|
||||
export class LigaturesAddon implements ITerminalAddon, ILigaturesApi {
|
||||
private readonly _fallbackLigatures: string[];
|
||||
private readonly _fontFeatureSettings?: string;
|
||||
|
||||
|
||||
@@ -457,7 +457,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
||||
export class SerializeAddon implements ITerminalAddon, ISerializeApi {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { Terminal, ITerminalAddon, IUnicodeHandling } from '@xterm/xterm';
|
||||
import type { UnicodeGraphemesAddon as IUnicodeGraphemesApi } from '@xterm/addon-unicode-graphemes';
|
||||
import { UnicodeGraphemeProvider } from './UnicodeGraphemeProvider';
|
||||
|
||||
export class UnicodeGraphemesAddon implements ITerminalAddon , IUnicodeGraphemesApi {
|
||||
export class UnicodeGraphemesAddon implements ITerminalAddon, IUnicodeGraphemesApi {
|
||||
private _provider15Graphemes?: UnicodeGraphemeProvider;
|
||||
private _provider15?: UnicodeGraphemeProvider;
|
||||
private _unicode?: IUnicodeHandling;
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { Terminal, ITerminalAddon } from '@xterm/xterm';
|
||||
import type { Unicode11Addon as IUnicode11Api } from '@xterm/addon-unicode11';
|
||||
import { UnicodeV11 } from './UnicodeV11';
|
||||
|
||||
export class Unicode11Addon implements ITerminalAddon , IUnicode11Api {
|
||||
export class Unicode11Addon implements ITerminalAddon, IUnicode11Api {
|
||||
public activate(terminal: Terminal): void {
|
||||
terminal.unicode.register(new UnicodeV11());
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ function handleLink(event: MouseEvent, uri: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export class WebLinksAddon implements ITerminalAddon , IWebLinksApi {
|
||||
export class WebLinksAddon implements ITerminalAddon, IWebLinksApi {
|
||||
private _terminal: Terminal | undefined;
|
||||
private _linkProvider: IDisposable | undefined;
|
||||
|
||||
|
||||
@@ -9,13 +9,12 @@ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRender
|
||||
import { ITerminal } from 'browser/Types';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { getSafariVersion, isSafari } from 'common/Platform';
|
||||
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { IWebGL2RenderingContext } from './Types';
|
||||
import { WebglRenderer } from './WebglRenderer';
|
||||
import { setTraceLogger } from 'common/services/LogService';
|
||||
import { Emitter, EventUtils } from 'common/Event';
|
||||
|
||||
export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi {
|
||||
export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi {
|
||||
private _terminal?: Terminal;
|
||||
private _renderer?: WebglRenderer;
|
||||
|
||||
@@ -66,13 +65,8 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
|
||||
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
|
||||
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
|
||||
const decorationService: IDecorationService = unsafeCore._decorationService;
|
||||
const logService: ILogService = unsafeCore._logService;
|
||||
const themeService: IThemeService = unsafeCore._themeService;
|
||||
|
||||
// Set trace logger just in case it hasn't been yet which could happen when the addon is
|
||||
// bundled separately to the core module
|
||||
setTraceLogger(logService);
|
||||
|
||||
this._renderer = this._register(new WebglRenderer(
|
||||
terminal,
|
||||
characterJoinerService,
|
||||
|
||||
@@ -35,6 +35,7 @@ export default tseslint.config(
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'@stylistic/comma-spacing': ['warn', { before: false, after: true }],
|
||||
'@stylistic/indent': ['warn', 2],
|
||||
'@stylistic/semi': ['warn', 'always'],
|
||||
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
|
||||
|
||||
Generated
+5
-4
@@ -41,7 +41,7 @@
|
||||
"jsdom": "^27.3.0",
|
||||
"mocha": "^10.1.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-pty": "1.1.0-beta19",
|
||||
"node-pty": "^1.2.0-beta.9",
|
||||
"nyc": "^17.1.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
@@ -6016,11 +6016,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-pty": {
|
||||
"version": "1.1.0-beta19",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta19.tgz",
|
||||
"integrity": "sha512-/p4Zu56EYDdXjjaLWzrIlFyrBnND11LQGP0/L6GEVGURfCNkAlHc3Twg/2I4NPxghimHXgvDlwp7Z2GtvDIh8A==",
|
||||
"version": "1.2.0-beta.9",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.9.tgz",
|
||||
"integrity": "sha512-XR3x/4OQZ7DlceespOQ99kk9jZHwmO78RuEIZtGsSPdsDi11s79VcK04aqcaK3x09Y6ORJM/KBz/oQ/p+WGRLg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.0"
|
||||
}
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@
|
||||
"jsdom": "^27.3.0",
|
||||
"mocha": "^10.1.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-pty": "1.1.0-beta19",
|
||||
"node-pty": "^1.2.0-beta.9",
|
||||
"nyc": "^17.1.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('RendererUtils', () => {
|
||||
line = 2;
|
||||
variantOffset = 0;
|
||||
cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];
|
||||
result = [3, 2, 0 ,2];
|
||||
result = [3, 2, 0, 2];
|
||||
for (let index = 0; index < cells.length; index++) {
|
||||
const cell = cells[index];
|
||||
variantOffset = computeNextVariantOffset(cell, line, variantOffset);
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
import { IKeyboardService } from 'browser/services/Services';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
import { evaluateKeyboardEventKitty, KittyKeyboardEventType, KittyKeyboardFlags, shouldUseKittyProtocol } from 'common/input/KittyKeyboard';
|
||||
import { evaluateKeyboardEventWin32 } from 'common/input/Win32InputMode';
|
||||
import { KittyKeyboard, KittyKeyboardEventType, KittyKeyboardFlags } from 'common/input/KittyKeyboard';
|
||||
import { Win32InputMode } from 'common/input/Win32InputMode';
|
||||
import { isMac } from 'common/Platform';
|
||||
import { ICoreService, IOptionsService } from 'common/services/Services';
|
||||
import { IKeyboardResult } from 'common/Types';
|
||||
@@ -14,38 +14,55 @@ import { IKeyboardResult } from 'common/Types';
|
||||
export class KeyboardService implements IKeyboardService {
|
||||
public serviceBrand: undefined;
|
||||
|
||||
private _win32InputMode: Win32InputMode | undefined;
|
||||
private _kittyKeyboard: KittyKeyboard | undefined;
|
||||
|
||||
constructor(
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService
|
||||
) {
|
||||
}
|
||||
|
||||
private _getWin32InputMode(): Win32InputMode {
|
||||
if (!this._win32InputMode) {
|
||||
this._win32InputMode = new Win32InputMode();
|
||||
}
|
||||
return this._win32InputMode;
|
||||
}
|
||||
|
||||
private _getKittyKeyboard(): KittyKeyboard {
|
||||
if (!this._kittyKeyboard) {
|
||||
this._kittyKeyboard = new KittyKeyboard();
|
||||
}
|
||||
return this._kittyKeyboard;
|
||||
}
|
||||
|
||||
public evaluateKeyDown(event: KeyboardEvent): IKeyboardResult {
|
||||
// Win32 input mode takes priority (most raw)
|
||||
if (this.useWin32InputMode) {
|
||||
return evaluateKeyboardEventWin32(event, true);
|
||||
return this._getWin32InputMode().evaluateKeyboardEvent(event, true);
|
||||
}
|
||||
const kittyFlags = this._coreService.kittyKeyboard.flags;
|
||||
return this.useKitty
|
||||
? evaluateKeyboardEventKitty(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS)
|
||||
? this._getKittyKeyboard().evaluate(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS)
|
||||
: evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, isMac, this._optionsService.rawOptions.macOptionIsMeta);
|
||||
}
|
||||
|
||||
public evaluateKeyUp(event: KeyboardEvent): IKeyboardResult | undefined {
|
||||
// Win32 input mode sends key up events
|
||||
if (this.useWin32InputMode) {
|
||||
return evaluateKeyboardEventWin32(event, false);
|
||||
return this._getWin32InputMode().evaluateKeyboardEvent(event, false);
|
||||
}
|
||||
const kittyFlags = this._coreService.kittyKeyboard.flags;
|
||||
if (this.useKitty && (kittyFlags & KittyKeyboardFlags.REPORT_EVENT_TYPES)) {
|
||||
return evaluateKeyboardEventKitty(event, kittyFlags, KittyKeyboardEventType.RELEASE);
|
||||
return this._getKittyKeyboard().evaluate(event, kittyFlags, KittyKeyboardEventType.RELEASE);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get useKitty(): boolean {
|
||||
const kittyFlags = this._coreService.kittyKeyboard.flags;
|
||||
return !!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard && shouldUseKittyProtocol(kittyFlags));
|
||||
return !!(this._optionsService.rawOptions.vtExtensions?.kittyKeyboard && KittyKeyboard.shouldUseProtocol(kittyFlags));
|
||||
}
|
||||
|
||||
public get useWin32InputMode(): boolean {
|
||||
|
||||
@@ -39,8 +39,6 @@ export function getSafariVersion(): number {
|
||||
// and ISO third level shifts.
|
||||
// http://stackoverflow.com/q/19877924/577598
|
||||
export const isMac = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'].includes(platform);
|
||||
export const isIpad = platform === 'iPad';
|
||||
export const isIphone = platform === 'iPhone';
|
||||
export const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].includes(platform);
|
||||
export const isLinux = platform.indexOf('Linux') >= 0;
|
||||
// Note that when this is true, isLinux will also be true.
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { concat } from 'common/TypedArrayUtils';
|
||||
|
||||
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
|
||||
|
||||
function deepEquals(a: TypedArray, b: TypedArray): void {
|
||||
assert.equal(a.length, b.length);
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
assert.equal(a[i], b[i]);
|
||||
}
|
||||
}
|
||||
|
||||
describe('typed array convenience functions', () => {
|
||||
it('concat', () => {
|
||||
const a = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
const b = new Uint8Array([6, 7, 8, 9, 0]);
|
||||
const merged = concat(a, b);
|
||||
deepEquals(merged, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]));
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
|
||||
|
||||
/**
|
||||
* Concat two typed arrays `a` and `b`.
|
||||
* Returns a new typed array.
|
||||
*/
|
||||
export function concat<T extends TypedArray>(a: T, b: T): T {
|
||||
const result = new (a.constructor as any)(a.length + b.length);
|
||||
result.set(a);
|
||||
result.set(b, a.length);
|
||||
return result;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { evaluateKeyboardEventKitty, KittyKeyboardEventType, KittyKeyboardFlags, shouldUseKittyProtocol } from 'common/input/KittyKeyboard';
|
||||
import { IKeyboardResult, IKeyboardEvent } from 'common/Types';
|
||||
import { KittyKeyboard, KittyKeyboardEventType, KittyKeyboardFlags } from 'common/input/KittyKeyboard';
|
||||
import { IKeyboardEvent } from 'common/Types';
|
||||
|
||||
function createEvent(partialEvent: Partial<IKeyboardEvent> = {}): IKeyboardEvent {
|
||||
return {
|
||||
@@ -17,71 +17,77 @@ function createEvent(partialEvent: Partial<IKeyboardEvent> = {}): IKeyboardEvent
|
||||
}
|
||||
|
||||
describe('KittyKeyboard', () => {
|
||||
describe('shouldUseKittyProtocol', () => {
|
||||
let kitty: KittyKeyboard;
|
||||
|
||||
beforeEach(() => {
|
||||
kitty = new KittyKeyboard();
|
||||
});
|
||||
|
||||
describe('shouldUseProtocol', () => {
|
||||
it('should return false when flags are 0', () => {
|
||||
assert.strictEqual(shouldUseKittyProtocol(0), false);
|
||||
assert.strictEqual(KittyKeyboard.shouldUseProtocol(0), false);
|
||||
});
|
||||
|
||||
it('should return true when any flag is set', () => {
|
||||
assert.strictEqual(shouldUseKittyProtocol(KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES), true);
|
||||
assert.strictEqual(shouldUseKittyProtocol(KittyKeyboardFlags.REPORT_EVENT_TYPES), true);
|
||||
assert.strictEqual(shouldUseKittyProtocol(0b11111), true);
|
||||
assert.strictEqual(KittyKeyboard.shouldUseProtocol(KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES), true);
|
||||
assert.strictEqual(KittyKeyboard.shouldUseProtocol(KittyKeyboardFlags.REPORT_EVENT_TYPES), true);
|
||||
assert.strictEqual(KittyKeyboard.shouldUseProtocol(0b11111), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('evaluateKeyboardEventKitty', () => {
|
||||
describe('evaluate', () => {
|
||||
describe('modifier encoding (value = 1 + modifiers)', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('shift+letter sends plain character in DISAMBIGUATE mode', () => {
|
||||
// Kitty spec: DISAMBIGUATE only encodes keys ambiguous in legacy encoding
|
||||
// Shift+a → "A" is not ambiguous, so send plain "A"
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'A', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, 'A');
|
||||
});
|
||||
|
||||
it('alt=3 (1+2) still uses CSI u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', altKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', altKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;3u');
|
||||
});
|
||||
|
||||
it('ctrl=5 (1+4)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;5u');
|
||||
});
|
||||
|
||||
it('super/meta=9 (1+8)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', metaKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', metaKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;9u');
|
||||
});
|
||||
|
||||
it('ctrl+shift=6 (1+4+1)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', ctrlKey: true, shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true, shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;6u');
|
||||
});
|
||||
|
||||
it('ctrl+alt=7 (1+4+2)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', ctrlKey: true, altKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true, altKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;7u');
|
||||
});
|
||||
|
||||
it('ctrl+alt+shift=8 (1+4+2+1)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', ctrlKey: true, altKey: true, shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true, altKey: true, shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;8u');
|
||||
});
|
||||
|
||||
it('ctrl+super=13 (1+4+8)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', ctrlKey: true, metaKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true, metaKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;13u');
|
||||
});
|
||||
|
||||
it('all four modifiers=16 (1+1+2+4+8)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', shiftKey: true, altKey: true, ctrlKey: true, metaKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', shiftKey: true, altKey: true, ctrlKey: true, metaKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;16u');
|
||||
});
|
||||
|
||||
it('no modifiers omits modifier field', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Escape' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[27u');
|
||||
});
|
||||
});
|
||||
@@ -90,42 +96,42 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('Escape → CSI 27 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Escape' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[27u');
|
||||
});
|
||||
|
||||
it('Enter → CSI 13 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Enter' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Enter' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[13u');
|
||||
});
|
||||
|
||||
it('Tab → CSI 9 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Tab' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Tab' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[9u');
|
||||
});
|
||||
|
||||
it('Backspace → CSI 127 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Backspace' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Backspace' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[127u');
|
||||
});
|
||||
|
||||
it('Space → CSI 32 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: ' ' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: ' ' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[32u');
|
||||
});
|
||||
|
||||
it('Shift+Tab → CSI 9;2 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Tab', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Tab', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[9;2u');
|
||||
});
|
||||
|
||||
it('Ctrl+Enter → CSI 13;5 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Enter', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Enter', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[13;5u');
|
||||
});
|
||||
|
||||
it('Alt+Escape → CSI 27;3 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Escape', altKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Escape', altKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[27;3u');
|
||||
});
|
||||
});
|
||||
@@ -134,42 +140,42 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('Insert → CSI 2 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Insert' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Insert' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[2~');
|
||||
});
|
||||
|
||||
it('Delete → CSI 3 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Delete' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Delete' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[3~');
|
||||
});
|
||||
|
||||
it('PageUp → CSI 5 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'PageUp' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'PageUp' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[5~');
|
||||
});
|
||||
|
||||
it('PageDown → CSI 6 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'PageDown' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'PageDown' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[6~');
|
||||
});
|
||||
|
||||
it('Home → CSI H', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Home' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Home' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[H');
|
||||
});
|
||||
|
||||
it('End → CSI F', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'End' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'End' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[F');
|
||||
});
|
||||
|
||||
it('Shift+PageUp → CSI 5;2 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'PageUp', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'PageUp', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[5;2~');
|
||||
});
|
||||
|
||||
it('Ctrl+Home → CSI 1;5 H', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Home', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Home', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[1;5H');
|
||||
});
|
||||
});
|
||||
@@ -178,37 +184,37 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('ArrowUp → CSI A', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ArrowUp' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ArrowUp' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[A');
|
||||
});
|
||||
|
||||
it('ArrowDown → CSI B', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ArrowDown' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ArrowDown' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[B');
|
||||
});
|
||||
|
||||
it('ArrowRight → CSI C', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ArrowRight' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ArrowRight' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[C');
|
||||
});
|
||||
|
||||
it('ArrowLeft → CSI D', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ArrowLeft' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ArrowLeft' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[D');
|
||||
});
|
||||
|
||||
it('Shift+ArrowUp → CSI 1;2 A', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ArrowUp', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ArrowUp', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[1;2A');
|
||||
});
|
||||
|
||||
it('Ctrl+ArrowLeft → CSI 1;5 D', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ArrowLeft', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ArrowLeft', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[1;5D');
|
||||
});
|
||||
|
||||
it('Ctrl+Shift+ArrowRight → CSI 1;6 C', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ArrowRight', ctrlKey: true, shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ArrowRight', ctrlKey: true, shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[1;6C');
|
||||
});
|
||||
});
|
||||
@@ -217,72 +223,72 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('F1 → CSI P (SS3 form)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F1' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F1' }), flags);
|
||||
assert.strictEqual(result.key, '\x1bOP');
|
||||
});
|
||||
|
||||
it('F2 → CSI Q (SS3 form)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F2' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F2' }), flags);
|
||||
assert.strictEqual(result.key, '\x1bOQ');
|
||||
});
|
||||
|
||||
it('F3 → CSI R (SS3 form)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F3' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F3' }), flags);
|
||||
assert.strictEqual(result.key, '\x1bOR');
|
||||
});
|
||||
|
||||
it('F4 → CSI S (SS3 form)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F4' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F4' }), flags);
|
||||
assert.strictEqual(result.key, '\x1bOS');
|
||||
});
|
||||
|
||||
it('F5 → CSI 15 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F5' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F5' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[15~');
|
||||
});
|
||||
|
||||
it('F6 → CSI 17 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F6' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F6' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[17~');
|
||||
});
|
||||
|
||||
it('F7 → CSI 18 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F7' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F7' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[18~');
|
||||
});
|
||||
|
||||
it('F8 → CSI 19 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F8' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F8' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[19~');
|
||||
});
|
||||
|
||||
it('F9 → CSI 20 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F9' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F9' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[20~');
|
||||
});
|
||||
|
||||
it('F10 → CSI 21 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F10' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F10' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[21~');
|
||||
});
|
||||
|
||||
it('F11 → CSI 23 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F11' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F11' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[23~');
|
||||
});
|
||||
|
||||
it('F12 → CSI 24 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F12' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F12' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[24~');
|
||||
});
|
||||
|
||||
it('Shift+F1 → CSI 1;2 P', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F1', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F1', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[1;2P');
|
||||
});
|
||||
|
||||
it('Ctrl+F5 → CSI 15;5 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F5', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F5', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[15;5~');
|
||||
});
|
||||
});
|
||||
@@ -291,22 +297,22 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('F13 → CSI 57376 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F13' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F13' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57376u');
|
||||
});
|
||||
|
||||
it('F14 → CSI 57377 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F14' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F14' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57377u');
|
||||
});
|
||||
|
||||
it('F20 → CSI 57383 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F20' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F20' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57383u');
|
||||
});
|
||||
|
||||
it('F24 → CSI 57387 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'F24' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'F24' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57387u');
|
||||
});
|
||||
});
|
||||
@@ -315,57 +321,57 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('Numpad0 → CSI 57399 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '0', code: 'Numpad0' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '0', code: 'Numpad0' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57399u');
|
||||
});
|
||||
|
||||
it('Numpad1 → CSI 57400 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '1', code: 'Numpad1' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '1', code: 'Numpad1' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57400u');
|
||||
});
|
||||
|
||||
it('Numpad9 → CSI 57408 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '9', code: 'Numpad9' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '9', code: 'Numpad9' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57408u');
|
||||
});
|
||||
|
||||
it('NumpadDecimal → CSI 57409 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '.', code: 'NumpadDecimal' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '.', code: 'NumpadDecimal' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57409u');
|
||||
});
|
||||
|
||||
it('NumpadDivide → CSI 57410 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '/', code: 'NumpadDivide' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '/', code: 'NumpadDivide' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57410u');
|
||||
});
|
||||
|
||||
it('NumpadMultiply → CSI 57411 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '*', code: 'NumpadMultiply' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '*', code: 'NumpadMultiply' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57411u');
|
||||
});
|
||||
|
||||
it('NumpadSubtract → CSI 57412 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '-', code: 'NumpadSubtract' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '-', code: 'NumpadSubtract' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57412u');
|
||||
});
|
||||
|
||||
it('NumpadAdd → CSI 57413 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '+', code: 'NumpadAdd' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '+', code: 'NumpadAdd' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57413u');
|
||||
});
|
||||
|
||||
it('NumpadEnter → CSI 57414 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Enter', code: 'NumpadEnter' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Enter', code: 'NumpadEnter' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57414u');
|
||||
});
|
||||
|
||||
it('NumpadEqual → CSI 57415 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '=', code: 'NumpadEqual' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '=', code: 'NumpadEqual' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57415u');
|
||||
});
|
||||
|
||||
it('Ctrl+Numpad5 → CSI 57404;5 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '5', code: 'Numpad5', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '5', code: 'Numpad5', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57404;5u');
|
||||
});
|
||||
});
|
||||
@@ -374,57 +380,57 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES;
|
||||
|
||||
it('Left Shift → CSI 57441 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57441;2u');
|
||||
});
|
||||
|
||||
it('Right Shift → CSI 57447 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Shift', code: 'ShiftRight', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftRight', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57447;2u');
|
||||
});
|
||||
|
||||
it('Left Control → CSI 57442 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Control', code: 'ControlLeft', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Control', code: 'ControlLeft', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57442;5u');
|
||||
});
|
||||
|
||||
it('Right Control → CSI 57448 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Control', code: 'ControlRight', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Control', code: 'ControlRight', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57448;5u');
|
||||
});
|
||||
|
||||
it('Left Alt → CSI 57443 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Alt', code: 'AltLeft', altKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Alt', code: 'AltLeft', altKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57443;3u');
|
||||
});
|
||||
|
||||
it('Right Alt → CSI 57449 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Alt', code: 'AltRight', altKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Alt', code: 'AltRight', altKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57449;3u');
|
||||
});
|
||||
|
||||
it('Left Meta/Super → CSI 57444 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Meta', code: 'MetaLeft', metaKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Meta', code: 'MetaLeft', metaKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57444;9u');
|
||||
});
|
||||
|
||||
it('Right Meta/Super → CSI 57450 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Meta', code: 'MetaRight', metaKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Meta', code: 'MetaRight', metaKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57450;9u');
|
||||
});
|
||||
|
||||
it('CapsLock → CSI 57358 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'CapsLock', code: 'CapsLock' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'CapsLock', code: 'CapsLock' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57358u');
|
||||
});
|
||||
|
||||
it('NumLock → CSI 57360 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'NumLock', code: 'NumLock' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'NumLock', code: 'NumLock' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57360u');
|
||||
});
|
||||
|
||||
it('ScrollLock → CSI 57359 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ScrollLock', code: 'ScrollLock' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ScrollLock', code: 'ScrollLock' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57359u');
|
||||
});
|
||||
});
|
||||
@@ -433,42 +439,42 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES | KittyKeyboardFlags.REPORT_EVENT_TYPES;
|
||||
|
||||
it('press event (default, no suffix)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.PRESS);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.PRESS);
|
||||
assert.strictEqual(result.key, '\x1b[97u');
|
||||
});
|
||||
|
||||
it('press event explicit :1 when modifiers present', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', ctrlKey: true }), flags, KittyKeyboardEventType.PRESS);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags, KittyKeyboardEventType.PRESS);
|
||||
assert.strictEqual(result.key, '\x1b[97;5u');
|
||||
});
|
||||
|
||||
it('repeat event → :2 suffix', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.REPEAT);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.REPEAT);
|
||||
assert.strictEqual(result.key, '\x1b[97;1:2u');
|
||||
});
|
||||
|
||||
it('release event → :3 suffix', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.RELEASE);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.RELEASE);
|
||||
assert.strictEqual(result.key, '\x1b[97;1:3u');
|
||||
});
|
||||
|
||||
it('release with modifier → mod:3', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', ctrlKey: true }), flags, KittyKeyboardEventType.RELEASE);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags, KittyKeyboardEventType.RELEASE);
|
||||
assert.strictEqual(result.key, '\x1b[97;5:3u');
|
||||
});
|
||||
|
||||
it('repeat with modifier → mod:2', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', shiftKey: true, altKey: true }), flags, KittyKeyboardEventType.REPEAT);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', shiftKey: true, altKey: true }), flags, KittyKeyboardEventType.REPEAT);
|
||||
assert.strictEqual(result.key, '\x1b[97;4:2u');
|
||||
});
|
||||
|
||||
it('functional key release → CSI code;1:3 ~', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Delete' }), flags, KittyKeyboardEventType.RELEASE);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Delete' }), flags, KittyKeyboardEventType.RELEASE);
|
||||
assert.strictEqual(result.key, '\x1b[3;1:3~');
|
||||
});
|
||||
|
||||
it('modifier key release includes its own bit cleared', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: false }), flags, KittyKeyboardEventType.RELEASE);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Shift', code: 'ShiftLeft', shiftKey: false }), flags, KittyKeyboardEventType.RELEASE);
|
||||
assert.strictEqual(result.key, '\x1b[57441;1:3u');
|
||||
});
|
||||
});
|
||||
@@ -477,34 +483,34 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES;
|
||||
|
||||
it('lowercase letter → CSI codepoint u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97u');
|
||||
});
|
||||
|
||||
it('uppercase letter uses lowercase codepoint', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'A', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;2u');
|
||||
});
|
||||
|
||||
it('digit → CSI codepoint u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '5' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '5' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[53u');
|
||||
});
|
||||
|
||||
it('punctuation → CSI codepoint u', () => {
|
||||
assert.strictEqual(evaluateKeyboardEventKitty(createEvent({ key: '.' }), flags).key, '\x1b[46u');
|
||||
assert.strictEqual(evaluateKeyboardEventKitty(createEvent({ key: ',' }), flags).key, '\x1b[44u');
|
||||
assert.strictEqual(evaluateKeyboardEventKitty(createEvent({ key: ';' }), flags).key, '\x1b[59u');
|
||||
assert.strictEqual(evaluateKeyboardEventKitty(createEvent({ key: '/' }), flags).key, '\x1b[47u');
|
||||
assert.strictEqual(kitty.evaluate(createEvent({ key: '.' }), flags).key, '\x1b[46u');
|
||||
assert.strictEqual(kitty.evaluate(createEvent({ key: ',' }), flags).key, '\x1b[44u');
|
||||
assert.strictEqual(kitty.evaluate(createEvent({ key: ';' }), flags).key, '\x1b[59u');
|
||||
assert.strictEqual(kitty.evaluate(createEvent({ key: '/' }), flags).key, '\x1b[47u');
|
||||
});
|
||||
|
||||
it('brackets → CSI codepoint u', () => {
|
||||
assert.strictEqual(evaluateKeyboardEventKitty(createEvent({ key: '[' }), flags).key, '\x1b[91u');
|
||||
assert.strictEqual(evaluateKeyboardEventKitty(createEvent({ key: ']' }), flags).key, '\x1b[93u');
|
||||
assert.strictEqual(kitty.evaluate(createEvent({ key: '[' }), flags).key, '\x1b[91u');
|
||||
assert.strictEqual(kitty.evaluate(createEvent({ key: ']' }), flags).key, '\x1b[93u');
|
||||
});
|
||||
|
||||
it('space → CSI 32 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: ' ' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: ' ' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[32u');
|
||||
});
|
||||
});
|
||||
@@ -513,38 +519,38 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES | KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT;
|
||||
|
||||
it('regular key includes text codepoint', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;;97u');
|
||||
});
|
||||
|
||||
it('shifted key includes shifted text', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'A', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;2;65u');
|
||||
});
|
||||
|
||||
it('Ctrl+key omits text (control code)', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', ctrlKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', ctrlKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;5u');
|
||||
});
|
||||
|
||||
it('functional key has no text', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Escape' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Escape' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[27u');
|
||||
});
|
||||
|
||||
it('release event has no text', () => {
|
||||
const flagsWithEvents = flags | KittyKeyboardFlags.REPORT_EVENT_TYPES;
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a' }), flagsWithEvents, KittyKeyboardEventType.RELEASE);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a' }), flagsWithEvents, KittyKeyboardEventType.RELEASE);
|
||||
assert.strictEqual(result.key, '\x1b[97;1:3u');
|
||||
});
|
||||
|
||||
it('digit with text', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '5' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '5' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[53;;53u');
|
||||
});
|
||||
|
||||
it('Shift+digit shows shifted symbol', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '%', shiftKey: true, code: 'Digit5' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '%', shiftKey: true, code: 'Digit5' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[53;2;37u');
|
||||
});
|
||||
});
|
||||
@@ -553,22 +559,22 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES | KittyKeyboardFlags.REPORT_ALTERNATE_KEYS;
|
||||
|
||||
it('Shift+a includes shifted key → CSI 97:65 ; 2 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97:65;2u');
|
||||
});
|
||||
|
||||
it('unshifted key has no alternate', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a', code: 'KeyA' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a', code: 'KeyA' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97u');
|
||||
});
|
||||
|
||||
it('Shift+5 includes shifted key → CSI 53:37 ; 2 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: '%', shiftKey: true, code: 'Digit5' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: '%', shiftKey: true, code: 'Digit5' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[53:37;2u');
|
||||
});
|
||||
|
||||
it('functional keys have no shifted alternate', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Escape', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Escape', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[27;2u');
|
||||
});
|
||||
});
|
||||
@@ -577,13 +583,13 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES | KittyKeyboardFlags.REPORT_ALTERNATE_KEYS | KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT;
|
||||
|
||||
it('Shift+a → CSI 97:65 ; 2 ; 65 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97:65;2;65u');
|
||||
});
|
||||
|
||||
it('Shift+a release → CSI 97:65 ; 2:3 u (no text)', () => {
|
||||
const flagsWithEvents = flags | KittyKeyboardFlags.REPORT_EVENT_TYPES;
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flagsWithEvents, KittyKeyboardEventType.RELEASE);
|
||||
const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true, code: 'KeyA' }), flagsWithEvents, KittyKeyboardEventType.RELEASE);
|
||||
assert.strictEqual(result.key, '\x1b[97:65;2:3u');
|
||||
});
|
||||
});
|
||||
@@ -592,7 +598,7 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('should not generate key sequence for release events', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.RELEASE);
|
||||
const result = kitty.evaluate(createEvent({ key: 'a' }), flags, KittyKeyboardEventType.RELEASE);
|
||||
assert.strictEqual(result.key, undefined);
|
||||
});
|
||||
});
|
||||
@@ -602,37 +608,37 @@ describe('KittyKeyboard', () => {
|
||||
|
||||
it('shift+letter sends plain character in DISAMBIGUATE mode', () => {
|
||||
// Shift+A produces printable "A", not ambiguous, so send plain character
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'A', shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'A', shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, 'A');
|
||||
});
|
||||
|
||||
it('ctrl+shift+a sends lowercase codepoint 97', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'A', ctrlKey: true, shiftKey: true }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'A', ctrlKey: true, shiftKey: true }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[97;6u');
|
||||
});
|
||||
|
||||
it('Dead key produces no output', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Dead' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Dead' }), flags);
|
||||
assert.strictEqual(result.key, undefined);
|
||||
});
|
||||
|
||||
it('Unidentified key produces no output', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Unidentified' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Unidentified' }), flags);
|
||||
assert.strictEqual(result.key, undefined);
|
||||
});
|
||||
|
||||
it('PrintScreen → CSI 57361 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'PrintScreen' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'PrintScreen' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57361u');
|
||||
});
|
||||
|
||||
it('Pause → CSI 57362 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'Pause' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'Pause' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57362u');
|
||||
});
|
||||
|
||||
it('ContextMenu → CSI 57363 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'ContextMenu' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'ContextMenu' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57363u');
|
||||
});
|
||||
});
|
||||
@@ -641,37 +647,37 @@ describe('KittyKeyboard', () => {
|
||||
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
|
||||
|
||||
it('MediaPlayPause → CSI 57430 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'MediaPlayPause' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'MediaPlayPause' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57430u');
|
||||
});
|
||||
|
||||
it('MediaStop → CSI 57432 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'MediaStop' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'MediaStop' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57432u');
|
||||
});
|
||||
|
||||
it('MediaTrackNext → CSI 57435 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'MediaTrackNext' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'MediaTrackNext' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57435u');
|
||||
});
|
||||
|
||||
it('MediaTrackPrevious → CSI 57436 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'MediaTrackPrevious' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'MediaTrackPrevious' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57436u');
|
||||
});
|
||||
|
||||
it('AudioVolumeDown → CSI 57438 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'AudioVolumeDown' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'AudioVolumeDown' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57438u');
|
||||
});
|
||||
|
||||
it('AudioVolumeUp → CSI 57439 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'AudioVolumeUp' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'AudioVolumeUp' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57439u');
|
||||
});
|
||||
|
||||
it('AudioVolumeMute → CSI 57440 u', () => {
|
||||
const result = evaluateKeyboardEventKitty(createEvent({ key: 'AudioVolumeMute' }), flags);
|
||||
const result = kitty.evaluate(createEvent({ key: 'AudioVolumeMute' }), flags);
|
||||
assert.strictEqual(result.key, '\x1b[57440u');
|
||||
});
|
||||
});
|
||||
|
||||
+406
-433
@@ -51,468 +51,441 @@ export const enum KittyKeyboardModifiers {
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional key codes for Kitty protocol.
|
||||
* Keys that don't produce text have specific unicode codepoint mappings.
|
||||
* Kitty keyboard protocol handler class.
|
||||
* Encapsulates all key code mappings and encoding logic.
|
||||
*/
|
||||
const FUNCTIONAL_KEY_CODES: { [key: string]: number } = {
|
||||
'Escape': 27,
|
||||
'Enter': 13,
|
||||
'Tab': 9,
|
||||
'Backspace': 127,
|
||||
'CapsLock': 57358,
|
||||
'ScrollLock': 57359,
|
||||
'NumLock': 57360,
|
||||
'PrintScreen': 57361,
|
||||
'Pause': 57362,
|
||||
'ContextMenu': 57363,
|
||||
// F13-F35 (F1-F12 use legacy encoding)
|
||||
'F13': 57376,
|
||||
'F14': 57377,
|
||||
'F15': 57378,
|
||||
'F16': 57379,
|
||||
'F17': 57380,
|
||||
'F18': 57381,
|
||||
'F19': 57382,
|
||||
'F20': 57383,
|
||||
'F21': 57384,
|
||||
'F22': 57385,
|
||||
'F23': 57386,
|
||||
'F24': 57387,
|
||||
'F25': 57388,
|
||||
// Keypad keys
|
||||
'KP_0': 57399,
|
||||
'KP_1': 57400,
|
||||
'KP_2': 57401,
|
||||
'KP_3': 57402,
|
||||
'KP_4': 57403,
|
||||
'KP_5': 57404,
|
||||
'KP_6': 57405,
|
||||
'KP_7': 57406,
|
||||
'KP_8': 57407,
|
||||
'KP_9': 57408,
|
||||
'KP_Decimal': 57409,
|
||||
'KP_Divide': 57410,
|
||||
'KP_Multiply': 57411,
|
||||
'KP_Subtract': 57412,
|
||||
'KP_Add': 57413,
|
||||
'KP_Enter': 57414,
|
||||
'KP_Equal': 57415,
|
||||
// Modifier keys
|
||||
'ShiftLeft': 57441,
|
||||
'ShiftRight': 57447,
|
||||
'ControlLeft': 57442,
|
||||
'ControlRight': 57448,
|
||||
'AltLeft': 57443,
|
||||
'AltRight': 57449,
|
||||
'MetaLeft': 57444,
|
||||
'MetaRight': 57450,
|
||||
// Media keys
|
||||
'MediaPlayPause': 57430,
|
||||
'MediaStop': 57432,
|
||||
'MediaTrackNext': 57435,
|
||||
'MediaTrackPrevious': 57436,
|
||||
'AudioVolumeDown': 57438,
|
||||
'AudioVolumeUp': 57439,
|
||||
'AudioVolumeMute': 57440
|
||||
};
|
||||
|
||||
/**
|
||||
* Keys that use CSI ~ encoding with a number parameter.
|
||||
*/
|
||||
const CSI_TILDE_KEYS: { [key: string]: number } = {
|
||||
'Insert': 2,
|
||||
'Delete': 3,
|
||||
'PageUp': 5,
|
||||
'PageDown': 6,
|
||||
'F5': 15,
|
||||
'F6': 17,
|
||||
'F7': 18,
|
||||
'F8': 19,
|
||||
'F9': 20,
|
||||
'F10': 21,
|
||||
'F11': 23,
|
||||
'F12': 24
|
||||
};
|
||||
|
||||
/**
|
||||
* Keys that use CSI letter encoding (arrows, Home, End).
|
||||
*/
|
||||
const CSI_LETTER_KEYS: { [key: string]: string } = {
|
||||
'ArrowUp': 'A',
|
||||
'ArrowDown': 'B',
|
||||
'ArrowRight': 'C',
|
||||
'ArrowLeft': 'D',
|
||||
'Home': 'H',
|
||||
'End': 'F'
|
||||
};
|
||||
|
||||
/**
|
||||
* Function keys F1-F4 use SS3 encoding without modifiers.
|
||||
*/
|
||||
const SS3_FUNCTION_KEYS: { [key: string]: string } = {
|
||||
'F1': 'P',
|
||||
'F2': 'Q',
|
||||
'F3': 'R',
|
||||
'F4': 'S'
|
||||
};
|
||||
|
||||
/**
|
||||
* Map browser key codes to Kitty numpad codes.
|
||||
*/
|
||||
function getNumpadKeyCode(ev: IKeyboardEvent): number | undefined {
|
||||
// Detect numpad via code property
|
||||
if (ev.code.startsWith('Numpad')) {
|
||||
const suffix = ev.code.slice(6);
|
||||
if (suffix >= '0' && suffix <= '9') {
|
||||
return 57399 + parseInt(suffix, 10);
|
||||
}
|
||||
switch (suffix) {
|
||||
case 'Decimal': return 57409;
|
||||
case 'Divide': return 57410;
|
||||
case 'Multiply': return 57411;
|
||||
case 'Subtract': return 57412;
|
||||
case 'Add': return 57413;
|
||||
case 'Enter': return 57414;
|
||||
case 'Equal': return 57415;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get modifier key code from code property.
|
||||
*/
|
||||
function getModifierKeyCode(ev: IKeyboardEvent): number | undefined {
|
||||
switch (ev.code) {
|
||||
case 'ShiftLeft': return 57441;
|
||||
case 'ShiftRight': return 57447;
|
||||
case 'ControlLeft': return 57442;
|
||||
case 'ControlRight': return 57448;
|
||||
case 'AltLeft': return 57443;
|
||||
case 'AltRight': return 57449;
|
||||
case 'MetaLeft': return 57444;
|
||||
case 'MetaRight': return 57450;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode modifiers for Kitty protocol.
|
||||
* Returns 1 + modifier bits, or 0 if no modifiers.
|
||||
*/
|
||||
function encodeModifiers(ev: IKeyboardEvent): number {
|
||||
let mods = 0;
|
||||
if (ev.shiftKey) mods |= KittyKeyboardModifiers.SHIFT;
|
||||
if (ev.altKey) mods |= KittyKeyboardModifiers.ALT;
|
||||
if (ev.ctrlKey) mods |= KittyKeyboardModifiers.CTRL;
|
||||
if (ev.metaKey) mods |= KittyKeyboardModifiers.SUPER;
|
||||
return mods > 0 ? mods + 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unicode key code for a keyboard event.
|
||||
* Returns the lowercase codepoint for letters.
|
||||
* For shifted keys, uses the code property to get the base key.
|
||||
*/
|
||||
function getKeyCode(ev: IKeyboardEvent): number | undefined {
|
||||
// Check for numpad first
|
||||
const numpadCode = getNumpadKeyCode(ev);
|
||||
if (numpadCode !== undefined) {
|
||||
return numpadCode;
|
||||
}
|
||||
|
||||
// Check for modifier keys
|
||||
const modifierCode = getModifierKeyCode(ev);
|
||||
if (modifierCode !== undefined) {
|
||||
return modifierCode;
|
||||
}
|
||||
|
||||
// Check functional keys
|
||||
const funcCode = FUNCTIONAL_KEY_CODES[ev.key];
|
||||
if (funcCode !== undefined) {
|
||||
return funcCode;
|
||||
}
|
||||
|
||||
// For shifted keys, use code property to get base key
|
||||
if (ev.shiftKey && ev.code) {
|
||||
// Handle Digit0-Digit9
|
||||
if (ev.code.startsWith('Digit') && ev.code.length === 6) {
|
||||
const digit = ev.code.charAt(5);
|
||||
if (digit >= '0' && digit <= '9') {
|
||||
return digit.charCodeAt(0);
|
||||
}
|
||||
}
|
||||
// Handle KeyA-KeyZ
|
||||
if (ev.code.startsWith('Key') && ev.code.length === 4) {
|
||||
const letter = ev.code.charAt(3).toLowerCase();
|
||||
return letter.charCodeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// For regular keys, use the key character's codepoint
|
||||
// Always use lowercase for letters (per spec)
|
||||
if (ev.key.length === 1) {
|
||||
const code = ev.key.codePointAt(0)!;
|
||||
// Convert uppercase A-Z to lowercase a-z
|
||||
if (code >= 65 && code <= 90) {
|
||||
return code + 32;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a key is a modifier key.
|
||||
*/
|
||||
function isModifierKey(ev: IKeyboardEvent): boolean {
|
||||
return ev.key === 'Shift' || ev.key === 'Control' || ev.key === 'Alt' || ev.key === 'Meta';
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a keyboard event using Kitty keyboard protocol.
|
||||
*
|
||||
* @param ev The keyboard event.
|
||||
* @param flags The active Kitty keyboard enhancement flags.
|
||||
* @param eventType The event type (press, repeat, release).
|
||||
* @returns The keyboard result with the encoded key sequence.
|
||||
*/
|
||||
export function evaluateKeyboardEventKitty(
|
||||
ev: IKeyboardEvent,
|
||||
flags: number,
|
||||
eventType: KittyKeyboardEventType = KittyKeyboardEventType.PRESS
|
||||
): IKeyboardResult {
|
||||
const result: IKeyboardResult = {
|
||||
type: KeyboardResultType.SEND_KEY,
|
||||
cancel: false,
|
||||
key: undefined
|
||||
export class KittyKeyboard {
|
||||
/**
|
||||
* Functional key codes for Kitty protocol.
|
||||
* Keys that don't produce text have specific unicode codepoint mappings.
|
||||
*/
|
||||
private readonly _functionalKeyCodes: { [key: string]: number } = {
|
||||
'Escape': 27,
|
||||
'Enter': 13,
|
||||
'Tab': 9,
|
||||
'Backspace': 127,
|
||||
'CapsLock': 57358,
|
||||
'ScrollLock': 57359,
|
||||
'NumLock': 57360,
|
||||
'PrintScreen': 57361,
|
||||
'Pause': 57362,
|
||||
'ContextMenu': 57363,
|
||||
// F13-F35 (F1-F12 use legacy encoding)
|
||||
'F13': 57376,
|
||||
'F14': 57377,
|
||||
'F15': 57378,
|
||||
'F16': 57379,
|
||||
'F17': 57380,
|
||||
'F18': 57381,
|
||||
'F19': 57382,
|
||||
'F20': 57383,
|
||||
'F21': 57384,
|
||||
'F22': 57385,
|
||||
'F23': 57386,
|
||||
'F24': 57387,
|
||||
'F25': 57388,
|
||||
// Keypad keys
|
||||
'KP_0': 57399,
|
||||
'KP_1': 57400,
|
||||
'KP_2': 57401,
|
||||
'KP_3': 57402,
|
||||
'KP_4': 57403,
|
||||
'KP_5': 57404,
|
||||
'KP_6': 57405,
|
||||
'KP_7': 57406,
|
||||
'KP_8': 57407,
|
||||
'KP_9': 57408,
|
||||
'KP_Decimal': 57409,
|
||||
'KP_Divide': 57410,
|
||||
'KP_Multiply': 57411,
|
||||
'KP_Subtract': 57412,
|
||||
'KP_Add': 57413,
|
||||
'KP_Enter': 57414,
|
||||
'KP_Equal': 57415,
|
||||
// Modifier keys
|
||||
'ShiftLeft': 57441,
|
||||
'ShiftRight': 57447,
|
||||
'ControlLeft': 57442,
|
||||
'ControlRight': 57448,
|
||||
'AltLeft': 57443,
|
||||
'AltRight': 57449,
|
||||
'MetaLeft': 57444,
|
||||
'MetaRight': 57450,
|
||||
// Media keys
|
||||
'MediaPlayPause': 57430,
|
||||
'MediaStop': 57432,
|
||||
'MediaTrackNext': 57435,
|
||||
'MediaTrackPrevious': 57436,
|
||||
'AudioVolumeDown': 57438,
|
||||
'AudioVolumeUp': 57439,
|
||||
'AudioVolumeMute': 57440
|
||||
};
|
||||
|
||||
const modifiers = encodeModifiers(ev);
|
||||
const isMod = isModifierKey(ev);
|
||||
const reportEventTypes = !!(flags & KittyKeyboardFlags.REPORT_EVENT_TYPES);
|
||||
/**
|
||||
* Keys that use CSI ~ encoding with a number parameter.
|
||||
*/
|
||||
private readonly _csiTildeKeys: { [key: string]: number } = {
|
||||
'Insert': 2,
|
||||
'Delete': 3,
|
||||
'PageUp': 5,
|
||||
'PageDown': 6,
|
||||
'F5': 15,
|
||||
'F6': 17,
|
||||
'F7': 18,
|
||||
'F8': 19,
|
||||
'F9': 20,
|
||||
'F10': 21,
|
||||
'F11': 23,
|
||||
'F12': 24
|
||||
};
|
||||
|
||||
// Don't report release events unless flag is set
|
||||
if (!reportEventTypes && eventType === KittyKeyboardEventType.RELEASE) {
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Keys that use CSI letter encoding (arrows, Home, End).
|
||||
*/
|
||||
private readonly _csiLetterKeys: { [key: string]: string } = {
|
||||
'ArrowUp': 'A',
|
||||
'ArrowDown': 'B',
|
||||
'ArrowRight': 'C',
|
||||
'ArrowLeft': 'D',
|
||||
'Home': 'H',
|
||||
'End': 'F'
|
||||
};
|
||||
|
||||
// Modifier-only keys require REPORT_ALL_KEYS_AS_ESCAPE_CODES or REPORT_EVENT_TYPES
|
||||
if (isMod && !(flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) && !reportEventTypes) {
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Function keys F1-F4 use SS3 encoding without modifiers.
|
||||
*/
|
||||
private readonly _ss3FunctionKeys: { [key: string]: string } = {
|
||||
'F1': 'P',
|
||||
'F2': 'Q',
|
||||
'F3': 'R',
|
||||
'F4': 'S'
|
||||
};
|
||||
|
||||
// Check for CSI letter keys (arrows, Home, End)
|
||||
const csiLetter = CSI_LETTER_KEYS[ev.key];
|
||||
if (csiLetter) {
|
||||
result.key = buildCsiLetterSequence(csiLetter, modifiers, eventType, reportEventTypes);
|
||||
result.cancel = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for SS3/CSI function keys (F1-F4)
|
||||
const ss3Letter = SS3_FUNCTION_KEYS[ev.key];
|
||||
if (ss3Letter) {
|
||||
result.key = buildSs3Sequence(ss3Letter, modifiers, eventType, reportEventTypes);
|
||||
result.cancel = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for CSI ~ keys (Insert, Delete, PageUp/Down, F5-F12)
|
||||
const tildeCode = CSI_TILDE_KEYS[ev.key];
|
||||
if (tildeCode !== undefined) {
|
||||
result.key = buildCsiTildeSequence(tildeCode, modifiers, eventType, reportEventTypes);
|
||||
result.cancel = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the key code for CSI u encoding
|
||||
const keyCode = getKeyCode(ev);
|
||||
if (keyCode === undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const isFunc = FUNCTIONAL_KEY_CODES[ev.key] !== undefined || getNumpadKeyCode(ev) !== undefined;
|
||||
|
||||
// Determine if we should use CSI u encoding
|
||||
let useCsiU = false;
|
||||
|
||||
if (flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) {
|
||||
useCsiU = true;
|
||||
} else if (reportEventTypes) {
|
||||
useCsiU = true;
|
||||
} else if (flags & KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES) {
|
||||
// Modifier-only keys already handled above
|
||||
// Use CSI u for keys that would be ambiguous in legacy encoding
|
||||
if (keyCode === 27 || keyCode === 127 || keyCode === 13 || keyCode === 9 || keyCode === 32) {
|
||||
// Escape, Backspace, Enter, Tab, Space
|
||||
useCsiU = true;
|
||||
} else if (isFunc) {
|
||||
useCsiU = true;
|
||||
} else if (modifiers > 0) {
|
||||
// Shift-only + printable character (e.g., Shift+a → "A") should NOT use CSI u
|
||||
// per Kitty spec: DISAMBIGUATE only encodes keys ambiguous in legacy encoding
|
||||
if (ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.key.length === 1) {
|
||||
useCsiU = false;
|
||||
} else {
|
||||
useCsiU = true;
|
||||
/**
|
||||
* Map browser key codes to Kitty numpad codes.
|
||||
*/
|
||||
private _getNumpadKeyCode(ev: IKeyboardEvent): number | undefined {
|
||||
if (ev.code.startsWith('Numpad')) {
|
||||
const suffix = ev.code.slice(6);
|
||||
if (suffix >= '0' && suffix <= '9') {
|
||||
return 57399 + parseInt(suffix, 10);
|
||||
}
|
||||
switch (suffix) {
|
||||
case 'Decimal': return 57409;
|
||||
case 'Divide': return 57410;
|
||||
case 'Multiply': return 57411;
|
||||
case 'Subtract': return 57412;
|
||||
case 'Add': return 57413;
|
||||
case 'Enter': return 57414;
|
||||
case 'Equal': return 57415;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (useCsiU) {
|
||||
result.key = buildCsiUSequence(ev, keyCode, modifiers, eventType, flags, isFunc, isMod);
|
||||
result.cancel = true;
|
||||
} else {
|
||||
// Legacy-compatible encoding for text keys without modifiers
|
||||
if (ev.key.length === 1 && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||
result.key = ev.key;
|
||||
/**
|
||||
* Get modifier key code from code property.
|
||||
*/
|
||||
private _getModifierKeyCode(ev: IKeyboardEvent): number | undefined {
|
||||
switch (ev.code) {
|
||||
case 'ShiftLeft': return 57441;
|
||||
case 'ShiftRight': return 57447;
|
||||
case 'ControlLeft': return 57442;
|
||||
case 'ControlRight': return 57448;
|
||||
case 'AltLeft': return 57443;
|
||||
case 'AltRight': return 57449;
|
||||
case 'MetaLeft': return 57444;
|
||||
case 'MetaRight': return 57450;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Encode modifiers for Kitty protocol.
|
||||
* Returns 1 + modifier bits, or 0 if no modifiers.
|
||||
*/
|
||||
private _encodeModifiers(ev: IKeyboardEvent): number {
|
||||
let mods = 0;
|
||||
if (ev.shiftKey) mods |= KittyKeyboardModifiers.SHIFT;
|
||||
if (ev.altKey) mods |= KittyKeyboardModifiers.ALT;
|
||||
if (ev.ctrlKey) mods |= KittyKeyboardModifiers.CTRL;
|
||||
if (ev.metaKey) mods |= KittyKeyboardModifiers.SUPER;
|
||||
return mods > 0 ? mods + 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build CSI letter sequence for arrow keys, Home, End.
|
||||
* Format: CSI [1;mod] letter
|
||||
*/
|
||||
function buildCsiLetterSequence(
|
||||
letter: string,
|
||||
modifiers: number,
|
||||
eventType: KittyKeyboardEventType,
|
||||
reportEventTypes: boolean
|
||||
): string {
|
||||
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
||||
|
||||
if (modifiers > 0 || needsEventType) {
|
||||
let seq = C0.ESC + '[1;' + (modifiers > 0 ? modifiers : '1');
|
||||
if (needsEventType) {
|
||||
seq += ':' + eventType;
|
||||
/**
|
||||
* Get the unicode key code for a keyboard event.
|
||||
* Returns the lowercase codepoint for letters.
|
||||
* For shifted keys, uses the code property to get the base key.
|
||||
*/
|
||||
private _getKeyCode(ev: IKeyboardEvent): number | undefined {
|
||||
const numpadCode = this._getNumpadKeyCode(ev);
|
||||
if (numpadCode !== undefined) {
|
||||
return numpadCode;
|
||||
}
|
||||
seq += letter;
|
||||
|
||||
const modifierCode = this._getModifierKeyCode(ev);
|
||||
if (modifierCode !== undefined) {
|
||||
return modifierCode;
|
||||
}
|
||||
|
||||
const funcCode = this._functionalKeyCodes[ev.key];
|
||||
if (funcCode !== undefined) {
|
||||
return funcCode;
|
||||
}
|
||||
|
||||
if (ev.shiftKey && ev.code) {
|
||||
if (ev.code.startsWith('Digit') && ev.code.length === 6) {
|
||||
const digit = ev.code.charAt(5);
|
||||
if (digit >= '0' && digit <= '9') {
|
||||
return digit.charCodeAt(0);
|
||||
}
|
||||
}
|
||||
if (ev.code.startsWith('Key') && ev.code.length === 4) {
|
||||
const letter = ev.code.charAt(3).toLowerCase();
|
||||
return letter.charCodeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.key.length === 1) {
|
||||
const code = ev.key.codePointAt(0)!;
|
||||
if (code >= 65 && code <= 90) {
|
||||
return code + 32;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a key is a modifier key.
|
||||
*/
|
||||
private _isModifierKey(ev: IKeyboardEvent): boolean {
|
||||
return ev.key === 'Shift' || ev.key === 'Control' || ev.key === 'Alt' || ev.key === 'Meta';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build CSI letter sequence for arrow keys, Home, End.
|
||||
* Format: CSI [1;mod] letter
|
||||
*/
|
||||
private _buildCsiLetterSequence(
|
||||
letter: string,
|
||||
modifiers: number,
|
||||
eventType: KittyKeyboardEventType,
|
||||
reportEventTypes: boolean
|
||||
): string {
|
||||
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
||||
|
||||
if (modifiers > 0 || needsEventType) {
|
||||
let seq = C0.ESC + '[1;' + (modifiers > 0 ? modifiers : '1');
|
||||
if (needsEventType) {
|
||||
seq += ':' + eventType;
|
||||
}
|
||||
seq += letter;
|
||||
return seq;
|
||||
}
|
||||
return C0.ESC + '[' + letter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SS3 sequence for F1-F4.
|
||||
* Without modifiers: SS3 letter
|
||||
* With modifiers: CSI 1;mod letter
|
||||
*/
|
||||
private _buildSs3Sequence(
|
||||
letter: string,
|
||||
modifiers: number,
|
||||
eventType: KittyKeyboardEventType,
|
||||
reportEventTypes: boolean
|
||||
): string {
|
||||
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
||||
|
||||
if (modifiers > 0 || needsEventType) {
|
||||
let seq = C0.ESC + '[1;' + (modifiers > 0 ? modifiers : '1');
|
||||
if (needsEventType) {
|
||||
seq += ':' + eventType;
|
||||
}
|
||||
seq += letter;
|
||||
return seq;
|
||||
}
|
||||
return C0.ESC + 'O' + letter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build CSI ~ sequence for Insert, Delete, PageUp/Down, F5-F12.
|
||||
* Format: CSI number [;mod[:event]] ~
|
||||
*/
|
||||
private _buildCsiTildeSequence(
|
||||
number: number,
|
||||
modifiers: number,
|
||||
eventType: KittyKeyboardEventType,
|
||||
reportEventTypes: boolean
|
||||
): string {
|
||||
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
||||
|
||||
let seq = C0.ESC + '[' + number;
|
||||
if (modifiers > 0 || needsEventType) {
|
||||
seq += ';' + (modifiers > 0 ? modifiers : '1');
|
||||
if (needsEventType) {
|
||||
seq += ':' + eventType;
|
||||
}
|
||||
}
|
||||
seq += '~';
|
||||
return seq;
|
||||
}
|
||||
return C0.ESC + '[' + letter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SS3 sequence for F1-F4.
|
||||
* Without modifiers: SS3 letter
|
||||
* With modifiers: CSI 1;mod letter
|
||||
*/
|
||||
function buildSs3Sequence(
|
||||
letter: string,
|
||||
modifiers: number,
|
||||
eventType: KittyKeyboardEventType,
|
||||
reportEventTypes: boolean
|
||||
): string {
|
||||
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
||||
/**
|
||||
* Build CSI u sequence.
|
||||
* Format: CSI keycode[:shifted[:base]] [;mod[:event][;text]] u
|
||||
*/
|
||||
private _buildCsiUSequence(
|
||||
ev: IKeyboardEvent,
|
||||
keyCode: number,
|
||||
modifiers: number,
|
||||
eventType: KittyKeyboardEventType,
|
||||
flags: number,
|
||||
isFunc: boolean,
|
||||
isMod: boolean
|
||||
): string {
|
||||
const reportEventTypes = !!(flags & KittyKeyboardFlags.REPORT_EVENT_TYPES);
|
||||
const reportAlternateKeys = !!(flags & KittyKeyboardFlags.REPORT_ALTERNATE_KEYS);
|
||||
|
||||
if (modifiers > 0 || needsEventType) {
|
||||
let seq = C0.ESC + '[1;' + (modifiers > 0 ? modifiers : '1');
|
||||
if (needsEventType) {
|
||||
seq += ':' + eventType;
|
||||
let seq = C0.ESC + '[' + keyCode;
|
||||
|
||||
let shiftedKey: number | undefined;
|
||||
if (reportAlternateKeys && ev.shiftKey && ev.key.length === 1 && !isFunc && !isMod) {
|
||||
shiftedKey = ev.key.codePointAt(0);
|
||||
seq += ':' + shiftedKey;
|
||||
}
|
||||
seq += letter;
|
||||
|
||||
const reportAssociatedText = !!(flags & KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT) &&
|
||||
eventType !== KittyKeyboardEventType.RELEASE &&
|
||||
ev.key.length === 1 &&
|
||||
!isFunc &&
|
||||
!isMod &&
|
||||
!ev.ctrlKey;
|
||||
const textCode = reportAssociatedText ? ev.key.codePointAt(0) : undefined;
|
||||
|
||||
const needsEventType = reportEventTypes &&
|
||||
eventType !== KittyKeyboardEventType.PRESS &&
|
||||
(eventType === KittyKeyboardEventType.RELEASE || textCode === undefined);
|
||||
|
||||
if (modifiers > 0 || needsEventType || textCode !== undefined) {
|
||||
seq += ';';
|
||||
if (modifiers > 0) {
|
||||
seq += modifiers;
|
||||
} else if (needsEventType) {
|
||||
seq += '1';
|
||||
}
|
||||
if (needsEventType) {
|
||||
seq += ':' + eventType;
|
||||
}
|
||||
}
|
||||
|
||||
if (textCode !== undefined) {
|
||||
seq += ';' + textCode;
|
||||
}
|
||||
|
||||
seq += 'u';
|
||||
return seq;
|
||||
}
|
||||
return C0.ESC + 'O' + letter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build CSI ~ sequence for Insert, Delete, PageUp/Down, F5-F12.
|
||||
* Format: CSI number [;mod[:event]] ~
|
||||
*/
|
||||
function buildCsiTildeSequence(
|
||||
number: number,
|
||||
modifiers: number,
|
||||
eventType: KittyKeyboardEventType,
|
||||
reportEventTypes: boolean
|
||||
): string {
|
||||
const needsEventType = reportEventTypes && eventType !== KittyKeyboardEventType.PRESS;
|
||||
/**
|
||||
* Evaluate a keyboard event using Kitty keyboard protocol.
|
||||
*
|
||||
* @param ev The keyboard event.
|
||||
* @param flags The active Kitty keyboard enhancement flags.
|
||||
* @param eventType The event type (press, repeat, release).
|
||||
* @returns The keyboard result with the encoded key sequence.
|
||||
*/
|
||||
public evaluate(
|
||||
ev: IKeyboardEvent,
|
||||
flags: number,
|
||||
eventType: KittyKeyboardEventType = KittyKeyboardEventType.PRESS
|
||||
): IKeyboardResult {
|
||||
const result: IKeyboardResult = {
|
||||
type: KeyboardResultType.SEND_KEY,
|
||||
cancel: false,
|
||||
key: undefined
|
||||
};
|
||||
|
||||
let seq = C0.ESC + '[' + number;
|
||||
if (modifiers > 0 || needsEventType) {
|
||||
seq += ';' + (modifiers > 0 ? modifiers : '1');
|
||||
if (needsEventType) {
|
||||
seq += ':' + eventType;
|
||||
const modifiers = this._encodeModifiers(ev);
|
||||
const isMod = this._isModifierKey(ev);
|
||||
const reportEventTypes = !!(flags & KittyKeyboardFlags.REPORT_EVENT_TYPES);
|
||||
|
||||
if (!reportEventTypes && eventType === KittyKeyboardEventType.RELEASE) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
seq += '~';
|
||||
return seq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build CSI u sequence.
|
||||
* Format: CSI keycode[:shifted[:base]] [;mod[:event][;text]] u
|
||||
*/
|
||||
function buildCsiUSequence(
|
||||
ev: IKeyboardEvent,
|
||||
keyCode: number,
|
||||
modifiers: number,
|
||||
eventType: KittyKeyboardEventType,
|
||||
flags: number,
|
||||
isFunc: boolean,
|
||||
isMod: boolean
|
||||
): string {
|
||||
const reportEventTypes = !!(flags & KittyKeyboardFlags.REPORT_EVENT_TYPES);
|
||||
const reportAlternateKeys = !!(flags & KittyKeyboardFlags.REPORT_ALTERNATE_KEYS);
|
||||
|
||||
let seq = C0.ESC + '[' + keyCode;
|
||||
|
||||
// Add shifted key alternate if REPORT_ALTERNATE_KEYS is set and shift is pressed
|
||||
// Only for text-producing keys (not functional or modifier keys)
|
||||
let shiftedKey: number | undefined;
|
||||
if (reportAlternateKeys && ev.shiftKey && ev.key.length === 1 && !isFunc && !isMod) {
|
||||
shiftedKey = ev.key.codePointAt(0);
|
||||
seq += ':' + shiftedKey;
|
||||
}
|
||||
|
||||
// Check if we need associated text (press and repeat events, not release)
|
||||
// Only for text-producing keys (not functional or modifier keys)
|
||||
// Also don't include text when ctrl is pressed (produces control code)
|
||||
const reportAssociatedText = !!(flags & KittyKeyboardFlags.REPORT_ASSOCIATED_TEXT) &&
|
||||
eventType !== KittyKeyboardEventType.RELEASE &&
|
||||
ev.key.length === 1 &&
|
||||
!isFunc &&
|
||||
!isMod &&
|
||||
!ev.ctrlKey;
|
||||
const textCode = reportAssociatedText ? ev.key.codePointAt(0) : undefined;
|
||||
|
||||
// Determine if we need event type suffix
|
||||
// For repeat: only include :2 when there's no text (text implies it's still useful input)
|
||||
// For release: always include :3
|
||||
const needsEventType = reportEventTypes &&
|
||||
eventType !== KittyKeyboardEventType.PRESS &&
|
||||
(eventType === KittyKeyboardEventType.RELEASE || textCode === undefined);
|
||||
|
||||
if (modifiers > 0 || needsEventType || textCode !== undefined) {
|
||||
seq += ';';
|
||||
if (modifiers > 0) {
|
||||
seq += modifiers;
|
||||
} else if (needsEventType) {
|
||||
seq += '1';
|
||||
if (isMod && !(flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) && !reportEventTypes) {
|
||||
return result;
|
||||
}
|
||||
if (needsEventType) {
|
||||
seq += ':' + eventType;
|
||||
|
||||
const csiLetter = this._csiLetterKeys[ev.key];
|
||||
if (csiLetter) {
|
||||
result.key = this._buildCsiLetterSequence(csiLetter, modifiers, eventType, reportEventTypes);
|
||||
result.cancel = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const ss3Letter = this._ss3FunctionKeys[ev.key];
|
||||
if (ss3Letter) {
|
||||
result.key = this._buildSs3Sequence(ss3Letter, modifiers, eventType, reportEventTypes);
|
||||
result.cancel = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const tildeCode = this._csiTildeKeys[ev.key];
|
||||
if (tildeCode !== undefined) {
|
||||
result.key = this._buildCsiTildeSequence(tildeCode, modifiers, eventType, reportEventTypes);
|
||||
result.cancel = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const keyCode = this._getKeyCode(ev);
|
||||
if (keyCode === undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const isFunc = this._functionalKeyCodes[ev.key] !== undefined || this._getNumpadKeyCode(ev) !== undefined;
|
||||
|
||||
let useCsiU = false;
|
||||
|
||||
if (flags & KittyKeyboardFlags.REPORT_ALL_KEYS_AS_ESCAPE_CODES) {
|
||||
useCsiU = true;
|
||||
} else if (reportEventTypes) {
|
||||
useCsiU = true;
|
||||
} else if (flags & KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES) {
|
||||
if (keyCode === 27 || keyCode === 127 || keyCode === 13 || keyCode === 9 || keyCode === 32) {
|
||||
useCsiU = true;
|
||||
} else if (isFunc) {
|
||||
useCsiU = true;
|
||||
} else if (modifiers > 0) {
|
||||
if (ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.key.length === 1) {
|
||||
useCsiU = false;
|
||||
} else {
|
||||
useCsiU = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useCsiU) {
|
||||
result.key = this._buildCsiUSequence(ev, keyCode, modifiers, eventType, flags, isFunc, isMod);
|
||||
result.cancel = true;
|
||||
} else {
|
||||
if (ev.key.length === 1 && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||
result.key = ev.key;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Add associated text if requested
|
||||
if (textCode !== undefined) {
|
||||
seq += ';' + textCode;
|
||||
/**
|
||||
* Check if Kitty protocol should be used based on flags.
|
||||
*/
|
||||
public static shouldUseProtocol(flags: number): boolean {
|
||||
return flags > 0;
|
||||
}
|
||||
|
||||
seq += 'u';
|
||||
return seq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a keyboard event should be handled by Kitty protocol.
|
||||
* Returns true if Kitty flags are active and the event should use Kitty encoding.
|
||||
*/
|
||||
export function shouldUseKittyProtocol(flags: number): boolean {
|
||||
return flags > 0;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { evaluateKeyboardEventWin32, Win32ControlKeyState } from 'common/input/Win32InputMode';
|
||||
import { Win32InputMode, Win32ControlKeyState } from 'common/input/Win32InputMode';
|
||||
import { IKeyboardEvent, KeyboardResultType } from 'common/Types';
|
||||
|
||||
type EventOpts = Partial<IKeyboardEvent>;
|
||||
@@ -18,18 +18,20 @@ const parse = (seq: string) => {
|
||||
return m ? { vk: +m[1], sc: +m[2], uc: +m[3], kd: +m[4], cs: +m[5], rc: +m[6] } : null;
|
||||
};
|
||||
|
||||
const win32 = new Win32InputMode();
|
||||
|
||||
const test = (opts: EventOpts, isDown: boolean, check: (p: ReturnType<typeof parse>) => void) => {
|
||||
const result = evaluateKeyboardEventWin32(ev(opts), isDown);
|
||||
const result = win32.evaluateKeyboardEvent(ev(opts), isDown);
|
||||
const parsed = parse(result.key!);
|
||||
assert.ok(parsed);
|
||||
check(parsed);
|
||||
};
|
||||
|
||||
describe('Win32InputMode', () => {
|
||||
describe('evaluateKeyboardEventWin32', () => {
|
||||
describe('evaluateKeyboardEvent', () => {
|
||||
describe('basic key encoding', () => {
|
||||
it('letter key press', () => {
|
||||
const result = evaluateKeyboardEventWin32(ev({ code: 'KeyA', key: 'a', keyCode: 65 }), true);
|
||||
const result = win32.evaluateKeyboardEvent(ev({ code: 'KeyA', key: 'a', keyCode: 65 }), true);
|
||||
assert.strictEqual(result.type, KeyboardResultType.SEND_KEY);
|
||||
assert.strictEqual(result.cancel, true);
|
||||
const p = parse(result.key!);
|
||||
@@ -135,7 +137,7 @@ describe('Win32InputMode', () => {
|
||||
|
||||
describe('sequence format', () => {
|
||||
it('valid CSI format', () => {
|
||||
const result = evaluateKeyboardEventWin32(ev({ code: 'KeyA', key: 'a', keyCode: 65 }), true);
|
||||
const result = win32.evaluateKeyboardEvent(ev({ code: 'KeyA', key: 'a', keyCode: 65 }), true);
|
||||
assert.ok(result.key?.startsWith('\x1b[') && result.key.endsWith('_'));
|
||||
assert.strictEqual(result.key?.slice(2, -1).split(';').length, 6);
|
||||
});
|
||||
|
||||
+249
-258
@@ -33,274 +33,265 @@ export const enum Win32ControlKeyState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping from browser KeyboardEvent.code to Win32 virtual key codes.
|
||||
* Based on https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
* Win32 input mode handler. Lookup tables are only initialized when this class
|
||||
* is instantiated, reducing bundle size for environments that don't use this mode.
|
||||
*/
|
||||
const CODE_TO_VK: { [code: string]: number } = {
|
||||
// Letters
|
||||
'KeyA': 0x41, 'KeyB': 0x42, 'KeyC': 0x43, 'KeyD': 0x44, 'KeyE': 0x45,
|
||||
'KeyF': 0x46, 'KeyG': 0x47, 'KeyH': 0x48, 'KeyI': 0x49, 'KeyJ': 0x4A,
|
||||
'KeyK': 0x4B, 'KeyL': 0x4C, 'KeyM': 0x4D, 'KeyN': 0x4E, 'KeyO': 0x4F,
|
||||
'KeyP': 0x50, 'KeyQ': 0x51, 'KeyR': 0x52, 'KeyS': 0x53, 'KeyT': 0x54,
|
||||
'KeyU': 0x55, 'KeyV': 0x56, 'KeyW': 0x57, 'KeyX': 0x58, 'KeyY': 0x59,
|
||||
'KeyZ': 0x5A,
|
||||
export class Win32InputMode {
|
||||
/**
|
||||
* Mapping from browser KeyboardEvent.code to Win32 virtual key codes.
|
||||
* Based on https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
*/
|
||||
private readonly _codeToVk: { [code: string]: number } = {
|
||||
// Letters
|
||||
'KeyA': 0x41, 'KeyB': 0x42, 'KeyC': 0x43, 'KeyD': 0x44, 'KeyE': 0x45,
|
||||
'KeyF': 0x46, 'KeyG': 0x47, 'KeyH': 0x48, 'KeyI': 0x49, 'KeyJ': 0x4A,
|
||||
'KeyK': 0x4B, 'KeyL': 0x4C, 'KeyM': 0x4D, 'KeyN': 0x4E, 'KeyO': 0x4F,
|
||||
'KeyP': 0x50, 'KeyQ': 0x51, 'KeyR': 0x52, 'KeyS': 0x53, 'KeyT': 0x54,
|
||||
'KeyU': 0x55, 'KeyV': 0x56, 'KeyW': 0x57, 'KeyX': 0x58, 'KeyY': 0x59,
|
||||
'KeyZ': 0x5A,
|
||||
|
||||
// Digits
|
||||
'Digit0': 0x30, 'Digit1': 0x31, 'Digit2': 0x32, 'Digit3': 0x33, 'Digit4': 0x34,
|
||||
'Digit5': 0x35, 'Digit6': 0x36, 'Digit7': 0x37, 'Digit8': 0x38, 'Digit9': 0x39,
|
||||
// Digits
|
||||
'Digit0': 0x30, 'Digit1': 0x31, 'Digit2': 0x32, 'Digit3': 0x33, 'Digit4': 0x34,
|
||||
'Digit5': 0x35, 'Digit6': 0x36, 'Digit7': 0x37, 'Digit8': 0x38, 'Digit9': 0x39,
|
||||
|
||||
// Function keys
|
||||
'F1': 0x70, 'F2': 0x71, 'F3': 0x72, 'F4': 0x73, 'F5': 0x74, 'F6': 0x75,
|
||||
'F7': 0x76, 'F8': 0x77, 'F9': 0x78, 'F10': 0x79, 'F11': 0x7A, 'F12': 0x7B,
|
||||
'F13': 0x7C, 'F14': 0x7D, 'F15': 0x7E, 'F16': 0x7F, 'F17': 0x80, 'F18': 0x81,
|
||||
'F19': 0x82, 'F20': 0x83, 'F21': 0x84, 'F22': 0x85, 'F23': 0x86, 'F24': 0x87,
|
||||
// Function keys
|
||||
'F1': 0x70, 'F2': 0x71, 'F3': 0x72, 'F4': 0x73, 'F5': 0x74, 'F6': 0x75,
|
||||
'F7': 0x76, 'F8': 0x77, 'F9': 0x78, 'F10': 0x79, 'F11': 0x7A, 'F12': 0x7B,
|
||||
'F13': 0x7C, 'F14': 0x7D, 'F15': 0x7E, 'F16': 0x7F, 'F17': 0x80, 'F18': 0x81,
|
||||
'F19': 0x82, 'F20': 0x83, 'F21': 0x84, 'F22': 0x85, 'F23': 0x86, 'F24': 0x87,
|
||||
|
||||
// Numpad
|
||||
'Numpad0': 0x60, 'Numpad1': 0x61, 'Numpad2': 0x62, 'Numpad3': 0x63, 'Numpad4': 0x64,
|
||||
'Numpad5': 0x65, 'Numpad6': 0x66, 'Numpad7': 0x67, 'Numpad8': 0x68, 'Numpad9': 0x69,
|
||||
'NumpadMultiply': 0x6A, 'NumpadAdd': 0x6B, 'NumpadSeparator': 0x6C,
|
||||
'NumpadSubtract': 0x6D, 'NumpadDecimal': 0x6E, 'NumpadDivide': 0x6F,
|
||||
'NumpadEnter': 0x0D, // Same as Enter but with ENHANCED_KEY flag
|
||||
'NumLock': 0x90,
|
||||
// Numpad
|
||||
'Numpad0': 0x60, 'Numpad1': 0x61, 'Numpad2': 0x62, 'Numpad3': 0x63, 'Numpad4': 0x64,
|
||||
'Numpad5': 0x65, 'Numpad6': 0x66, 'Numpad7': 0x67, 'Numpad8': 0x68, 'Numpad9': 0x69,
|
||||
'NumpadMultiply': 0x6A, 'NumpadAdd': 0x6B, 'NumpadSeparator': 0x6C,
|
||||
'NumpadSubtract': 0x6D, 'NumpadDecimal': 0x6E, 'NumpadDivide': 0x6F,
|
||||
'NumpadEnter': 0x0D, // Same as Enter but with ENHANCED_KEY flag
|
||||
'NumLock': 0x90,
|
||||
|
||||
// Navigation
|
||||
'ArrowUp': 0x26, 'ArrowDown': 0x28, 'ArrowLeft': 0x25, 'ArrowRight': 0x27,
|
||||
'Home': 0x24, 'End': 0x23, 'PageUp': 0x21, 'PageDown': 0x22,
|
||||
'Insert': 0x2D, 'Delete': 0x2E,
|
||||
// Navigation
|
||||
'ArrowUp': 0x26, 'ArrowDown': 0x28, 'ArrowLeft': 0x25, 'ArrowRight': 0x27,
|
||||
'Home': 0x24, 'End': 0x23, 'PageUp': 0x21, 'PageDown': 0x22,
|
||||
'Insert': 0x2D, 'Delete': 0x2E,
|
||||
|
||||
// Modifiers
|
||||
'ShiftLeft': 0x10, 'ShiftRight': 0x10,
|
||||
'ControlLeft': 0x11, 'ControlRight': 0x11,
|
||||
'AltLeft': 0x12, 'AltRight': 0x12,
|
||||
'MetaLeft': 0x5B, 'MetaRight': 0x5C,
|
||||
'CapsLock': 0x14, 'ScrollLock': 0x91,
|
||||
// Modifiers
|
||||
'ShiftLeft': 0x10, 'ShiftRight': 0x10,
|
||||
'ControlLeft': 0x11, 'ControlRight': 0x11,
|
||||
'AltLeft': 0x12, 'AltRight': 0x12,
|
||||
'MetaLeft': 0x5B, 'MetaRight': 0x5C,
|
||||
'CapsLock': 0x14, 'ScrollLock': 0x91,
|
||||
|
||||
// Special keys
|
||||
'Escape': 0x1B, 'Enter': 0x0D, 'Tab': 0x09, 'Space': 0x20,
|
||||
'Backspace': 0x08, 'Pause': 0x13, 'ContextMenu': 0x5D, 'PrintScreen': 0x2C,
|
||||
// Special keys
|
||||
'Escape': 0x1B, 'Enter': 0x0D, 'Tab': 0x09, 'Space': 0x20,
|
||||
'Backspace': 0x08, 'Pause': 0x13, 'ContextMenu': 0x5D, 'PrintScreen': 0x2C,
|
||||
|
||||
// OEM keys (US keyboard layout)
|
||||
'Semicolon': 0xBA, // ;:
|
||||
'Equal': 0xBB, // =+
|
||||
'Comma': 0xBC, // ,<
|
||||
'Minus': 0xBD, // -_
|
||||
'Period': 0xBE, // .>
|
||||
'Slash': 0xBF, // /?
|
||||
'Backquote': 0xC0, // `~
|
||||
'BracketLeft': 0xDB, // [{
|
||||
'Backslash': 0xDC, // \|
|
||||
'BracketRight': 0xDD, // ]}
|
||||
'Quote': 0xDE, // '"
|
||||
'IntlBackslash': 0xE2, // Non-US backslash
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping from browser KeyboardEvent.code to approximate Win32 scan codes.
|
||||
* Note: Scan codes can vary by keyboard layout. These are approximations
|
||||
* based on standard US keyboard layout.
|
||||
*/
|
||||
const CODE_TO_SCANCODE: { [code: string]: number } = {
|
||||
// Letters (row by row)
|
||||
'KeyQ': 0x10, 'KeyW': 0x11, 'KeyE': 0x12, 'KeyR': 0x13, 'KeyT': 0x14,
|
||||
'KeyY': 0x15, 'KeyU': 0x16, 'KeyI': 0x17, 'KeyO': 0x18, 'KeyP': 0x19,
|
||||
'KeyA': 0x1E, 'KeyS': 0x1F, 'KeyD': 0x20, 'KeyF': 0x21, 'KeyG': 0x22,
|
||||
'KeyH': 0x23, 'KeyJ': 0x24, 'KeyK': 0x25, 'KeyL': 0x26,
|
||||
'KeyZ': 0x2C, 'KeyX': 0x2D, 'KeyC': 0x2E, 'KeyV': 0x2F, 'KeyB': 0x30,
|
||||
'KeyN': 0x31, 'KeyM': 0x32,
|
||||
|
||||
// Digits
|
||||
'Digit1': 0x02, 'Digit2': 0x03, 'Digit3': 0x04, 'Digit4': 0x05, 'Digit5': 0x06,
|
||||
'Digit6': 0x07, 'Digit7': 0x08, 'Digit8': 0x09, 'Digit9': 0x0A, 'Digit0': 0x0B,
|
||||
|
||||
// Function keys
|
||||
'F1': 0x3B, 'F2': 0x3C, 'F3': 0x3D, 'F4': 0x3E, 'F5': 0x3F, 'F6': 0x40,
|
||||
'F7': 0x41, 'F8': 0x42, 'F9': 0x43, 'F10': 0x44, 'F11': 0x57, 'F12': 0x58,
|
||||
|
||||
// Numpad
|
||||
'Numpad0': 0x52, 'Numpad1': 0x4F, 'Numpad2': 0x50, 'Numpad3': 0x51, 'Numpad4': 0x4B,
|
||||
'Numpad5': 0x4C, 'Numpad6': 0x4D, 'Numpad7': 0x47, 'Numpad8': 0x48, 'Numpad9': 0x49,
|
||||
'NumpadMultiply': 0x37, 'NumpadAdd': 0x4E, 'NumpadSubtract': 0x4A,
|
||||
'NumpadDecimal': 0x53, 'NumpadDivide': 0x35, 'NumpadEnter': 0x1C,
|
||||
'NumLock': 0x45,
|
||||
|
||||
// Navigation (extended keys)
|
||||
'ArrowUp': 0x48, 'ArrowDown': 0x50, 'ArrowLeft': 0x4B, 'ArrowRight': 0x4D,
|
||||
'Home': 0x47, 'End': 0x4F, 'PageUp': 0x49, 'PageDown': 0x51,
|
||||
'Insert': 0x52, 'Delete': 0x53,
|
||||
|
||||
// Modifiers
|
||||
'ShiftLeft': 0x2A, 'ShiftRight': 0x36,
|
||||
'ControlLeft': 0x1D, 'ControlRight': 0x1D,
|
||||
'AltLeft': 0x38, 'AltRight': 0x38,
|
||||
'CapsLock': 0x3A, 'ScrollLock': 0x46,
|
||||
|
||||
// Special keys
|
||||
'Escape': 0x01, 'Enter': 0x1C, 'Tab': 0x0F, 'Space': 0x39,
|
||||
'Backspace': 0x0E, 'Pause': 0x45,
|
||||
|
||||
// OEM keys
|
||||
'Semicolon': 0x27, 'Equal': 0x0D, 'Comma': 0x33, 'Minus': 0x0C,
|
||||
'Period': 0x34, 'Slash': 0x35, 'Backquote': 0x29,
|
||||
'BracketLeft': 0x1A, 'Backslash': 0x2B, 'BracketRight': 0x1B, 'Quote': 0x28,
|
||||
};
|
||||
|
||||
/**
|
||||
* Codes that represent enhanced keys (extended keyboard keys).
|
||||
*/
|
||||
const ENHANCED_KEY_CODES = new Set([
|
||||
'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',
|
||||
'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete',
|
||||
'NumpadEnter', 'NumpadDivide',
|
||||
'ControlRight', 'AltRight',
|
||||
'PrintScreen', 'Pause', 'ContextMenu',
|
||||
'MetaLeft', 'MetaRight',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Mapping of special keys (ev.key values) to their Unicode control character codes.
|
||||
* These keys have multi-character ev.key strings but produce control characters.
|
||||
* @see https://docs.microsoft.com/en-us/windows/console/key-event-record-str
|
||||
*/
|
||||
const KEY_TO_CONTROL_CHAR: { [key: string]: number } = {
|
||||
'Enter': 0x0D, // Carriage return
|
||||
'Backspace': 0x08, // Backspace
|
||||
'Tab': 0x09, // Horizontal tab
|
||||
'Escape': 0x1B, // Escape
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the Win32 virtual key code for a keyboard event.
|
||||
*/
|
||||
function getVirtualKeyCode(ev: IKeyboardEvent): number {
|
||||
// Try code-based lookup first
|
||||
const vk = CODE_TO_VK[ev.code];
|
||||
if (vk !== undefined) {
|
||||
return vk;
|
||||
}
|
||||
|
||||
// Fall back to keyCode for unmapped keys
|
||||
// Note: keyCode is deprecated but provides reasonable fallback
|
||||
return ev.keyCode || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Win32 scan code for a keyboard event.
|
||||
* Returns 0 if unknown (scan codes vary by hardware).
|
||||
*/
|
||||
function getScanCode(ev: IKeyboardEvent): number {
|
||||
return CODE_TO_SCANCODE[ev.code] || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unicode character for a keyboard event.
|
||||
* Returns 0 for non-character keys.
|
||||
*/
|
||||
function getUnicodeChar(ev: IKeyboardEvent): number {
|
||||
// Handle special keys that produce control characters
|
||||
// Ctrl modifies some of these: Ctrl+Enter=LF, Ctrl+Backspace=DEL
|
||||
if (ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||
if (ev.key === 'Enter') {
|
||||
return 0x0A; // Line feed (Ctrl+Enter)
|
||||
}
|
||||
if (ev.key === 'Backspace') {
|
||||
return 0x7F; // DEL (Ctrl+Backspace)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for special keys that always produce control characters
|
||||
const controlChar = KEY_TO_CONTROL_CHAR[ev.key];
|
||||
if (controlChar !== undefined) {
|
||||
return controlChar;
|
||||
}
|
||||
|
||||
// Only single-character keys produce unicode output
|
||||
if (ev.key.length === 1) {
|
||||
const codePoint = ev.key.codePointAt(0) || 0;
|
||||
|
||||
// Handle Ctrl+letter combinations - these produce control characters (0x01-0x1A)
|
||||
if (ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||
// Convert A-Z or a-z to control character (Ctrl+A = 0x01, Ctrl+C = 0x03, etc.)
|
||||
if (codePoint >= 0x41 && codePoint <= 0x5A) { // A-Z
|
||||
return codePoint - 0x40;
|
||||
}
|
||||
if (codePoint >= 0x61 && codePoint <= 0x7A) { // a-z
|
||||
return codePoint - 0x60;
|
||||
}
|
||||
}
|
||||
|
||||
return codePoint;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Win32 control key state flags.
|
||||
*/
|
||||
function getControlKeyState(ev: IKeyboardEvent): number {
|
||||
let state = 0;
|
||||
|
||||
if (ev.shiftKey) {
|
||||
state |= Win32ControlKeyState.SHIFT_PRESSED;
|
||||
}
|
||||
|
||||
// Note: We can't distinguish left/right for ctrl/alt in standard browser events,
|
||||
// so we use the generic pressed flags. The right-side flags are used when
|
||||
// we can detect them (e.g., via code property).
|
||||
if (ev.ctrlKey) {
|
||||
if (ev.code === 'ControlRight') {
|
||||
state |= Win32ControlKeyState.RIGHT_CTRL_PRESSED;
|
||||
} else {
|
||||
state |= Win32ControlKeyState.LEFT_CTRL_PRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.altKey) {
|
||||
if (ev.code === 'AltRight') {
|
||||
state |= Win32ControlKeyState.RIGHT_ALT_PRESSED;
|
||||
} else {
|
||||
state |= Win32ControlKeyState.LEFT_ALT_PRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for enhanced key
|
||||
if (ENHANCED_KEY_CODES.has(ev.code)) {
|
||||
state |= Win32ControlKeyState.ENHANCED_KEY;
|
||||
}
|
||||
|
||||
// Note: CapsLock, NumLock, ScrollLock states are not reliably available
|
||||
// in standard browser keyboard events. We could potentially detect them
|
||||
// via getModifierState() but this may not be available in all environments.
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a keyboard event using Win32 input mode.
|
||||
*
|
||||
* @param ev The keyboard event.
|
||||
* @param isKeyDown Whether this is a keydown (true) or keyup (false) event.
|
||||
* @returns The keyboard result with the encoded key sequence.
|
||||
*/
|
||||
export function evaluateKeyboardEventWin32(
|
||||
ev: IKeyboardEvent,
|
||||
isKeyDown: boolean
|
||||
): IKeyboardResult {
|
||||
const result: IKeyboardResult = {
|
||||
type: KeyboardResultType.SEND_KEY,
|
||||
cancel: false,
|
||||
key: undefined
|
||||
// OEM keys (US keyboard layout)
|
||||
'Semicolon': 0xBA, // ;:
|
||||
'Equal': 0xBB, // =+
|
||||
'Comma': 0xBC, // ,<
|
||||
'Minus': 0xBD, // -_
|
||||
'Period': 0xBE, // .>
|
||||
'Slash': 0xBF, // /?
|
||||
'Backquote': 0xC0, // `~
|
||||
'BracketLeft': 0xDB, // [{
|
||||
'Backslash': 0xDC, // \|
|
||||
'BracketRight': 0xDD, // ]}
|
||||
'Quote': 0xDE, // '"
|
||||
'IntlBackslash': 0xE2 // Non-US backslash
|
||||
};
|
||||
|
||||
const vk = getVirtualKeyCode(ev);
|
||||
const sc = getScanCode(ev);
|
||||
const uc = getUnicodeChar(ev);
|
||||
const kd = isKeyDown ? 1 : 0;
|
||||
const cs = getControlKeyState(ev);
|
||||
const rc = 1; // Repeat count, always 1 for now
|
||||
/**
|
||||
* Mapping from browser KeyboardEvent.code to approximate Win32 scan codes.
|
||||
* Note: Scan codes can vary by keyboard layout. These are approximations
|
||||
* based on standard US keyboard layout.
|
||||
*/
|
||||
private readonly _codeToScancode: { [code: string]: number } = {
|
||||
// Letters (row by row)
|
||||
'KeyQ': 0x10, 'KeyW': 0x11, 'KeyE': 0x12, 'KeyR': 0x13, 'KeyT': 0x14,
|
||||
'KeyY': 0x15, 'KeyU': 0x16, 'KeyI': 0x17, 'KeyO': 0x18, 'KeyP': 0x19,
|
||||
'KeyA': 0x1E, 'KeyS': 0x1F, 'KeyD': 0x20, 'KeyF': 0x21, 'KeyG': 0x22,
|
||||
'KeyH': 0x23, 'KeyJ': 0x24, 'KeyK': 0x25, 'KeyL': 0x26,
|
||||
'KeyZ': 0x2C, 'KeyX': 0x2D, 'KeyC': 0x2E, 'KeyV': 0x2F, 'KeyB': 0x30,
|
||||
'KeyN': 0x31, 'KeyM': 0x32,
|
||||
|
||||
// Format: CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _
|
||||
result.key = `${C0.ESC}[${vk};${sc};${uc};${kd};${cs};${rc}_`;
|
||||
result.cancel = true;
|
||||
// Digits
|
||||
'Digit1': 0x02, 'Digit2': 0x03, 'Digit3': 0x04, 'Digit4': 0x05, 'Digit5': 0x06,
|
||||
'Digit6': 0x07, 'Digit7': 0x08, 'Digit8': 0x09, 'Digit9': 0x0A, 'Digit0': 0x0B,
|
||||
|
||||
return result;
|
||||
// Function keys
|
||||
'F1': 0x3B, 'F2': 0x3C, 'F3': 0x3D, 'F4': 0x3E, 'F5': 0x3F, 'F6': 0x40,
|
||||
'F7': 0x41, 'F8': 0x42, 'F9': 0x43, 'F10': 0x44, 'F11': 0x57, 'F12': 0x58,
|
||||
|
||||
// Numpad
|
||||
'Numpad0': 0x52, 'Numpad1': 0x4F, 'Numpad2': 0x50, 'Numpad3': 0x51, 'Numpad4': 0x4B,
|
||||
'Numpad5': 0x4C, 'Numpad6': 0x4D, 'Numpad7': 0x47, 'Numpad8': 0x48, 'Numpad9': 0x49,
|
||||
'NumpadMultiply': 0x37, 'NumpadAdd': 0x4E, 'NumpadSubtract': 0x4A,
|
||||
'NumpadDecimal': 0x53, 'NumpadDivide': 0x35, 'NumpadEnter': 0x1C,
|
||||
'NumLock': 0x45,
|
||||
|
||||
// Navigation (extended keys)
|
||||
'ArrowUp': 0x48, 'ArrowDown': 0x50, 'ArrowLeft': 0x4B, 'ArrowRight': 0x4D,
|
||||
'Home': 0x47, 'End': 0x4F, 'PageUp': 0x49, 'PageDown': 0x51,
|
||||
'Insert': 0x52, 'Delete': 0x53,
|
||||
|
||||
// Modifiers
|
||||
'ShiftLeft': 0x2A, 'ShiftRight': 0x36,
|
||||
'ControlLeft': 0x1D, 'ControlRight': 0x1D,
|
||||
'AltLeft': 0x38, 'AltRight': 0x38,
|
||||
'CapsLock': 0x3A, 'ScrollLock': 0x46,
|
||||
|
||||
// Special keys
|
||||
'Escape': 0x01, 'Enter': 0x1C, 'Tab': 0x0F, 'Space': 0x39,
|
||||
'Backspace': 0x0E, 'Pause': 0x45,
|
||||
|
||||
// OEM keys
|
||||
'Semicolon': 0x27, 'Equal': 0x0D, 'Comma': 0x33, 'Minus': 0x0C,
|
||||
'Period': 0x34, 'Slash': 0x35, 'Backquote': 0x29,
|
||||
'BracketLeft': 0x1A, 'Backslash': 0x2B, 'BracketRight': 0x1B, 'Quote': 0x28
|
||||
};
|
||||
|
||||
/**
|
||||
* Codes that represent enhanced keys (extended keyboard keys).
|
||||
*/
|
||||
private readonly _enhancedKeyCodes = new Set([
|
||||
'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',
|
||||
'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete',
|
||||
'NumpadEnter', 'NumpadDivide',
|
||||
'ControlRight', 'AltRight',
|
||||
'PrintScreen', 'Pause', 'ContextMenu',
|
||||
'MetaLeft', 'MetaRight'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Mapping of special keys (ev.key values) to their Unicode control character codes.
|
||||
* These keys have multi-character ev.key strings but produce control characters.
|
||||
* @see https://docs.microsoft.com/en-us/windows/console/key-event-record-str
|
||||
*/
|
||||
private readonly _keyToControlChar: { [key: string]: number } = {
|
||||
'Enter': 0x0D, // Carriage return
|
||||
'Backspace': 0x08, // Backspace
|
||||
'Tab': 0x09, // Horizontal tab
|
||||
'Escape': 0x1B // Escape
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the Win32 virtual key code for a keyboard event.
|
||||
*/
|
||||
private _getVirtualKeyCode(ev: IKeyboardEvent): number {
|
||||
const vk = this._codeToVk[ev.code];
|
||||
if (vk !== undefined) {
|
||||
return vk;
|
||||
}
|
||||
// Fall back to keyCode for unmapped keys
|
||||
return ev.keyCode || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Win32 scan code for a keyboard event.
|
||||
* Returns 0 if unknown (scan codes vary by hardware).
|
||||
*/
|
||||
private _getScanCode(ev: IKeyboardEvent): number {
|
||||
return this._codeToScancode[ev.code] || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unicode character for a keyboard event.
|
||||
* Returns 0 for non-character keys.
|
||||
*/
|
||||
private _getUnicodeChar(ev: IKeyboardEvent): number {
|
||||
// Handle special keys that produce control characters
|
||||
// Ctrl modifies some of these: Ctrl+Enter=LF, Ctrl+Backspace=DEL
|
||||
if (ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||
if (ev.key === 'Enter') {
|
||||
return 0x0A; // Line feed (Ctrl+Enter)
|
||||
}
|
||||
if (ev.key === 'Backspace') {
|
||||
return 0x7F; // DEL (Ctrl+Backspace)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for special keys that always produce control characters
|
||||
const controlChar = this._keyToControlChar[ev.key];
|
||||
if (controlChar !== undefined) {
|
||||
return controlChar;
|
||||
}
|
||||
|
||||
// Only single-character keys produce unicode output
|
||||
if (ev.key.length === 1) {
|
||||
const codePoint = ev.key.codePointAt(0) || 0;
|
||||
|
||||
// Handle Ctrl+letter combinations - these produce control characters (0x01-0x1A)
|
||||
if (ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||
// Convert A-Z or a-z to control character (Ctrl+A = 0x01, Ctrl+C = 0x03, etc.)
|
||||
if (codePoint >= 0x41 && codePoint <= 0x5A) { // A-Z
|
||||
return codePoint - 0x40;
|
||||
}
|
||||
if (codePoint >= 0x61 && codePoint <= 0x7A) { // a-z
|
||||
return codePoint - 0x60;
|
||||
}
|
||||
}
|
||||
|
||||
return codePoint;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Win32 control key state flags.
|
||||
*/
|
||||
private _getControlKeyState(ev: IKeyboardEvent): number {
|
||||
let state = 0;
|
||||
|
||||
if (ev.shiftKey) {
|
||||
state |= Win32ControlKeyState.SHIFT_PRESSED;
|
||||
}
|
||||
|
||||
// Note: We can't distinguish left/right for ctrl/alt in standard browser events,
|
||||
// so we use the generic pressed flags. The right-side flags are used when
|
||||
// we can detect them (e.g., via code property).
|
||||
if (ev.ctrlKey) {
|
||||
if (ev.code === 'ControlRight') {
|
||||
state |= Win32ControlKeyState.RIGHT_CTRL_PRESSED;
|
||||
} else {
|
||||
state |= Win32ControlKeyState.LEFT_CTRL_PRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.altKey) {
|
||||
if (ev.code === 'AltRight') {
|
||||
state |= Win32ControlKeyState.RIGHT_ALT_PRESSED;
|
||||
} else {
|
||||
state |= Win32ControlKeyState.LEFT_ALT_PRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for enhanced key
|
||||
if (this._enhancedKeyCodes.has(ev.code)) {
|
||||
state |= Win32ControlKeyState.ENHANCED_KEY;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a keyboard event using Win32 input mode.
|
||||
*
|
||||
* @param ev The keyboard event.
|
||||
* @param isKeyDown Whether this is a keydown (true) or keyup (false) event.
|
||||
* @returns The keyboard result with the encoded key sequence.
|
||||
*/
|
||||
public evaluateKeyboardEvent(ev: IKeyboardEvent, isKeyDown: boolean): IKeyboardResult {
|
||||
const vk = this._getVirtualKeyCode(ev);
|
||||
const sc = this._getScanCode(ev);
|
||||
const uc = this._getUnicodeChar(ev);
|
||||
const kd = isKeyDown ? 1 : 0;
|
||||
const cs = this._getControlKeyState(ev);
|
||||
const rc = 1; // Repeat count, always 1 for now
|
||||
|
||||
// Format: CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _
|
||||
return {
|
||||
type: KeyboardResultType.SEND_KEY,
|
||||
cancel: true,
|
||||
key: `${C0.ESC}[${vk};${sc};${uc};${kd};${cs};${rc}_`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,6 @@ export class LogService extends Disposable implements ILogService {
|
||||
super();
|
||||
this._updateLogLevel();
|
||||
this._register(this._optionsService.onSpecificOptionChange('logLevel', () => this._updateLogLevel()));
|
||||
|
||||
// For trace logging, assume the latest created log service is valid
|
||||
traceLogger = this;
|
||||
}
|
||||
|
||||
private _updateLogLevel(): void {
|
||||
@@ -95,30 +92,3 @@ export class LogService extends Disposable implements ILogService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let traceLogger: ILogService;
|
||||
export function setTraceLogger(logger: ILogService): void {
|
||||
traceLogger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* A decorator that can be used to automatically log trace calls to the decorated function.
|
||||
*/
|
||||
export function traceCall(_target: any, key: string, descriptor: any): any {
|
||||
if (typeof descriptor.value !== 'function') {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
const fnKey = 'value';
|
||||
const fn = descriptor.value;
|
||||
descriptor[fnKey] = function (...args: any[]) {
|
||||
// Early exit
|
||||
if (traceLogger.logLevel !== LogLevelEnum.TRACE) {
|
||||
return fn.apply(this, args);
|
||||
}
|
||||
|
||||
traceLogger.trace(`GlyphRenderer#${fn.name}(${args.map(e => JSON.stringify(e)).join(', ')})`);
|
||||
const result = fn.apply(this, args);
|
||||
traceLogger.trace(`GlyphRenderer#${fn.name} return`, result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user