source dump of claude code
at main 120 lines 3.8 kB view raw
1import type { Key } from '../ink.js' 2import type { ParsedBinding, ParsedKeystroke } from './types.js' 3 4/** 5 * Modifier keys from Ink's Key type that we care about for matching. 6 * Note: `fn` from Key is intentionally excluded as it's rarely used and 7 * not commonly configurable in terminal applications. 8 */ 9type InkModifiers = Pick<Key, 'ctrl' | 'shift' | 'meta' | 'super'> 10 11/** 12 * Extract modifiers from an Ink Key object. 13 * This function ensures we're explicitly extracting the modifiers we care about. 14 */ 15function getInkModifiers(key: Key): InkModifiers { 16 return { 17 ctrl: key.ctrl, 18 shift: key.shift, 19 meta: key.meta, 20 super: key.super, 21 } 22} 23 24/** 25 * Extract the normalized key name from Ink's Key + input. 26 * Maps Ink's boolean flags (key.escape, key.return, etc.) to string names 27 * that match our ParsedKeystroke.key format. 28 */ 29export function getKeyName(input: string, key: Key): string | null { 30 if (key.escape) return 'escape' 31 if (key.return) return 'enter' 32 if (key.tab) return 'tab' 33 if (key.backspace) return 'backspace' 34 if (key.delete) return 'delete' 35 if (key.upArrow) return 'up' 36 if (key.downArrow) return 'down' 37 if (key.leftArrow) return 'left' 38 if (key.rightArrow) return 'right' 39 if (key.pageUp) return 'pageup' 40 if (key.pageDown) return 'pagedown' 41 if (key.wheelUp) return 'wheelup' 42 if (key.wheelDown) return 'wheeldown' 43 if (key.home) return 'home' 44 if (key.end) return 'end' 45 if (input.length === 1) return input.toLowerCase() 46 return null 47} 48 49/** 50 * Check if all modifiers match between Ink Key and ParsedKeystroke. 51 * 52 * Alt and Meta: Ink historically set `key.meta` for Alt/Option. A `meta` 53 * modifier in config is treated as an alias for `alt` — both match when 54 * `key.meta` is true. 55 * 56 * Super (Cmd/Win): distinct from alt/meta. Only arrives via the kitty 57 * keyboard protocol on supporting terminals. A `cmd`/`super` binding will 58 * simply never fire on terminals that don't send it. 59 */ 60function modifiersMatch( 61 inkMods: InkModifiers, 62 target: ParsedKeystroke, 63): boolean { 64 // Check ctrl modifier 65 if (inkMods.ctrl !== target.ctrl) return false 66 67 // Check shift modifier 68 if (inkMods.shift !== target.shift) return false 69 70 // Alt and meta both map to key.meta in Ink (terminal limitation) 71 // So we check if EITHER alt OR meta is required in target 72 const targetNeedsMeta = target.alt || target.meta 73 if (inkMods.meta !== targetNeedsMeta) return false 74 75 // Super (cmd/win) is a distinct modifier from alt/meta 76 if (inkMods.super !== target.super) return false 77 78 return true 79} 80 81/** 82 * Check if a ParsedKeystroke matches the given Ink input + Key. 83 * 84 * The display text will show platform-appropriate names (opt on macOS, alt elsewhere). 85 */ 86export function matchesKeystroke( 87 input: string, 88 key: Key, 89 target: ParsedKeystroke, 90): boolean { 91 const keyName = getKeyName(input, key) 92 if (keyName !== target.key) return false 93 94 const inkMods = getInkModifiers(key) 95 96 // QUIRK: Ink sets key.meta=true when escape is pressed (see input-event.ts). 97 // This is a legacy behavior from how escape sequences work in terminals. 98 // We need to ignore the meta modifier when matching the escape key itself, 99 // otherwise bindings like "escape" (without modifiers) would never match. 100 if (key.escape) { 101 return modifiersMatch({ ...inkMods, meta: false }, target) 102 } 103 104 return modifiersMatch(inkMods, target) 105} 106 107/** 108 * Check if Ink's Key + input matches a parsed binding's first keystroke. 109 * For single-keystroke bindings only (Phase 1). 110 */ 111export function matchesBinding( 112 input: string, 113 key: Key, 114 binding: ParsedBinding, 115): boolean { 116 if (binding.chord.length !== 1) return false 117 const keystroke = binding.chord[0] 118 if (!keystroke) return false 119 return matchesKeystroke(input, key, keystroke) 120}