mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 09:01:14 +00:00
Expand keymap coverage and add key tests (#752)
This commit is contained in:
parent
cceb5908d9
commit
131f1a25f3
2 changed files with 404 additions and 21 deletions
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* Supports both legacy terminal sequences and Kitty keyboard protocol.
|
||||
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
||||
* Reference: https://github.com/sst/opentui/blob/7da92b4088aebfe27b9f691c04163a48821e49fd/packages/core/src/lib/parse.keypress.ts
|
||||
*
|
||||
* Symbol keys are also supported, however some ctrl+symbol combos
|
||||
* overlap with ASCII codes, e.g. ctrl+[ = ESC.
|
||||
|
|
@ -112,6 +113,8 @@ type SpecialKey =
|
|||
| "space"
|
||||
| "backspace"
|
||||
| "delete"
|
||||
| "insert"
|
||||
| "clear"
|
||||
| "home"
|
||||
| "end"
|
||||
| "pageUp"
|
||||
|
|
@ -119,7 +122,19 @@ type SpecialKey =
|
|||
| "up"
|
||||
| "down"
|
||||
| "left"
|
||||
| "right";
|
||||
| "right"
|
||||
| "f1"
|
||||
| "f2"
|
||||
| "f3"
|
||||
| "f4"
|
||||
| "f5"
|
||||
| "f6"
|
||||
| "f7"
|
||||
| "f8"
|
||||
| "f9"
|
||||
| "f10"
|
||||
| "f11"
|
||||
| "f12";
|
||||
|
||||
type BaseKey = Letter | SymbolKey | SpecialKey;
|
||||
|
||||
|
|
@ -164,6 +179,8 @@ export const Key = {
|
|||
space: "space" as const,
|
||||
backspace: "backspace" as const,
|
||||
delete: "delete" as const,
|
||||
insert: "insert" as const,
|
||||
clear: "clear" as const,
|
||||
home: "home" as const,
|
||||
end: "end" as const,
|
||||
pageUp: "pageUp" as const,
|
||||
|
|
@ -172,6 +189,18 @@ export const Key = {
|
|||
down: "down" as const,
|
||||
left: "left" as const,
|
||||
right: "right" as const,
|
||||
f1: "f1" as const,
|
||||
f2: "f2" as const,
|
||||
f3: "f3" as const,
|
||||
f4: "f4" as const,
|
||||
f5: "f5" as const,
|
||||
f6: "f6" as const,
|
||||
f7: "f7" as const,
|
||||
f8: "f8" as const,
|
||||
f9: "f9" as const,
|
||||
f10: "f10" as const,
|
||||
f11: "f11" as const,
|
||||
f12: "f12" as const,
|
||||
|
||||
// Symbol keys
|
||||
backtick: "`" as const,
|
||||
|
|
@ -294,6 +323,135 @@ const FUNCTIONAL_CODEPOINTS = {
|
|||
end: -15,
|
||||
} as const;
|
||||
|
||||
const LEGACY_KEY_SEQUENCES = {
|
||||
up: ["\x1b[A", "\x1bOA"],
|
||||
down: ["\x1b[B", "\x1bOB"],
|
||||
right: ["\x1b[C", "\x1bOC"],
|
||||
left: ["\x1b[D", "\x1bOD"],
|
||||
home: ["\x1b[H", "\x1bOH", "\x1b[1~", "\x1b[7~"],
|
||||
end: ["\x1b[F", "\x1bOF", "\x1b[4~", "\x1b[8~"],
|
||||
insert: ["\x1b[2~"],
|
||||
delete: ["\x1b[3~"],
|
||||
pageUp: ["\x1b[5~", "\x1b[[5~"],
|
||||
pageDown: ["\x1b[6~", "\x1b[[6~"],
|
||||
clear: ["\x1b[E", "\x1bOE"],
|
||||
f1: ["\x1bOP", "\x1b[11~", "\x1b[[A"],
|
||||
f2: ["\x1bOQ", "\x1b[12~", "\x1b[[B"],
|
||||
f3: ["\x1bOR", "\x1b[13~", "\x1b[[C"],
|
||||
f4: ["\x1bOS", "\x1b[14~", "\x1b[[D"],
|
||||
f5: ["\x1b[15~", "\x1b[[E"],
|
||||
f6: ["\x1b[17~"],
|
||||
f7: ["\x1b[18~"],
|
||||
f8: ["\x1b[19~"],
|
||||
f9: ["\x1b[20~"],
|
||||
f10: ["\x1b[21~"],
|
||||
f11: ["\x1b[23~"],
|
||||
f12: ["\x1b[24~"],
|
||||
} as const;
|
||||
|
||||
const LEGACY_SHIFT_SEQUENCES = {
|
||||
up: ["\x1b[a"],
|
||||
down: ["\x1b[b"],
|
||||
right: ["\x1b[c"],
|
||||
left: ["\x1b[d"],
|
||||
clear: ["\x1b[e"],
|
||||
insert: ["\x1b[2$"],
|
||||
delete: ["\x1b[3$"],
|
||||
pageUp: ["\x1b[5$"],
|
||||
pageDown: ["\x1b[6$"],
|
||||
home: ["\x1b[7$"],
|
||||
end: ["\x1b[8$"],
|
||||
} as const;
|
||||
|
||||
const LEGACY_CTRL_SEQUENCES = {
|
||||
up: ["\x1bOa"],
|
||||
down: ["\x1bOb"],
|
||||
right: ["\x1bOc"],
|
||||
left: ["\x1bOd"],
|
||||
clear: ["\x1bOe"],
|
||||
insert: ["\x1b[2^"],
|
||||
delete: ["\x1b[3^"],
|
||||
pageUp: ["\x1b[5^"],
|
||||
pageDown: ["\x1b[6^"],
|
||||
home: ["\x1b[7^"],
|
||||
end: ["\x1b[8^"],
|
||||
} as const;
|
||||
|
||||
const LEGACY_SEQUENCE_KEY_IDS: Record<string, KeyId> = {
|
||||
"\x1bOA": "up",
|
||||
"\x1bOB": "down",
|
||||
"\x1bOC": "right",
|
||||
"\x1bOD": "left",
|
||||
"\x1bOH": "home",
|
||||
"\x1bOF": "end",
|
||||
"\x1b[E": "clear",
|
||||
"\x1bOE": "clear",
|
||||
"\x1bOe": "ctrl+clear",
|
||||
"\x1b[e": "shift+clear",
|
||||
"\x1b[2~": "insert",
|
||||
"\x1b[2$": "shift+insert",
|
||||
"\x1b[2^": "ctrl+insert",
|
||||
"\x1b[3$": "shift+delete",
|
||||
"\x1b[3^": "ctrl+delete",
|
||||
"\x1b[[5~": "pageUp",
|
||||
"\x1b[[6~": "pageDown",
|
||||
"\x1b[a": "shift+up",
|
||||
"\x1b[b": "shift+down",
|
||||
"\x1b[c": "shift+right",
|
||||
"\x1b[d": "shift+left",
|
||||
"\x1bOa": "ctrl+up",
|
||||
"\x1bOb": "ctrl+down",
|
||||
"\x1bOc": "ctrl+right",
|
||||
"\x1bOd": "ctrl+left",
|
||||
"\x1b[5$": "shift+pageUp",
|
||||
"\x1b[6$": "shift+pageDown",
|
||||
"\x1b[7$": "shift+home",
|
||||
"\x1b[8$": "shift+end",
|
||||
"\x1b[5^": "ctrl+pageUp",
|
||||
"\x1b[6^": "ctrl+pageDown",
|
||||
"\x1b[7^": "ctrl+home",
|
||||
"\x1b[8^": "ctrl+end",
|
||||
"\x1bOP": "f1",
|
||||
"\x1bOQ": "f2",
|
||||
"\x1bOR": "f3",
|
||||
"\x1bOS": "f4",
|
||||
"\x1b[11~": "f1",
|
||||
"\x1b[12~": "f2",
|
||||
"\x1b[13~": "f3",
|
||||
"\x1b[14~": "f4",
|
||||
"\x1b[[A": "f1",
|
||||
"\x1b[[B": "f2",
|
||||
"\x1b[[C": "f3",
|
||||
"\x1b[[D": "f4",
|
||||
"\x1b[[E": "f5",
|
||||
"\x1b[15~": "f5",
|
||||
"\x1b[17~": "f6",
|
||||
"\x1b[18~": "f7",
|
||||
"\x1b[19~": "f8",
|
||||
"\x1b[20~": "f9",
|
||||
"\x1b[21~": "f10",
|
||||
"\x1b[23~": "f11",
|
||||
"\x1b[24~": "f12",
|
||||
"\x1bb": "alt+left",
|
||||
"\x1bf": "alt+right",
|
||||
"\x1bp": "alt+up",
|
||||
"\x1bn": "alt+down",
|
||||
} as const;
|
||||
|
||||
type LegacyModifierKey = keyof typeof LEGACY_SHIFT_SEQUENCES;
|
||||
|
||||
const matchesLegacySequence = (data: string, sequences: readonly string[]): boolean => sequences.includes(data);
|
||||
|
||||
const matchesLegacyModifierSequence = (data: string, key: LegacyModifierKey, modifier: number): boolean => {
|
||||
if (modifier === MODIFIERS.shift) {
|
||||
return matchesLegacySequence(data, LEGACY_SHIFT_SEQUENCES[key]);
|
||||
}
|
||||
if (modifier === MODIFIERS.ctrl) {
|
||||
return matchesLegacySequence(data, LEGACY_CTRL_SEQUENCES[key]);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Kitty Protocol Parsing
|
||||
// =============================================================================
|
||||
|
|
@ -534,6 +692,14 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||
return data === "\x1b" || matchesKittySequence(data, CODEPOINTS.escape, 0);
|
||||
|
||||
case "space":
|
||||
if (!_kittyProtocolActive) {
|
||||
if (ctrl && !alt && !shift && data === "\x00") {
|
||||
return true;
|
||||
}
|
||||
if (alt && !ctrl && !shift && data === "\x1b ") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (modifier === 0) {
|
||||
return data === " " || matchesKittySequence(data, CODEPOINTS.space, 0);
|
||||
}
|
||||
|
|
@ -592,6 +758,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||
if (modifier === 0) {
|
||||
return (
|
||||
data === "\r" ||
|
||||
(!_kittyProtocolActive && data === "\n") ||
|
||||
data === "\x1bOM" || // SS3 M (numpad enter in some terminals)
|
||||
matchesKittySequence(data, CODEPOINTS.enter, 0) ||
|
||||
matchesKittySequence(data, CODEPOINTS.kpEnter, 0)
|
||||
|
|
@ -604,62 +771,121 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||
|
||||
case "backspace":
|
||||
if (alt && !ctrl && !shift) {
|
||||
return data === "\x1b\x7f" || matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt);
|
||||
if (!_kittyProtocolActive) {
|
||||
return data === "\x1b\x7f" || data === "\x1b\b";
|
||||
}
|
||||
return matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt);
|
||||
}
|
||||
if (modifier === 0) {
|
||||
return data === "\x7f" || data === "\x08" || matchesKittySequence(data, CODEPOINTS.backspace, 0);
|
||||
}
|
||||
return matchesKittySequence(data, CODEPOINTS.backspace, modifier);
|
||||
|
||||
case "insert":
|
||||
if (modifier === 0) {
|
||||
return (
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.insert) ||
|
||||
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "insert", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, modifier);
|
||||
|
||||
case "delete":
|
||||
if (modifier === 0) {
|
||||
return data === "\x1b[3~" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);
|
||||
return (
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.delete) ||
|
||||
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "delete", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, modifier);
|
||||
|
||||
case "clear":
|
||||
if (modifier === 0) {
|
||||
return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.clear);
|
||||
}
|
||||
return matchesLegacyModifierSequence(data, "clear", modifier);
|
||||
|
||||
case "home":
|
||||
if (modifier === 0) {
|
||||
return (
|
||||
data === "\x1b[H" ||
|
||||
data === "\x1b[1~" ||
|
||||
data === "\x1b[7~" ||
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.home) ||
|
||||
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "home", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, modifier);
|
||||
|
||||
case "end":
|
||||
if (modifier === 0) {
|
||||
return (
|
||||
data === "\x1b[F" ||
|
||||
data === "\x1b[4~" ||
|
||||
data === "\x1b[8~" ||
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.end) ||
|
||||
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "end", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, modifier);
|
||||
|
||||
case "pageUp":
|
||||
if (modifier === 0) {
|
||||
return data === "\x1b[5~" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, 0);
|
||||
return (
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageUp) ||
|
||||
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "pageUp", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, modifier);
|
||||
|
||||
case "pageDown":
|
||||
if (modifier === 0) {
|
||||
return data === "\x1b[6~" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, 0);
|
||||
return (
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageDown) ||
|
||||
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "pageDown", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, modifier);
|
||||
|
||||
case "up":
|
||||
if (alt && !ctrl && !shift) {
|
||||
return data === "\x1bp" || matchesKittySequence(data, ARROW_CODEPOINTS.up, MODIFIERS.alt);
|
||||
}
|
||||
if (modifier === 0) {
|
||||
return data === "\x1b[A" || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);
|
||||
return (
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.up) ||
|
||||
matchesKittySequence(data, ARROW_CODEPOINTS.up, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "up", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);
|
||||
|
||||
case "down":
|
||||
if (alt && !ctrl && !shift) {
|
||||
return data === "\x1bn" || matchesKittySequence(data, ARROW_CODEPOINTS.down, MODIFIERS.alt);
|
||||
}
|
||||
if (modifier === 0) {
|
||||
return data === "\x1b[B" || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);
|
||||
return (
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.down) ||
|
||||
matchesKittySequence(data, ARROW_CODEPOINTS.down, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "down", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);
|
||||
|
||||
|
|
@ -667,15 +893,26 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||
if (alt && !ctrl && !shift) {
|
||||
return (
|
||||
data === "\x1b[1;3D" ||
|
||||
(!_kittyProtocolActive && data === "\x1bB") ||
|
||||
data === "\x1bb" ||
|
||||
matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt)
|
||||
);
|
||||
}
|
||||
if (ctrl && !alt && !shift) {
|
||||
return data === "\x1b[1;5D" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);
|
||||
return (
|
||||
data === "\x1b[1;5D" ||
|
||||
matchesLegacyModifierSequence(data, "left", MODIFIERS.ctrl) ||
|
||||
matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl)
|
||||
);
|
||||
}
|
||||
if (modifier === 0) {
|
||||
return data === "\x1b[D" || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);
|
||||
return (
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.left) ||
|
||||
matchesKittySequence(data, ARROW_CODEPOINTS.left, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "left", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);
|
||||
|
||||
|
|
@ -683,23 +920,57 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||
if (alt && !ctrl && !shift) {
|
||||
return (
|
||||
data === "\x1b[1;3C" ||
|
||||
(!_kittyProtocolActive && data === "\x1bF") ||
|
||||
data === "\x1bf" ||
|
||||
matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt)
|
||||
);
|
||||
}
|
||||
if (ctrl && !alt && !shift) {
|
||||
return data === "\x1b[1;5C" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);
|
||||
return (
|
||||
data === "\x1b[1;5C" ||
|
||||
matchesLegacyModifierSequence(data, "right", MODIFIERS.ctrl) ||
|
||||
matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl)
|
||||
);
|
||||
}
|
||||
if (modifier === 0) {
|
||||
return data === "\x1b[C" || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);
|
||||
return (
|
||||
matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.right) ||
|
||||
matchesKittySequence(data, ARROW_CODEPOINTS.right, 0)
|
||||
);
|
||||
}
|
||||
if (matchesLegacyModifierSequence(data, "right", modifier)) {
|
||||
return true;
|
||||
}
|
||||
return matchesKittySequence(data, ARROW_CODEPOINTS.right, modifier);
|
||||
|
||||
case "f1":
|
||||
case "f2":
|
||||
case "f3":
|
||||
case "f4":
|
||||
case "f5":
|
||||
case "f6":
|
||||
case "f7":
|
||||
case "f8":
|
||||
case "f9":
|
||||
case "f10":
|
||||
case "f11":
|
||||
case "f12": {
|
||||
if (modifier !== 0) {
|
||||
return false;
|
||||
}
|
||||
const functionKey = key as keyof typeof LEGACY_KEY_SEQUENCES;
|
||||
return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[functionKey]);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle single letter keys (a-z) and some symbols
|
||||
if (key.length === 1 && ((key >= "a" && key <= "z") || SYMBOL_KEYS.has(key))) {
|
||||
const codepoint = key.charCodeAt(0);
|
||||
|
||||
if (ctrl && alt && !shift && !_kittyProtocolActive && key >= "a" && key <= "z") {
|
||||
return data === `\x1b${rawCtrlChar(key)}`;
|
||||
}
|
||||
|
||||
if (ctrl && !shift && !alt) {
|
||||
const raw = rawCtrlChar(key);
|
||||
if (data === raw) return true;
|
||||
|
|
@ -755,6 +1026,7 @@ export function parseKey(data: string): string | undefined {
|
|||
else if (effectiveCodepoint === CODEPOINTS.space) keyName = "space";
|
||||
else if (effectiveCodepoint === CODEPOINTS.backspace) keyName = "backspace";
|
||||
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = "delete";
|
||||
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert) keyName = "insert";
|
||||
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home) keyName = "home";
|
||||
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end) keyName = "end";
|
||||
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp) keyName = "pageUp";
|
||||
|
|
@ -780,21 +1052,34 @@ export function parseKey(data: string): string | undefined {
|
|||
if (data === "\x1b\r" || data === "\n") return "shift+enter";
|
||||
}
|
||||
|
||||
const legacySequenceKeyId = LEGACY_SEQUENCE_KEY_IDS[data];
|
||||
if (legacySequenceKeyId) return legacySequenceKeyId;
|
||||
|
||||
// Legacy sequences (used when Kitty protocol is not active, or for unambiguous sequences)
|
||||
if (data === "\x1b") return "escape";
|
||||
if (data === "\t") return "tab";
|
||||
if (data === "\r" || data === "\x1bOM") return "enter";
|
||||
if (data === "\r" || (!_kittyProtocolActive && data === "\n") || data === "\x1bOM") return "enter";
|
||||
if (data === "\x00") return "ctrl+space";
|
||||
if (data === " ") return "space";
|
||||
if (data === "\x7f" || data === "\x08") return "backspace";
|
||||
if (data === "\x1b[Z") return "shift+tab";
|
||||
if (!_kittyProtocolActive && data === "\x1b\r") return "alt+enter";
|
||||
if (data === "\x1b\x7f") return "alt+backspace";
|
||||
if (!_kittyProtocolActive && data === "\x1b ") return "alt+space";
|
||||
if (!_kittyProtocolActive && (data === "\x1b\x7f" || data === "\x1b\b")) return "alt+backspace";
|
||||
if (!_kittyProtocolActive && data === "\x1bB") return "alt+left";
|
||||
if (!_kittyProtocolActive && data === "\x1bF") return "alt+right";
|
||||
if (!_kittyProtocolActive && data.length === 2 && data[0] === "\x1b") {
|
||||
const code = data.charCodeAt(1);
|
||||
if (code >= 1 && code <= 26) {
|
||||
return `ctrl+alt+${String.fromCharCode(code + 96)}`;
|
||||
}
|
||||
}
|
||||
if (data === "\x1b[A") return "up";
|
||||
if (data === "\x1b[B") return "down";
|
||||
if (data === "\x1b[C") return "right";
|
||||
if (data === "\x1b[D") return "left";
|
||||
if (data === "\x1b[H") return "home";
|
||||
if (data === "\x1b[F") return "end";
|
||||
if (data === "\x1b[H" || data === "\x1bOH") return "home";
|
||||
if (data === "\x1b[F" || data === "\x1bOF") return "end";
|
||||
if (data === "\x1b[3~") return "delete";
|
||||
if (data === "\x1b[5~") return "pageUp";
|
||||
if (data === "\x1b[6~") return "pageDown";
|
||||
|
|
|
|||
|
|
@ -117,12 +117,87 @@ describe("matchesKey", () => {
|
|||
assert.strictEqual(matchesKey("\x1b", "escape"), true);
|
||||
});
|
||||
|
||||
it("should match legacy linefeed as enter", () => {
|
||||
setKittyProtocolActive(false);
|
||||
assert.strictEqual(matchesKey("\n", "enter"), true);
|
||||
assert.strictEqual(parseKey("\n"), "enter");
|
||||
});
|
||||
|
||||
it("should treat linefeed as shift+enter when kitty active", () => {
|
||||
setKittyProtocolActive(true);
|
||||
assert.strictEqual(matchesKey("\n", "shift+enter"), true);
|
||||
assert.strictEqual(matchesKey("\n", "enter"), false);
|
||||
assert.strictEqual(parseKey("\n"), "shift+enter");
|
||||
setKittyProtocolActive(false);
|
||||
});
|
||||
|
||||
it("should parse ctrl+space", () => {
|
||||
setKittyProtocolActive(false);
|
||||
assert.strictEqual(matchesKey("\x00", "ctrl+space"), true);
|
||||
assert.strictEqual(parseKey("\x00"), "ctrl+space");
|
||||
});
|
||||
|
||||
it("should parse legacy alt-prefixed sequences only when kitty inactive", () => {
|
||||
setKittyProtocolActive(false);
|
||||
assert.strictEqual(matchesKey("\x1b ", "alt+space"), true);
|
||||
assert.strictEqual(parseKey("\x1b "), "alt+space");
|
||||
assert.strictEqual(matchesKey("\x1b\b", "alt+backspace"), true);
|
||||
assert.strictEqual(parseKey("\x1b\b"), "alt+backspace");
|
||||
assert.strictEqual(matchesKey("\x1b\x03", "ctrl+alt+c"), true);
|
||||
assert.strictEqual(parseKey("\x1b\x03"), "ctrl+alt+c");
|
||||
assert.strictEqual(matchesKey("\x1bB", "alt+left"), true);
|
||||
assert.strictEqual(parseKey("\x1bB"), "alt+left");
|
||||
assert.strictEqual(matchesKey("\x1bF", "alt+right"), true);
|
||||
assert.strictEqual(parseKey("\x1bF"), "alt+right");
|
||||
|
||||
setKittyProtocolActive(true);
|
||||
assert.strictEqual(matchesKey("\x1b ", "alt+space"), false);
|
||||
assert.strictEqual(parseKey("\x1b "), undefined);
|
||||
assert.strictEqual(matchesKey("\x1b\b", "alt+backspace"), false);
|
||||
assert.strictEqual(parseKey("\x1b\b"), undefined);
|
||||
assert.strictEqual(matchesKey("\x1b\x03", "ctrl+alt+c"), false);
|
||||
assert.strictEqual(parseKey("\x1b\x03"), undefined);
|
||||
assert.strictEqual(matchesKey("\x1bB", "alt+left"), false);
|
||||
assert.strictEqual(parseKey("\x1bB"), undefined);
|
||||
assert.strictEqual(matchesKey("\x1bF", "alt+right"), false);
|
||||
assert.strictEqual(parseKey("\x1bF"), undefined);
|
||||
setKittyProtocolActive(false);
|
||||
});
|
||||
|
||||
it("should match arrow keys", () => {
|
||||
assert.strictEqual(matchesKey("\x1b[A", "up"), true);
|
||||
assert.strictEqual(matchesKey("\x1b[B", "down"), true);
|
||||
assert.strictEqual(matchesKey("\x1b[C", "right"), true);
|
||||
assert.strictEqual(matchesKey("\x1b[D", "left"), true);
|
||||
});
|
||||
|
||||
it("should match SS3 arrows and home/end", () => {
|
||||
assert.strictEqual(matchesKey("\x1bOA", "up"), true);
|
||||
assert.strictEqual(matchesKey("\x1bOB", "down"), true);
|
||||
assert.strictEqual(matchesKey("\x1bOC", "right"), true);
|
||||
assert.strictEqual(matchesKey("\x1bOD", "left"), true);
|
||||
assert.strictEqual(matchesKey("\x1bOH", "home"), true);
|
||||
assert.strictEqual(matchesKey("\x1bOF", "end"), true);
|
||||
});
|
||||
|
||||
it("should match legacy function keys and clear", () => {
|
||||
assert.strictEqual(matchesKey("\x1bOP", "f1"), true);
|
||||
assert.strictEqual(matchesKey("\x1b[24~", "f12"), true);
|
||||
assert.strictEqual(matchesKey("\x1b[E", "clear"), true);
|
||||
});
|
||||
|
||||
it("should match alt+arrows", () => {
|
||||
assert.strictEqual(matchesKey("\x1bp", "alt+up"), true);
|
||||
assert.strictEqual(matchesKey("\x1bp", "up"), false);
|
||||
});
|
||||
|
||||
it("should match rxvt modifier sequences", () => {
|
||||
assert.strictEqual(matchesKey("\x1b[a", "shift+up"), true);
|
||||
assert.strictEqual(matchesKey("\x1bOa", "ctrl+up"), true);
|
||||
assert.strictEqual(matchesKey("\x1b[2$", "shift+insert"), true);
|
||||
assert.strictEqual(matchesKey("\x1b[2^", "ctrl+insert"), true);
|
||||
assert.strictEqual(matchesKey("\x1b[7$", "shift+home"), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -155,6 +230,8 @@ describe("parseKey", () => {
|
|||
assert.strictEqual(parseKey("\x1b"), "escape");
|
||||
assert.strictEqual(parseKey("\t"), "tab");
|
||||
assert.strictEqual(parseKey("\r"), "enter");
|
||||
assert.strictEqual(parseKey("\n"), "enter");
|
||||
assert.strictEqual(parseKey("\x00"), "ctrl+space");
|
||||
assert.strictEqual(parseKey(" "), "space");
|
||||
});
|
||||
|
||||
|
|
@ -164,5 +241,26 @@ describe("parseKey", () => {
|
|||
assert.strictEqual(parseKey("\x1b[C"), "right");
|
||||
assert.strictEqual(parseKey("\x1b[D"), "left");
|
||||
});
|
||||
|
||||
it("should parse SS3 arrows and home/end", () => {
|
||||
assert.strictEqual(parseKey("\x1bOA"), "up");
|
||||
assert.strictEqual(parseKey("\x1bOB"), "down");
|
||||
assert.strictEqual(parseKey("\x1bOC"), "right");
|
||||
assert.strictEqual(parseKey("\x1bOD"), "left");
|
||||
assert.strictEqual(parseKey("\x1bOH"), "home");
|
||||
assert.strictEqual(parseKey("\x1bOF"), "end");
|
||||
});
|
||||
|
||||
it("should parse legacy function and modifier sequences", () => {
|
||||
assert.strictEqual(parseKey("\x1bOP"), "f1");
|
||||
assert.strictEqual(parseKey("\x1b[24~"), "f12");
|
||||
assert.strictEqual(parseKey("\x1b[E"), "clear");
|
||||
assert.strictEqual(parseKey("\x1b[2^"), "ctrl+insert");
|
||||
assert.strictEqual(parseKey("\x1bp"), "alt+up");
|
||||
});
|
||||
|
||||
it("should parse double bracket pageUp", () => {
|
||||
assert.strictEqual(parseKey("\x1b[[5~"), "pageUp");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue