fix(tui): complete super key helper support closes #2979
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added support for multiple `--append-system-prompt` flags, each value is appended to the system prompt separated by double newlines ([#3169](https://github.com/badlogic/pi-mono/pull/3169) by [@aliou](https://github.com/aliou))
|
- Added support for multiple `--append-system-prompt` flags, each value is appended to the system prompt separated by double newlines ([#3169](https://github.com/badlogic/pi-mono/pull/3169) by [@aliou](https://github.com/aliou))
|
||||||
|
- Added interactive keybinding support for Kitty `super`-modified shortcuts such as `super+k`, `super+enter`, and `ctrl+super+k` ([#3111](https://github.com/badlogic/pi-mono/pull/3111) by [@sudosubin](https://github.com/sudosubin))
|
||||||
|
|
||||||
## [0.67.1] - 2026-04-13
|
## [0.67.1] - 2026-04-13
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added Kitty keyboard protocol support for `super`-modified keybindings such as `super+k`, `super+enter`, and `ctrl+super+k`, including typed `Key.super(...)` helpers ([#3111](https://github.com/badlogic/pi-mono/pull/3111) by [@sudosubin](https://github.com/sudosubin))
|
||||||
|
|
||||||
## [0.67.1] - 2026-04-13
|
## [0.67.1] - 2026-04-13
|
||||||
|
|
||||||
## [0.67.0] - 2026-04-13
|
## [0.67.0] - 2026-04-13
|
||||||
|
|||||||
+47
-42
@@ -139,28 +139,17 @@ type SpecialKey =
|
|||||||
| "f12";
|
| "f12";
|
||||||
|
|
||||||
type BaseKey = Letter | Digit | SymbolKey | SpecialKey;
|
type BaseKey = Letter | Digit | SymbolKey | SpecialKey;
|
||||||
|
type ModifierName = "ctrl" | "shift" | "alt" | "super";
|
||||||
|
|
||||||
|
type ModifiedKeyId<Key extends string, RemainingModifiers extends ModifierName = ModifierName> = {
|
||||||
|
[M in RemainingModifiers]: `${M}+${Key}` | `${M}+${ModifiedKeyId<Key, Exclude<RemainingModifiers, M>>}`;
|
||||||
|
}[RemainingModifiers];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union type of all valid key identifiers.
|
* Union type of all valid key identifiers.
|
||||||
* Provides autocomplete and catches typos at compile time.
|
* Provides autocomplete and catches typos at compile time.
|
||||||
*/
|
*/
|
||||||
export type KeyId =
|
export type KeyId = BaseKey | ModifiedKeyId<BaseKey>;
|
||||||
| BaseKey
|
|
||||||
| `ctrl+${BaseKey}`
|
|
||||||
| `shift+${BaseKey}`
|
|
||||||
| `alt+${BaseKey}`
|
|
||||||
| `ctrl+shift+${BaseKey}`
|
|
||||||
| `shift+ctrl+${BaseKey}`
|
|
||||||
| `ctrl+alt+${BaseKey}`
|
|
||||||
| `alt+ctrl+${BaseKey}`
|
|
||||||
| `shift+alt+${BaseKey}`
|
|
||||||
| `alt+shift+${BaseKey}`
|
|
||||||
| `ctrl+shift+alt+${BaseKey}`
|
|
||||||
| `ctrl+alt+shift+${BaseKey}`
|
|
||||||
| `shift+ctrl+alt+${BaseKey}`
|
|
||||||
| `shift+alt+ctrl+${BaseKey}`
|
|
||||||
| `alt+ctrl+shift+${BaseKey}`
|
|
||||||
| `alt+shift+ctrl+${BaseKey}`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper object for creating typed key identifiers with autocomplete.
|
* Helper object for creating typed key identifiers with autocomplete.
|
||||||
@@ -168,8 +157,8 @@ export type KeyId =
|
|||||||
* Usage:
|
* Usage:
|
||||||
* - Key.escape, Key.enter, Key.tab, etc. for special keys
|
* - Key.escape, Key.enter, Key.tab, etc. for special keys
|
||||||
* - Key.backtick, Key.comma, Key.period, etc. for symbol keys
|
* - Key.backtick, Key.comma, Key.period, etc. for symbol keys
|
||||||
* - Key.ctrl("c"), Key.alt("x") for single modifier
|
* - Key.ctrl("c"), Key.alt("x"), Key.super("k") for single modifiers
|
||||||
* - Key.ctrlShift("p"), Key.ctrlAlt("x") for combined modifiers
|
* - Key.ctrlShift("p"), Key.ctrlAlt("x"), Key.ctrlSuper("k") for combined modifiers
|
||||||
*/
|
*/
|
||||||
export const Key = {
|
export const Key = {
|
||||||
// Special keys
|
// Special keys
|
||||||
@@ -241,6 +230,7 @@ export const Key = {
|
|||||||
ctrl: <K extends BaseKey>(key: K): `ctrl+${K}` => `ctrl+${key}`,
|
ctrl: <K extends BaseKey>(key: K): `ctrl+${K}` => `ctrl+${key}`,
|
||||||
shift: <K extends BaseKey>(key: K): `shift+${K}` => `shift+${key}`,
|
shift: <K extends BaseKey>(key: K): `shift+${K}` => `shift+${key}`,
|
||||||
alt: <K extends BaseKey>(key: K): `alt+${K}` => `alt+${key}`,
|
alt: <K extends BaseKey>(key: K): `alt+${K}` => `alt+${key}`,
|
||||||
|
super: <K extends BaseKey>(key: K): `super+${K}` => `super+${key}`,
|
||||||
|
|
||||||
// Combined modifiers
|
// Combined modifiers
|
||||||
ctrlShift: <K extends BaseKey>(key: K): `ctrl+shift+${K}` => `ctrl+shift+${key}`,
|
ctrlShift: <K extends BaseKey>(key: K): `ctrl+shift+${K}` => `ctrl+shift+${key}`,
|
||||||
@@ -249,9 +239,16 @@ export const Key = {
|
|||||||
altCtrl: <K extends BaseKey>(key: K): `alt+ctrl+${K}` => `alt+ctrl+${key}`,
|
altCtrl: <K extends BaseKey>(key: K): `alt+ctrl+${K}` => `alt+ctrl+${key}`,
|
||||||
shiftAlt: <K extends BaseKey>(key: K): `shift+alt+${K}` => `shift+alt+${key}`,
|
shiftAlt: <K extends BaseKey>(key: K): `shift+alt+${K}` => `shift+alt+${key}`,
|
||||||
altShift: <K extends BaseKey>(key: K): `alt+shift+${K}` => `alt+shift+${key}`,
|
altShift: <K extends BaseKey>(key: K): `alt+shift+${K}` => `alt+shift+${key}`,
|
||||||
|
ctrlSuper: <K extends BaseKey>(key: K): `ctrl+super+${K}` => `ctrl+super+${key}`,
|
||||||
|
superCtrl: <K extends BaseKey>(key: K): `super+ctrl+${K}` => `super+ctrl+${key}`,
|
||||||
|
shiftSuper: <K extends BaseKey>(key: K): `shift+super+${K}` => `shift+super+${key}`,
|
||||||
|
superShift: <K extends BaseKey>(key: K): `super+shift+${K}` => `super+shift+${key}`,
|
||||||
|
altSuper: <K extends BaseKey>(key: K): `alt+super+${K}` => `alt+super+${key}`,
|
||||||
|
superAlt: <K extends BaseKey>(key: K): `super+alt+${K}` => `super+alt+${key}`,
|
||||||
|
|
||||||
// Triple modifiers
|
// Triple modifiers
|
||||||
ctrlShiftAlt: <K extends BaseKey>(key: K): `ctrl+shift+alt+${K}` => `ctrl+shift+alt+${key}`,
|
ctrlShiftAlt: <K extends BaseKey>(key: K): `ctrl+shift+alt+${K}` => `ctrl+shift+alt+${key}`,
|
||||||
|
ctrlShiftSuper: <K extends BaseKey>(key: K): `ctrl+shift+super+${K}` => `ctrl+shift+super+${key}`,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -296,6 +293,7 @@ const MODIFIERS = {
|
|||||||
shift: 1,
|
shift: 1,
|
||||||
alt: 2,
|
alt: 2,
|
||||||
ctrl: 4,
|
ctrl: 4,
|
||||||
|
super: 8,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const LOCK_MASK = 64 + 128; // Caps Lock + Num Lock
|
const LOCK_MASK = 64 + 128; // Caps Lock + Num Lock
|
||||||
@@ -759,15 +757,18 @@ function matchesPrintableModifyOtherKeys(data: string, expectedKeycode: number,
|
|||||||
function formatKeyNameWithModifiers(keyName: string, modifier: number): string | undefined {
|
function formatKeyNameWithModifiers(keyName: string, modifier: number): string | undefined {
|
||||||
const mods: string[] = [];
|
const mods: string[] = [];
|
||||||
const effectiveMod = modifier & ~LOCK_MASK;
|
const effectiveMod = modifier & ~LOCK_MASK;
|
||||||
const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt;
|
const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt | MODIFIERS.super;
|
||||||
if ((effectiveMod & ~supportedModifierMask) !== 0) return undefined;
|
if ((effectiveMod & ~supportedModifierMask) !== 0) return undefined;
|
||||||
if (effectiveMod & MODIFIERS.shift) mods.push("shift");
|
if (effectiveMod & MODIFIERS.shift) mods.push("shift");
|
||||||
if (effectiveMod & MODIFIERS.ctrl) mods.push("ctrl");
|
if (effectiveMod & MODIFIERS.ctrl) mods.push("ctrl");
|
||||||
if (effectiveMod & MODIFIERS.alt) mods.push("alt");
|
if (effectiveMod & MODIFIERS.alt) mods.push("alt");
|
||||||
|
if (effectiveMod & MODIFIERS.super) mods.push("super");
|
||||||
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseKeyId(keyId: string): { key: string; ctrl: boolean; shift: boolean; alt: boolean } | null {
|
function parseKeyId(
|
||||||
|
keyId: string,
|
||||||
|
): { key: string; ctrl: boolean; shift: boolean; alt: boolean; super: boolean } | null {
|
||||||
const parts = keyId.toLowerCase().split("+");
|
const parts = keyId.toLowerCase().split("+");
|
||||||
const key = parts[parts.length - 1];
|
const key = parts[parts.length - 1];
|
||||||
if (!key) return null;
|
if (!key) return null;
|
||||||
@@ -776,6 +777,7 @@ function parseKeyId(keyId: string): { key: string; ctrl: boolean; shift: boolean
|
|||||||
ctrl: parts.includes("ctrl"),
|
ctrl: parts.includes("ctrl"),
|
||||||
shift: parts.includes("shift"),
|
shift: parts.includes("shift"),
|
||||||
alt: parts.includes("alt"),
|
alt: parts.includes("alt"),
|
||||||
|
super: parts.includes("super"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -788,9 +790,10 @@ function parseKeyId(keyId: string): { key: string; ctrl: boolean; shift: boolean
|
|||||||
* - Ctrl combinations: "ctrl+c", "ctrl+z", etc.
|
* - Ctrl combinations: "ctrl+c", "ctrl+z", etc.
|
||||||
* - Shift combinations: "shift+tab", "shift+enter"
|
* - Shift combinations: "shift+tab", "shift+enter"
|
||||||
* - Alt combinations: "alt+enter", "alt+backspace"
|
* - Alt combinations: "alt+enter", "alt+backspace"
|
||||||
* - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x"
|
* - Super combinations: "super+k", "super+enter"
|
||||||
|
* - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x", "ctrl+super+k"
|
||||||
*
|
*
|
||||||
* Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p")
|
* Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p"), Key.super("k")
|
||||||
*
|
*
|
||||||
* @param data - Raw input data from terminal
|
* @param data - Raw input data from terminal
|
||||||
* @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c"))
|
* @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c"))
|
||||||
@@ -799,11 +802,12 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
const parsed = parseKeyId(keyId);
|
const parsed = parseKeyId(keyId);
|
||||||
if (!parsed) return false;
|
if (!parsed) return false;
|
||||||
|
|
||||||
const { key, ctrl, shift, alt } = parsed;
|
const { key, ctrl, shift, alt, super: superModifier } = parsed;
|
||||||
let modifier = 0;
|
let modifier = 0;
|
||||||
if (shift) modifier |= MODIFIERS.shift;
|
if (shift) modifier |= MODIFIERS.shift;
|
||||||
if (alt) modifier |= MODIFIERS.alt;
|
if (alt) modifier |= MODIFIERS.alt;
|
||||||
if (ctrl) modifier |= MODIFIERS.ctrl;
|
if (ctrl) modifier |= MODIFIERS.ctrl;
|
||||||
|
if (superModifier) modifier |= MODIFIERS.super;
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "escape":
|
case "escape":
|
||||||
@@ -817,10 +821,10 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
|
|
||||||
case "space":
|
case "space":
|
||||||
if (!_kittyProtocolActive) {
|
if (!_kittyProtocolActive) {
|
||||||
if (ctrl && !alt && !shift && data === "\x00") {
|
if (modifier === MODIFIERS.ctrl && data === "\x00") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (alt && !ctrl && !shift && data === "\x1b ") {
|
if (modifier === MODIFIERS.alt && data === "\x1b ") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -837,7 +841,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case "tab":
|
case "tab":
|
||||||
if (shift && !ctrl && !alt) {
|
if (modifier === MODIFIERS.shift) {
|
||||||
return (
|
return (
|
||||||
data === "\x1b[Z" ||
|
data === "\x1b[Z" ||
|
||||||
matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift) ||
|
matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift) ||
|
||||||
@@ -854,7 +858,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
|
|
||||||
case "enter":
|
case "enter":
|
||||||
case "return":
|
case "return":
|
||||||
if (shift && !ctrl && !alt) {
|
if (modifier === MODIFIERS.shift) {
|
||||||
// CSI u sequences (standard Kitty protocol)
|
// CSI u sequences (standard Kitty protocol)
|
||||||
if (
|
if (
|
||||||
matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) ||
|
matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) ||
|
||||||
@@ -874,7 +878,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (alt && !ctrl && !shift) {
|
if (modifier === MODIFIERS.alt) {
|
||||||
// CSI u sequences (standard Kitty protocol)
|
// CSI u sequences (standard Kitty protocol)
|
||||||
if (
|
if (
|
||||||
matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) ||
|
matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) ||
|
||||||
@@ -909,7 +913,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case "backspace":
|
case "backspace":
|
||||||
if (alt && !ctrl && !shift) {
|
if (modifier === MODIFIERS.alt) {
|
||||||
if (data === "\x1b\x7f" || data === "\x1b\b") {
|
if (data === "\x1b\x7f" || data === "\x1b\b") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -918,7 +922,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
matchesModifyOtherKeys(data, CODEPOINTS.backspace, MODIFIERS.alt)
|
matchesModifyOtherKeys(data, CODEPOINTS.backspace, MODIFIERS.alt)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (ctrl && !alt && !shift) {
|
if (modifier === MODIFIERS.ctrl) {
|
||||||
// Legacy raw 0x08 is ambiguous: it can be Ctrl+Backspace on Windows
|
// Legacy raw 0x08 is ambiguous: it can be Ctrl+Backspace on Windows
|
||||||
// Terminal or plain Backspace on other terminals, while also
|
// Terminal or plain Backspace on other terminals, while also
|
||||||
// overlapping with Ctrl+H.
|
// overlapping with Ctrl+H.
|
||||||
@@ -1019,7 +1023,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, modifier);
|
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, modifier);
|
||||||
|
|
||||||
case "up":
|
case "up":
|
||||||
if (alt && !ctrl && !shift) {
|
if (modifier === MODIFIERS.alt) {
|
||||||
return data === "\x1bp" || matchesKittySequence(data, ARROW_CODEPOINTS.up, MODIFIERS.alt);
|
return data === "\x1bp" || matchesKittySequence(data, ARROW_CODEPOINTS.up, MODIFIERS.alt);
|
||||||
}
|
}
|
||||||
if (modifier === 0) {
|
if (modifier === 0) {
|
||||||
@@ -1034,7 +1038,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
return matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);
|
return matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);
|
||||||
|
|
||||||
case "down":
|
case "down":
|
||||||
if (alt && !ctrl && !shift) {
|
if (modifier === MODIFIERS.alt) {
|
||||||
return data === "\x1bn" || matchesKittySequence(data, ARROW_CODEPOINTS.down, MODIFIERS.alt);
|
return data === "\x1bn" || matchesKittySequence(data, ARROW_CODEPOINTS.down, MODIFIERS.alt);
|
||||||
}
|
}
|
||||||
if (modifier === 0) {
|
if (modifier === 0) {
|
||||||
@@ -1049,7 +1053,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
return matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);
|
return matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);
|
||||||
|
|
||||||
case "left":
|
case "left":
|
||||||
if (alt && !ctrl && !shift) {
|
if (modifier === MODIFIERS.alt) {
|
||||||
return (
|
return (
|
||||||
data === "\x1b[1;3D" ||
|
data === "\x1b[1;3D" ||
|
||||||
(!_kittyProtocolActive && data === "\x1bB") ||
|
(!_kittyProtocolActive && data === "\x1bB") ||
|
||||||
@@ -1057,7 +1061,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt)
|
matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (ctrl && !alt && !shift) {
|
if (modifier === MODIFIERS.ctrl) {
|
||||||
return (
|
return (
|
||||||
data === "\x1b[1;5D" ||
|
data === "\x1b[1;5D" ||
|
||||||
matchesLegacyModifierSequence(data, "left", MODIFIERS.ctrl) ||
|
matchesLegacyModifierSequence(data, "left", MODIFIERS.ctrl) ||
|
||||||
@@ -1076,7 +1080,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);
|
return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);
|
||||||
|
|
||||||
case "right":
|
case "right":
|
||||||
if (alt && !ctrl && !shift) {
|
if (modifier === MODIFIERS.alt) {
|
||||||
return (
|
return (
|
||||||
data === "\x1b[1;3C" ||
|
data === "\x1b[1;3C" ||
|
||||||
(!_kittyProtocolActive && data === "\x1bF") ||
|
(!_kittyProtocolActive && data === "\x1bF") ||
|
||||||
@@ -1084,7 +1088,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt)
|
matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (ctrl && !alt && !shift) {
|
if (modifier === MODIFIERS.ctrl) {
|
||||||
return (
|
return (
|
||||||
data === "\x1b[1;5C" ||
|
data === "\x1b[1;5C" ||
|
||||||
matchesLegacyModifierSequence(data, "right", MODIFIERS.ctrl) ||
|
matchesLegacyModifierSequence(data, "right", MODIFIERS.ctrl) ||
|
||||||
@@ -1129,19 +1133,20 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
const isLetter = key >= "a" && key <= "z";
|
const isLetter = key >= "a" && key <= "z";
|
||||||
const isDigit = isDigitKey(key);
|
const isDigit = isDigitKey(key);
|
||||||
|
|
||||||
if (ctrl && alt && !shift && !_kittyProtocolActive && rawCtrl) {
|
if (modifier === MODIFIERS.ctrl + MODIFIERS.alt && !_kittyProtocolActive && rawCtrl) {
|
||||||
// Legacy: ctrl+alt+key is ESC followed by the control character.
|
// Legacy: ctrl+alt+key is ESC followed by the control character.
|
||||||
// If that legacy form does not match, continue so CSI-u and
|
// If that legacy form does not match, continue so CSI-u and
|
||||||
// modifyOtherKeys sequences from tmux can still be recognized.
|
// modifyOtherKeys sequences from tmux can still be recognized.
|
||||||
if (data === `\x1b${rawCtrl}`) return true;
|
if (data === `\x1b${rawCtrl}`) return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (alt && !ctrl && !shift && !_kittyProtocolActive && (isLetter || isDigit)) {
|
if (modifier === MODIFIERS.alt && !_kittyProtocolActive && (isLetter || isDigit)) {
|
||||||
// Legacy: alt+letter/digit is ESC followed by the key
|
// Legacy: alt+letter/digit is ESC followed by the key
|
||||||
if (data === `\x1b${key}`) return true;
|
if (data === `\x1b${key}`) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctrl && !shift && !alt) {
|
if (modifier === MODIFIERS.ctrl) {
|
||||||
// Legacy: ctrl+key sends the control character
|
// Legacy: ctrl+key sends the control character
|
||||||
if (rawCtrl && data === rawCtrl) return true;
|
if (rawCtrl && data === rawCtrl) return true;
|
||||||
return (
|
return (
|
||||||
@@ -1150,14 +1155,14 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctrl && shift && !alt) {
|
if (modifier === MODIFIERS.shift + MODIFIERS.ctrl) {
|
||||||
return (
|
return (
|
||||||
matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl) ||
|
matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl) ||
|
||||||
matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl)
|
matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shift && !ctrl && !alt) {
|
if (modifier === MODIFIERS.shift) {
|
||||||
// Legacy: shift+letter produces uppercase
|
// Legacy: shift+letter produces uppercase
|
||||||
if (isLetter && data === key.toUpperCase()) return true;
|
if (isLetter && data === key.toUpperCase()) return true;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
import { decodeKittyPrintable, matchesKey, parseKey, setKittyProtocolActive } from "../src/keys.js";
|
import { decodeKittyPrintable, Key, matchesKey, parseKey, setKittyProtocolActive } from "../src/keys.js";
|
||||||
|
|
||||||
function withEnv(name: string, value: string | undefined, fn: () => void): void {
|
function withEnv(name: string, value: string | undefined, fn: () => void): void {
|
||||||
const previous = process.env[name];
|
const previous = process.env[name];
|
||||||
@@ -66,6 +66,21 @@ describe("matchesKey", () => {
|
|||||||
setKittyProtocolActive(false);
|
setKittyProtocolActive(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should match super-modified Kitty bindings, including combined modifiers", () => {
|
||||||
|
setKittyProtocolActive(true);
|
||||||
|
assert.strictEqual(matchesKey("\x1b[107;9u", "super+k"), true);
|
||||||
|
assert.strictEqual(matchesKey("\x1b[13;9u", "super+enter"), true);
|
||||||
|
assert.strictEqual(matchesKey("\x1b[107;13u", Key.ctrlSuper("k")), true);
|
||||||
|
assert.strictEqual(matchesKey("\x1b[107;13u", "ctrl+super+k"), true);
|
||||||
|
assert.strictEqual(matchesKey("\x1b[107;14u", "ctrl+shift+super+k"), true);
|
||||||
|
assert.strictEqual(matchesKey("\x1b[107;13u", "super+k"), false);
|
||||||
|
assert.strictEqual(parseKey("\x1b[107;9u"), "super+k");
|
||||||
|
assert.strictEqual(parseKey("\x1b[13;9u"), "super+enter");
|
||||||
|
assert.strictEqual(parseKey("\x1b[107;13u"), "ctrl+super+k");
|
||||||
|
assert.strictEqual(parseKey("\x1b[107;14u"), "shift+ctrl+super+k");
|
||||||
|
setKittyProtocolActive(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("should match digit bindings via Kitty CSI-u", () => {
|
it("should match digit bindings via Kitty CSI-u", () => {
|
||||||
setKittyProtocolActive(true);
|
setKittyProtocolActive(true);
|
||||||
assert.strictEqual(matchesKey("\x1b[49u", "1"), true);
|
assert.strictEqual(matchesKey("\x1b[49u", "1"), true);
|
||||||
@@ -474,7 +489,7 @@ describe("parseKey", () => {
|
|||||||
|
|
||||||
it("should ignore Kitty CSI-u with unsupported modifiers", () => {
|
it("should ignore Kitty CSI-u with unsupported modifiers", () => {
|
||||||
setKittyProtocolActive(true);
|
setKittyProtocolActive(true);
|
||||||
assert.strictEqual(parseKey("\x1b[99;9u"), undefined);
|
assert.strictEqual(parseKey("\x1b[99;17u"), undefined);
|
||||||
setKittyProtocolActive(false);
|
setKittyProtocolActive(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user