summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/keyMatchers.ts
diff options
context:
space:
mode:
authorLee Won Jun <[email protected]>2025-08-09 16:03:17 +0900
committerGitHub <[email protected]>2025-08-09 07:03:17 +0000
commitb8084ba8158b89facd49fd78a51abb80b1db54da (patch)
tree5fd41e255b5118d53798c29d9fad95478a1ed582 /packages/cli/src/ui/keyMatchers.ts
parent6487cc16895976ef6c983f8beca08a64addb6688 (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.ts105
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 };