Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/browser/services/KeyboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class KeyboardService implements IKeyboardService {
}
const kittyFlags = this._coreService.kittyKeyboard.flags;
return this.useKitty
? this._getKittyKeyboard().evaluate(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS)
? this._getKittyKeyboard().evaluate(event, kittyFlags, event.repeat ? KittyKeyboardEventType.REPEAT : KittyKeyboardEventType.PRESS, isMac && this._optionsService.rawOptions.macOptionIsMeta)
: evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, isMac, this._optionsService.rawOptions.macOptionIsMeta);
}

Expand All @@ -51,7 +51,7 @@ export class KeyboardService implements IKeyboardService {
}
const kittyFlags = this._coreService.kittyKeyboard.flags;
if (this.useKitty && (kittyFlags & KittyKeyboardFlags.REPORT_EVENT_TYPES)) {
return this._getKittyKeyboard().evaluate(event, kittyFlags, KittyKeyboardEventType.RELEASE);
return this._getKittyKeyboard().evaluate(event, kittyFlags, KittyKeyboardEventType.RELEASE, isMac && this._optionsService.rawOptions.macOptionIsMeta);
}
return undefined;
}
Expand Down
81 changes: 81 additions & 0 deletions src/common/input/KittyKeyboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -729,5 +729,86 @@ describe('KittyKeyboard', () => {
assert.strictEqual(result.key, '\x1b[57440u');
});
});

describe('macOS Option as Alt (macOptionIsMeta)', () => {
const flags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES;
const press = KittyKeyboardEventType.PRESS;

it('Opt+f (key=ƒ) → CSI 102;3 u', () => {
const result = kitty.evaluate(createEvent({ key: 'ƒ', code: 'KeyF', altKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[102;3u');
});

it('Opt+b (key=∫) → CSI 98;3 u', () => {
const result = kitty.evaluate(createEvent({ key: '∫', code: 'KeyB', altKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[98;3u');
});

it('Opt+d (key=∂) → CSI 100;3 u', () => {
const result = kitty.evaluate(createEvent({ key: '∂', code: 'KeyD', altKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[100;3u');
});

it('Opt+n dead key (key=Dead, code=KeyN) → CSI 110;3 u', () => {
const result = kitty.evaluate(createEvent({ key: 'Dead', code: 'KeyN', altKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[110;3u');
});

it('Opt+e dead key (key=Dead, code=KeyE) → CSI 101;3 u', () => {
const result = kitty.evaluate(createEvent({ key: 'Dead', code: 'KeyE', altKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[101;3u');
});

it('Opt+u dead key (key=Dead, code=KeyU) → CSI 117;3 u', () => {
const result = kitty.evaluate(createEvent({ key: 'Dead', code: 'KeyU', altKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[117;3u');
});

it('Opt+5 (key=∞) → CSI 53;3 u', () => {
const result = kitty.evaluate(createEvent({ key: '∞', code: 'Digit5', altKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[53;3u');
});

it('Opt+Shift+f (key=Ï) → CSI 102;4 u', () => {
const result = kitty.evaluate(createEvent({ key: 'Ï', code: 'KeyF', altKey: true, shiftKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[102;4u');
});

it('Ctrl+Opt+f (key=ƒ) → CSI 102;7 u', () => {
const result = kitty.evaluate(createEvent({ key: 'ƒ', code: 'KeyF', altKey: true, ctrlKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[102;7u');
});

it('does not unwind when macOptionAsAlt is false (Linux Alt is a chord)', () => {
const result = kitty.evaluate(createEvent({ key: 'a', code: 'KeyA', altKey: true }), flags, press, false);
assert.strictEqual(result.key, '\x1b[97;3u');
});

it('does not unwind on Linux AZERTY (key=a, code=KeyQ) — uses ev.key not ev.code', () => {
const result = kitty.evaluate(createEvent({ key: 'a', code: 'KeyQ', altKey: true }), flags, press, false);
assert.strictEqual(result.key, '\x1b[97;3u');
});

it('does not unwind when macOptionAsAlt is false even with composed key', () => {
const result = kitty.evaluate(createEvent({ key: 'ƒ', code: 'KeyF', altKey: true }), flags, press, false);
assert.strictEqual(result.key, '\x1b[402;3u');
});

it('does not unwind when altKey is false', () => {
const result = kitty.evaluate(createEvent({ key: 'ƒ', code: 'KeyF' }), flags, press, true);
assert.strictEqual(result.key, 'ƒ');
});

it('falls through when ev.code is not Key*/Digit* (Opt+;)', () => {
const result = kitty.evaluate(createEvent({ key: '…', code: 'Semicolon', altKey: true }), flags, press, true);
assert.strictEqual(result.key, '\x1b[8230;3u');
});

it('Opt+f release with REPORT_EVENT_TYPES → CSI 102;3:3 u', () => {
const releaseFlags = KittyKeyboardFlags.DISAMBIGUATE_ESCAPE_CODES | KittyKeyboardFlags.REPORT_EVENT_TYPES;
const result = kitty.evaluate(createEvent({ key: 'ƒ', code: 'KeyF', altKey: true }), releaseFlags, KittyKeyboardEventType.RELEASE, true);
assert.strictEqual(result.key, '\x1b[102;3:3u');
});
});
});
});
10 changes: 6 additions & 4 deletions src/common/input/KittyKeyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export class KittyKeyboard {
* Returns the lowercase codepoint for letters.
* For shifted keys, uses the code property to get the base key.
*/
private _getKeyCode(ev: IKeyboardEvent): number | undefined {
private _getKeyCode(ev: IKeyboardEvent, macOptionAsAlt: boolean): number | undefined {
const numpadCode = this._getNumpadKeyCode(ev);
if (numpadCode !== undefined) {
return numpadCode;
Expand All @@ -234,7 +234,7 @@ export class KittyKeyboard {
return funcCode;
}

if (ev.shiftKey && ev.code) {
if ((ev.shiftKey || (macOptionAsAlt && ev.altKey)) && ev.code) {
if (ev.code.startsWith('Digit') && ev.code.length === 6) {
const digit = ev.code.charAt(5);
if (digit >= '0' && digit <= '9') {
Expand Down Expand Up @@ -410,12 +410,14 @@ export class KittyKeyboard {
* @param ev The keyboard event.
* @param flags The active Kitty keyboard enhancement flags.
* @param eventType The event type (press, repeat, release).
* @param macOptionAsAlt When true, macOS Option-composed ev.key values are unwound via ev.code.
* @returns The keyboard result with the encoded key sequence.
*/
public evaluate(
ev: IKeyboardEvent,
flags: number,
eventType: KittyKeyboardEventType = KittyKeyboardEventType.PRESS
eventType: KittyKeyboardEventType = KittyKeyboardEventType.PRESS,
macOptionAsAlt: boolean = false
): IKeyboardResult {
const result: IKeyboardResult = {
type: KeyboardResultType.SEND_KEY,
Expand Down Expand Up @@ -464,7 +466,7 @@ export class KittyKeyboard {
return result;
}

const keyCode = this._getKeyCode(ev);
const keyCode = this._getKeyCode(ev, macOptionAsAlt);
if (keyCode === undefined) {
return result;
}
Expand Down
Loading