diff options
| author | Lee Won Jun <[email protected]> | 2025-08-09 16:03:17 +0900 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-09 07:03:17 +0000 |
| commit | b8084ba8158b89facd49fd78a51abb80b1db54da (patch) | |
| tree | 5fd41e255b5118d53798c29d9fad95478a1ed582 /packages/cli/src/ui/keyMatchers.ts | |
| parent | 6487cc16895976ef6c983f8beca08a64addb6688 (diff) | |
Centralize Key Binding Logic and Refactor (Reopen) (#5356)
Co-authored-by: Lee-WonJun <[email protected]>
Diffstat (limited to 'packages/cli/src/ui/keyMatchers.ts')
| -rw-r--r-- | packages/cli/src/ui/keyMatchers.ts | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/packages/cli/src/ui/keyMatchers.ts b/packages/cli/src/ui/keyMatchers.ts new file mode 100644 index 00000000..651343af --- /dev/null +++ b/packages/cli/src/ui/keyMatchers.ts @@ -0,0 +1,105 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { Key } from './hooks/useKeypress.js'; +import { + Command, + KeyBinding, + KeyBindingConfig, + defaultKeyBindings, +} from '../config/keyBindings.js'; + +/** + * Matches a KeyBinding against an actual Key press + * Pure data-driven matching logic + */ +function matchKeyBinding(keyBinding: KeyBinding, key: Key): boolean { + // Either key name or sequence must match (but not both should be defined) + let keyMatches = false; + + if (keyBinding.key !== undefined) { + keyMatches = keyBinding.key === key.name; + } else if (keyBinding.sequence !== undefined) { + keyMatches = keyBinding.sequence === key.sequence; + } else { + // Neither key nor sequence defined - invalid binding + return false; + } + + if (!keyMatches) { + return false; + } + + // Check modifiers - follow original logic: + // undefined = ignore this modifier (original behavior) + // true = modifier must be pressed + // false = modifier must NOT be pressed + + if (keyBinding.ctrl !== undefined && key.ctrl !== keyBinding.ctrl) { + return false; + } + + if (keyBinding.shift !== undefined && key.shift !== keyBinding.shift) { + return false; + } + + if (keyBinding.command !== undefined && key.meta !== keyBinding.command) { + return false; + } + + if (keyBinding.paste !== undefined && key.paste !== keyBinding.paste) { + return false; + } + + return true; +} + +/** + * Checks if a key matches any of the bindings for a command + */ +function matchCommand( + command: Command, + key: Key, + config: KeyBindingConfig = defaultKeyBindings, +): boolean { + const bindings = config[command]; + return bindings.some((binding) => matchKeyBinding(binding, key)); +} + +/** + * Key matcher function type + */ +type KeyMatcher = (key: Key) => boolean; + +/** + * Type for key matchers mapped to Command enum + */ +export type KeyMatchers = { + readonly [C in Command]: KeyMatcher; +}; + +/** + * Creates key matchers from a key binding configuration + */ +export function createKeyMatchers( + config: KeyBindingConfig = defaultKeyBindings, +): KeyMatchers { + const matchers = {} as { [C in Command]: KeyMatcher }; + + for (const command of Object.values(Command)) { + matchers[command] = (key: Key) => matchCommand(command, key, config); + } + + return matchers as KeyMatchers; +} + +/** + * Default key binding matchers using the default configuration + */ +export const keyMatchers: KeyMatchers = createKeyMatchers(defaultKeyBindings); + +// Re-export Command for convenience +export { Command }; |
