@mariozechner/pi-tui 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/keys.d.ts CHANGED
@@ -189,6 +189,12 @@ export declare function isEnter(data: string): boolean;
189
189
  * Handles both legacy (\x7f, \x08) and Kitty protocol.
190
190
  */
191
191
  export declare function isBackspace(data: string): boolean;
192
+ /**
193
+ * Check if input matches Shift+Backspace (Kitty protocol).
194
+ * Returns true so caller can treat it as regular backspace.
195
+ * Ignores lock key bits.
196
+ */
197
+ export declare function isShiftBackspace(data: string): boolean;
192
198
  /**
193
199
  * Check if input matches Shift+Enter.
194
200
  * Ignores lock key bits.
@@ -199,6 +205,12 @@ export declare function isShiftEnter(data: string): boolean;
199
205
  * Ignores lock key bits.
200
206
  */
201
207
  export declare function isAltEnter(data: string): boolean;
208
+ /**
209
+ * Check if input matches Shift+Space (Kitty protocol).
210
+ * Returns true so caller can insert a regular space.
211
+ * Ignores lock key bits.
212
+ */
213
+ export declare function isShiftSpace(data: string): boolean;
202
214
  /**
203
215
  * Check if input matches Option/Alt+Left (word navigation).
204
216
  * Handles multiple formats including Kitty protocol.
@@ -234,4 +246,10 @@ export declare function isEnd(data: string): boolean;
234
246
  * Handles legacy format and Kitty protocol with lock key modifiers.
235
247
  */
236
248
  export declare function isDelete(data: string): boolean;
249
+ /**
250
+ * Check if input matches Shift+Delete (Kitty protocol).
251
+ * Returns true so caller can treat it as regular delete.
252
+ * Ignores lock key bits.
253
+ */
254
+ export declare function isShiftDelete(data: string): boolean;
237
255
  //# sourceMappingURL=keys.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Kitty keyboard protocol key sequence helpers.\n *\n * The Kitty keyboard protocol sends enhanced escape sequences in the format:\n * \\x1b[<codepoint>;<modifier>u\n *\n * Modifier bits (before adding 1 for transmission):\n * - Shift: 1 (value 2)\n * - Alt: 2 (value 3)\n * - Ctrl: 4 (value 5)\n * - Super: 8 (value 9)\n * - Hyper: 16\n * - Meta: 32\n * - Caps_Lock: 64\n * - Num_Lock: 128\n *\n * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n *\n * NOTE: Some terminals (e.g., Ghostty on Linux) include lock key states\n * (Caps Lock, Num Lock) in the modifier field. We mask these out when\n * checking for key combinations since they shouldn't affect behavior.\n */\n\n// Common codepoints\nconst CODEPOINTS = {\n\t// Letters (lowercase ASCII)\n\ta: 97,\n\tc: 99,\n\td: 100,\n\te: 101,\n\tg: 103,\n\tk: 107,\n\tl: 108,\n\to: 111,\n\tp: 112,\n\tt: 116,\n\tu: 117,\n\tw: 119,\n\tz: 122,\n\n\t// Special keys\n\tescape: 27,\n\ttab: 9,\n\tenter: 13,\n\tbackspace: 127,\n} as const;\n\n// Lock key bits to ignore when matching (Caps Lock + Num Lock)\nconst LOCK_MASK = 64 + 128; // 192\n\n// Modifier bits (before adding 1)\nconst MODIFIERS = {\n\tshift: 1,\n\talt: 2,\n\tctrl: 4,\n\tsuper: 8,\n} as const;\n\n/**\n * Build a Kitty keyboard protocol sequence for a key with modifier.\n */\nfunction kittySequence(codepoint: number, modifier: number): string {\n\treturn `\\x1b[${codepoint};${modifier + 1}u`;\n}\n\n/**\n * Parsed Kitty keyboard protocol sequence.\n */\ninterface ParsedKittySequence {\n\tcodepoint: number;\n\tmodifier: number; // Actual modifier bits (after subtracting 1)\n}\n\n/**\n * Parse a Kitty keyboard protocol sequence.\n * Handles formats:\n * - \\x1b[<codepoint>u (no modifier)\n * - \\x1b[<codepoint>;<modifier>u (with modifier)\n * - \\x1b[1;<modifier>A/B/C/D (arrow keys with modifier)\n *\n * Returns null if not a valid Kitty sequence.\n */\n// Virtual codepoints for functional keys (negative to avoid conflicts)\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\nfunction parseKittySequence(data: string): ParsedKittySequence | null {\n\t// Match CSI u format: \\x1b[<num>u or \\x1b[<num>;<mod>u\n\tconst csiUMatch = data.match(/^\\x1b\\[(\\d+)(?:;(\\d+))?u$/);\n\tif (csiUMatch) {\n\t\tconst codepoint = parseInt(csiUMatch[1]!, 10);\n\t\tconst modValue = csiUMatch[2] ? parseInt(csiUMatch[2], 10) : 1;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\t// Match arrow keys with modifier: \\x1b[1;<mod>A/B/C/D\n\tconst arrowMatch = data.match(/^\\x1b\\[1;(\\d+)([ABCD])$/);\n\tif (arrowMatch) {\n\t\tconst modValue = parseInt(arrowMatch[1]!, 10);\n\t\t// Map arrow letters to virtual codepoints for easier matching\n\t\tconst arrowCodes: Record<string, number> = { A: -1, B: -2, C: -3, D: -4 };\n\t\tconst codepoint = arrowCodes[arrowMatch[2]!]!;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\t// Match functional keys with ~ terminator: \\x1b[<num>~ or \\x1b[<num>;<mod>~\n\t// DELETE=3, INSERT=2, PAGEUP=5, PAGEDOWN=6, etc.\n\tconst funcMatch = data.match(/^\\x1b\\[(\\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\t// Map functional key numbers to virtual codepoints\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, // Alternative home\n\t\t\t8: FUNCTIONAL_CODEPOINTS.end, // Alternative end\n\t\t};\n\t\tconst codepoint = funcCodes[keyNum];\n\t\tif (codepoint !== undefined) {\n\t\t\treturn { codepoint, modifier: modValue - 1 };\n\t\t}\n\t}\n\n\t// Match Home/End with modifier: \\x1b[1;<mod>H/F\n\tconst homeEndMatch = data.match(/^\\x1b\\[1;(\\d+)([HF])$/);\n\tif (homeEndMatch) {\n\t\tconst modValue = parseInt(homeEndMatch[1]!, 10);\n\t\tconst codepoint = homeEndMatch[2] === \"H\" ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\treturn null;\n}\n\n/**\n * Check if a Kitty sequence matches the expected codepoint and modifier,\n * ignoring lock key bits (Caps Lock, Num Lock).\n */\nfunction matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {\n\tconst parsed = parseKittySequence(data);\n\tif (!parsed) return false;\n\n\t// Mask out lock bits from both sides for comparison\n\tconst actualMod = parsed.modifier & ~LOCK_MASK;\n\tconst expectedMod = expectedModifier & ~LOCK_MASK;\n\n\treturn parsed.codepoint === expectedCodepoint && actualMod === expectedMod;\n}\n\n// Pre-built sequences for common key combinations\nexport const Keys = {\n\t// Ctrl+<letter> combinations\n\tCTRL_A: kittySequence(CODEPOINTS.a, MODIFIERS.ctrl),\n\tCTRL_C: kittySequence(CODEPOINTS.c, MODIFIERS.ctrl),\n\tCTRL_D: kittySequence(CODEPOINTS.d, MODIFIERS.ctrl),\n\tCTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl),\n\tCTRL_G: kittySequence(CODEPOINTS.g, MODIFIERS.ctrl),\n\tCTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl),\n\tCTRL_L: kittySequence(CODEPOINTS.l, MODIFIERS.ctrl),\n\tCTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl),\n\tCTRL_P: kittySequence(CODEPOINTS.p, MODIFIERS.ctrl),\n\tCTRL_T: kittySequence(CODEPOINTS.t, MODIFIERS.ctrl),\n\tCTRL_U: kittySequence(CODEPOINTS.u, MODIFIERS.ctrl),\n\tCTRL_W: kittySequence(CODEPOINTS.w, MODIFIERS.ctrl),\n\tCTRL_Z: kittySequence(CODEPOINTS.z, MODIFIERS.ctrl),\n\n\t// Enter combinations\n\tSHIFT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.shift),\n\tALT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.alt),\n\tCTRL_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.ctrl),\n\n\t// Tab combinations\n\tSHIFT_TAB: kittySequence(CODEPOINTS.tab, MODIFIERS.shift),\n\n\t// Backspace combinations\n\tALT_BACKSPACE: kittySequence(CODEPOINTS.backspace, MODIFIERS.alt),\n} as const;\n\n/**\n * Check if input matches a Kitty protocol Ctrl+<key> sequence.\n * Ignores lock key bits (Caps Lock, Num Lock).\n * @param data - The input data to check\n * @param key - Single lowercase letter (e.g., 'c' for Ctrl+C)\n */\nexport function isKittyCtrl(data: string, key: string): boolean {\n\tif (key.length !== 1) return false;\n\tconst codepoint = key.charCodeAt(0);\n\t// Check exact match first (fast path)\n\tif (data === kittySequence(codepoint, MODIFIERS.ctrl)) return true;\n\t// Check with lock bits masked out\n\treturn matchesKittySequence(data, codepoint, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches a Kitty protocol key sequence with specific modifier.\n * Ignores lock key bits (Caps Lock, Num Lock).\n * @param data - The input data to check\n * @param codepoint - ASCII codepoint of the key\n * @param modifier - Modifier value (use MODIFIERS constants)\n */\nexport function isKittyKey(data: string, codepoint: number, modifier: number): boolean {\n\t// Check exact match first (fast path)\n\tif (data === kittySequence(codepoint, modifier)) return true;\n\t// Check with lock bits masked out\n\treturn matchesKittySequence(data, codepoint, modifier);\n}\n\n// Raw control character codes\nconst RAW = {\n\tCTRL_A: \"\\x01\",\n\tCTRL_C: \"\\x03\",\n\tCTRL_D: \"\\x04\",\n\tCTRL_E: \"\\x05\",\n\tCTRL_G: \"\\x07\",\n\tCTRL_K: \"\\x0b\",\n\tCTRL_L: \"\\x0c\",\n\tCTRL_O: \"\\x0f\",\n\tCTRL_P: \"\\x10\",\n\tCTRL_T: \"\\x14\",\n\tCTRL_U: \"\\x15\",\n\tCTRL_W: \"\\x17\",\n\tCTRL_Z: \"\\x1a\",\n\tALT_BACKSPACE: \"\\x1b\\x7f\",\n\tSHIFT_TAB: \"\\x1b[Z\",\n} as const;\n\n/**\n * Check if input matches Ctrl+A (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlA(data: string): boolean {\n\treturn data === RAW.CTRL_A || data === Keys.CTRL_A || matchesKittySequence(data, CODEPOINTS.a, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+C (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlC(data: string): boolean {\n\treturn data === RAW.CTRL_C || data === Keys.CTRL_C || matchesKittySequence(data, CODEPOINTS.c, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+D (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlD(data: string): boolean {\n\treturn data === RAW.CTRL_D || data === Keys.CTRL_D || matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+E (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlE(data: string): boolean {\n\treturn data === RAW.CTRL_E || data === Keys.CTRL_E || matchesKittySequence(data, CODEPOINTS.e, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+G (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlG(data: string): boolean {\n\treturn data === RAW.CTRL_G || data === Keys.CTRL_G || matchesKittySequence(data, CODEPOINTS.g, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+K (raw byte or Kitty protocol).\n * Ignores lock key bits.\n * Also checks if first byte is 0x0b for compatibility with terminals\n * that may send trailing bytes.\n */\nexport function isCtrlK(data: string): boolean {\n\treturn (\n\t\tdata === RAW.CTRL_K ||\n\t\t(data.length > 0 && data.charCodeAt(0) === 0x0b) ||\n\t\tdata === Keys.CTRL_K ||\n\t\tmatchesKittySequence(data, CODEPOINTS.k, MODIFIERS.ctrl)\n\t);\n}\n\n/**\n * Check if input matches Ctrl+L (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlL(data: string): boolean {\n\treturn data === RAW.CTRL_L || data === Keys.CTRL_L || matchesKittySequence(data, CODEPOINTS.l, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+O (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlO(data: string): boolean {\n\treturn data === RAW.CTRL_O || data === Keys.CTRL_O || matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+O (Kitty protocol only).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlO(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+P (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlP(data: string): boolean {\n\treturn data === RAW.CTRL_P || data === Keys.CTRL_P || matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+P (Kitty protocol only).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlP(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+D (Kitty protocol only, for debug).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlD(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+T (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlT(data: string): boolean {\n\treturn data === RAW.CTRL_T || data === Keys.CTRL_T || matchesKittySequence(data, CODEPOINTS.t, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+U (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlU(data: string): boolean {\n\treturn data === RAW.CTRL_U || data === Keys.CTRL_U || matchesKittySequence(data, CODEPOINTS.u, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+W (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlW(data: string): boolean {\n\treturn data === RAW.CTRL_W || data === Keys.CTRL_W || matchesKittySequence(data, CODEPOINTS.w, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+Z (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlZ(data: string): boolean {\n\treturn data === RAW.CTRL_Z || data === Keys.CTRL_Z || matchesKittySequence(data, CODEPOINTS.z, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Alt+Backspace (legacy or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isAltBackspace(data: string): boolean {\n\treturn (\n\t\tdata === RAW.ALT_BACKSPACE ||\n\t\tdata === Keys.ALT_BACKSPACE ||\n\t\tmatchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt)\n\t);\n}\n\n/**\n * Check if input matches Shift+Tab (legacy or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isShiftTab(data: string): boolean {\n\treturn (\n\t\tdata === RAW.SHIFT_TAB || data === Keys.SHIFT_TAB || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift)\n\t);\n}\n\n/**\n * Check if input matches the Escape key (raw byte or Kitty protocol).\n * Raw: \\x1b (single byte)\n * Kitty: \\x1b[27u (codepoint 27 = escape)\n * Ignores lock key bits.\n */\nexport function isEscape(data: string): boolean {\n\treturn data === \"\\x1b\" || data === `\\x1b[${CODEPOINTS.escape}u` || matchesKittySequence(data, CODEPOINTS.escape, 0);\n}\n\n// Arrow key virtual codepoints (negative to avoid conflicts with real codepoints)\nconst ARROW_CODEPOINTS = {\n\tup: -1,\n\tdown: -2,\n\tright: -3,\n\tleft: -4,\n} as const;\n\n/**\n * Check if input matches Arrow Up key.\n * Handles both legacy (\\x1b[A) and Kitty protocol with modifiers.\n */\nexport function isArrowUp(data: string): boolean {\n\treturn data === \"\\x1b[A\" || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);\n}\n\n/**\n * Check if input matches Arrow Down key.\n * Handles both legacy (\\x1b[B) and Kitty protocol with modifiers.\n */\nexport function isArrowDown(data: string): boolean {\n\treturn data === \"\\x1b[B\" || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);\n}\n\n/**\n * Check if input matches Arrow Right key.\n * Handles both legacy (\\x1b[C) and Kitty protocol with modifiers.\n */\nexport function isArrowRight(data: string): boolean {\n\treturn data === \"\\x1b[C\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);\n}\n\n/**\n * Check if input matches Arrow Left key.\n * Handles both legacy (\\x1b[D) and Kitty protocol with modifiers.\n */\nexport function isArrowLeft(data: string): boolean {\n\treturn data === \"\\x1b[D\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);\n}\n\n/**\n * Check if input matches plain Tab key (no modifiers).\n * Handles both legacy (\\t) and Kitty protocol.\n */\nexport function isTab(data: string): boolean {\n\treturn data === \"\\t\" || matchesKittySequence(data, CODEPOINTS.tab, 0);\n}\n\n/**\n * Check if input matches plain Enter/Return key (no modifiers).\n * Handles both legacy (\\r) and Kitty protocol.\n */\nexport function isEnter(data: string): boolean {\n\treturn data === \"\\r\" || matchesKittySequence(data, CODEPOINTS.enter, 0);\n}\n\n/**\n * Check if input matches plain Backspace key (no modifiers).\n * Handles both legacy (\\x7f, \\x08) and Kitty protocol.\n */\nexport function isBackspace(data: string): boolean {\n\treturn data === \"\\x7f\" || data === \"\\x08\" || matchesKittySequence(data, CODEPOINTS.backspace, 0);\n}\n\n/**\n * Check if input matches Shift+Enter.\n * Ignores lock key bits.\n */\nexport function isShiftEnter(data: string): boolean {\n\treturn data === Keys.SHIFT_ENTER || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift);\n}\n\n/**\n * Check if input matches Alt+Enter.\n * Ignores lock key bits.\n */\nexport function isAltEnter(data: string): boolean {\n\treturn data === Keys.ALT_ENTER || data === \"\\x1b\\r\" || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Option/Alt+Left (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isAltLeft(data: string): boolean {\n\treturn data === \"\\x1b[1;3D\" || data === \"\\x1bb\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Option/Alt+Right (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isAltRight(data: string): boolean {\n\treturn data === \"\\x1b[1;3C\" || data === \"\\x1bf\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Ctrl+Left (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isCtrlLeft(data: string): boolean {\n\treturn data === \"\\x1b[1;5D\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+Right (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isCtrlRight(data: string): boolean {\n\treturn data === \"\\x1b[1;5C\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Home key.\n * Handles legacy formats and Kitty protocol with lock key modifiers.\n */\nexport function isHome(data: string): boolean {\n\treturn (\n\t\tdata === \"\\x1b[H\" ||\n\t\tdata === \"\\x1b[1~\" ||\n\t\tdata === \"\\x1b[7~\" ||\n\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)\n\t);\n}\n\n/**\n * Check if input matches End key.\n * Handles legacy formats and Kitty protocol with lock key modifiers.\n */\nexport function isEnd(data: string): boolean {\n\treturn (\n\t\tdata === \"\\x1b[F\" ||\n\t\tdata === \"\\x1b[4~\" ||\n\t\tdata === \"\\x1b[8~\" ||\n\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)\n\t);\n}\n\n/**\n * Check if input matches Delete key (forward delete).\n * Handles legacy format and Kitty protocol with lock key modifiers.\n */\nexport function isDelete(data: string): boolean {\n\treturn data === \"\\x1b[3~\" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);\n}\n"]}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Kitty keyboard protocol key sequence helpers.\n *\n * The Kitty keyboard protocol sends enhanced escape sequences in the format:\n * \\x1b[<codepoint>;<modifier>u\n *\n * Modifier bits (before adding 1 for transmission):\n * - Shift: 1 (value 2)\n * - Alt: 2 (value 3)\n * - Ctrl: 4 (value 5)\n * - Super: 8 (value 9)\n * - Hyper: 16\n * - Meta: 32\n * - Caps_Lock: 64\n * - Num_Lock: 128\n *\n * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n *\n * NOTE: Some terminals (e.g., Ghostty on Linux) include lock key states\n * (Caps Lock, Num Lock) in the modifier field. We mask these out when\n * checking for key combinations since they shouldn't affect behavior.\n */\n\n// Common codepoints\nconst CODEPOINTS = {\n\t// Letters (lowercase ASCII)\n\ta: 97,\n\tc: 99,\n\td: 100,\n\te: 101,\n\tg: 103,\n\tk: 107,\n\tl: 108,\n\to: 111,\n\tp: 112,\n\tt: 116,\n\tu: 117,\n\tw: 119,\n\tz: 122,\n\n\t// Special keys\n\tescape: 27,\n\ttab: 9,\n\tenter: 13,\n\tspace: 32,\n\tbackspace: 127,\n} as const;\n\n// Lock key bits to ignore when matching (Caps Lock + Num Lock)\nconst LOCK_MASK = 64 + 128; // 192\n\n// Modifier bits (before adding 1)\nconst MODIFIERS = {\n\tshift: 1,\n\talt: 2,\n\tctrl: 4,\n\tsuper: 8,\n} as const;\n\n/**\n * Build a Kitty keyboard protocol sequence for a key with modifier.\n */\nfunction kittySequence(codepoint: number, modifier: number): string {\n\treturn `\\x1b[${codepoint};${modifier + 1}u`;\n}\n\n/**\n * Parsed Kitty keyboard protocol sequence.\n */\ninterface ParsedKittySequence {\n\tcodepoint: number;\n\tmodifier: number; // Actual modifier bits (after subtracting 1)\n}\n\n/**\n * Parse a Kitty keyboard protocol sequence.\n * Handles formats:\n * - \\x1b[<codepoint>u (no modifier)\n * - \\x1b[<codepoint>;<modifier>u (with modifier)\n * - \\x1b[1;<modifier>A/B/C/D (arrow keys with modifier)\n *\n * Returns null if not a valid Kitty sequence.\n */\n// Virtual codepoints for functional keys (negative to avoid conflicts)\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\nfunction parseKittySequence(data: string): ParsedKittySequence | null {\n\t// Match CSI u format: \\x1b[<num>u or \\x1b[<num>;<mod>u\n\tconst csiUMatch = data.match(/^\\x1b\\[(\\d+)(?:;(\\d+))?u$/);\n\tif (csiUMatch) {\n\t\tconst codepoint = parseInt(csiUMatch[1]!, 10);\n\t\tconst modValue = csiUMatch[2] ? parseInt(csiUMatch[2], 10) : 1;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\t// Match arrow keys with modifier: \\x1b[1;<mod>A/B/C/D\n\tconst arrowMatch = data.match(/^\\x1b\\[1;(\\d+)([ABCD])$/);\n\tif (arrowMatch) {\n\t\tconst modValue = parseInt(arrowMatch[1]!, 10);\n\t\t// Map arrow letters to virtual codepoints for easier matching\n\t\tconst arrowCodes: Record<string, number> = { A: -1, B: -2, C: -3, D: -4 };\n\t\tconst codepoint = arrowCodes[arrowMatch[2]!]!;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\t// Match functional keys with ~ terminator: \\x1b[<num>~ or \\x1b[<num>;<mod>~\n\t// DELETE=3, INSERT=2, PAGEUP=5, PAGEDOWN=6, etc.\n\tconst funcMatch = data.match(/^\\x1b\\[(\\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\t// Map functional key numbers to virtual codepoints\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, // Alternative home\n\t\t\t8: FUNCTIONAL_CODEPOINTS.end, // Alternative end\n\t\t};\n\t\tconst codepoint = funcCodes[keyNum];\n\t\tif (codepoint !== undefined) {\n\t\t\treturn { codepoint, modifier: modValue - 1 };\n\t\t}\n\t}\n\n\t// Match Home/End with modifier: \\x1b[1;<mod>H/F\n\tconst homeEndMatch = data.match(/^\\x1b\\[1;(\\d+)([HF])$/);\n\tif (homeEndMatch) {\n\t\tconst modValue = parseInt(homeEndMatch[1]!, 10);\n\t\tconst codepoint = homeEndMatch[2] === \"H\" ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\treturn null;\n}\n\n/**\n * Check if a Kitty sequence matches the expected codepoint and modifier,\n * ignoring lock key bits (Caps Lock, Num Lock).\n */\nfunction matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {\n\tconst parsed = parseKittySequence(data);\n\tif (!parsed) return false;\n\n\t// Mask out lock bits from both sides for comparison\n\tconst actualMod = parsed.modifier & ~LOCK_MASK;\n\tconst expectedMod = expectedModifier & ~LOCK_MASK;\n\n\treturn parsed.codepoint === expectedCodepoint && actualMod === expectedMod;\n}\n\n// Pre-built sequences for common key combinations\nexport const Keys = {\n\t// Ctrl+<letter> combinations\n\tCTRL_A: kittySequence(CODEPOINTS.a, MODIFIERS.ctrl),\n\tCTRL_C: kittySequence(CODEPOINTS.c, MODIFIERS.ctrl),\n\tCTRL_D: kittySequence(CODEPOINTS.d, MODIFIERS.ctrl),\n\tCTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl),\n\tCTRL_G: kittySequence(CODEPOINTS.g, MODIFIERS.ctrl),\n\tCTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl),\n\tCTRL_L: kittySequence(CODEPOINTS.l, MODIFIERS.ctrl),\n\tCTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl),\n\tCTRL_P: kittySequence(CODEPOINTS.p, MODIFIERS.ctrl),\n\tCTRL_T: kittySequence(CODEPOINTS.t, MODIFIERS.ctrl),\n\tCTRL_U: kittySequence(CODEPOINTS.u, MODIFIERS.ctrl),\n\tCTRL_W: kittySequence(CODEPOINTS.w, MODIFIERS.ctrl),\n\tCTRL_Z: kittySequence(CODEPOINTS.z, MODIFIERS.ctrl),\n\n\t// Enter combinations\n\tSHIFT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.shift),\n\tALT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.alt),\n\tCTRL_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.ctrl),\n\n\t// Tab combinations\n\tSHIFT_TAB: kittySequence(CODEPOINTS.tab, MODIFIERS.shift),\n\n\t// Backspace combinations\n\tALT_BACKSPACE: kittySequence(CODEPOINTS.backspace, MODIFIERS.alt),\n} as const;\n\n/**\n * Check if input matches a Kitty protocol Ctrl+<key> sequence.\n * Ignores lock key bits (Caps Lock, Num Lock).\n * @param data - The input data to check\n * @param key - Single lowercase letter (e.g., 'c' for Ctrl+C)\n */\nexport function isKittyCtrl(data: string, key: string): boolean {\n\tif (key.length !== 1) return false;\n\tconst codepoint = key.charCodeAt(0);\n\t// Check exact match first (fast path)\n\tif (data === kittySequence(codepoint, MODIFIERS.ctrl)) return true;\n\t// Check with lock bits masked out\n\treturn matchesKittySequence(data, codepoint, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches a Kitty protocol key sequence with specific modifier.\n * Ignores lock key bits (Caps Lock, Num Lock).\n * @param data - The input data to check\n * @param codepoint - ASCII codepoint of the key\n * @param modifier - Modifier value (use MODIFIERS constants)\n */\nexport function isKittyKey(data: string, codepoint: number, modifier: number): boolean {\n\t// Check exact match first (fast path)\n\tif (data === kittySequence(codepoint, modifier)) return true;\n\t// Check with lock bits masked out\n\treturn matchesKittySequence(data, codepoint, modifier);\n}\n\n// Raw control character codes\nconst RAW = {\n\tCTRL_A: \"\\x01\",\n\tCTRL_C: \"\\x03\",\n\tCTRL_D: \"\\x04\",\n\tCTRL_E: \"\\x05\",\n\tCTRL_G: \"\\x07\",\n\tCTRL_K: \"\\x0b\",\n\tCTRL_L: \"\\x0c\",\n\tCTRL_O: \"\\x0f\",\n\tCTRL_P: \"\\x10\",\n\tCTRL_T: \"\\x14\",\n\tCTRL_U: \"\\x15\",\n\tCTRL_W: \"\\x17\",\n\tCTRL_Z: \"\\x1a\",\n\tALT_BACKSPACE: \"\\x1b\\x7f\",\n\tSHIFT_TAB: \"\\x1b[Z\",\n} as const;\n\n/**\n * Check if input matches Ctrl+A (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlA(data: string): boolean {\n\treturn data === RAW.CTRL_A || data === Keys.CTRL_A || matchesKittySequence(data, CODEPOINTS.a, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+C (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlC(data: string): boolean {\n\treturn data === RAW.CTRL_C || data === Keys.CTRL_C || matchesKittySequence(data, CODEPOINTS.c, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+D (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlD(data: string): boolean {\n\treturn data === RAW.CTRL_D || data === Keys.CTRL_D || matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+E (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlE(data: string): boolean {\n\treturn data === RAW.CTRL_E || data === Keys.CTRL_E || matchesKittySequence(data, CODEPOINTS.e, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+G (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlG(data: string): boolean {\n\treturn data === RAW.CTRL_G || data === Keys.CTRL_G || matchesKittySequence(data, CODEPOINTS.g, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+K (raw byte or Kitty protocol).\n * Ignores lock key bits.\n * Also checks if first byte is 0x0b for compatibility with terminals\n * that may send trailing bytes.\n */\nexport function isCtrlK(data: string): boolean {\n\treturn (\n\t\tdata === RAW.CTRL_K ||\n\t\t(data.length > 0 && data.charCodeAt(0) === 0x0b) ||\n\t\tdata === Keys.CTRL_K ||\n\t\tmatchesKittySequence(data, CODEPOINTS.k, MODIFIERS.ctrl)\n\t);\n}\n\n/**\n * Check if input matches Ctrl+L (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlL(data: string): boolean {\n\treturn data === RAW.CTRL_L || data === Keys.CTRL_L || matchesKittySequence(data, CODEPOINTS.l, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+O (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlO(data: string): boolean {\n\treturn data === RAW.CTRL_O || data === Keys.CTRL_O || matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+O (Kitty protocol only).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlO(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+P (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlP(data: string): boolean {\n\treturn data === RAW.CTRL_P || data === Keys.CTRL_P || matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+P (Kitty protocol only).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlP(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+D (Kitty protocol only, for debug).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlD(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+T (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlT(data: string): boolean {\n\treturn data === RAW.CTRL_T || data === Keys.CTRL_T || matchesKittySequence(data, CODEPOINTS.t, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+U (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlU(data: string): boolean {\n\treturn data === RAW.CTRL_U || data === Keys.CTRL_U || matchesKittySequence(data, CODEPOINTS.u, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+W (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlW(data: string): boolean {\n\treturn data === RAW.CTRL_W || data === Keys.CTRL_W || matchesKittySequence(data, CODEPOINTS.w, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+Z (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlZ(data: string): boolean {\n\treturn data === RAW.CTRL_Z || data === Keys.CTRL_Z || matchesKittySequence(data, CODEPOINTS.z, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Alt+Backspace (legacy or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isAltBackspace(data: string): boolean {\n\treturn (\n\t\tdata === RAW.ALT_BACKSPACE ||\n\t\tdata === Keys.ALT_BACKSPACE ||\n\t\tmatchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt)\n\t);\n}\n\n/**\n * Check if input matches Shift+Tab (legacy or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isShiftTab(data: string): boolean {\n\treturn (\n\t\tdata === RAW.SHIFT_TAB || data === Keys.SHIFT_TAB || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift)\n\t);\n}\n\n/**\n * Check if input matches the Escape key (raw byte or Kitty protocol).\n * Raw: \\x1b (single byte)\n * Kitty: \\x1b[27u (codepoint 27 = escape)\n * Ignores lock key bits.\n */\nexport function isEscape(data: string): boolean {\n\treturn data === \"\\x1b\" || data === `\\x1b[${CODEPOINTS.escape}u` || matchesKittySequence(data, CODEPOINTS.escape, 0);\n}\n\n// Arrow key virtual codepoints (negative to avoid conflicts with real codepoints)\nconst ARROW_CODEPOINTS = {\n\tup: -1,\n\tdown: -2,\n\tright: -3,\n\tleft: -4,\n} as const;\n\n/**\n * Check if input matches Arrow Up key.\n * Handles both legacy (\\x1b[A) and Kitty protocol with modifiers.\n */\nexport function isArrowUp(data: string): boolean {\n\treturn data === \"\\x1b[A\" || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);\n}\n\n/**\n * Check if input matches Arrow Down key.\n * Handles both legacy (\\x1b[B) and Kitty protocol with modifiers.\n */\nexport function isArrowDown(data: string): boolean {\n\treturn data === \"\\x1b[B\" || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);\n}\n\n/**\n * Check if input matches Arrow Right key.\n * Handles both legacy (\\x1b[C) and Kitty protocol with modifiers.\n */\nexport function isArrowRight(data: string): boolean {\n\treturn data === \"\\x1b[C\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);\n}\n\n/**\n * Check if input matches Arrow Left key.\n * Handles both legacy (\\x1b[D) and Kitty protocol with modifiers.\n */\nexport function isArrowLeft(data: string): boolean {\n\treturn data === \"\\x1b[D\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);\n}\n\n/**\n * Check if input matches plain Tab key (no modifiers).\n * Handles both legacy (\\t) and Kitty protocol.\n */\nexport function isTab(data: string): boolean {\n\treturn data === \"\\t\" || matchesKittySequence(data, CODEPOINTS.tab, 0);\n}\n\n/**\n * Check if input matches plain Enter/Return key (no modifiers).\n * Handles both legacy (\\r) and Kitty protocol.\n */\nexport function isEnter(data: string): boolean {\n\treturn data === \"\\r\" || matchesKittySequence(data, CODEPOINTS.enter, 0);\n}\n\n/**\n * Check if input matches plain Backspace key (no modifiers).\n * Handles both legacy (\\x7f, \\x08) and Kitty protocol.\n */\nexport function isBackspace(data: string): boolean {\n\treturn data === \"\\x7f\" || data === \"\\x08\" || matchesKittySequence(data, CODEPOINTS.backspace, 0);\n}\n\n/**\n * Check if input matches Shift+Backspace (Kitty protocol).\n * Returns true so caller can treat it as regular backspace.\n * Ignores lock key bits.\n */\nexport function isShiftBackspace(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.shift);\n}\n\n/**\n * Check if input matches Shift+Enter.\n * Ignores lock key bits.\n */\nexport function isShiftEnter(data: string): boolean {\n\treturn data === Keys.SHIFT_ENTER || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift);\n}\n\n/**\n * Check if input matches Alt+Enter.\n * Ignores lock key bits.\n */\nexport function isAltEnter(data: string): boolean {\n\treturn data === Keys.ALT_ENTER || data === \"\\x1b\\r\" || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Shift+Space (Kitty protocol).\n * Returns true so caller can insert a regular space.\n * Ignores lock key bits.\n */\nexport function isShiftSpace(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.space, MODIFIERS.shift);\n}\n\n/**\n * Check if input matches Option/Alt+Left (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isAltLeft(data: string): boolean {\n\treturn data === \"\\x1b[1;3D\" || data === \"\\x1bb\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Option/Alt+Right (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isAltRight(data: string): boolean {\n\treturn data === \"\\x1b[1;3C\" || data === \"\\x1bf\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Ctrl+Left (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isCtrlLeft(data: string): boolean {\n\treturn data === \"\\x1b[1;5D\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+Right (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isCtrlRight(data: string): boolean {\n\treturn data === \"\\x1b[1;5C\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Home key.\n * Handles legacy formats and Kitty protocol with lock key modifiers.\n */\nexport function isHome(data: string): boolean {\n\treturn (\n\t\tdata === \"\\x1b[H\" ||\n\t\tdata === \"\\x1b[1~\" ||\n\t\tdata === \"\\x1b[7~\" ||\n\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)\n\t);\n}\n\n/**\n * Check if input matches End key.\n * Handles legacy formats and Kitty protocol with lock key modifiers.\n */\nexport function isEnd(data: string): boolean {\n\treturn (\n\t\tdata === \"\\x1b[F\" ||\n\t\tdata === \"\\x1b[4~\" ||\n\t\tdata === \"\\x1b[8~\" ||\n\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)\n\t);\n}\n\n/**\n * Check if input matches Delete key (forward delete).\n * Handles legacy format and Kitty protocol with lock key modifiers.\n */\nexport function isDelete(data: string): boolean {\n\treturn data === \"\\x1b[3~\" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);\n}\n\n/**\n * Check if input matches Shift+Delete (Kitty protocol).\n * Returns true so caller can treat it as regular delete.\n * Ignores lock key bits.\n */\nexport function isShiftDelete(data: string): boolean {\n\treturn matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, MODIFIERS.shift);\n}\n"]}
package/dist/keys.js CHANGED
@@ -40,6 +40,7 @@ const CODEPOINTS = {
40
40
  escape: 27,
41
41
  tab: 9,
42
42
  enter: 13,
43
+ space: 32,
43
44
  backspace: 127,
44
45
  };
45
46
  // Lock key bits to ignore when matching (Caps Lock + Num Lock)
@@ -405,6 +406,14 @@ export function isEnter(data) {
405
406
  export function isBackspace(data) {
406
407
  return data === "\x7f" || data === "\x08" || matchesKittySequence(data, CODEPOINTS.backspace, 0);
407
408
  }
409
+ /**
410
+ * Check if input matches Shift+Backspace (Kitty protocol).
411
+ * Returns true so caller can treat it as regular backspace.
412
+ * Ignores lock key bits.
413
+ */
414
+ export function isShiftBackspace(data) {
415
+ return matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.shift);
416
+ }
408
417
  /**
409
418
  * Check if input matches Shift+Enter.
410
419
  * Ignores lock key bits.
@@ -419,6 +428,14 @@ export function isShiftEnter(data) {
419
428
  export function isAltEnter(data) {
420
429
  return data === Keys.ALT_ENTER || data === "\x1b\r" || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt);
421
430
  }
431
+ /**
432
+ * Check if input matches Shift+Space (Kitty protocol).
433
+ * Returns true so caller can insert a regular space.
434
+ * Ignores lock key bits.
435
+ */
436
+ export function isShiftSpace(data) {
437
+ return matchesKittySequence(data, CODEPOINTS.space, MODIFIERS.shift);
438
+ }
422
439
  /**
423
440
  * Check if input matches Option/Alt+Left (word navigation).
424
441
  * Handles multiple formats including Kitty protocol.
@@ -474,4 +491,12 @@ export function isEnd(data) {
474
491
  export function isDelete(data) {
475
492
  return data === "\x1b[3~" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);
476
493
  }
494
+ /**
495
+ * Check if input matches Shift+Delete (Kitty protocol).
496
+ * Returns true so caller can treat it as regular delete.
497
+ * Ignores lock key bits.
498
+ */
499
+ export function isShiftDelete(data) {
500
+ return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, MODIFIERS.shift);
501
+ }
477
502
  //# sourceMappingURL=keys.js.map
package/dist/keys.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Kitty keyboard protocol key sequence helpers.\n *\n * The Kitty keyboard protocol sends enhanced escape sequences in the format:\n * \\x1b[<codepoint>;<modifier>u\n *\n * Modifier bits (before adding 1 for transmission):\n * - Shift: 1 (value 2)\n * - Alt: 2 (value 3)\n * - Ctrl: 4 (value 5)\n * - Super: 8 (value 9)\n * - Hyper: 16\n * - Meta: 32\n * - Caps_Lock: 64\n * - Num_Lock: 128\n *\n * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n *\n * NOTE: Some terminals (e.g., Ghostty on Linux) include lock key states\n * (Caps Lock, Num Lock) in the modifier field. We mask these out when\n * checking for key combinations since they shouldn't affect behavior.\n */\n\n// Common codepoints\nconst CODEPOINTS = {\n\t// Letters (lowercase ASCII)\n\ta: 97,\n\tc: 99,\n\td: 100,\n\te: 101,\n\tg: 103,\n\tk: 107,\n\tl: 108,\n\to: 111,\n\tp: 112,\n\tt: 116,\n\tu: 117,\n\tw: 119,\n\tz: 122,\n\n\t// Special keys\n\tescape: 27,\n\ttab: 9,\n\tenter: 13,\n\tbackspace: 127,\n} as const;\n\n// Lock key bits to ignore when matching (Caps Lock + Num Lock)\nconst LOCK_MASK = 64 + 128; // 192\n\n// Modifier bits (before adding 1)\nconst MODIFIERS = {\n\tshift: 1,\n\talt: 2,\n\tctrl: 4,\n\tsuper: 8,\n} as const;\n\n/**\n * Build a Kitty keyboard protocol sequence for a key with modifier.\n */\nfunction kittySequence(codepoint: number, modifier: number): string {\n\treturn `\\x1b[${codepoint};${modifier + 1}u`;\n}\n\n/**\n * Parsed Kitty keyboard protocol sequence.\n */\ninterface ParsedKittySequence {\n\tcodepoint: number;\n\tmodifier: number; // Actual modifier bits (after subtracting 1)\n}\n\n/**\n * Parse a Kitty keyboard protocol sequence.\n * Handles formats:\n * - \\x1b[<codepoint>u (no modifier)\n * - \\x1b[<codepoint>;<modifier>u (with modifier)\n * - \\x1b[1;<modifier>A/B/C/D (arrow keys with modifier)\n *\n * Returns null if not a valid Kitty sequence.\n */\n// Virtual codepoints for functional keys (negative to avoid conflicts)\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\nfunction parseKittySequence(data: string): ParsedKittySequence | null {\n\t// Match CSI u format: \\x1b[<num>u or \\x1b[<num>;<mod>u\n\tconst csiUMatch = data.match(/^\\x1b\\[(\\d+)(?:;(\\d+))?u$/);\n\tif (csiUMatch) {\n\t\tconst codepoint = parseInt(csiUMatch[1]!, 10);\n\t\tconst modValue = csiUMatch[2] ? parseInt(csiUMatch[2], 10) : 1;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\t// Match arrow keys with modifier: \\x1b[1;<mod>A/B/C/D\n\tconst arrowMatch = data.match(/^\\x1b\\[1;(\\d+)([ABCD])$/);\n\tif (arrowMatch) {\n\t\tconst modValue = parseInt(arrowMatch[1]!, 10);\n\t\t// Map arrow letters to virtual codepoints for easier matching\n\t\tconst arrowCodes: Record<string, number> = { A: -1, B: -2, C: -3, D: -4 };\n\t\tconst codepoint = arrowCodes[arrowMatch[2]!]!;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\t// Match functional keys with ~ terminator: \\x1b[<num>~ or \\x1b[<num>;<mod>~\n\t// DELETE=3, INSERT=2, PAGEUP=5, PAGEDOWN=6, etc.\n\tconst funcMatch = data.match(/^\\x1b\\[(\\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\t// Map functional key numbers to virtual codepoints\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, // Alternative home\n\t\t\t8: FUNCTIONAL_CODEPOINTS.end, // Alternative end\n\t\t};\n\t\tconst codepoint = funcCodes[keyNum];\n\t\tif (codepoint !== undefined) {\n\t\t\treturn { codepoint, modifier: modValue - 1 };\n\t\t}\n\t}\n\n\t// Match Home/End with modifier: \\x1b[1;<mod>H/F\n\tconst homeEndMatch = data.match(/^\\x1b\\[1;(\\d+)([HF])$/);\n\tif (homeEndMatch) {\n\t\tconst modValue = parseInt(homeEndMatch[1]!, 10);\n\t\tconst codepoint = homeEndMatch[2] === \"H\" ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\treturn null;\n}\n\n/**\n * Check if a Kitty sequence matches the expected codepoint and modifier,\n * ignoring lock key bits (Caps Lock, Num Lock).\n */\nfunction matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {\n\tconst parsed = parseKittySequence(data);\n\tif (!parsed) return false;\n\n\t// Mask out lock bits from both sides for comparison\n\tconst actualMod = parsed.modifier & ~LOCK_MASK;\n\tconst expectedMod = expectedModifier & ~LOCK_MASK;\n\n\treturn parsed.codepoint === expectedCodepoint && actualMod === expectedMod;\n}\n\n// Pre-built sequences for common key combinations\nexport const Keys = {\n\t// Ctrl+<letter> combinations\n\tCTRL_A: kittySequence(CODEPOINTS.a, MODIFIERS.ctrl),\n\tCTRL_C: kittySequence(CODEPOINTS.c, MODIFIERS.ctrl),\n\tCTRL_D: kittySequence(CODEPOINTS.d, MODIFIERS.ctrl),\n\tCTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl),\n\tCTRL_G: kittySequence(CODEPOINTS.g, MODIFIERS.ctrl),\n\tCTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl),\n\tCTRL_L: kittySequence(CODEPOINTS.l, MODIFIERS.ctrl),\n\tCTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl),\n\tCTRL_P: kittySequence(CODEPOINTS.p, MODIFIERS.ctrl),\n\tCTRL_T: kittySequence(CODEPOINTS.t, MODIFIERS.ctrl),\n\tCTRL_U: kittySequence(CODEPOINTS.u, MODIFIERS.ctrl),\n\tCTRL_W: kittySequence(CODEPOINTS.w, MODIFIERS.ctrl),\n\tCTRL_Z: kittySequence(CODEPOINTS.z, MODIFIERS.ctrl),\n\n\t// Enter combinations\n\tSHIFT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.shift),\n\tALT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.alt),\n\tCTRL_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.ctrl),\n\n\t// Tab combinations\n\tSHIFT_TAB: kittySequence(CODEPOINTS.tab, MODIFIERS.shift),\n\n\t// Backspace combinations\n\tALT_BACKSPACE: kittySequence(CODEPOINTS.backspace, MODIFIERS.alt),\n} as const;\n\n/**\n * Check if input matches a Kitty protocol Ctrl+<key> sequence.\n * Ignores lock key bits (Caps Lock, Num Lock).\n * @param data - The input data to check\n * @param key - Single lowercase letter (e.g., 'c' for Ctrl+C)\n */\nexport function isKittyCtrl(data: string, key: string): boolean {\n\tif (key.length !== 1) return false;\n\tconst codepoint = key.charCodeAt(0);\n\t// Check exact match first (fast path)\n\tif (data === kittySequence(codepoint, MODIFIERS.ctrl)) return true;\n\t// Check with lock bits masked out\n\treturn matchesKittySequence(data, codepoint, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches a Kitty protocol key sequence with specific modifier.\n * Ignores lock key bits (Caps Lock, Num Lock).\n * @param data - The input data to check\n * @param codepoint - ASCII codepoint of the key\n * @param modifier - Modifier value (use MODIFIERS constants)\n */\nexport function isKittyKey(data: string, codepoint: number, modifier: number): boolean {\n\t// Check exact match first (fast path)\n\tif (data === kittySequence(codepoint, modifier)) return true;\n\t// Check with lock bits masked out\n\treturn matchesKittySequence(data, codepoint, modifier);\n}\n\n// Raw control character codes\nconst RAW = {\n\tCTRL_A: \"\\x01\",\n\tCTRL_C: \"\\x03\",\n\tCTRL_D: \"\\x04\",\n\tCTRL_E: \"\\x05\",\n\tCTRL_G: \"\\x07\",\n\tCTRL_K: \"\\x0b\",\n\tCTRL_L: \"\\x0c\",\n\tCTRL_O: \"\\x0f\",\n\tCTRL_P: \"\\x10\",\n\tCTRL_T: \"\\x14\",\n\tCTRL_U: \"\\x15\",\n\tCTRL_W: \"\\x17\",\n\tCTRL_Z: \"\\x1a\",\n\tALT_BACKSPACE: \"\\x1b\\x7f\",\n\tSHIFT_TAB: \"\\x1b[Z\",\n} as const;\n\n/**\n * Check if input matches Ctrl+A (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlA(data: string): boolean {\n\treturn data === RAW.CTRL_A || data === Keys.CTRL_A || matchesKittySequence(data, CODEPOINTS.a, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+C (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlC(data: string): boolean {\n\treturn data === RAW.CTRL_C || data === Keys.CTRL_C || matchesKittySequence(data, CODEPOINTS.c, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+D (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlD(data: string): boolean {\n\treturn data === RAW.CTRL_D || data === Keys.CTRL_D || matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+E (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlE(data: string): boolean {\n\treturn data === RAW.CTRL_E || data === Keys.CTRL_E || matchesKittySequence(data, CODEPOINTS.e, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+G (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlG(data: string): boolean {\n\treturn data === RAW.CTRL_G || data === Keys.CTRL_G || matchesKittySequence(data, CODEPOINTS.g, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+K (raw byte or Kitty protocol).\n * Ignores lock key bits.\n * Also checks if first byte is 0x0b for compatibility with terminals\n * that may send trailing bytes.\n */\nexport function isCtrlK(data: string): boolean {\n\treturn (\n\t\tdata === RAW.CTRL_K ||\n\t\t(data.length > 0 && data.charCodeAt(0) === 0x0b) ||\n\t\tdata === Keys.CTRL_K ||\n\t\tmatchesKittySequence(data, CODEPOINTS.k, MODIFIERS.ctrl)\n\t);\n}\n\n/**\n * Check if input matches Ctrl+L (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlL(data: string): boolean {\n\treturn data === RAW.CTRL_L || data === Keys.CTRL_L || matchesKittySequence(data, CODEPOINTS.l, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+O (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlO(data: string): boolean {\n\treturn data === RAW.CTRL_O || data === Keys.CTRL_O || matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+O (Kitty protocol only).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlO(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+P (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlP(data: string): boolean {\n\treturn data === RAW.CTRL_P || data === Keys.CTRL_P || matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+P (Kitty protocol only).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlP(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+D (Kitty protocol only, for debug).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlD(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+T (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlT(data: string): boolean {\n\treturn data === RAW.CTRL_T || data === Keys.CTRL_T || matchesKittySequence(data, CODEPOINTS.t, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+U (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlU(data: string): boolean {\n\treturn data === RAW.CTRL_U || data === Keys.CTRL_U || matchesKittySequence(data, CODEPOINTS.u, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+W (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlW(data: string): boolean {\n\treturn data === RAW.CTRL_W || data === Keys.CTRL_W || matchesKittySequence(data, CODEPOINTS.w, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+Z (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlZ(data: string): boolean {\n\treturn data === RAW.CTRL_Z || data === Keys.CTRL_Z || matchesKittySequence(data, CODEPOINTS.z, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Alt+Backspace (legacy or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isAltBackspace(data: string): boolean {\n\treturn (\n\t\tdata === RAW.ALT_BACKSPACE ||\n\t\tdata === Keys.ALT_BACKSPACE ||\n\t\tmatchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt)\n\t);\n}\n\n/**\n * Check if input matches Shift+Tab (legacy or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isShiftTab(data: string): boolean {\n\treturn (\n\t\tdata === RAW.SHIFT_TAB || data === Keys.SHIFT_TAB || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift)\n\t);\n}\n\n/**\n * Check if input matches the Escape key (raw byte or Kitty protocol).\n * Raw: \\x1b (single byte)\n * Kitty: \\x1b[27u (codepoint 27 = escape)\n * Ignores lock key bits.\n */\nexport function isEscape(data: string): boolean {\n\treturn data === \"\\x1b\" || data === `\\x1b[${CODEPOINTS.escape}u` || matchesKittySequence(data, CODEPOINTS.escape, 0);\n}\n\n// Arrow key virtual codepoints (negative to avoid conflicts with real codepoints)\nconst ARROW_CODEPOINTS = {\n\tup: -1,\n\tdown: -2,\n\tright: -3,\n\tleft: -4,\n} as const;\n\n/**\n * Check if input matches Arrow Up key.\n * Handles both legacy (\\x1b[A) and Kitty protocol with modifiers.\n */\nexport function isArrowUp(data: string): boolean {\n\treturn data === \"\\x1b[A\" || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);\n}\n\n/**\n * Check if input matches Arrow Down key.\n * Handles both legacy (\\x1b[B) and Kitty protocol with modifiers.\n */\nexport function isArrowDown(data: string): boolean {\n\treturn data === \"\\x1b[B\" || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);\n}\n\n/**\n * Check if input matches Arrow Right key.\n * Handles both legacy (\\x1b[C) and Kitty protocol with modifiers.\n */\nexport function isArrowRight(data: string): boolean {\n\treturn data === \"\\x1b[C\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);\n}\n\n/**\n * Check if input matches Arrow Left key.\n * Handles both legacy (\\x1b[D) and Kitty protocol with modifiers.\n */\nexport function isArrowLeft(data: string): boolean {\n\treturn data === \"\\x1b[D\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);\n}\n\n/**\n * Check if input matches plain Tab key (no modifiers).\n * Handles both legacy (\\t) and Kitty protocol.\n */\nexport function isTab(data: string): boolean {\n\treturn data === \"\\t\" || matchesKittySequence(data, CODEPOINTS.tab, 0);\n}\n\n/**\n * Check if input matches plain Enter/Return key (no modifiers).\n * Handles both legacy (\\r) and Kitty protocol.\n */\nexport function isEnter(data: string): boolean {\n\treturn data === \"\\r\" || matchesKittySequence(data, CODEPOINTS.enter, 0);\n}\n\n/**\n * Check if input matches plain Backspace key (no modifiers).\n * Handles both legacy (\\x7f, \\x08) and Kitty protocol.\n */\nexport function isBackspace(data: string): boolean {\n\treturn data === \"\\x7f\" || data === \"\\x08\" || matchesKittySequence(data, CODEPOINTS.backspace, 0);\n}\n\n/**\n * Check if input matches Shift+Enter.\n * Ignores lock key bits.\n */\nexport function isShiftEnter(data: string): boolean {\n\treturn data === Keys.SHIFT_ENTER || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift);\n}\n\n/**\n * Check if input matches Alt+Enter.\n * Ignores lock key bits.\n */\nexport function isAltEnter(data: string): boolean {\n\treturn data === Keys.ALT_ENTER || data === \"\\x1b\\r\" || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Option/Alt+Left (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isAltLeft(data: string): boolean {\n\treturn data === \"\\x1b[1;3D\" || data === \"\\x1bb\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Option/Alt+Right (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isAltRight(data: string): boolean {\n\treturn data === \"\\x1b[1;3C\" || data === \"\\x1bf\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Ctrl+Left (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isCtrlLeft(data: string): boolean {\n\treturn data === \"\\x1b[1;5D\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+Right (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isCtrlRight(data: string): boolean {\n\treturn data === \"\\x1b[1;5C\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Home key.\n * Handles legacy formats and Kitty protocol with lock key modifiers.\n */\nexport function isHome(data: string): boolean {\n\treturn (\n\t\tdata === \"\\x1b[H\" ||\n\t\tdata === \"\\x1b[1~\" ||\n\t\tdata === \"\\x1b[7~\" ||\n\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)\n\t);\n}\n\n/**\n * Check if input matches End key.\n * Handles legacy formats and Kitty protocol with lock key modifiers.\n */\nexport function isEnd(data: string): boolean {\n\treturn (\n\t\tdata === \"\\x1b[F\" ||\n\t\tdata === \"\\x1b[4~\" ||\n\t\tdata === \"\\x1b[8~\" ||\n\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)\n\t);\n}\n\n/**\n * Check if input matches Delete key (forward delete).\n * Handles legacy format and Kitty protocol with lock key modifiers.\n */\nexport function isDelete(data: string): boolean {\n\treturn data === \"\\x1b[3~\" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);\n}\n"]}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Kitty keyboard protocol key sequence helpers.\n *\n * The Kitty keyboard protocol sends enhanced escape sequences in the format:\n * \\x1b[<codepoint>;<modifier>u\n *\n * Modifier bits (before adding 1 for transmission):\n * - Shift: 1 (value 2)\n * - Alt: 2 (value 3)\n * - Ctrl: 4 (value 5)\n * - Super: 8 (value 9)\n * - Hyper: 16\n * - Meta: 32\n * - Caps_Lock: 64\n * - Num_Lock: 128\n *\n * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n *\n * NOTE: Some terminals (e.g., Ghostty on Linux) include lock key states\n * (Caps Lock, Num Lock) in the modifier field. We mask these out when\n * checking for key combinations since they shouldn't affect behavior.\n */\n\n// Common codepoints\nconst CODEPOINTS = {\n\t// Letters (lowercase ASCII)\n\ta: 97,\n\tc: 99,\n\td: 100,\n\te: 101,\n\tg: 103,\n\tk: 107,\n\tl: 108,\n\to: 111,\n\tp: 112,\n\tt: 116,\n\tu: 117,\n\tw: 119,\n\tz: 122,\n\n\t// Special keys\n\tescape: 27,\n\ttab: 9,\n\tenter: 13,\n\tspace: 32,\n\tbackspace: 127,\n} as const;\n\n// Lock key bits to ignore when matching (Caps Lock + Num Lock)\nconst LOCK_MASK = 64 + 128; // 192\n\n// Modifier bits (before adding 1)\nconst MODIFIERS = {\n\tshift: 1,\n\talt: 2,\n\tctrl: 4,\n\tsuper: 8,\n} as const;\n\n/**\n * Build a Kitty keyboard protocol sequence for a key with modifier.\n */\nfunction kittySequence(codepoint: number, modifier: number): string {\n\treturn `\\x1b[${codepoint};${modifier + 1}u`;\n}\n\n/**\n * Parsed Kitty keyboard protocol sequence.\n */\ninterface ParsedKittySequence {\n\tcodepoint: number;\n\tmodifier: number; // Actual modifier bits (after subtracting 1)\n}\n\n/**\n * Parse a Kitty keyboard protocol sequence.\n * Handles formats:\n * - \\x1b[<codepoint>u (no modifier)\n * - \\x1b[<codepoint>;<modifier>u (with modifier)\n * - \\x1b[1;<modifier>A/B/C/D (arrow keys with modifier)\n *\n * Returns null if not a valid Kitty sequence.\n */\n// Virtual codepoints for functional keys (negative to avoid conflicts)\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\nfunction parseKittySequence(data: string): ParsedKittySequence | null {\n\t// Match CSI u format: \\x1b[<num>u or \\x1b[<num>;<mod>u\n\tconst csiUMatch = data.match(/^\\x1b\\[(\\d+)(?:;(\\d+))?u$/);\n\tif (csiUMatch) {\n\t\tconst codepoint = parseInt(csiUMatch[1]!, 10);\n\t\tconst modValue = csiUMatch[2] ? parseInt(csiUMatch[2], 10) : 1;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\t// Match arrow keys with modifier: \\x1b[1;<mod>A/B/C/D\n\tconst arrowMatch = data.match(/^\\x1b\\[1;(\\d+)([ABCD])$/);\n\tif (arrowMatch) {\n\t\tconst modValue = parseInt(arrowMatch[1]!, 10);\n\t\t// Map arrow letters to virtual codepoints for easier matching\n\t\tconst arrowCodes: Record<string, number> = { A: -1, B: -2, C: -3, D: -4 };\n\t\tconst codepoint = arrowCodes[arrowMatch[2]!]!;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\t// Match functional keys with ~ terminator: \\x1b[<num>~ or \\x1b[<num>;<mod>~\n\t// DELETE=3, INSERT=2, PAGEUP=5, PAGEDOWN=6, etc.\n\tconst funcMatch = data.match(/^\\x1b\\[(\\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\t// Map functional key numbers to virtual codepoints\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, // Alternative home\n\t\t\t8: FUNCTIONAL_CODEPOINTS.end, // Alternative end\n\t\t};\n\t\tconst codepoint = funcCodes[keyNum];\n\t\tif (codepoint !== undefined) {\n\t\t\treturn { codepoint, modifier: modValue - 1 };\n\t\t}\n\t}\n\n\t// Match Home/End with modifier: \\x1b[1;<mod>H/F\n\tconst homeEndMatch = data.match(/^\\x1b\\[1;(\\d+)([HF])$/);\n\tif (homeEndMatch) {\n\t\tconst modValue = parseInt(homeEndMatch[1]!, 10);\n\t\tconst codepoint = homeEndMatch[2] === \"H\" ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end;\n\t\treturn { codepoint, modifier: modValue - 1 };\n\t}\n\n\treturn null;\n}\n\n/**\n * Check if a Kitty sequence matches the expected codepoint and modifier,\n * ignoring lock key bits (Caps Lock, Num Lock).\n */\nfunction matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {\n\tconst parsed = parseKittySequence(data);\n\tif (!parsed) return false;\n\n\t// Mask out lock bits from both sides for comparison\n\tconst actualMod = parsed.modifier & ~LOCK_MASK;\n\tconst expectedMod = expectedModifier & ~LOCK_MASK;\n\n\treturn parsed.codepoint === expectedCodepoint && actualMod === expectedMod;\n}\n\n// Pre-built sequences for common key combinations\nexport const Keys = {\n\t// Ctrl+<letter> combinations\n\tCTRL_A: kittySequence(CODEPOINTS.a, MODIFIERS.ctrl),\n\tCTRL_C: kittySequence(CODEPOINTS.c, MODIFIERS.ctrl),\n\tCTRL_D: kittySequence(CODEPOINTS.d, MODIFIERS.ctrl),\n\tCTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl),\n\tCTRL_G: kittySequence(CODEPOINTS.g, MODIFIERS.ctrl),\n\tCTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl),\n\tCTRL_L: kittySequence(CODEPOINTS.l, MODIFIERS.ctrl),\n\tCTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl),\n\tCTRL_P: kittySequence(CODEPOINTS.p, MODIFIERS.ctrl),\n\tCTRL_T: kittySequence(CODEPOINTS.t, MODIFIERS.ctrl),\n\tCTRL_U: kittySequence(CODEPOINTS.u, MODIFIERS.ctrl),\n\tCTRL_W: kittySequence(CODEPOINTS.w, MODIFIERS.ctrl),\n\tCTRL_Z: kittySequence(CODEPOINTS.z, MODIFIERS.ctrl),\n\n\t// Enter combinations\n\tSHIFT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.shift),\n\tALT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.alt),\n\tCTRL_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.ctrl),\n\n\t// Tab combinations\n\tSHIFT_TAB: kittySequence(CODEPOINTS.tab, MODIFIERS.shift),\n\n\t// Backspace combinations\n\tALT_BACKSPACE: kittySequence(CODEPOINTS.backspace, MODIFIERS.alt),\n} as const;\n\n/**\n * Check if input matches a Kitty protocol Ctrl+<key> sequence.\n * Ignores lock key bits (Caps Lock, Num Lock).\n * @param data - The input data to check\n * @param key - Single lowercase letter (e.g., 'c' for Ctrl+C)\n */\nexport function isKittyCtrl(data: string, key: string): boolean {\n\tif (key.length !== 1) return false;\n\tconst codepoint = key.charCodeAt(0);\n\t// Check exact match first (fast path)\n\tif (data === kittySequence(codepoint, MODIFIERS.ctrl)) return true;\n\t// Check with lock bits masked out\n\treturn matchesKittySequence(data, codepoint, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches a Kitty protocol key sequence with specific modifier.\n * Ignores lock key bits (Caps Lock, Num Lock).\n * @param data - The input data to check\n * @param codepoint - ASCII codepoint of the key\n * @param modifier - Modifier value (use MODIFIERS constants)\n */\nexport function isKittyKey(data: string, codepoint: number, modifier: number): boolean {\n\t// Check exact match first (fast path)\n\tif (data === kittySequence(codepoint, modifier)) return true;\n\t// Check with lock bits masked out\n\treturn matchesKittySequence(data, codepoint, modifier);\n}\n\n// Raw control character codes\nconst RAW = {\n\tCTRL_A: \"\\x01\",\n\tCTRL_C: \"\\x03\",\n\tCTRL_D: \"\\x04\",\n\tCTRL_E: \"\\x05\",\n\tCTRL_G: \"\\x07\",\n\tCTRL_K: \"\\x0b\",\n\tCTRL_L: \"\\x0c\",\n\tCTRL_O: \"\\x0f\",\n\tCTRL_P: \"\\x10\",\n\tCTRL_T: \"\\x14\",\n\tCTRL_U: \"\\x15\",\n\tCTRL_W: \"\\x17\",\n\tCTRL_Z: \"\\x1a\",\n\tALT_BACKSPACE: \"\\x1b\\x7f\",\n\tSHIFT_TAB: \"\\x1b[Z\",\n} as const;\n\n/**\n * Check if input matches Ctrl+A (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlA(data: string): boolean {\n\treturn data === RAW.CTRL_A || data === Keys.CTRL_A || matchesKittySequence(data, CODEPOINTS.a, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+C (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlC(data: string): boolean {\n\treturn data === RAW.CTRL_C || data === Keys.CTRL_C || matchesKittySequence(data, CODEPOINTS.c, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+D (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlD(data: string): boolean {\n\treturn data === RAW.CTRL_D || data === Keys.CTRL_D || matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+E (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlE(data: string): boolean {\n\treturn data === RAW.CTRL_E || data === Keys.CTRL_E || matchesKittySequence(data, CODEPOINTS.e, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+G (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlG(data: string): boolean {\n\treturn data === RAW.CTRL_G || data === Keys.CTRL_G || matchesKittySequence(data, CODEPOINTS.g, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+K (raw byte or Kitty protocol).\n * Ignores lock key bits.\n * Also checks if first byte is 0x0b for compatibility with terminals\n * that may send trailing bytes.\n */\nexport function isCtrlK(data: string): boolean {\n\treturn (\n\t\tdata === RAW.CTRL_K ||\n\t\t(data.length > 0 && data.charCodeAt(0) === 0x0b) ||\n\t\tdata === Keys.CTRL_K ||\n\t\tmatchesKittySequence(data, CODEPOINTS.k, MODIFIERS.ctrl)\n\t);\n}\n\n/**\n * Check if input matches Ctrl+L (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlL(data: string): boolean {\n\treturn data === RAW.CTRL_L || data === Keys.CTRL_L || matchesKittySequence(data, CODEPOINTS.l, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+O (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlO(data: string): boolean {\n\treturn data === RAW.CTRL_O || data === Keys.CTRL_O || matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+O (Kitty protocol only).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlO(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+P (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlP(data: string): boolean {\n\treturn data === RAW.CTRL_P || data === Keys.CTRL_P || matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+P (Kitty protocol only).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlP(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Shift+Ctrl+D (Kitty protocol only, for debug).\n * Ignores lock key bits.\n */\nexport function isShiftCtrlD(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.shift + MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+T (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlT(data: string): boolean {\n\treturn data === RAW.CTRL_T || data === Keys.CTRL_T || matchesKittySequence(data, CODEPOINTS.t, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+U (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlU(data: string): boolean {\n\treturn data === RAW.CTRL_U || data === Keys.CTRL_U || matchesKittySequence(data, CODEPOINTS.u, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+W (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlW(data: string): boolean {\n\treturn data === RAW.CTRL_W || data === Keys.CTRL_W || matchesKittySequence(data, CODEPOINTS.w, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+Z (raw byte or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isCtrlZ(data: string): boolean {\n\treturn data === RAW.CTRL_Z || data === Keys.CTRL_Z || matchesKittySequence(data, CODEPOINTS.z, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Alt+Backspace (legacy or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isAltBackspace(data: string): boolean {\n\treturn (\n\t\tdata === RAW.ALT_BACKSPACE ||\n\t\tdata === Keys.ALT_BACKSPACE ||\n\t\tmatchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt)\n\t);\n}\n\n/**\n * Check if input matches Shift+Tab (legacy or Kitty protocol).\n * Ignores lock key bits.\n */\nexport function isShiftTab(data: string): boolean {\n\treturn (\n\t\tdata === RAW.SHIFT_TAB || data === Keys.SHIFT_TAB || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift)\n\t);\n}\n\n/**\n * Check if input matches the Escape key (raw byte or Kitty protocol).\n * Raw: \\x1b (single byte)\n * Kitty: \\x1b[27u (codepoint 27 = escape)\n * Ignores lock key bits.\n */\nexport function isEscape(data: string): boolean {\n\treturn data === \"\\x1b\" || data === `\\x1b[${CODEPOINTS.escape}u` || matchesKittySequence(data, CODEPOINTS.escape, 0);\n}\n\n// Arrow key virtual codepoints (negative to avoid conflicts with real codepoints)\nconst ARROW_CODEPOINTS = {\n\tup: -1,\n\tdown: -2,\n\tright: -3,\n\tleft: -4,\n} as const;\n\n/**\n * Check if input matches Arrow Up key.\n * Handles both legacy (\\x1b[A) and Kitty protocol with modifiers.\n */\nexport function isArrowUp(data: string): boolean {\n\treturn data === \"\\x1b[A\" || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);\n}\n\n/**\n * Check if input matches Arrow Down key.\n * Handles both legacy (\\x1b[B) and Kitty protocol with modifiers.\n */\nexport function isArrowDown(data: string): boolean {\n\treturn data === \"\\x1b[B\" || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);\n}\n\n/**\n * Check if input matches Arrow Right key.\n * Handles both legacy (\\x1b[C) and Kitty protocol with modifiers.\n */\nexport function isArrowRight(data: string): boolean {\n\treturn data === \"\\x1b[C\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);\n}\n\n/**\n * Check if input matches Arrow Left key.\n * Handles both legacy (\\x1b[D) and Kitty protocol with modifiers.\n */\nexport function isArrowLeft(data: string): boolean {\n\treturn data === \"\\x1b[D\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);\n}\n\n/**\n * Check if input matches plain Tab key (no modifiers).\n * Handles both legacy (\\t) and Kitty protocol.\n */\nexport function isTab(data: string): boolean {\n\treturn data === \"\\t\" || matchesKittySequence(data, CODEPOINTS.tab, 0);\n}\n\n/**\n * Check if input matches plain Enter/Return key (no modifiers).\n * Handles both legacy (\\r) and Kitty protocol.\n */\nexport function isEnter(data: string): boolean {\n\treturn data === \"\\r\" || matchesKittySequence(data, CODEPOINTS.enter, 0);\n}\n\n/**\n * Check if input matches plain Backspace key (no modifiers).\n * Handles both legacy (\\x7f, \\x08) and Kitty protocol.\n */\nexport function isBackspace(data: string): boolean {\n\treturn data === \"\\x7f\" || data === \"\\x08\" || matchesKittySequence(data, CODEPOINTS.backspace, 0);\n}\n\n/**\n * Check if input matches Shift+Backspace (Kitty protocol).\n * Returns true so caller can treat it as regular backspace.\n * Ignores lock key bits.\n */\nexport function isShiftBackspace(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.shift);\n}\n\n/**\n * Check if input matches Shift+Enter.\n * Ignores lock key bits.\n */\nexport function isShiftEnter(data: string): boolean {\n\treturn data === Keys.SHIFT_ENTER || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift);\n}\n\n/**\n * Check if input matches Alt+Enter.\n * Ignores lock key bits.\n */\nexport function isAltEnter(data: string): boolean {\n\treturn data === Keys.ALT_ENTER || data === \"\\x1b\\r\" || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Shift+Space (Kitty protocol).\n * Returns true so caller can insert a regular space.\n * Ignores lock key bits.\n */\nexport function isShiftSpace(data: string): boolean {\n\treturn matchesKittySequence(data, CODEPOINTS.space, MODIFIERS.shift);\n}\n\n/**\n * Check if input matches Option/Alt+Left (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isAltLeft(data: string): boolean {\n\treturn data === \"\\x1b[1;3D\" || data === \"\\x1bb\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Option/Alt+Right (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isAltRight(data: string): boolean {\n\treturn data === \"\\x1b[1;3C\" || data === \"\\x1bf\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt);\n}\n\n/**\n * Check if input matches Ctrl+Left (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isCtrlLeft(data: string): boolean {\n\treturn data === \"\\x1b[1;5D\" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Ctrl+Right (word navigation).\n * Handles multiple formats including Kitty protocol.\n */\nexport function isCtrlRight(data: string): boolean {\n\treturn data === \"\\x1b[1;5C\" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);\n}\n\n/**\n * Check if input matches Home key.\n * Handles legacy formats and Kitty protocol with lock key modifiers.\n */\nexport function isHome(data: string): boolean {\n\treturn (\n\t\tdata === \"\\x1b[H\" ||\n\t\tdata === \"\\x1b[1~\" ||\n\t\tdata === \"\\x1b[7~\" ||\n\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)\n\t);\n}\n\n/**\n * Check if input matches End key.\n * Handles legacy formats and Kitty protocol with lock key modifiers.\n */\nexport function isEnd(data: string): boolean {\n\treturn (\n\t\tdata === \"\\x1b[F\" ||\n\t\tdata === \"\\x1b[4~\" ||\n\t\tdata === \"\\x1b[8~\" ||\n\t\tmatchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)\n\t);\n}\n\n/**\n * Check if input matches Delete key (forward delete).\n * Handles legacy format and Kitty protocol with lock key modifiers.\n */\nexport function isDelete(data: string): boolean {\n\treturn data === \"\\x1b[3~\" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);\n}\n\n/**\n * Check if input matches Shift+Delete (Kitty protocol).\n * Returns true so caller can treat it as regular delete.\n * Ignores lock key bits.\n */\nexport function isShiftDelete(data: string): boolean {\n\treturn matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, MODIFIERS.shift);\n}\n"]}
@@ -13,6 +13,7 @@ export interface Terminal {
13
13
  clearLine(): void;
14
14
  clearFromCursor(): void;
15
15
  clearScreen(): void;
16
+ setTitle(title: string): void;
16
17
  }
17
18
  /**
18
19
  * Real terminal using process.stdin/stdout
@@ -32,5 +33,6 @@ export declare class ProcessTerminal implements Terminal {
32
33
  clearLine(): void;
33
34
  clearFromCursor(): void;
34
35
  clearScreen(): void;
36
+ setTitle(title: string): void;
35
37
  }
36
38
  //# sourceMappingURL=terminal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IAExB,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAGnE,IAAI,IAAI,IAAI,CAAC;IAGb,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,IAAI,OAAO,IAAI,MAAM,CAAC;IACtB,IAAI,IAAI,IAAI,MAAM,CAAC;IAGnB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAG5B,UAAU,IAAI,IAAI,CAAC;IACnB,UAAU,IAAI,IAAI,CAAC;IAGnB,SAAS,IAAI,IAAI,CAAC;IAClB,eAAe,IAAI,IAAI,CAAC;IACxB,WAAW,IAAI,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC/C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAC,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAa;IAEnC,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAwBjE;IAED,IAAI,IAAI,IAAI,CAqBX;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAS1B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,SAAS,IAAI,IAAI,CAEhB;IAED,eAAe,IAAI,IAAI,CAEtB;IAED,WAAW,IAAI,IAAI,CAElB;CACD","sourcesContent":["/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\n\t\t// Enable Kitty keyboard protocol (disambiguate escape codes)\n\t\t// This makes terminals like Ghostty, Kitty, WezTerm send enhanced key sequences\n\t\t// e.g., Shift+Enter becomes \\x1b[13;2u instead of just \\r\n\t\t// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n\t\tprocess.stdout.write(\"\\x1b[>1u\");\n\n\t\t// Set up event handlers\n\t\tprocess.stdin.on(\"data\", this.inputHandler);\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\t}\n\n\tstop(): void {\n\t\t// Disable bracketed paste mode\n\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\n\t\t// Disable Kitty keyboard protocol (pop the flags we pushed)\n\t\tprocess.stdout.write(\"\\x1b[<u\");\n\n\t\t// Remove event handlers\n\t\tif (this.inputHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.inputHandler);\n\t\t\tthis.inputHandler = undefined;\n\t\t}\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n}\n"]}
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,QAAQ;IAExB,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAGnE,IAAI,IAAI,IAAI,CAAC;IAGb,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,IAAI,OAAO,IAAI,MAAM,CAAC;IACtB,IAAI,IAAI,IAAI,MAAM,CAAC;IAGnB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAG5B,UAAU,IAAI,IAAI,CAAC;IACnB,UAAU,IAAI,IAAI,CAAC;IAGnB,SAAS,IAAI,IAAI,CAAC;IAClB,eAAe,IAAI,IAAI,CAAC;IACxB,WAAW,IAAI,IAAI,CAAC;IAGpB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC/C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAC,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAa;IAEnC,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAwBjE;IAED,IAAI,IAAI,IAAI,CAqBX;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAS1B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,SAAS,IAAI,IAAI,CAEhB;IAED,eAAe,IAAI,IAAI,CAEtB;IAED,WAAW,IAAI,IAAI,CAElB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5B;CACD","sourcesContent":["/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n\n\t// Title operations\n\tsetTitle(title: string): void; // Set terminal window title\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\n\t\t// Enable Kitty keyboard protocol (disambiguate escape codes)\n\t\t// This makes terminals like Ghostty, Kitty, WezTerm send enhanced key sequences\n\t\t// e.g., Shift+Enter becomes \\x1b[13;2u instead of just \\r\n\t\t// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n\t\tprocess.stdout.write(\"\\x1b[>1u\");\n\n\t\t// Set up event handlers\n\t\tprocess.stdin.on(\"data\", this.inputHandler);\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\t}\n\n\tstop(): void {\n\t\t// Disable bracketed paste mode\n\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\n\t\t// Disable Kitty keyboard protocol (pop the flags we pushed)\n\t\tprocess.stdout.write(\"\\x1b[<u\");\n\n\t\t// Remove event handlers\n\t\tif (this.inputHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.inputHandler);\n\t\t\tthis.inputHandler = undefined;\n\t\t}\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n\n\tsetTitle(title: string): void {\n\t\t// OSC 0;title BEL - set terminal window title\n\t\tprocess.stdout.write(`\\x1b]0;${title}\\x07`);\n\t}\n}\n"]}
package/dist/terminal.js CHANGED
@@ -80,5 +80,9 @@ export class ProcessTerminal {
80
80
  clearScreen() {
81
81
  process.stdout.write("\x1b[2J\x1b[H"); // Clear screen and move to home (1,1)
82
82
  }
83
+ setTitle(title) {
84
+ // OSC 0;title BEL - set terminal window title
85
+ process.stdout.write(`\x1b]0;${title}\x07`);
86
+ }
83
87
  }
84
88
  //# sourceMappingURL=terminal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\n\t\t// Enable Kitty keyboard protocol (disambiguate escape codes)\n\t\t// This makes terminals like Ghostty, Kitty, WezTerm send enhanced key sequences\n\t\t// e.g., Shift+Enter becomes \\x1b[13;2u instead of just \\r\n\t\t// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n\t\tprocess.stdout.write(\"\\x1b[>1u\");\n\n\t\t// Set up event handlers\n\t\tprocess.stdin.on(\"data\", this.inputHandler);\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\t}\n\n\tstop(): void {\n\t\t// Disable bracketed paste mode\n\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\n\t\t// Disable Kitty keyboard protocol (pop the flags we pushed)\n\t\tprocess.stdout.write(\"\\x1b[<u\");\n\n\t\t// Remove event handlers\n\t\tif (this.inputHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.inputHandler);\n\t\t\tthis.inputHandler = undefined;\n\t\t}\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n}\n"]}
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n\n\t// Title operations\n\tsetTitle(title: string): void; // Set terminal window title\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\n\t\t// Enable Kitty keyboard protocol (disambiguate escape codes)\n\t\t// This makes terminals like Ghostty, Kitty, WezTerm send enhanced key sequences\n\t\t// e.g., Shift+Enter becomes \\x1b[13;2u instead of just \\r\n\t\t// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n\t\tprocess.stdout.write(\"\\x1b[>1u\");\n\n\t\t// Set up event handlers\n\t\tprocess.stdin.on(\"data\", this.inputHandler);\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\t}\n\n\tstop(): void {\n\t\t// Disable bracketed paste mode\n\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\n\t\t// Disable Kitty keyboard protocol (pop the flags we pushed)\n\t\tprocess.stdout.write(\"\\x1b[<u\");\n\n\t\t// Remove event handlers\n\t\tif (this.inputHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.inputHandler);\n\t\t\tthis.inputHandler = undefined;\n\t\t}\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n\n\tsetTitle(title: string): void {\n\t\t// OSC 0;title BEL - set terminal window title\n\t\tprocess.stdout.write(`\\x1b]0;${title}\\x07`);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAAC,SAAS,CAE7C;AAmED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAiDhD;AAiSD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBtE;AAkFD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAuED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CASzG;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAc,GAAG,MAAM,CAkEhG","sourcesContent":["import { eastAsianWidth } from \"get-east-asian-width\";\n\n// Grapheme segmenter (shared instance)\nconst segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n\n/**\n * Get the shared grapheme segmenter instance.\n */\nexport function getSegmenter(): Intl.Segmenter {\n\treturn segmenter;\n}\n\n/**\n * Check if a grapheme cluster (after segmentation) could possibly be an RGI emoji.\n * This is a fast heuristic to avoid the expensive rgiEmojiRegex test.\n * The tested Unicode blocks are deliberately broad to account for future\n * Unicode additions.\n */\nfunction couldBeEmoji(segment: string): boolean {\n\tconst cp = segment.codePointAt(0)!;\n\treturn (\n\t\t(cp >= 0x1f000 && cp <= 0x1fbff) || // Emoji and Pictograph\n\t\t(cp >= 0x2300 && cp <= 0x23ff) || // Misc technical\n\t\t(cp >= 0x2600 && cp <= 0x27bf) || // Misc symbols, dingbats\n\t\t(cp >= 0x2b50 && cp <= 0x2b55) || // Specific stars/circles\n\t\tsegment.includes(\"\\uFE0F\") || // Contains VS16 (emoji presentation selector)\n\t\tsegment.length > 2 // Multi-codepoint sequences (ZWJ, skin tones, etc.)\n\t);\n}\n\n// Regexes for character classification (same as string-width library)\nconst zeroWidthRegex = /^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$/v;\nconst leadingNonPrintingRegex = /^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+/v;\nconst rgiEmojiRegex = /^\\p{RGI_Emoji}$/v;\n\n// Cache for non-ASCII strings\nconst WIDTH_CACHE_SIZE = 512;\nconst widthCache = new Map<string, number>();\n\n/**\n * Calculate the terminal width of a single grapheme cluster.\n * Based on code from the string-width library, but includes a possible-emoji\n * check to avoid running the RGI_Emoji regex unnecessarily.\n */\nfunction graphemeWidth(segment: string): number {\n\t// Zero-width clusters\n\tif (zeroWidthRegex.test(segment)) {\n\t\treturn 0;\n\t}\n\n\t// Emoji check with pre-filter\n\tif (couldBeEmoji(segment) && rgiEmojiRegex.test(segment)) {\n\t\treturn 2;\n\t}\n\n\t// Get base visible codepoint\n\tconst base = segment.replace(leadingNonPrintingRegex, \"\");\n\tconst cp = base.codePointAt(0);\n\tif (cp === undefined) {\n\t\treturn 0;\n\t}\n\n\tlet width = eastAsianWidth(cp);\n\n\t// Trailing halfwidth/fullwidth forms\n\tif (segment.length > 1) {\n\t\tfor (const char of segment.slice(1)) {\n\t\t\tconst c = char.codePointAt(0)!;\n\t\t\tif (c >= 0xff00 && c <= 0xffef) {\n\t\t\t\twidth += eastAsianWidth(c);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn width;\n}\n\n/**\n * Calculate the visible width of a string in terminal columns.\n */\nexport function visibleWidth(str: string): number {\n\tif (str.length === 0) {\n\t\treturn 0;\n\t}\n\n\t// Fast path: pure ASCII printable\n\tlet isPureAscii = true;\n\tfor (let i = 0; i < str.length; i++) {\n\t\tconst code = str.charCodeAt(i);\n\t\tif (code < 0x20 || code > 0x7e) {\n\t\t\tisPureAscii = false;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (isPureAscii) {\n\t\treturn str.length;\n\t}\n\n\t// Check cache\n\tconst cached = widthCache.get(str);\n\tif (cached !== undefined) {\n\t\treturn cached;\n\t}\n\n\t// Normalize: tabs to 3 spaces, strip ANSI escape codes\n\tlet clean = str;\n\tif (str.includes(\"\\t\")) {\n\t\tclean = clean.replace(/\\t/g, \" \");\n\t}\n\tif (clean.includes(\"\\x1b\")) {\n\t\tclean = clean.replace(/\\x1b\\[[0-9;]*[mGKHJ]/g, \"\");\n\t}\n\n\t// Calculate width\n\tlet width = 0;\n\tfor (const { segment } of segmenter.segment(clean)) {\n\t\twidth += graphemeWidth(segment);\n\t}\n\n\t// Cache result\n\tif (widthCache.size >= WIDTH_CACHE_SIZE) {\n\t\tconst firstKey = widthCache.keys().next().value;\n\t\tif (firstKey !== undefined) {\n\t\t\twidthCache.delete(firstKey);\n\t\t}\n\t}\n\twidthCache.set(str, width);\n\n\treturn width;\n}\n\n/**\n * Extract ANSI escape sequences from a string at the given position.\n */\nfunction extractAnsiCode(str: string, pos: number): { code: string; length: number } | null {\n\tif (pos >= str.length || str[pos] !== \"\\x1b\" || str[pos + 1] !== \"[\") {\n\t\treturn null;\n\t}\n\n\tlet j = pos + 2;\n\twhile (j < str.length && str[j] && !/[mGKHJ]/.test(str[j]!)) {\n\t\tj++;\n\t}\n\n\tif (j < str.length) {\n\t\treturn {\n\t\t\tcode: str.substring(pos, j + 1),\n\t\t\tlength: j + 1 - pos,\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Track active ANSI SGR codes to preserve styling across line breaks.\n */\nclass AnsiCodeTracker {\n\t// Track individual attributes separately so we can reset them specifically\n\tprivate bold = false;\n\tprivate dim = false;\n\tprivate italic = false;\n\tprivate underline = false;\n\tprivate blink = false;\n\tprivate inverse = false;\n\tprivate hidden = false;\n\tprivate strikethrough = false;\n\tprivate fgColor: string | null = null; // Stores the full code like \"31\" or \"38;5;240\"\n\tprivate bgColor: string | null = null; // Stores the full code like \"41\" or \"48;5;240\"\n\n\tprocess(ansiCode: string): void {\n\t\tif (!ansiCode.endsWith(\"m\")) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Extract the parameters between \\x1b[ and m\n\t\tconst match = ansiCode.match(/\\x1b\\[([\\d;]*)m/);\n\t\tif (!match) return;\n\n\t\tconst params = match[1];\n\t\tif (params === \"\" || params === \"0\") {\n\t\t\t// Full reset\n\t\t\tthis.reset();\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse parameters (can be semicolon-separated)\n\t\tconst parts = params.split(\";\");\n\t\tlet i = 0;\n\t\twhile (i < parts.length) {\n\t\t\tconst code = Number.parseInt(parts[i], 10);\n\n\t\t\t// Handle 256-color and RGB codes which consume multiple parameters\n\t\t\tif (code === 38 || code === 48) {\n\t\t\t\t// 38;5;N (256 color fg) or 38;2;R;G;B (RGB fg)\n\t\t\t\t// 48;5;N (256 color bg) or 48;2;R;G;B (RGB bg)\n\t\t\t\tif (parts[i + 1] === \"5\" && parts[i + 2] !== undefined) {\n\t\t\t\t\t// 256 color: 38;5;N or 48;5;N\n\t\t\t\t\tconst colorCode = `${parts[i]};${parts[i + 1]};${parts[i + 2]}`;\n\t\t\t\t\tif (code === 38) {\n\t\t\t\t\t\tthis.fgColor = colorCode;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.bgColor = colorCode;\n\t\t\t\t\t}\n\t\t\t\t\ti += 3;\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if (parts[i + 1] === \"2\" && parts[i + 4] !== undefined) {\n\t\t\t\t\t// RGB color: 38;2;R;G;B or 48;2;R;G;B\n\t\t\t\t\tconst colorCode = `${parts[i]};${parts[i + 1]};${parts[i + 2]};${parts[i + 3]};${parts[i + 4]}`;\n\t\t\t\t\tif (code === 38) {\n\t\t\t\t\t\tthis.fgColor = colorCode;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.bgColor = colorCode;\n\t\t\t\t\t}\n\t\t\t\t\ti += 5;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Standard SGR codes\n\t\t\tswitch (code) {\n\t\t\t\tcase 0:\n\t\t\t\t\tthis.reset();\n\t\t\t\t\tbreak;\n\t\t\t\tcase 1:\n\t\t\t\t\tthis.bold = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.dim = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.italic = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.underline = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 5:\n\t\t\t\t\tthis.blink = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 7:\n\t\t\t\t\tthis.inverse = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 8:\n\t\t\t\t\tthis.hidden = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 9:\n\t\t\t\t\tthis.strikethrough = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 21:\n\t\t\t\t\tthis.bold = false;\n\t\t\t\t\tbreak; // Some terminals\n\t\t\t\tcase 22:\n\t\t\t\t\tthis.bold = false;\n\t\t\t\t\tthis.dim = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 23:\n\t\t\t\t\tthis.italic = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 24:\n\t\t\t\t\tthis.underline = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 25:\n\t\t\t\t\tthis.blink = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 27:\n\t\t\t\t\tthis.inverse = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 28:\n\t\t\t\t\tthis.hidden = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 29:\n\t\t\t\t\tthis.strikethrough = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 39:\n\t\t\t\t\tthis.fgColor = null;\n\t\t\t\t\tbreak; // Default fg\n\t\t\t\tcase 49:\n\t\t\t\t\tthis.bgColor = null;\n\t\t\t\t\tbreak; // Default bg\n\t\t\t\tdefault:\n\t\t\t\t\t// Standard foreground colors 30-37, 90-97\n\t\t\t\t\tif ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) {\n\t\t\t\t\t\tthis.fgColor = String(code);\n\t\t\t\t\t}\n\t\t\t\t\t// Standard background colors 40-47, 100-107\n\t\t\t\t\telse if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107)) {\n\t\t\t\t\t\tthis.bgColor = String(code);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\t}\n\n\tprivate reset(): void {\n\t\tthis.bold = false;\n\t\tthis.dim = false;\n\t\tthis.italic = false;\n\t\tthis.underline = false;\n\t\tthis.blink = false;\n\t\tthis.inverse = false;\n\t\tthis.hidden = false;\n\t\tthis.strikethrough = false;\n\t\tthis.fgColor = null;\n\t\tthis.bgColor = null;\n\t}\n\n\tgetActiveCodes(): string {\n\t\tconst codes: string[] = [];\n\t\tif (this.bold) codes.push(\"1\");\n\t\tif (this.dim) codes.push(\"2\");\n\t\tif (this.italic) codes.push(\"3\");\n\t\tif (this.underline) codes.push(\"4\");\n\t\tif (this.blink) codes.push(\"5\");\n\t\tif (this.inverse) codes.push(\"7\");\n\t\tif (this.hidden) codes.push(\"8\");\n\t\tif (this.strikethrough) codes.push(\"9\");\n\t\tif (this.fgColor) codes.push(this.fgColor);\n\t\tif (this.bgColor) codes.push(this.bgColor);\n\n\t\tif (codes.length === 0) return \"\";\n\t\treturn `\\x1b[${codes.join(\";\")}m`;\n\t}\n\n\thasActiveCodes(): boolean {\n\t\treturn (\n\t\t\tthis.bold ||\n\t\t\tthis.dim ||\n\t\t\tthis.italic ||\n\t\t\tthis.underline ||\n\t\t\tthis.blink ||\n\t\t\tthis.inverse ||\n\t\t\tthis.hidden ||\n\t\t\tthis.strikethrough ||\n\t\t\tthis.fgColor !== null ||\n\t\t\tthis.bgColor !== null\n\t\t);\n\t}\n\n\t/**\n\t * Get reset codes for attributes that need to be turned off at line end,\n\t * specifically underline which bleeds into padding.\n\t * Returns empty string if no problematic attributes are active.\n\t */\n\tgetLineEndReset(): string {\n\t\t// Only underline causes visual bleeding into padding\n\t\t// Other attributes like colors don't visually bleed to padding\n\t\tif (this.underline) {\n\t\t\treturn \"\\x1b[24m\"; // Underline off only\n\t\t}\n\t\treturn \"\";\n\t}\n}\n\nfunction updateTrackerFromText(text: string, tracker: AnsiCodeTracker): void {\n\tlet i = 0;\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\ttracker.process(ansiResult.code);\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n}\n\n/**\n * Split text into words while keeping ANSI codes attached.\n */\nfunction splitIntoTokensWithAnsi(text: string): string[] {\n\tconst tokens: string[] = [];\n\tlet current = \"\";\n\tlet pendingAnsi = \"\"; // ANSI codes waiting to be attached to next visible content\n\tlet inWhitespace = false;\n\tlet i = 0;\n\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\t// Hold ANSI codes separately - they'll be attached to the next visible char\n\t\t\tpendingAnsi += ansiResult.code;\n\t\t\ti += ansiResult.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst char = text[i];\n\t\tconst charIsSpace = char === \" \";\n\n\t\tif (charIsSpace !== inWhitespace && current) {\n\t\t\t// Switching between whitespace and non-whitespace, push current token\n\t\t\ttokens.push(current);\n\t\t\tcurrent = \"\";\n\t\t}\n\n\t\t// Attach any pending ANSI codes to this visible character\n\t\tif (pendingAnsi) {\n\t\t\tcurrent += pendingAnsi;\n\t\t\tpendingAnsi = \"\";\n\t\t}\n\n\t\tinWhitespace = charIsSpace;\n\t\tcurrent += char;\n\t\ti++;\n\t}\n\n\t// Handle any remaining pending ANSI codes (attach to last token)\n\tif (pendingAnsi) {\n\t\tcurrent += pendingAnsi;\n\t}\n\n\tif (current) {\n\t\ttokens.push(current);\n\t}\n\n\treturn tokens;\n}\n\n/**\n * Wrap text with ANSI codes preserved.\n *\n * ONLY does word wrapping - NO padding, NO background colors.\n * Returns lines where each line is <= width visible chars.\n * Active ANSI codes are preserved across line breaks.\n *\n * @param text - Text to wrap (may contain ANSI codes and newlines)\n * @param width - Maximum visible width per line\n * @returns Array of wrapped lines (NOT padded to width)\n */\nexport function wrapTextWithAnsi(text: string, width: number): string[] {\n\tif (!text) {\n\t\treturn [\"\"];\n\t}\n\n\t// Handle newlines by processing each line separately\n\t// Track ANSI state across lines so styles carry over after literal newlines\n\tconst inputLines = text.split(\"\\n\");\n\tconst result: string[] = [];\n\tconst tracker = new AnsiCodeTracker();\n\n\tfor (const inputLine of inputLines) {\n\t\t// Prepend active ANSI codes from previous lines (except for first line)\n\t\tconst prefix = result.length > 0 ? tracker.getActiveCodes() : \"\";\n\t\tresult.push(...wrapSingleLine(prefix + inputLine, width));\n\t\t// Update tracker with codes from this line for next iteration\n\t\tupdateTrackerFromText(inputLine, tracker);\n\t}\n\n\treturn result.length > 0 ? result : [\"\"];\n}\n\nfunction wrapSingleLine(line: string, width: number): string[] {\n\tif (!line) {\n\t\treturn [\"\"];\n\t}\n\n\tconst visibleLength = visibleWidth(line);\n\tif (visibleLength <= width) {\n\t\treturn [line];\n\t}\n\n\tconst wrapped: string[] = [];\n\tconst tracker = new AnsiCodeTracker();\n\tconst tokens = splitIntoTokensWithAnsi(line);\n\n\tlet currentLine = \"\";\n\tlet currentVisibleLength = 0;\n\n\tfor (const token of tokens) {\n\t\tconst tokenVisibleLength = visibleWidth(token);\n\t\tconst isWhitespace = token.trim() === \"\";\n\n\t\t// Token itself is too long - break it character by character\n\t\tif (tokenVisibleLength > width && !isWhitespace) {\n\t\t\tif (currentLine) {\n\t\t\t\t// Add specific reset for underline only (preserves background)\n\t\t\t\tconst lineEndReset = tracker.getLineEndReset();\n\t\t\t\tif (lineEndReset) {\n\t\t\t\t\tcurrentLine += lineEndReset;\n\t\t\t\t}\n\t\t\t\twrapped.push(currentLine);\n\t\t\t\tcurrentLine = \"\";\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t}\n\n\t\t\t// Break long token - breakLongWord handles its own resets\n\t\t\tconst broken = breakLongWord(token, width, tracker);\n\t\t\twrapped.push(...broken.slice(0, -1));\n\t\t\tcurrentLine = broken[broken.length - 1];\n\t\t\tcurrentVisibleLength = visibleWidth(currentLine);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check if adding this token would exceed width\n\t\tconst totalNeeded = currentVisibleLength + tokenVisibleLength;\n\n\t\tif (totalNeeded > width && currentVisibleLength > 0) {\n\t\t\t// Add specific reset for underline only (preserves background)\n\t\t\tlet lineToWrap = currentLine.trimEnd();\n\t\t\tconst lineEndReset = tracker.getLineEndReset();\n\t\t\tif (lineEndReset) {\n\t\t\t\tlineToWrap += lineEndReset;\n\t\t\t}\n\t\t\twrapped.push(lineToWrap);\n\t\t\tif (isWhitespace) {\n\t\t\t\t// Don't start new line with whitespace\n\t\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t} else {\n\t\t\t\tcurrentLine = tracker.getActiveCodes() + token;\n\t\t\t\tcurrentVisibleLength = tokenVisibleLength;\n\t\t\t}\n\t\t} else {\n\t\t\t// Add to current line\n\t\t\tcurrentLine += token;\n\t\t\tcurrentVisibleLength += tokenVisibleLength;\n\t\t}\n\n\t\tupdateTrackerFromText(token, tracker);\n\t}\n\n\tif (currentLine) {\n\t\t// No reset at end of final line - let caller handle it\n\t\twrapped.push(currentLine);\n\t}\n\n\treturn wrapped.length > 0 ? wrapped : [\"\"];\n}\n\nconst PUNCTUATION_REGEX = /[(){}[\\]<>.,;:'\"!?+\\-=*/\\\\|&%^$#@~`]/;\n\n/**\n * Check if a character is whitespace.\n */\nexport function isWhitespaceChar(char: string): boolean {\n\treturn /\\s/.test(char);\n}\n\n/**\n * Check if a character is punctuation.\n */\nexport function isPunctuationChar(char: string): boolean {\n\treturn PUNCTUATION_REGEX.test(char);\n}\n\nfunction breakLongWord(word: string, width: number, tracker: AnsiCodeTracker): string[] {\n\tconst lines: string[] = [];\n\tlet currentLine = tracker.getActiveCodes();\n\tlet currentWidth = 0;\n\n\t// First, separate ANSI codes from visible content\n\t// We need to handle ANSI codes specially since they're not graphemes\n\tlet i = 0;\n\tconst segments: Array<{ type: \"ansi\" | \"grapheme\"; value: string }> = [];\n\n\twhile (i < word.length) {\n\t\tconst ansiResult = extractAnsiCode(word, i);\n\t\tif (ansiResult) {\n\t\t\tsegments.push({ type: \"ansi\", value: ansiResult.code });\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\t// Find the next ANSI code or end of string\n\t\t\tlet end = i;\n\t\t\twhile (end < word.length) {\n\t\t\t\tconst nextAnsi = extractAnsiCode(word, end);\n\t\t\t\tif (nextAnsi) break;\n\t\t\t\tend++;\n\t\t\t}\n\t\t\t// Segment this non-ANSI portion into graphemes\n\t\t\tconst textPortion = word.slice(i, end);\n\t\t\tfor (const seg of segmenter.segment(textPortion)) {\n\t\t\t\tsegments.push({ type: \"grapheme\", value: seg.segment });\n\t\t\t}\n\t\t\ti = end;\n\t\t}\n\t}\n\n\t// Now process segments\n\tfor (const seg of segments) {\n\t\tif (seg.type === \"ansi\") {\n\t\t\tcurrentLine += seg.value;\n\t\t\ttracker.process(seg.value);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst grapheme = seg.value;\n\t\t// Skip empty graphemes to avoid issues with string-width calculation\n\t\tif (!grapheme) continue;\n\n\t\tconst graphemeWidth = visibleWidth(grapheme);\n\n\t\tif (currentWidth + graphemeWidth > width) {\n\t\t\t// Add specific reset for underline only (preserves background)\n\t\t\tconst lineEndReset = tracker.getLineEndReset();\n\t\t\tif (lineEndReset) {\n\t\t\t\tcurrentLine += lineEndReset;\n\t\t\t}\n\t\t\tlines.push(currentLine);\n\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\tcurrentWidth = 0;\n\t\t}\n\n\t\tcurrentLine += grapheme;\n\t\tcurrentWidth += graphemeWidth;\n\t}\n\n\tif (currentLine) {\n\t\t// No reset at end of final segment - caller handles continuation\n\t\tlines.push(currentLine);\n\t}\n\n\treturn lines.length > 0 ? lines : [\"\"];\n}\n\n/**\n * Apply background color to a line, padding to full width.\n *\n * @param line - Line of text (may contain ANSI codes)\n * @param width - Total width to pad to\n * @param bgFn - Background color function\n * @returns Line with background applied and padded to width\n */\nexport function applyBackgroundToLine(line: string, width: number, bgFn: (text: string) => string): string {\n\t// Calculate padding needed\n\tconst visibleLen = visibleWidth(line);\n\tconst paddingNeeded = Math.max(0, width - visibleLen);\n\tconst padding = \" \".repeat(paddingNeeded);\n\n\t// Apply background to content + padding\n\tconst withPadding = line + padding;\n\treturn bgFn(withPadding);\n}\n\n/**\n * Truncate text to fit within a maximum visible width, adding ellipsis if needed.\n * Properly handles ANSI escape codes (they don't count toward width).\n *\n * @param text - Text to truncate (may contain ANSI codes)\n * @param maxWidth - Maximum visible width\n * @param ellipsis - Ellipsis string to append when truncating (default: \"...\")\n * @returns Truncated text with ellipsis if it exceeded maxWidth\n */\nexport function truncateToWidth(text: string, maxWidth: number, ellipsis: string = \"...\"): string {\n\tconst textVisibleWidth = visibleWidth(text);\n\n\tif (textVisibleWidth <= maxWidth) {\n\t\treturn text;\n\t}\n\n\tconst ellipsisWidth = visibleWidth(ellipsis);\n\tconst targetWidth = maxWidth - ellipsisWidth;\n\n\tif (targetWidth <= 0) {\n\t\treturn ellipsis.substring(0, maxWidth);\n\t}\n\n\t// Separate ANSI codes from visible content using grapheme segmentation\n\tlet i = 0;\n\tconst segments: Array<{ type: \"ansi\" | \"grapheme\"; value: string }> = [];\n\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\tsegments.push({ type: \"ansi\", value: ansiResult.code });\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\t// Find the next ANSI code or end of string\n\t\t\tlet end = i;\n\t\t\twhile (end < text.length) {\n\t\t\t\tconst nextAnsi = extractAnsiCode(text, end);\n\t\t\t\tif (nextAnsi) break;\n\t\t\t\tend++;\n\t\t\t}\n\t\t\t// Segment this non-ANSI portion into graphemes\n\t\t\tconst textPortion = text.slice(i, end);\n\t\t\tfor (const seg of segmenter.segment(textPortion)) {\n\t\t\t\tsegments.push({ type: \"grapheme\", value: seg.segment });\n\t\t\t}\n\t\t\ti = end;\n\t\t}\n\t}\n\n\t// Build truncated string from segments\n\tlet result = \"\";\n\tlet currentWidth = 0;\n\n\tfor (const seg of segments) {\n\t\tif (seg.type === \"ansi\") {\n\t\t\tresult += seg.value;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst grapheme = seg.value;\n\t\t// Skip empty graphemes to avoid issues with string-width calculation\n\t\tif (!grapheme) continue;\n\n\t\tconst graphemeWidth = visibleWidth(grapheme);\n\n\t\tif (currentWidth + graphemeWidth > targetWidth) {\n\t\t\tbreak;\n\t\t}\n\n\t\tresult += grapheme;\n\t\tcurrentWidth += graphemeWidth;\n\t}\n\n\t// Add reset code before ellipsis to prevent styling leaking into it\n\treturn `${result}\\x1b[0m${ellipsis}`;\n}\n"]}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAAC,SAAS,CAE7C;AAmED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAoDhD;AAiSD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBtE;AAkFD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAuED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CASzG;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAc,GAAG,MAAM,CAkEhG","sourcesContent":["import { eastAsianWidth } from \"get-east-asian-width\";\n\n// Grapheme segmenter (shared instance)\nconst segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n\n/**\n * Get the shared grapheme segmenter instance.\n */\nexport function getSegmenter(): Intl.Segmenter {\n\treturn segmenter;\n}\n\n/**\n * Check if a grapheme cluster (after segmentation) could possibly be an RGI emoji.\n * This is a fast heuristic to avoid the expensive rgiEmojiRegex test.\n * The tested Unicode blocks are deliberately broad to account for future\n * Unicode additions.\n */\nfunction couldBeEmoji(segment: string): boolean {\n\tconst cp = segment.codePointAt(0)!;\n\treturn (\n\t\t(cp >= 0x1f000 && cp <= 0x1fbff) || // Emoji and Pictograph\n\t\t(cp >= 0x2300 && cp <= 0x23ff) || // Misc technical\n\t\t(cp >= 0x2600 && cp <= 0x27bf) || // Misc symbols, dingbats\n\t\t(cp >= 0x2b50 && cp <= 0x2b55) || // Specific stars/circles\n\t\tsegment.includes(\"\\uFE0F\") || // Contains VS16 (emoji presentation selector)\n\t\tsegment.length > 2 // Multi-codepoint sequences (ZWJ, skin tones, etc.)\n\t);\n}\n\n// Regexes for character classification (same as string-width library)\nconst zeroWidthRegex = /^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$/v;\nconst leadingNonPrintingRegex = /^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+/v;\nconst rgiEmojiRegex = /^\\p{RGI_Emoji}$/v;\n\n// Cache for non-ASCII strings\nconst WIDTH_CACHE_SIZE = 512;\nconst widthCache = new Map<string, number>();\n\n/**\n * Calculate the terminal width of a single grapheme cluster.\n * Based on code from the string-width library, but includes a possible-emoji\n * check to avoid running the RGI_Emoji regex unnecessarily.\n */\nfunction graphemeWidth(segment: string): number {\n\t// Zero-width clusters\n\tif (zeroWidthRegex.test(segment)) {\n\t\treturn 0;\n\t}\n\n\t// Emoji check with pre-filter\n\tif (couldBeEmoji(segment) && rgiEmojiRegex.test(segment)) {\n\t\treturn 2;\n\t}\n\n\t// Get base visible codepoint\n\tconst base = segment.replace(leadingNonPrintingRegex, \"\");\n\tconst cp = base.codePointAt(0);\n\tif (cp === undefined) {\n\t\treturn 0;\n\t}\n\n\tlet width = eastAsianWidth(cp);\n\n\t// Trailing halfwidth/fullwidth forms\n\tif (segment.length > 1) {\n\t\tfor (const char of segment.slice(1)) {\n\t\t\tconst c = char.codePointAt(0)!;\n\t\t\tif (c >= 0xff00 && c <= 0xffef) {\n\t\t\t\twidth += eastAsianWidth(c);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn width;\n}\n\n/**\n * Calculate the visible width of a string in terminal columns.\n */\nexport function visibleWidth(str: string): number {\n\tif (str.length === 0) {\n\t\treturn 0;\n\t}\n\n\t// Fast path: pure ASCII printable\n\tlet isPureAscii = true;\n\tfor (let i = 0; i < str.length; i++) {\n\t\tconst code = str.charCodeAt(i);\n\t\tif (code < 0x20 || code > 0x7e) {\n\t\t\tisPureAscii = false;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (isPureAscii) {\n\t\treturn str.length;\n\t}\n\n\t// Check cache\n\tconst cached = widthCache.get(str);\n\tif (cached !== undefined) {\n\t\treturn cached;\n\t}\n\n\t// Normalize: tabs to 3 spaces, strip ANSI escape codes\n\tlet clean = str;\n\tif (str.includes(\"\\t\")) {\n\t\tclean = clean.replace(/\\t/g, \" \");\n\t}\n\tif (clean.includes(\"\\x1b\")) {\n\t\t// Strip SGR codes (\\x1b[...m) and cursor codes (\\x1b[...G/K/H/J)\n\t\tclean = clean.replace(/\\x1b\\[[0-9;]*[mGKHJ]/g, \"\");\n\t\t// Strip OSC 8 hyperlinks: \\x1b]8;;URL\\x07 and \\x1b]8;;\\x07\n\t\tclean = clean.replace(/\\x1b\\]8;;[^\\x07]*\\x07/g, \"\");\n\t}\n\n\t// Calculate width\n\tlet width = 0;\n\tfor (const { segment } of segmenter.segment(clean)) {\n\t\twidth += graphemeWidth(segment);\n\t}\n\n\t// Cache result\n\tif (widthCache.size >= WIDTH_CACHE_SIZE) {\n\t\tconst firstKey = widthCache.keys().next().value;\n\t\tif (firstKey !== undefined) {\n\t\t\twidthCache.delete(firstKey);\n\t\t}\n\t}\n\twidthCache.set(str, width);\n\n\treturn width;\n}\n\n/**\n * Extract ANSI escape sequences from a string at the given position.\n */\nfunction extractAnsiCode(str: string, pos: number): { code: string; length: number } | null {\n\tif (pos >= str.length || str[pos] !== \"\\x1b\" || str[pos + 1] !== \"[\") {\n\t\treturn null;\n\t}\n\n\tlet j = pos + 2;\n\twhile (j < str.length && str[j] && !/[mGKHJ]/.test(str[j]!)) {\n\t\tj++;\n\t}\n\n\tif (j < str.length) {\n\t\treturn {\n\t\t\tcode: str.substring(pos, j + 1),\n\t\t\tlength: j + 1 - pos,\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Track active ANSI SGR codes to preserve styling across line breaks.\n */\nclass AnsiCodeTracker {\n\t// Track individual attributes separately so we can reset them specifically\n\tprivate bold = false;\n\tprivate dim = false;\n\tprivate italic = false;\n\tprivate underline = false;\n\tprivate blink = false;\n\tprivate inverse = false;\n\tprivate hidden = false;\n\tprivate strikethrough = false;\n\tprivate fgColor: string | null = null; // Stores the full code like \"31\" or \"38;5;240\"\n\tprivate bgColor: string | null = null; // Stores the full code like \"41\" or \"48;5;240\"\n\n\tprocess(ansiCode: string): void {\n\t\tif (!ansiCode.endsWith(\"m\")) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Extract the parameters between \\x1b[ and m\n\t\tconst match = ansiCode.match(/\\x1b\\[([\\d;]*)m/);\n\t\tif (!match) return;\n\n\t\tconst params = match[1];\n\t\tif (params === \"\" || params === \"0\") {\n\t\t\t// Full reset\n\t\t\tthis.reset();\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse parameters (can be semicolon-separated)\n\t\tconst parts = params.split(\";\");\n\t\tlet i = 0;\n\t\twhile (i < parts.length) {\n\t\t\tconst code = Number.parseInt(parts[i], 10);\n\n\t\t\t// Handle 256-color and RGB codes which consume multiple parameters\n\t\t\tif (code === 38 || code === 48) {\n\t\t\t\t// 38;5;N (256 color fg) or 38;2;R;G;B (RGB fg)\n\t\t\t\t// 48;5;N (256 color bg) or 48;2;R;G;B (RGB bg)\n\t\t\t\tif (parts[i + 1] === \"5\" && parts[i + 2] !== undefined) {\n\t\t\t\t\t// 256 color: 38;5;N or 48;5;N\n\t\t\t\t\tconst colorCode = `${parts[i]};${parts[i + 1]};${parts[i + 2]}`;\n\t\t\t\t\tif (code === 38) {\n\t\t\t\t\t\tthis.fgColor = colorCode;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.bgColor = colorCode;\n\t\t\t\t\t}\n\t\t\t\t\ti += 3;\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if (parts[i + 1] === \"2\" && parts[i + 4] !== undefined) {\n\t\t\t\t\t// RGB color: 38;2;R;G;B or 48;2;R;G;B\n\t\t\t\t\tconst colorCode = `${parts[i]};${parts[i + 1]};${parts[i + 2]};${parts[i + 3]};${parts[i + 4]}`;\n\t\t\t\t\tif (code === 38) {\n\t\t\t\t\t\tthis.fgColor = colorCode;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.bgColor = colorCode;\n\t\t\t\t\t}\n\t\t\t\t\ti += 5;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Standard SGR codes\n\t\t\tswitch (code) {\n\t\t\t\tcase 0:\n\t\t\t\t\tthis.reset();\n\t\t\t\t\tbreak;\n\t\t\t\tcase 1:\n\t\t\t\t\tthis.bold = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tthis.dim = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tthis.italic = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tthis.underline = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 5:\n\t\t\t\t\tthis.blink = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 7:\n\t\t\t\t\tthis.inverse = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 8:\n\t\t\t\t\tthis.hidden = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 9:\n\t\t\t\t\tthis.strikethrough = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 21:\n\t\t\t\t\tthis.bold = false;\n\t\t\t\t\tbreak; // Some terminals\n\t\t\t\tcase 22:\n\t\t\t\t\tthis.bold = false;\n\t\t\t\t\tthis.dim = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 23:\n\t\t\t\t\tthis.italic = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 24:\n\t\t\t\t\tthis.underline = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 25:\n\t\t\t\t\tthis.blink = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 27:\n\t\t\t\t\tthis.inverse = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 28:\n\t\t\t\t\tthis.hidden = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 29:\n\t\t\t\t\tthis.strikethrough = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 39:\n\t\t\t\t\tthis.fgColor = null;\n\t\t\t\t\tbreak; // Default fg\n\t\t\t\tcase 49:\n\t\t\t\t\tthis.bgColor = null;\n\t\t\t\t\tbreak; // Default bg\n\t\t\t\tdefault:\n\t\t\t\t\t// Standard foreground colors 30-37, 90-97\n\t\t\t\t\tif ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) {\n\t\t\t\t\t\tthis.fgColor = String(code);\n\t\t\t\t\t}\n\t\t\t\t\t// Standard background colors 40-47, 100-107\n\t\t\t\t\telse if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107)) {\n\t\t\t\t\t\tthis.bgColor = String(code);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\t}\n\n\tprivate reset(): void {\n\t\tthis.bold = false;\n\t\tthis.dim = false;\n\t\tthis.italic = false;\n\t\tthis.underline = false;\n\t\tthis.blink = false;\n\t\tthis.inverse = false;\n\t\tthis.hidden = false;\n\t\tthis.strikethrough = false;\n\t\tthis.fgColor = null;\n\t\tthis.bgColor = null;\n\t}\n\n\tgetActiveCodes(): string {\n\t\tconst codes: string[] = [];\n\t\tif (this.bold) codes.push(\"1\");\n\t\tif (this.dim) codes.push(\"2\");\n\t\tif (this.italic) codes.push(\"3\");\n\t\tif (this.underline) codes.push(\"4\");\n\t\tif (this.blink) codes.push(\"5\");\n\t\tif (this.inverse) codes.push(\"7\");\n\t\tif (this.hidden) codes.push(\"8\");\n\t\tif (this.strikethrough) codes.push(\"9\");\n\t\tif (this.fgColor) codes.push(this.fgColor);\n\t\tif (this.bgColor) codes.push(this.bgColor);\n\n\t\tif (codes.length === 0) return \"\";\n\t\treturn `\\x1b[${codes.join(\";\")}m`;\n\t}\n\n\thasActiveCodes(): boolean {\n\t\treturn (\n\t\t\tthis.bold ||\n\t\t\tthis.dim ||\n\t\t\tthis.italic ||\n\t\t\tthis.underline ||\n\t\t\tthis.blink ||\n\t\t\tthis.inverse ||\n\t\t\tthis.hidden ||\n\t\t\tthis.strikethrough ||\n\t\t\tthis.fgColor !== null ||\n\t\t\tthis.bgColor !== null\n\t\t);\n\t}\n\n\t/**\n\t * Get reset codes for attributes that need to be turned off at line end,\n\t * specifically underline which bleeds into padding.\n\t * Returns empty string if no problematic attributes are active.\n\t */\n\tgetLineEndReset(): string {\n\t\t// Only underline causes visual bleeding into padding\n\t\t// Other attributes like colors don't visually bleed to padding\n\t\tif (this.underline) {\n\t\t\treturn \"\\x1b[24m\"; // Underline off only\n\t\t}\n\t\treturn \"\";\n\t}\n}\n\nfunction updateTrackerFromText(text: string, tracker: AnsiCodeTracker): void {\n\tlet i = 0;\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\ttracker.process(ansiResult.code);\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n}\n\n/**\n * Split text into words while keeping ANSI codes attached.\n */\nfunction splitIntoTokensWithAnsi(text: string): string[] {\n\tconst tokens: string[] = [];\n\tlet current = \"\";\n\tlet pendingAnsi = \"\"; // ANSI codes waiting to be attached to next visible content\n\tlet inWhitespace = false;\n\tlet i = 0;\n\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\t// Hold ANSI codes separately - they'll be attached to the next visible char\n\t\t\tpendingAnsi += ansiResult.code;\n\t\t\ti += ansiResult.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst char = text[i];\n\t\tconst charIsSpace = char === \" \";\n\n\t\tif (charIsSpace !== inWhitespace && current) {\n\t\t\t// Switching between whitespace and non-whitespace, push current token\n\t\t\ttokens.push(current);\n\t\t\tcurrent = \"\";\n\t\t}\n\n\t\t// Attach any pending ANSI codes to this visible character\n\t\tif (pendingAnsi) {\n\t\t\tcurrent += pendingAnsi;\n\t\t\tpendingAnsi = \"\";\n\t\t}\n\n\t\tinWhitespace = charIsSpace;\n\t\tcurrent += char;\n\t\ti++;\n\t}\n\n\t// Handle any remaining pending ANSI codes (attach to last token)\n\tif (pendingAnsi) {\n\t\tcurrent += pendingAnsi;\n\t}\n\n\tif (current) {\n\t\ttokens.push(current);\n\t}\n\n\treturn tokens;\n}\n\n/**\n * Wrap text with ANSI codes preserved.\n *\n * ONLY does word wrapping - NO padding, NO background colors.\n * Returns lines where each line is <= width visible chars.\n * Active ANSI codes are preserved across line breaks.\n *\n * @param text - Text to wrap (may contain ANSI codes and newlines)\n * @param width - Maximum visible width per line\n * @returns Array of wrapped lines (NOT padded to width)\n */\nexport function wrapTextWithAnsi(text: string, width: number): string[] {\n\tif (!text) {\n\t\treturn [\"\"];\n\t}\n\n\t// Handle newlines by processing each line separately\n\t// Track ANSI state across lines so styles carry over after literal newlines\n\tconst inputLines = text.split(\"\\n\");\n\tconst result: string[] = [];\n\tconst tracker = new AnsiCodeTracker();\n\n\tfor (const inputLine of inputLines) {\n\t\t// Prepend active ANSI codes from previous lines (except for first line)\n\t\tconst prefix = result.length > 0 ? tracker.getActiveCodes() : \"\";\n\t\tresult.push(...wrapSingleLine(prefix + inputLine, width));\n\t\t// Update tracker with codes from this line for next iteration\n\t\tupdateTrackerFromText(inputLine, tracker);\n\t}\n\n\treturn result.length > 0 ? result : [\"\"];\n}\n\nfunction wrapSingleLine(line: string, width: number): string[] {\n\tif (!line) {\n\t\treturn [\"\"];\n\t}\n\n\tconst visibleLength = visibleWidth(line);\n\tif (visibleLength <= width) {\n\t\treturn [line];\n\t}\n\n\tconst wrapped: string[] = [];\n\tconst tracker = new AnsiCodeTracker();\n\tconst tokens = splitIntoTokensWithAnsi(line);\n\n\tlet currentLine = \"\";\n\tlet currentVisibleLength = 0;\n\n\tfor (const token of tokens) {\n\t\tconst tokenVisibleLength = visibleWidth(token);\n\t\tconst isWhitespace = token.trim() === \"\";\n\n\t\t// Token itself is too long - break it character by character\n\t\tif (tokenVisibleLength > width && !isWhitespace) {\n\t\t\tif (currentLine) {\n\t\t\t\t// Add specific reset for underline only (preserves background)\n\t\t\t\tconst lineEndReset = tracker.getLineEndReset();\n\t\t\t\tif (lineEndReset) {\n\t\t\t\t\tcurrentLine += lineEndReset;\n\t\t\t\t}\n\t\t\t\twrapped.push(currentLine);\n\t\t\t\tcurrentLine = \"\";\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t}\n\n\t\t\t// Break long token - breakLongWord handles its own resets\n\t\t\tconst broken = breakLongWord(token, width, tracker);\n\t\t\twrapped.push(...broken.slice(0, -1));\n\t\t\tcurrentLine = broken[broken.length - 1];\n\t\t\tcurrentVisibleLength = visibleWidth(currentLine);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check if adding this token would exceed width\n\t\tconst totalNeeded = currentVisibleLength + tokenVisibleLength;\n\n\t\tif (totalNeeded > width && currentVisibleLength > 0) {\n\t\t\t// Add specific reset for underline only (preserves background)\n\t\t\tlet lineToWrap = currentLine.trimEnd();\n\t\t\tconst lineEndReset = tracker.getLineEndReset();\n\t\t\tif (lineEndReset) {\n\t\t\t\tlineToWrap += lineEndReset;\n\t\t\t}\n\t\t\twrapped.push(lineToWrap);\n\t\t\tif (isWhitespace) {\n\t\t\t\t// Don't start new line with whitespace\n\t\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t} else {\n\t\t\t\tcurrentLine = tracker.getActiveCodes() + token;\n\t\t\t\tcurrentVisibleLength = tokenVisibleLength;\n\t\t\t}\n\t\t} else {\n\t\t\t// Add to current line\n\t\t\tcurrentLine += token;\n\t\t\tcurrentVisibleLength += tokenVisibleLength;\n\t\t}\n\n\t\tupdateTrackerFromText(token, tracker);\n\t}\n\n\tif (currentLine) {\n\t\t// No reset at end of final line - let caller handle it\n\t\twrapped.push(currentLine);\n\t}\n\n\treturn wrapped.length > 0 ? wrapped : [\"\"];\n}\n\nconst PUNCTUATION_REGEX = /[(){}[\\]<>.,;:'\"!?+\\-=*/\\\\|&%^$#@~`]/;\n\n/**\n * Check if a character is whitespace.\n */\nexport function isWhitespaceChar(char: string): boolean {\n\treturn /\\s/.test(char);\n}\n\n/**\n * Check if a character is punctuation.\n */\nexport function isPunctuationChar(char: string): boolean {\n\treturn PUNCTUATION_REGEX.test(char);\n}\n\nfunction breakLongWord(word: string, width: number, tracker: AnsiCodeTracker): string[] {\n\tconst lines: string[] = [];\n\tlet currentLine = tracker.getActiveCodes();\n\tlet currentWidth = 0;\n\n\t// First, separate ANSI codes from visible content\n\t// We need to handle ANSI codes specially since they're not graphemes\n\tlet i = 0;\n\tconst segments: Array<{ type: \"ansi\" | \"grapheme\"; value: string }> = [];\n\n\twhile (i < word.length) {\n\t\tconst ansiResult = extractAnsiCode(word, i);\n\t\tif (ansiResult) {\n\t\t\tsegments.push({ type: \"ansi\", value: ansiResult.code });\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\t// Find the next ANSI code or end of string\n\t\t\tlet end = i;\n\t\t\twhile (end < word.length) {\n\t\t\t\tconst nextAnsi = extractAnsiCode(word, end);\n\t\t\t\tif (nextAnsi) break;\n\t\t\t\tend++;\n\t\t\t}\n\t\t\t// Segment this non-ANSI portion into graphemes\n\t\t\tconst textPortion = word.slice(i, end);\n\t\t\tfor (const seg of segmenter.segment(textPortion)) {\n\t\t\t\tsegments.push({ type: \"grapheme\", value: seg.segment });\n\t\t\t}\n\t\t\ti = end;\n\t\t}\n\t}\n\n\t// Now process segments\n\tfor (const seg of segments) {\n\t\tif (seg.type === \"ansi\") {\n\t\t\tcurrentLine += seg.value;\n\t\t\ttracker.process(seg.value);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst grapheme = seg.value;\n\t\t// Skip empty graphemes to avoid issues with string-width calculation\n\t\tif (!grapheme) continue;\n\n\t\tconst graphemeWidth = visibleWidth(grapheme);\n\n\t\tif (currentWidth + graphemeWidth > width) {\n\t\t\t// Add specific reset for underline only (preserves background)\n\t\t\tconst lineEndReset = tracker.getLineEndReset();\n\t\t\tif (lineEndReset) {\n\t\t\t\tcurrentLine += lineEndReset;\n\t\t\t}\n\t\t\tlines.push(currentLine);\n\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\tcurrentWidth = 0;\n\t\t}\n\n\t\tcurrentLine += grapheme;\n\t\tcurrentWidth += graphemeWidth;\n\t}\n\n\tif (currentLine) {\n\t\t// No reset at end of final segment - caller handles continuation\n\t\tlines.push(currentLine);\n\t}\n\n\treturn lines.length > 0 ? lines : [\"\"];\n}\n\n/**\n * Apply background color to a line, padding to full width.\n *\n * @param line - Line of text (may contain ANSI codes)\n * @param width - Total width to pad to\n * @param bgFn - Background color function\n * @returns Line with background applied and padded to width\n */\nexport function applyBackgroundToLine(line: string, width: number, bgFn: (text: string) => string): string {\n\t// Calculate padding needed\n\tconst visibleLen = visibleWidth(line);\n\tconst paddingNeeded = Math.max(0, width - visibleLen);\n\tconst padding = \" \".repeat(paddingNeeded);\n\n\t// Apply background to content + padding\n\tconst withPadding = line + padding;\n\treturn bgFn(withPadding);\n}\n\n/**\n * Truncate text to fit within a maximum visible width, adding ellipsis if needed.\n * Properly handles ANSI escape codes (they don't count toward width).\n *\n * @param text - Text to truncate (may contain ANSI codes)\n * @param maxWidth - Maximum visible width\n * @param ellipsis - Ellipsis string to append when truncating (default: \"...\")\n * @returns Truncated text with ellipsis if it exceeded maxWidth\n */\nexport function truncateToWidth(text: string, maxWidth: number, ellipsis: string = \"...\"): string {\n\tconst textVisibleWidth = visibleWidth(text);\n\n\tif (textVisibleWidth <= maxWidth) {\n\t\treturn text;\n\t}\n\n\tconst ellipsisWidth = visibleWidth(ellipsis);\n\tconst targetWidth = maxWidth - ellipsisWidth;\n\n\tif (targetWidth <= 0) {\n\t\treturn ellipsis.substring(0, maxWidth);\n\t}\n\n\t// Separate ANSI codes from visible content using grapheme segmentation\n\tlet i = 0;\n\tconst segments: Array<{ type: \"ansi\" | \"grapheme\"; value: string }> = [];\n\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\tsegments.push({ type: \"ansi\", value: ansiResult.code });\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\t// Find the next ANSI code or end of string\n\t\t\tlet end = i;\n\t\t\twhile (end < text.length) {\n\t\t\t\tconst nextAnsi = extractAnsiCode(text, end);\n\t\t\t\tif (nextAnsi) break;\n\t\t\t\tend++;\n\t\t\t}\n\t\t\t// Segment this non-ANSI portion into graphemes\n\t\t\tconst textPortion = text.slice(i, end);\n\t\t\tfor (const seg of segmenter.segment(textPortion)) {\n\t\t\t\tsegments.push({ type: \"grapheme\", value: seg.segment });\n\t\t\t}\n\t\t\ti = end;\n\t\t}\n\t}\n\n\t// Build truncated string from segments\n\tlet result = \"\";\n\tlet currentWidth = 0;\n\n\tfor (const seg of segments) {\n\t\tif (seg.type === \"ansi\") {\n\t\t\tresult += seg.value;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst grapheme = seg.value;\n\t\t// Skip empty graphemes to avoid issues with string-width calculation\n\t\tif (!grapheme) continue;\n\n\t\tconst graphemeWidth = visibleWidth(grapheme);\n\n\t\tif (currentWidth + graphemeWidth > targetWidth) {\n\t\t\tbreak;\n\t\t}\n\n\t\tresult += grapheme;\n\t\tcurrentWidth += graphemeWidth;\n\t}\n\n\t// Add reset code before ellipsis to prevent styling leaking into it\n\treturn `${result}\\x1b[0m${ellipsis}`;\n}\n"]}
package/dist/utils.js CHANGED
@@ -92,7 +92,10 @@ export function visibleWidth(str) {
92
92
  clean = clean.replace(/\t/g, " ");
93
93
  }
94
94
  if (clean.includes("\x1b")) {
95
+ // Strip SGR codes (\x1b[...m) and cursor codes (\x1b[...G/K/H/J)
95
96
  clean = clean.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "");
97
+ // Strip OSC 8 hyperlinks: \x1b]8;;URL\x07 and \x1b]8;;\x07
98
+ clean = clean.replace(/\x1b\]8;;[^\x07]*\x07/g, "");
96
99
  }
97
100
  // Calculate width
98
101
  let width = 0;