{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Keyboard input handling for terminal applications.\n *\n * Supports both legacy terminal sequences and Kitty keyboard protocol.\n * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n * Reference: https://github.com/sst/opentui/blob/7da92b4088aebfe27b9f691c04163a48821e49fd/packages/core/src/lib/parse.keypress.ts\n *\n * Symbol keys are also supported, however some ctrl+symbol combos\n * overlap with ASCII codes, e.g. ctrl+[ = ESC.\n * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-ctrl-mapping-of-ascii-keys\n * Those can still be * used for ctrl+shift combos\n *\n * API:\n * - matchesKey(data, keyId) - Check if input matches a key identifier\n * - parseKey(data) - Parse input and return the key identifier\n * - Key - Helper object for creating typed key identifiers\n * - setKittyProtocolActive(active) - Set global Kitty protocol state\n * - isKittyProtocolActive() - Query global Kitty protocol state\n */\n\n// =============================================================================\n// Global Kitty Protocol State\n// =============================================================================\n\nlet _kittyProtocolActive = false;\n\n/**\n * Set the global Kitty keyboard protocol state.\n * Called by ProcessTerminal after detecting protocol support.\n */\nexport function setKittyProtocolActive(active: boolean): void {\n\t_kittyProtocolActive = active;\n}\n\n/**\n * Query whether Kitty keyboard protocol is currently active.\n */\nexport function isKittyProtocolActive(): boolean {\n\treturn _kittyProtocolActive;\n}\n\n// =============================================================================\n// Type-Safe Key Identifiers\n// =============================================================================\n\ntype Letter =\n\t| \"a\"\n\t| \"b\"\n\t| \"c\"\n\t| \"d\"\n\t| \"e\"\n\t| \"f\"\n\t| \"g\"\n\t| \"h\"\n\t| \"i\"\n\t| \"j\"\n\t| \"k\"\n\t| \"l\"\n\t| \"m\"\n\t| \"n\"\n\t| \"o\"\n\t| \"p\"\n\t| \"q\"\n\t| \"r\"\n\t| \"s\"\n\t| \"t\"\n\t| \"u\"\n\t| \"v\"\n\t| \"w\"\n\t| \"x\"\n\t| \"y\"\n\t| \"z\";\n\ntype Digit = \"0\" | \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"7\" | \"8\" | \"9\";\n\ntype SymbolKey =\n\t| \"`\"\n\t| \"-\"\n\t| \"=\"\n\t| \"[\"\n\t| \"]\"\n\t| \"\\\\\"\n\t| \";\"\n\t| \"'\"\n\t| \",\"\n\t| \".\"\n\t| \"/\"\n\t| \"!\"\n\t| \"@\"\n\t| \"#\"\n\t| \"$\"\n\t| \"%\"\n\t| \"^\"\n\t| \"&\"\n\t| \"*\"\n\t| \"(\"\n\t| \")\"\n\t| \"_\"\n\t| \"+\"\n\t| \"|\"\n\t| \"~\"\n\t| \"{\"\n\t| \"}\"\n\t| \":\"\n\t| \"<\"\n\t| \">\"\n\t| \"?\";\n\ntype SpecialKey =\n\t| \"escape\"\n\t| \"esc\"\n\t| \"enter\"\n\t| \"return\"\n\t| \"tab\"\n\t| \"space\"\n\t| \"backspace\"\n\t| \"delete\"\n\t| \"insert\"\n\t| \"clear\"\n\t| \"home\"\n\t| \"end\"\n\t| \"pageUp\"\n\t| \"pageDown\"\n\t| \"up\"\n\t| \"down\"\n\t| \"left\"\n\t| \"right\"\n\t| \"f1\"\n\t| \"f2\"\n\t| \"f3\"\n\t| \"f4\"\n\t| \"f5\"\n\t| \"f6\"\n\t| \"f7\"\n\t| \"f8\"\n\t| \"f9\"\n\t| \"f10\"\n\t| \"f11\"\n\t| \"f12\";\n\ntype BaseKey = Letter | Digit | SymbolKey | SpecialKey;\n\n/**\n * Union type of all valid key identifiers.\n * Provides autocomplete and catches typos at compile time.\n */\nexport type KeyId =\n\t| BaseKey\n\t| `ctrl+${BaseKey}`\n\t| `shift+${BaseKey}`\n\t| `alt+${BaseKey}`\n\t| `ctrl+shift+${BaseKey}`\n\t| `shift+ctrl+${BaseKey}`\n\t| `ctrl+alt+${BaseKey}`\n\t| `alt+ctrl+${BaseKey}`\n\t| `shift+alt+${BaseKey}`\n\t| `alt+shift+${BaseKey}`\n\t| `ctrl+shift+alt+${BaseKey}`\n\t| `ctrl+alt+shift+${BaseKey}`\n\t| `shift+ctrl+alt+${BaseKey}`\n\t| `shift+alt+ctrl+${BaseKey}`\n\t| `alt+ctrl+shift+${BaseKey}`\n\t| `alt+shift+ctrl+${BaseKey}`;\n\n/**\n * Helper object for creating typed key identifiers with autocomplete.\n *\n * Usage:\n * - Key.escape, Key.enter, Key.tab, etc. for special keys\n * - Key.backtick, Key.comma, Key.period, etc. for symbol keys\n * - Key.ctrl(\"c\"), Key.alt(\"x\") for single modifier\n * - Key.ctrlShift(\"p\"), Key.ctrlAlt(\"x\") for combined modifiers\n */\nexport const Key = {\n\t// Special keys\n\tescape: \"escape\" as const,\n\tesc: \"esc\" as const,\n\tenter: \"enter\" as const,\n\treturn: \"return\" as const,\n\ttab: \"tab\" as const,\n\tspace: \"space\" as const,\n\tbackspace: \"backspace\" as const,\n\tdelete: \"delete\" as const,\n\tinsert: \"insert\" as const,\n\tclear: \"clear\" as const,\n\thome: \"home\" as const,\n\tend: \"end\" as const,\n\tpageUp: \"pageUp\" as const,\n\tpageDown: \"pageDown\" as const,\n\tup: \"up\" as const,\n\tdown: \"down\" as const,\n\tleft: \"left\" as const,\n\tright: \"right\" as const,\n\tf1: \"f1\" as const,\n\tf2: \"f2\" as const,\n\tf3: \"f3\" as const,\n\tf4: \"f4\" as const,\n\tf5: \"f5\" as const,\n\tf6: \"f6\" as const,\n\tf7: \"f7\" as const,\n\tf8: \"f8\" as const,\n\tf9: \"f9\" as const,\n\tf10: \"f10\" as const,\n\tf11: \"f11\" as const,\n\tf12: \"f12\" as const,\n\n\t// Symbol keys\n\tbacktick: \"`\" as const,\n\thyphen: \"-\" as const,\n\tequals: \"=\" as const,\n\tleftbracket: \"[\" as const,\n\trightbracket: \"]\" as const,\n\tbackslash: \"\\\\\" as const,\n\tsemicolon: \";\" as const,\n\tquote: \"'\" as const,\n\tcomma: \",\" as const,\n\tperiod: \".\" as const,\n\tslash: \"/\" as const,\n\texclamation: \"!\" as const,\n\tat: \"@\" as const,\n\thash: \"#\" as const,\n\tdollar: \"$\" as const,\n\tpercent: \"%\" as const,\n\tcaret: \"^\" as const,\n\tampersand: \"&\" as const,\n\tasterisk: \"*\" as const,\n\tleftparen: \"(\" as const,\n\trightparen: \")\" as const,\n\tunderscore: \"_\" as const,\n\tplus: \"+\" as const,\n\tpipe: \"|\" as const,\n\ttilde: \"~\" as const,\n\tleftbrace: \"{\" as const,\n\trightbrace: \"}\" as const,\n\tcolon: \":\" as const,\n\tlessthan: \"<\" as const,\n\tgreaterthan: \">\" as const,\n\tquestion: \"?\" as const,\n\n\t// Single modifiers\n\tctrl: <K extends BaseKey>(key: K): `ctrl+${K}` => `ctrl+${key}`,\n\tshift: <K extends BaseKey>(key: K): `shift+${K}` => `shift+${key}`,\n\talt: <K extends BaseKey>(key: K): `alt+${K}` => `alt+${key}`,\n\n\t// Combined modifiers\n\tctrlShift: <K extends BaseKey>(key: K): `ctrl+shift+${K}` => `ctrl+shift+${key}`,\n\tshiftCtrl: <K extends BaseKey>(key: K): `shift+ctrl+${K}` => `shift+ctrl+${key}`,\n\tctrlAlt: <K extends BaseKey>(key: K): `ctrl+alt+${K}` => `ctrl+alt+${key}`,\n\taltCtrl: <K extends BaseKey>(key: K): `alt+ctrl+${K}` => `alt+ctrl+${key}`,\n\tshiftAlt: <K extends BaseKey>(key: K): `shift+alt+${K}` => `shift+alt+${key}`,\n\taltShift: <K extends BaseKey>(key: K): `alt+shift+${K}` => `alt+shift+${key}`,\n\n\t// Triple modifiers\n\tctrlShiftAlt: <K extends BaseKey>(key: K): `ctrl+shift+alt+${K}` => `ctrl+shift+alt+${key}`,\n} as const;\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst SYMBOL_KEYS = new Set([\n\t\"`\",\n\t\"-\",\n\t\"=\",\n\t\"[\",\n\t\"]\",\n\t\"\\\\\",\n\t\";\",\n\t\"'\",\n\t\",\",\n\t\".\",\n\t\"/\",\n\t\"!\",\n\t\"@\",\n\t\"#\",\n\t\"$\",\n\t\"%\",\n\t\"^\",\n\t\"&\",\n\t\"*\",\n\t\"(\",\n\t\")\",\n\t\"_\",\n\t\"+\",\n\t\"|\",\n\t\"~\",\n\t\"{\",\n\t\"}\",\n\t\":\",\n\t\"<\",\n\t\">\",\n\t\"?\",\n]);\n\nconst MODIFIERS = {\n\tshift: 1,\n\talt: 2,\n\tctrl: 4,\n} as const;\n\nconst LOCK_MASK = 64 + 128; // Caps Lock + Num Lock\n\nconst CODEPOINTS = {\n\tescape: 27,\n\ttab: 9,\n\tenter: 13,\n\tspace: 32,\n\tbackspace: 127,\n\tkpEnter: 57414, // Numpad Enter (Kitty protocol)\n} as const;\n\nconst ARROW_CODEPOINTS = {\n\tup: -1,\n\tdown: -2,\n\tright: -3,\n\tleft: -4,\n} as const;\n\nconst FUNCTIONAL_CODEPOINTS = {\n\tdelete: -10,\n\tinsert: -11,\n\tpageUp: -12,\n\tpageDown: -13,\n\thome: -14,\n\tend: -15,\n} as const;\n\nconst KITTY_FUNCTIONAL_KEY_EQUIVALENTS = new Map<number, number>([\n\t[57399, 48], // KP_0 -> 0\n\t[57400, 49], // KP_1 -> 1\n\t[57401, 50], // KP_2 -> 2\n\t[57402, 51], // KP_3 -> 3\n\t[57403, 52], // KP_4 -> 4\n\t[57404, 53], // KP_5 -> 5\n\t[57405, 54], // KP_6 -> 6\n\t[57406, 55], // KP_7 -> 7\n\t[57407, 56], // KP_8 -> 8\n\t[57408, 57], // KP_9 -> 9\n\t[57409, 46], // KP_DECIMAL -> .\n\t[57410, 47], // KP_DIVIDE -> /\n\t[57411, 42], // KP_MULTIPLY -> *\n\t[57412, 45], // KP_SUBTRACT -> -\n\t[57413, 43], // KP_ADD -> +\n\t[57415, 61], // KP_EQUAL -> =\n\t[57416, 44], // KP_SEPARATOR -> ,\n\t[57417, ARROW_CODEPOINTS.left],\n\t[57418, ARROW_CODEPOINTS.right],\n\t[57419, ARROW_CODEPOINTS.up],\n\t[57420, ARROW_CODEPOINTS.down],\n\t[57421, FUNCTIONAL_CODEPOINTS.pageUp],\n\t[57422, FUNCTIONAL_CODEPOINTS.pageDown],\n\t[57423, FUNCTIONAL_CODEPOINTS.home],\n\t[57424, FUNCTIONAL_CODEPOINTS.end],\n\t[57425, FUNCTIONAL_CODEPOINTS.insert],\n\t[57426, FUNCTIONAL_CODEPOINTS.delete],\n]);\n\nfunction normalizeKittyFunctionalCodepoint(codepoint: number): number {\n\treturn KITTY_FUNCTIONAL_KEY_EQUIVALENTS.get(codepoint) ?? codepoint;\n}\n\nconst LEGACY_KEY_SEQUENCES = {\n\tup: [\"\\x1b[A\", \"\\x1bOA\"],\n\tdown: [\"\\x1b[B\", \"\\x1bOB\"],\n\tright: [\"\\x1b[C\", \"\\x1bOC\"],\n\tleft: [\"\\x1b[D\", \"\\x1bOD\"],\n\thome: [\"\\x1b[H\", \"\\x1bOH\", \"\\x1b[1~\", \"\\x1b[7~\"],\n\tend: [\"\\x1b[F\", \"\\x1bOF\", \"\\x1b[4~\", \"\\x1b[8~\"],\n\tinsert: [\"\\x1b[2~\"],\n\tdelete: [\"\\x1b[3~\"],\n\tpageUp: [\"\\x1b[5~\", \"\\x1b[[5~\"],\n\tpageDown: [\"\\x1b[6~\", \"\\x1b[[6~\"],\n\tclear: [\"\\x1b[E\", \"\\x1bOE\"],\n\tf1: [\"\\x1bOP\", \"\\x1b[11~\", \"\\x1b[[A\"],\n\tf2: [\"\\x1bOQ\", \"\\x1b[12~\", \"\\x1b[[B\"],\n\tf3: [\"\\x1bOR\", \"\\x1b[13~\", \"\\x1b[[C\"],\n\tf4: [\"\\x1bOS\", \"\\x1b[14~\", \"\\x1b[[D\"],\n\tf5: [\"\\x1b[15~\", \"\\x1b[[E\"],\n\tf6: [\"\\x1b[17~\"],\n\tf7: [\"\\x1b[18~\"],\n\tf8: [\"\\x1b[19~\"],\n\tf9: [\"\\x1b[20~\"],\n\tf10: [\"\\x1b[21~\"],\n\tf11: [\"\\x1b[23~\"],\n\tf12: [\"\\x1b[24~\"],\n} as const;\n\nconst LEGACY_SHIFT_SEQUENCES = {\n\tup: [\"\\x1b[a\"],\n\tdown: [\"\\x1b[b\"],\n\tright: [\"\\x1b[c\"],\n\tleft: [\"\\x1b[d\"],\n\tclear: [\"\\x1b[e\"],\n\tinsert: [\"\\x1b[2$\"],\n\tdelete: [\"\\x1b[3$\"],\n\tpageUp: [\"\\x1b[5$\"],\n\tpageDown: [\"\\x1b[6$\"],\n\thome: [\"\\x1b[7$\"],\n\tend: [\"\\x1b[8$\"],\n} as const;\n\nconst LEGACY_CTRL_SEQUENCES = {\n\tup: [\"\\x1bOa\"],\n\tdown: [\"\\x1bOb\"],\n\tright: [\"\\x1bOc\"],\n\tleft: [\"\\x1bOd\"],\n\tclear: [\"\\x1bOe\"],\n\tinsert: [\"\\x1b[2^\"],\n\tdelete: [\"\\x1b[3^\"],\n\tpageUp: [\"\\x1b[5^\"],\n\tpageDown: [\"\\x1b[6^\"],\n\thome: [\"\\x1b[7^\"],\n\tend: [\"\\x1b[8^\"],\n} as const;\n\nconst LEGACY_SEQUENCE_KEY_IDS: Record<string, KeyId> = {\n\t\"\\x1bOA\": \"up\",\n\t\"\\x1bOB\": \"down\",\n\t\"\\x1bOC\": \"right\",\n\t\"\\x1bOD\": \"left\",\n\t\"\\x1bOH\": \"home\",\n\t\"\\x1bOF\": \"end\",\n\t\"\\x1b[E\": \"clear\",\n\t\"\\x1bOE\": \"clear\",\n\t\"\\x1bOe\": \"ctrl+clear\",\n\t\"\\x1b[e\": \"shift+clear\",\n\t\"\\x1b[2~\": \"insert\",\n\t\"\\x1b[2$\": \"shift+insert\",\n\t\"\\x1b[2^\": \"ctrl+insert\",\n\t\"\\x1b[3$\": \"shift+delete\",\n\t\"\\x1b[3^\": \"ctrl+delete\",\n\t\"\\x1b[[5~\": \"pageUp\",\n\t\"\\x1b[[6~\": \"pageDown\",\n\t\"\\x1b[a\": \"shift+up\",\n\t\"\\x1b[b\": \"shift+down\",\n\t\"\\x1b[c\": \"shift+right\",\n\t\"\\x1b[d\": \"shift+left\",\n\t\"\\x1bOa\": \"ctrl+up\",\n\t\"\\x1bOb\": \"ctrl+down\",\n\t\"\\x1bOc\": \"ctrl+right\",\n\t\"\\x1bOd\": \"ctrl+left\",\n\t\"\\x1b[5$\": \"shift+pageUp\",\n\t\"\\x1b[6$\": \"shift+pageDown\",\n\t\"\\x1b[7$\": \"shift+home\",\n\t\"\\x1b[8$\": \"shift+end\",\n\t\"\\x1b[5^\": \"ctrl+pageUp\",\n\t\"\\x1b[6^\": \"ctrl+pageDown\",\n\t\"\\x1b[7^\": \"ctrl+home\",\n\t\"\\x1b[8^\": \"ctrl+end\",\n\t\"\\x1bOP\": \"f1\",\n\t\"\\x1bOQ\": \"f2\",\n\t\"\\x1bOR\": \"f3\",\n\t\"\\x1bOS\": \"f4\",\n\t\"\\x1b[11~\": \"f1\",\n\t\"\\x1b[12~\": \"f2\",\n\t\"\\x1b[13~\": \"f3\",\n\t\"\\x1b[14~\": \"f4\",\n\t\"\\x1b[[A\": \"f1\",\n\t\"\\x1b[[B\": \"f2\",\n\t\"\\x1b[[C\": \"f3\",\n\t\"\\x1b[[D\": \"f4\",\n\t\"\\x1b[[E\": \"f5\",\n\t\"\\x1b[15~\": \"f5\",\n\t\"\\x1b[17~\": \"f6\",\n\t\"\\x1b[18~\": \"f7\",\n\t\"\\x1b[19~\": \"f8\",\n\t\"\\x1b[20~\": \"f9\",\n\t\"\\x1b[21~\": \"f10\",\n\t\"\\x1b[23~\": \"f11\",\n\t\"\\x1b[24~\": \"f12\",\n\t\"\\x1bb\": \"alt+left\",\n\t\"\\x1bf\": \"alt+right\",\n\t\"\\x1bp\": \"alt+up\",\n\t\"\\x1bn\": \"alt+down\",\n} as const;\n\ntype LegacyModifierKey = keyof typeof LEGACY_SHIFT_SEQUENCES;\n\nconst matchesLegacySequence = (data: string, sequences: readonly string[]): boolean => sequences.includes(data);\n\nconst matchesLegacyModifierSequence = (data: string, key: LegacyModifierKey, modifier: number): boolean => {\n\tif (modifier === MODIFIERS.shift) {\n\t\treturn matchesLegacySequence(data, LEGACY_SHIFT_SEQUENCES[key]);\n\t}\n\tif (modifier === MODIFIERS.ctrl) {\n\t\treturn matchesLegacySequence(data, LEGACY_CTRL_SEQUENCES[key]);\n\t}\n\treturn false;\n};\n\n// =============================================================================\n// Kitty Protocol Parsing\n// =============================================================================\n\n/**\n * Event types from Kitty keyboard protocol (flag 2)\n * 1 = key press, 2 = key repeat, 3 = key release\n */\nexport type KeyEventType = \"press\" | \"repeat\" | \"release\";\n\ninterface ParsedKittySequence {\n\tcodepoint: number;\n\tshiftedKey?: number; // Shifted version of the key (when shift is pressed)\n\tbaseLayoutKey?: number; // Key in standard PC-101 layout (for non-Latin layouts)\n\tmodifier: number;\n\teventType: KeyEventType;\n}\n\ninterface ParsedModifyOtherKeysSequence {\n\tcodepoint: number;\n\tmodifier: number;\n}\n\n// Store the last parsed event type for isKeyRelease() to query\nlet _lastEventType: KeyEventType = \"press\";\n\n/**\n * Check if the last parsed key event was a key release.\n * Only meaningful when Kitty keyboard protocol with flag 2 is active.\n */\nexport function isKeyRelease(data: string): boolean {\n\t// Don't treat bracketed paste content as key release, even if it contains\n\t// patterns like \":3F\" (e.g., bluetooth MAC addresses like \"90:62:3F:A5\").\n\t// Terminal.ts re-wraps paste content with bracketed paste markers before\n\t// passing to TUI, so pasted data will always contain \\x1b[200~.\n\tif (data.includes(\"\\x1b[200~\")) {\n\t\treturn false;\n\t}\n\n\t// Quick check: release events with flag 2 contain \":3\"\n\t// Format: \\x1b[<codepoint>;<modifier>:3u\n\tif (\n\t\tdata.includes(\":3u\") ||\n\t\tdata.includes(\":3~\") ||\n\t\tdata.includes(\":3A\") ||\n\t\tdata.includes(\":3B\") ||\n\t\tdata.includes(\":3C\") ||\n\t\tdata.includes(\":3D\") ||\n\t\tdata.includes(\":3H\") ||\n\t\tdata.includes(\":3F\")\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Check if the last parsed key event was a key repeat.\n * Only meaningful when Kitty keyboard protocol with flag 2 is active.\n */\nexport function isKeyRepeat(data: string): boolean {\n\t// Don't treat bracketed paste content as key repeat, even if it contains\n\t// patterns like \":2F\". See isKeyRelease() for details.\n\tif (data.includes(\"\\x1b[200~\")) {\n\t\treturn false;\n\t}\n\n\tif (\n\t\tdata.includes(\":2u\") ||\n\t\tdata.includes(\":2~\") ||\n\t\tdata.includes(\":2A\") ||\n\t\tdata.includes(\":2B\") ||\n\t\tdata.includes(\":2C\") ||\n\t\tdata.includes(\":2D\") ||\n\t\tdata.includes(\":2H\") ||\n\t\tdata.includes(\":2F\")\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nfunction parseEventType(eventTypeStr: string | undefined): KeyEventType {\n\tif (!eventTypeStr) return \"press\";\n\tconst eventType = parseInt(eventTypeStr, 10);\n\tif (eventType === 2) return \"repeat\";\n\tif (eventType === 3) return \"release\";\n\treturn \"press\";\n}\n\nfunction parseKittySequence(data: string): ParsedKittySequence | null {\n\t// CSI u format with alternate keys (flag 4):\n\t// \\x1b[<codepoint>u\n\t// \\x1b[<codepoint>;<mod>u\n\t// \\x1b[<codepoint>;<mod>:<event>u\n\t// \\x1b[<codepoint>:<shifted>;<mod>u\n\t// \\x1b[<codepoint>:<shifted>:<base>;<mod>u\n\t// \\x1b[<codepoint>::<base>;<mod>u (no shifted key, only base)\n\t//\n\t// With flag 2, event type is appended after modifier colon: 1=press, 2=repeat, 3=release\n\t// With flag 4, alternate keys are appended after codepoint with colons\n\tconst csiUMatch = data.match(/^\\x1b\\[(\\d+)(?::(\\d*))?(?::(\\d+))?(?:;(\\d+))?(?::(\\d+))?u$/);\n\tif (csiUMatch) {\n\t\tconst codepoint = parseInt(csiUMatch[1]!, 10);\n\t\tconst shiftedKey = csiUMatch[2] && csiUMatch[2].length > 0 ? parseInt(csiUMatch[2], 10) : undefined;\n\t\tconst baseLayoutKey = csiUMatch[3] ? parseInt(csiUMatch[3], 10) : undefined;\n\t\tconst modValue = csiUMatch[4] ? parseInt(csiUMatch[4], 10) : 1;\n\t\tconst eventType = parseEventType(csiUMatch[5]);\n\t\t_lastEventType = eventType;\n\t\treturn { codepoint, shiftedKey, baseLayoutKey, modifier: modValue - 1, eventType };\n\t}\n\n\t// Arrow keys with modifier: \\x1b[1;<mod>A/B/C/D or \\x1b[1;<mod>:<event>A/B/C/D\n\tconst arrowMatch = data.match(/^\\x1b\\[1;(\\d+)(?::(\\d+))?([ABCD])$/);\n\tif (arrowMatch) {\n\t\tconst modValue = parseInt(arrowMatch[1]!, 10);\n\t\tconst eventType = parseEventType(arrowMatch[2]);\n\t\tconst arrowCodes: Record<string, number> = { A: -1, B: -2, C: -3, D: -4 };\n\t\t_lastEventType = eventType;\n\t\treturn { codepoint: arrowCodes[arrowMatch[3]!]!, modifier: modValue - 1, eventType };\n\t}\n\n\t// Functional keys: \\x1b[<num>~ or \\x1b[<num>;<mod>~ or \\x1b[<num>;<mod>:<event>~\n\tconst funcMatch = data.match(/^\\x1b\\[(\\d+)(?:;(\\d+))?(?::(\\d+))?~$/);\n\tif (funcMatch) {\n\t\tconst keyNum = parseInt(funcMatch[1]!, 10);\n\t\tconst modValue = funcMatch[2] ? parseInt(funcMatch[2], 10) : 1;\n\t\tconst eventType = parseEventType(funcMatch[3]);\n\t\tconst funcCodes: Record<number, number> = {\n\t\t\t2: FUNCTIONAL_CODEPOINTS.insert,\n\t\t\t3: FUNCTIONAL_CODEPOINTS.delete,\n\t\t\t5: FUNCTIONAL_CODEPOINTS.pageUp,\n\t\t\t6: FUNCTIONAL_CODEPOINTS.pageDown,\n\t\t\t7: FUNCTIONAL_CODEPOINTS.home,\n\t\t\t8: FUNCTIONAL_CODEPOINTS.end,\n\t\t};\n\t\tconst codepoint = funcCodes[keyNum];\n\t\tif (codepoint !== undefined) {\n\t\t\t_lastEventType = eventType;\n\t\t\treturn { codepoint, modifier: modValue - 1, eventType };\n\t\t}\n\t}\n\n\t// Home/End with modifier: \\x1b[1;<mod>H/F or \\x1b[1;<mod>:<event>H/F\n\tconst homeEndMatch = data.match(/^\\x1b\\[1;(\\d+)(?::(\\d+))?([HF])$/);\n\tif (homeEndMatch) {\n\t\tconst modValue = parseInt(homeEndMatch[1]!, 10);\n\t\tconst eventType = parseEventType(homeEndMatch[2]);\n\t\tconst codepoint = homeEndMatch[3] === \"H\" ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end;\n\t\t_lastEventType = eventType;\n\t\treturn { codepoint, modifier: modValue - 1, eventType };\n\t}\n\n\treturn null;\n}\n\nfunction matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {\n\tconst parsed = parseKittySequence(data);\n\tif (!parsed) return false;\n\tconst actualMod = parsed.modifier & ~LOCK_MASK;\n\tconst expectedMod = expectedModifier & ~LOCK_MASK;\n\n\t// Check if modifiers match\n\tif (actualMod !== expectedMod) return false;\n\n\tconst normalizedCodepoint = normalizeKittyFunctionalCodepoint(parsed.codepoint);\n\tconst normalizedExpectedCodepoint = normalizeKittyFunctionalCodepoint(expectedCodepoint);\n\n\t// Primary match: codepoint matches directly after normalizing functional keys\n\tif (normalizedCodepoint === normalizedExpectedCodepoint) return true;\n\n\t// Alternate match: use base layout key for non-Latin keyboard layouts.\n\t// This allows Ctrl+С (Cyrillic) to match Ctrl+c (Latin) when terminal reports\n\t// the base layout key (the key in standard PC-101 layout).\n\t//\n\t// Only fall back to base layout key when the codepoint is NOT already a\n\t// recognized Latin letter (a-z) or symbol (e.g., /, -, [, ;, etc.).\n\t// When the codepoint is a recognized key, it is authoritative regardless\n\t// of physical key position. This prevents remapped layouts (Dvorak, Colemak,\n\t// xremap, etc.) from causing false matches: both letters and symbols move\n\t// to different physical positions, so Ctrl+K could falsely match Ctrl+V\n\t// (letter remapping) and Ctrl+/ could falsely match Ctrl+[ (symbol remapping)\n\t// if the base layout key were always considered.\n\tif (parsed.baseLayoutKey !== undefined && parsed.baseLayoutKey === expectedCodepoint) {\n\t\tconst cp = normalizedCodepoint;\n\t\tconst isLatinLetter = cp >= 97 && cp <= 122; // a-z\n\t\tconst isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(cp));\n\t\tif (!isLatinLetter && !isKnownSymbol) return true;\n\t}\n\n\treturn false;\n}\n\nfunction parseModifyOtherKeysSequence(data: string): ParsedModifyOtherKeysSequence | null {\n\tconst match = data.match(/^\\x1b\\[27;(\\d+);(\\d+)~$/);\n\tif (!match) return null;\n\tconst modValue = parseInt(match[1]!, 10);\n\tconst codepoint = parseInt(match[2]!, 10);\n\treturn { codepoint, modifier: modValue - 1 };\n}\n\n/**\n * Match xterm modifyOtherKeys format: CSI 27 ; modifiers ; keycode ~\n * This is used by terminals when Kitty protocol is not enabled.\n * Modifier values are 1-indexed: 2=shift, 3=alt, 5=ctrl, etc.\n */\nfunction matchesModifyOtherKeys(data: string, expectedKeycode: number, expectedModifier: number): boolean {\n\tconst parsed = parseModifyOtherKeysSequence(data);\n\tif (!parsed) return false;\n\treturn parsed.codepoint === expectedKeycode && parsed.modifier === expectedModifier;\n}\n\nfunction isWindowsTerminalSession(): boolean {\n\treturn (\n\t\tBoolean(process.env.WT_SESSION) && !process.env.SSH_CONNECTION && !process.env.SSH_CLIENT && !process.env.SSH_TTY\n\t);\n}\n\n/**\n * Raw 0x08 (BS) is ambiguous in legacy terminals.\n *\n * - Windows Terminal uses it for Ctrl+Backspace.\n * - Some legacy terminals and tmux setups send it for plain Backspace.\n *\n * Prefer explicit Kitty / CSI-u / modifyOtherKeys sequences whenever they are\n * available. Fall back to a Windows Terminal heuristic only for raw BS bytes.\n */\nfunction matchesRawBackspace(data: string, expectedModifier: number): boolean {\n\tif (data === \"\\x7f\") return expectedModifier === 0;\n\tif (data !== \"\\x08\") return false;\n\treturn isWindowsTerminalSession() ? expectedModifier === MODIFIERS.ctrl : expectedModifier === 0;\n}\n\n// =============================================================================\n// Generic Key Matching\n// =============================================================================\n\n/**\n * Get the control character for a key.\n * Uses the universal formula: code & 0x1f (mask to lower 5 bits)\n *\n * Works for:\n * - Letters a-z → 1-26\n * - Symbols [\\]_ → 27, 28, 29, 31\n * - Also maps - to same as _ (same physical key on US keyboards)\n */\nfunction rawCtrlChar(key: string): string | null {\n\tconst char = key.toLowerCase();\n\tconst code = char.charCodeAt(0);\n\tif ((code >= 97 && code <= 122) || char === \"[\" || char === \"\\\\\" || char === \"]\" || char === \"_\") {\n\t\treturn String.fromCharCode(code & 0x1f);\n\t}\n\t// Handle - as _ (same physical key on US keyboards)\n\tif (char === \"-\") {\n\t\treturn String.fromCharCode(31); // Same as Ctrl+_\n\t}\n\treturn null;\n}\n\nfunction isDigitKey(key: string): boolean {\n\treturn key >= \"0\" && key <= \"9\";\n}\n\nfunction matchesPrintableModifyOtherKeys(data: string, expectedKeycode: number, expectedModifier: number): boolean {\n\tif (expectedModifier === 0) return false;\n\treturn matchesModifyOtherKeys(data, expectedKeycode, expectedModifier);\n}\n\nfunction formatKeyNameWithModifiers(keyName: string, modifier: number): string | undefined {\n\tconst mods: string[] = [];\n\tconst effectiveMod = modifier & ~LOCK_MASK;\n\tconst supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt;\n\tif ((effectiveMod & ~supportedModifierMask) !== 0) return undefined;\n\tif (effectiveMod & MODIFIERS.shift) mods.push(\"shift\");\n\tif (effectiveMod & MODIFIERS.ctrl) mods.push(\"ctrl\");\n\tif (effectiveMod & MODIFIERS.alt) mods.push(\"alt\");\n\treturn mods.length > 0 ? `${mods.join(\"+\")}+${keyName}` : keyName;\n}\n\nfunction parseKeyId(keyId: string): { key: string; ctrl: boolean; shift: boolean; alt: boolean } | null {\n\tconst parts = keyId.toLowerCase().split(\"+\");\n\tconst key = parts[parts.length - 1];\n\tif (!key) return null;\n\treturn {\n\t\tkey,\n\t\tctrl: parts.includes(\"ctrl\"),\n\t\tshift: parts.includes(\"shift\"),\n\t\talt: parts.includes(\"alt\"),\n\t};\n}\n\n/**\n * Match input data against a key identifier string.\n *\n * Supported key identifiers:\n * - Single keys: \"escape\", \"tab\", \"enter\", \"backspace\", \"delete\", \"home\", \"end\", \"space\"\n * - Arrow keys: \"up\", \"down\", \"left\", \"right\"\n * - Ctrl combinations: \"ctrl+c\", \"ctrl+z\", etc.\n * - Shift combinations: \"shift+tab\", \"shift+enter\"\n * - Alt combinations: \"alt+enter\", \"alt+backspace\"\n * - Combined modifiers: \"shift+ctrl+p\", \"ctrl+alt+x\"\n *\n * Use the Key helper for autocomplete: Key.ctrl(\"c\"), Key.escape, Key.ctrlShift(\"p\")\n *\n * @param data - Raw input data from terminal\n * @param keyId - Key identifier (e.g., \"ctrl+c\", \"escape\", Key.ctrl(\"c\"))\n */\nexport function matchesKey(data: string, keyId: KeyId): boolean {\n\tconst parsed = parseKeyId(keyId);\n\tif (!parsed) return false;\n\n\tconst { key, ctrl, shift, alt } = parsed;\n\tlet modifier = 0;\n\tif (shift) modifier |= MODIFIERS.shift;\n\tif (alt) modifier |= MODIFIERS.alt;\n\tif (ctrl) modifier |= MODIFIERS.ctrl;\n\n\tswitch (key) {\n\t\tcase \"escape\":\n\t\tcase \"esc\":\n\t\t\tif (modifier !== 0) return false;\n\t\t\treturn (\n\t\t\t\tdata === \"\\x1b\" ||\n\t\t\t\tmatchesKittySequence(data, CODEPOINTS.escape, 0) ||\n\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.escape, 0)\n\t\t\t);\n\n\t\tcase \"space\":\n\t\t\tif (!_kittyProtocolActive) {\n\t\t\t\tif (ctrl && !alt && !shift && data === \"\\x00\") {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (alt && !ctrl && !shift && data === \"\\x1b \") {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tdata === \" \" ||\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.space, 0) ||\n\t\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.space, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn (\n\t\t\t\tmatchesKittySequence(data, CODEPOINTS.space, modifier) ||\n\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.space, modifier)\n\t\t\t);\n\n\t\tcase \"tab\":\n\t\t\tif (shift && !ctrl && !alt) {\n\t\t\t\treturn (\n\t\t\t\t\tdata === \"\\x1b[Z\" ||\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift) ||\n\t\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.tab, MODIFIERS.shift)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn data === \"\\t\" || matchesKittySequence(data, CODEPOINTS.tab, 0);\n\t\t\t}\n\t\t\treturn (\n\t\t\t\tmatchesKittySequence(data, CODEPOINTS.tab, modifier) ||\n\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.tab, modifier)\n\t\t\t);\n\n\t\tcase \"enter\":\n\t\tcase \"return\":\n\t\t\tif (shift && !ctrl && !alt) {\n\t\t\t\t// CSI u sequences (standard Kitty protocol)\n\t\t\t\tif (\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) ||\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.shift)\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\t// xterm modifyOtherKeys format (fallback when Kitty protocol not enabled)\n\t\t\t\tif (matchesModifyOtherKeys(data, CODEPOINTS.enter, MODIFIERS.shift)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\t// When Kitty protocol is active, legacy sequences are custom terminal mappings\n\t\t\t\t// \\x1b\\r = Kitty's \"map shift+enter send_text all \\e\\r\"\n\t\t\t\t// \\n = Ghostty's \"keybind = shift+enter=text:\\n\"\n\t\t\t\tif (_kittyProtocolActive) {\n\t\t\t\t\treturn data === \"\\x1b\\r\" || data === \"\\n\";\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (alt && !ctrl && !shift) {\n\t\t\t\t// CSI u sequences (standard Kitty protocol)\n\t\t\t\tif (\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) ||\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.alt)\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\t// xterm modifyOtherKeys format (fallback when Kitty protocol not enabled)\n\t\t\t\tif (matchesModifyOtherKeys(data, CODEPOINTS.enter, MODIFIERS.alt)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\t// \\x1b\\r is alt+enter only in legacy mode (no Kitty protocol)\n\t\t\t\t// When Kitty protocol is active, alt+enter comes as CSI u sequence\n\t\t\t\tif (!_kittyProtocolActive) {\n\t\t\t\t\treturn data === \"\\x1b\\r\";\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tdata === \"\\r\" ||\n\t\t\t\t\t(!_kittyProtocolActive && data === \"\\n\") ||\n\t\t\t\t\tdata === \"\\x1bOM\" || // SS3 M (numpad enter in some terminals)\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.enter, 0) ||\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.kpEnter, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn (\n\t\t\t\tmatchesKittySequence(data, CODEPOINTS.enter, modifier) ||\n\t\t\t\tmatchesKittySequence(data, CODEPOINTS.kpEnter, modifier) ||\n\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.enter, modifier)\n\t\t\t);\n\n\t\tcase \"backspace\":\n\t\t\tif (alt && !ctrl && !shift) {\n\t\t\t\tif (data === \"\\x1b\\x7f\" || data === \"\\x1b\\b\") {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn (\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt) ||\n\t\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.backspace, MODIFIERS.alt)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (ctrl && !alt && !shift) {\n\t\t\t\t// Legacy raw 0x08 is ambiguous: it can be Ctrl+Backspace on Windows\n\t\t\t\t// Terminal or plain Backspace on other terminals, while also\n\t\t\t\t// overlapping with Ctrl+H.\n\t\t\t\tif (matchesRawBackspace(data, MODIFIERS.ctrl)) return true;\n\t\t\t\treturn (\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.ctrl) ||\n\t\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.backspace, MODIFIERS.ctrl)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesRawBackspace(data, 0) ||\n\t\t\t\t\tmatchesKittySequence(data, CODEPOINTS.backspace, 0) ||\n\t\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.backspace, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn (\n\t\t\t\tmatchesKittySequence(data, CODEPOINTS.backspace, modifier) ||\n\t\t\t\tmatchesModifyOtherKeys(data, CODEPOINTS.backspace, modifier)\n\t\t\t);\n\n\t\tcase \"insert\":\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.insert) ||\n\t\t\t\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"insert\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, modifier);\n\n\t\tcase \"delete\":\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.delete) ||\n\t\t\t\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"delete\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, modifier);\n\n\t\tcase \"clear\":\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.clear);\n\t\t\t}\n\t\t\treturn matchesLegacyModifierSequence(data, \"clear\", modifier);\n\n\t\tcase \"home\":\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.home) ||\n\t\t\t\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"home\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, modifier);\n\n\t\tcase \"end\":\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.end) ||\n\t\t\t\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"end\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, modifier);\n\n\t\tcase \"pageup\":\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageUp) ||\n\t\t\t\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"pageUp\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, modifier);\n\n\t\tcase \"pagedown\":\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageDown) ||\n\t\t\t\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"pageDown\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, modifier);\n\n\t\tcase \"up\":\n\t\t\tif (alt && !ctrl && !shift) {\n\t\t\t\treturn data === \"\\x1bp\" || matchesKittySequence(data, ARROW_CODEPOINTS.up, MODIFIERS.alt);\n\t\t\t}\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.up) ||\n\t\t\t\t\tmatchesKittySequence(data, ARROW_CODEPOINTS.up, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"up\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);\n\n\t\tcase \"down\":\n\t\t\tif (alt && !ctrl && !shift) {\n\t\t\t\treturn data === \"\\x1bn\" || matchesKittySequence(data, ARROW_CODEPOINTS.down, MODIFIERS.alt);\n\t\t\t}\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.down) ||\n\t\t\t\t\tmatchesKittySequence(data, ARROW_CODEPOINTS.down, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"down\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);\n\n\t\tcase \"left\":\n\t\t\tif (alt && !ctrl && !shift) {\n\t\t\t\treturn (\n\t\t\t\t\tdata === \"\\x1b[1;3D\" ||\n\t\t\t\t\t(!_kittyProtocolActive && data === \"\\x1bB\") ||\n\t\t\t\t\tdata === \"\\x1bb\" ||\n\t\t\t\t\tmatchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (ctrl && !alt && !shift) {\n\t\t\t\treturn (\n\t\t\t\t\tdata === \"\\x1b[1;5D\" ||\n\t\t\t\t\tmatchesLegacyModifierSequence(data, \"left\", MODIFIERS.ctrl) ||\n\t\t\t\t\tmatchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.left) ||\n\t\t\t\t\tmatchesKittySequence(data, ARROW_CODEPOINTS.left, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"left\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);\n\n\t\tcase \"right\":\n\t\t\tif (alt && !ctrl && !shift) {\n\t\t\t\treturn (\n\t\t\t\t\tdata === \"\\x1b[1;3C\" ||\n\t\t\t\t\t(!_kittyProtocolActive && data === \"\\x1bF\") ||\n\t\t\t\t\tdata === \"\\x1bf\" ||\n\t\t\t\t\tmatchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (ctrl && !alt && !shift) {\n\t\t\t\treturn (\n\t\t\t\t\tdata === \"\\x1b[1;5C\" ||\n\t\t\t\t\tmatchesLegacyModifierSequence(data, \"right\", MODIFIERS.ctrl) ||\n\t\t\t\t\tmatchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (modifier === 0) {\n\t\t\t\treturn (\n\t\t\t\t\tmatchesLegacySequence(data, LEGACY_KEY_SEQUENCES.right) ||\n\t\t\t\t\tmatchesKittySequence(data, ARROW_CODEPOINTS.right, 0)\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (matchesLegacyModifierSequence(data, \"right\", modifier)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn matchesKittySequence(data, ARROW_CODEPOINTS.right, modifier);\n\n\t\tcase \"f1\":\n\t\tcase \"f2\":\n\t\tcase \"f3\":\n\t\tcase \"f4\":\n\t\tcase \"f5\":\n\t\tcase \"f6\":\n\t\tcase \"f7\":\n\t\tcase \"f8\":\n\t\tcase \"f9\":\n\t\tcase \"f10\":\n\t\tcase \"f11\":\n\t\tcase \"f12\": {\n\t\t\tif (modifier !== 0) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst functionKey = key as keyof typeof LEGACY_KEY_SEQUENCES;\n\t\t\treturn matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[functionKey]);\n\t\t}\n\t}\n\n\t// Handle single letter/digit keys and symbols\n\tif (key.length === 1 && ((key >= \"a\" && key <= \"z\") || isDigitKey(key) || SYMBOL_KEYS.has(key))) {\n\t\tconst codepoint = key.charCodeAt(0);\n\t\tconst rawCtrl = rawCtrlChar(key);\n\t\tconst isLetter = key >= \"a\" && key <= \"z\";\n\t\tconst isDigit = isDigitKey(key);\n\n\t\tif (ctrl && alt && !shift && !_kittyProtocolActive && rawCtrl) {\n\t\t\t// Legacy: ctrl+alt+key is ESC followed by the control character\n\t\t\treturn data === `\\x1b${rawCtrl}`;\n\t\t}\n\n\t\tif (alt && !ctrl && !shift && !_kittyProtocolActive && (isLetter || isDigit)) {\n\t\t\t// Legacy: alt+letter/digit is ESC followed by the key\n\t\t\tif (data === `\\x1b${key}`) return true;\n\t\t}\n\n\t\tif (ctrl && !shift && !alt) {\n\t\t\t// Legacy: ctrl+key sends the control character\n\t\t\tif (rawCtrl && data === rawCtrl) return true;\n\t\t\treturn (\n\t\t\t\tmatchesKittySequence(data, codepoint, MODIFIERS.ctrl) ||\n\t\t\t\tmatchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.ctrl)\n\t\t\t);\n\t\t}\n\n\t\tif (ctrl && shift && !alt) {\n\t\t\treturn (\n\t\t\t\tmatchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl) ||\n\t\t\t\tmatchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl)\n\t\t\t);\n\t\t}\n\n\t\tif (shift && !ctrl && !alt) {\n\t\t\t// Legacy: shift+letter produces uppercase\n\t\t\tif (isLetter && data === key.toUpperCase()) return true;\n\t\t\treturn (\n\t\t\t\tmatchesKittySequence(data, codepoint, MODIFIERS.shift) ||\n\t\t\t\tmatchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift)\n\t\t\t);\n\t\t}\n\n\t\tif (modifier !== 0) {\n\t\t\treturn (\n\t\t\t\tmatchesKittySequence(data, codepoint, modifier) ||\n\t\t\t\tmatchesPrintableModifyOtherKeys(data, codepoint, modifier)\n\t\t\t);\n\t\t}\n\n\t\t// Check both raw char and Kitty sequence (needed for release events)\n\t\treturn data === key || matchesKittySequence(data, codepoint, 0);\n\t}\n\n\treturn false;\n}\n\n/**\n * Parse input data and return the key identifier if recognized.\n *\n * @param data - Raw input data from terminal\n * @returns Key identifier string (e.g., \"ctrl+c\") or undefined\n */\nfunction formatParsedKey(codepoint: number, modifier: number, baseLayoutKey?: number): string | undefined {\n\tconst normalizedCodepoint = normalizeKittyFunctionalCodepoint(codepoint);\n\n\t// Use base layout key only when codepoint is not a recognized Latin\n\t// letter (a-z), digit (0-9), or symbol (/, -, [, ;, etc.). For those,\n\t// the codepoint is authoritative regardless of physical key position.\n\t// This prevents remapped layouts (Dvorak, Colemak, xremap, etc.) from\n\t// reporting the wrong key name based on the QWERTY physical position.\n\tconst isLatinLetter = normalizedCodepoint >= 97 && normalizedCodepoint <= 122; // a-z\n\tconst isDigit = normalizedCodepoint >= 48 && normalizedCodepoint <= 57; // 0-9\n\tconst isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(normalizedCodepoint));\n\tconst effectiveCodepoint =\n\t\tisLatinLetter || isDigit || isKnownSymbol ? normalizedCodepoint : (baseLayoutKey ?? normalizedCodepoint);\n\n\tlet keyName: string | undefined;\n\tif (effectiveCodepoint === CODEPOINTS.escape) keyName = \"escape\";\n\telse if (effectiveCodepoint === CODEPOINTS.tab) keyName = \"tab\";\n\telse if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter) keyName = \"enter\";\n\telse if (effectiveCodepoint === CODEPOINTS.space) keyName = \"space\";\n\telse if (effectiveCodepoint === CODEPOINTS.backspace) keyName = \"backspace\";\n\telse if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = \"delete\";\n\telse if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert) keyName = \"insert\";\n\telse if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home) keyName = \"home\";\n\telse if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end) keyName = \"end\";\n\telse if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp) keyName = \"pageUp\";\n\telse if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown) keyName = \"pageDown\";\n\telse if (effectiveCodepoint === ARROW_CODEPOINTS.up) keyName = \"up\";\n\telse if (effectiveCodepoint === ARROW_CODEPOINTS.down) keyName = \"down\";\n\telse if (effectiveCodepoint === ARROW_CODEPOINTS.left) keyName = \"left\";\n\telse if (effectiveCodepoint === ARROW_CODEPOINTS.right) keyName = \"right\";\n\telse if (effectiveCodepoint >= 48 && effectiveCodepoint <= 57) keyName = String.fromCharCode(effectiveCodepoint);\n\telse if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122) keyName = String.fromCharCode(effectiveCodepoint);\n\telse if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint))) keyName = String.fromCharCode(effectiveCodepoint);\n\n\tif (!keyName) return undefined;\n\treturn formatKeyNameWithModifiers(keyName, modifier);\n}\n\nexport function parseKey(data: string): string | undefined {\n\tconst kitty = parseKittySequence(data);\n\tif (kitty) {\n\t\treturn formatParsedKey(kitty.codepoint, kitty.modifier, kitty.baseLayoutKey);\n\t}\n\n\tconst modifyOtherKeys = parseModifyOtherKeysSequence(data);\n\tif (modifyOtherKeys) {\n\t\treturn formatParsedKey(modifyOtherKeys.codepoint, modifyOtherKeys.modifier);\n\t}\n\n\t// Mode-aware legacy sequences\n\t// When Kitty protocol is active, ambiguous sequences are interpreted as custom terminal mappings:\n\t// - \\x1b\\r = shift+enter (Kitty mapping), not alt+enter\n\t// - \\n = shift+enter (Ghostty mapping)\n\tif (_kittyProtocolActive) {\n\t\tif (data === \"\\x1b\\r\" || data === \"\\n\") return \"shift+enter\";\n\t}\n\n\tconst legacySequenceKeyId = LEGACY_SEQUENCE_KEY_IDS[data];\n\tif (legacySequenceKeyId) return legacySequenceKeyId;\n\n\t// Legacy sequences (used when Kitty protocol is not active, or for unambiguous sequences)\n\tif (data === \"\\x1b\") return \"escape\";\n\tif (data === \"\\x1c\") return \"ctrl+\\\\\";\n\tif (data === \"\\x1d\") return \"ctrl+]\";\n\tif (data === \"\\x1f\") return \"ctrl+-\";\n\tif (data === \"\\x1b\\x1b\") return \"ctrl+alt+[\";\n\tif (data === \"\\x1b\\x1c\") return \"ctrl+alt+\\\\\";\n\tif (data === \"\\x1b\\x1d\") return \"ctrl+alt+]\";\n\tif (data === \"\\x1b\\x1f\") return \"ctrl+alt+-\";\n\tif (data === \"\\t\") return \"tab\";\n\tif (data === \"\\r\" || (!_kittyProtocolActive && data === \"\\n\") || data === \"\\x1bOM\") return \"enter\";\n\tif (data === \"\\x00\") return \"ctrl+space\";\n\tif (data === \" \") return \"space\";\n\tif (data === \"\\x7f\") return \"backspace\";\n\tif (data === \"\\x08\") return isWindowsTerminalSession() ? \"ctrl+backspace\" : \"backspace\";\n\tif (data === \"\\x1b[Z\") return \"shift+tab\";\n\tif (!_kittyProtocolActive && data === \"\\x1b\\r\") return \"alt+enter\";\n\tif (!_kittyProtocolActive && data === \"\\x1b \") return \"alt+space\";\n\tif (data === \"\\x1b\\x7f\" || data === \"\\x1b\\b\") return \"alt+backspace\";\n\tif (!_kittyProtocolActive && data === \"\\x1bB\") return \"alt+left\";\n\tif (!_kittyProtocolActive && data === \"\\x1bF\") return \"alt+right\";\n\tif (!_kittyProtocolActive && data.length === 2 && data[0] === \"\\x1b\") {\n\t\tconst code = data.charCodeAt(1);\n\t\tif (code >= 1 && code <= 26) {\n\t\t\treturn `ctrl+alt+${String.fromCharCode(code + 96)}`;\n\t\t}\n\t\t// Legacy alt+letter/digit (ESC followed by the key)\n\t\tif ((code >= 97 && code <= 122) || (code >= 48 && code <= 57)) {\n\t\t\treturn `alt+${String.fromCharCode(code)}`;\n\t\t}\n\t}\n\tif (data === \"\\x1b[A\") return \"up\";\n\tif (data === \"\\x1b[B\") return \"down\";\n\tif (data === \"\\x1b[C\") return \"right\";\n\tif (data === \"\\x1b[D\") return \"left\";\n\tif (data === \"\\x1b[H\" || data === \"\\x1bOH\") return \"home\";\n\tif (data === \"\\x1b[F\" || data === \"\\x1bOF\") return \"end\";\n\tif (data === \"\\x1b[3~\") return \"delete\";\n\tif (data === \"\\x1b[5~\") return \"pageUp\";\n\tif (data === \"\\x1b[6~\") return \"pageDown\";\n\n\t// Raw Ctrl+letter\n\tif (data.length === 1) {\n\t\tconst code = data.charCodeAt(0);\n\t\tif (code >= 1 && code <= 26) {\n\t\t\treturn `ctrl+${String.fromCharCode(code + 96)}`;\n\t\t}\n\t\tif (code >= 32 && code <= 126) {\n\t\t\treturn data;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n// =============================================================================\n// Kitty CSI-u Printable Decoding\n// =============================================================================\n\nconst KITTY_CSI_U_REGEX = /^\\x1b\\[(\\d+)(?::(\\d*))?(?::(\\d+))?(?:;(\\d+))?(?::(\\d+))?u$/;\nconst KITTY_PRINTABLE_ALLOWED_MODIFIERS = MODIFIERS.shift | LOCK_MASK;\n\n/**\n * Decode a Kitty CSI-u sequence into a printable character, if applicable.\n *\n * When Kitty keyboard protocol flag 1 (disambiguate) is active, terminals send\n * CSI-u sequences for all keys, including plain printable characters. This\n * function extracts the printable character from such sequences.\n *\n * Only accepts plain or Shift-modified keys. Rejects Ctrl, Alt, and unsupported\n * modifier combinations (those are handled by keybinding matching instead).\n * Prefers the shifted keycode when Shift is held and a shifted key is reported.\n *\n * @param data - Raw input data from terminal\n * @returns The printable character, or undefined if not a printable CSI-u sequence\n */\nexport function decodeKittyPrintable(data: string): string | undefined {\n\tconst match = data.match(KITTY_CSI_U_REGEX);\n\tif (!match) return undefined;\n\n\t// CSI-u groups: <codepoint>[:<shifted>[:<base>]];<mod>[:<event>]u\n\tconst codepoint = Number.parseInt(match[1] ?? \"\", 10);\n\tif (!Number.isFinite(codepoint)) return undefined;\n\n\tconst shiftedKey = match[2] && match[2].length > 0 ? Number.parseInt(match[2], 10) : undefined;\n\tconst modValue = match[4] ? Number.parseInt(match[4], 10) : 1;\n\t// Modifiers are 1-indexed in CSI-u; normalize to our bitmask.\n\tconst modifier = Number.isFinite(modValue) ? modValue - 1 : 0;\n\n\t// Only accept printable CSI-u input for plain or Shift-modified text keys.\n\t// Reject unsupported modifier bits (e.g. Super/Meta) to avoid inserting\n\t// characters from modifier-only terminal events.\n\tif ((modifier & ~KITTY_PRINTABLE_ALLOWED_MODIFIERS) !== 0) return undefined;\n\tif (modifier & (MODIFIERS.alt | MODIFIERS.ctrl)) return undefined;\n\n\t// Prefer the shifted keycode when Shift is held.\n\tlet effectiveCodepoint = codepoint;\n\tif (modifier & MODIFIERS.shift && typeof shiftedKey === \"number\") {\n\t\teffectiveCodepoint = shiftedKey;\n\t}\n\teffectiveCodepoint = normalizeKittyFunctionalCodepoint(effectiveCodepoint);\n\t// Drop control characters or invalid codepoints.\n\tif (!Number.isFinite(effectiveCodepoint) || effectiveCodepoint < 32) return undefined;\n\n\ttry {\n\t\treturn String.fromCodePoint(effectiveCodepoint);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}