diff --git a/packages/tui/src/index.ts b/packages/tui/src/index.ts index 32fb2356..23f9262e 100644 --- a/packages/tui/src/index.ts +++ b/packages/tui/src/index.ts @@ -27,11 +27,10 @@ export { type EditorKeybindingsConfig, EditorKeybindingsManager, getEditorKeybindings, - type KeyId, setEditorKeybindings, } from "./keybindings.js"; // Keyboard input handling -export { matchesKey, parseKey } from "./keys.js"; +export { Key, type KeyId, matchesKey, parseKey } from "./keys.js"; // Terminal interface and implementations export { ProcessTerminal, type Terminal } from "./terminal.js"; // Terminal image support diff --git a/packages/tui/src/keybindings.ts b/packages/tui/src/keybindings.ts index 168c354d..0e3ad244 100644 --- a/packages/tui/src/keybindings.ts +++ b/packages/tui/src/keybindings.ts @@ -1,4 +1,4 @@ -import { matchesKey } from "./keys.js"; +import { type KeyId, matchesKey } from "./keys.js"; /** * Editor actions that can be bound to keys. @@ -31,10 +31,8 @@ export type EditorAction = // Clipboard | "copy"; -/** - * Key identifier string (e.g., "ctrl+c", "shift+ctrl+p", "escape"). - */ -export type KeyId = string; +// Re-export KeyId from keys.ts +export type { KeyId }; /** * Editor keybindings configuration. diff --git a/packages/tui/src/keys.ts b/packages/tui/src/keys.ts index 8d1b583d..4ec1503f 100644 --- a/packages/tui/src/keys.ts +++ b/packages/tui/src/keys.ts @@ -7,8 +7,123 @@ * API: * - matchesKey(data, keyId) - Check if input matches a key identifier * - parseKey(data) - Parse input and return the key identifier + * - Key - Helper object for creating typed key identifiers */ +// ============================================================================= +// Type-Safe Key Identifiers +// ============================================================================= + +type Letter = + | "a" + | "b" + | "c" + | "d" + | "e" + | "f" + | "g" + | "h" + | "i" + | "j" + | "k" + | "l" + | "m" + | "n" + | "o" + | "p" + | "q" + | "r" + | "s" + | "t" + | "u" + | "v" + | "w" + | "x" + | "y" + | "z"; + +type SpecialKey = + | "escape" + | "esc" + | "enter" + | "return" + | "tab" + | "space" + | "backspace" + | "delete" + | "home" + | "end" + | "up" + | "down" + | "left" + | "right"; + +type BaseKey = Letter | SpecialKey; + +/** + * Union type of all valid key identifiers. + * Provides autocomplete and catches typos at compile time. + */ +export type KeyId = + | BaseKey + | `ctrl+${BaseKey}` + | `shift+${BaseKey}` + | `alt+${BaseKey}` + | `ctrl+shift+${BaseKey}` + | `shift+ctrl+${BaseKey}` + | `ctrl+alt+${BaseKey}` + | `alt+ctrl+${BaseKey}` + | `shift+alt+${BaseKey}` + | `alt+shift+${BaseKey}` + | `ctrl+shift+alt+${BaseKey}` + | `ctrl+alt+shift+${BaseKey}` + | `shift+ctrl+alt+${BaseKey}` + | `shift+alt+ctrl+${BaseKey}` + | `alt+ctrl+shift+${BaseKey}` + | `alt+shift+ctrl+${BaseKey}`; + +/** + * Helper object for creating typed key identifiers with autocomplete. + * + * Usage: + * - Key.escape, Key.enter, Key.tab, etc. for special keys + * - Key.ctrl("c"), Key.alt("x") for single modifier + * - Key.ctrlShift("p"), Key.ctrlAlt("x") for combined modifiers + */ +export const Key = { + // Special keys + escape: "escape" as const, + esc: "esc" as const, + enter: "enter" as const, + return: "return" as const, + tab: "tab" as const, + space: "space" as const, + backspace: "backspace" as const, + delete: "delete" as const, + home: "home" as const, + end: "end" as const, + up: "up" as const, + down: "down" as const, + left: "left" as const, + right: "right" as const, + + // Single modifiers + ctrl: (key: K): `ctrl+${K}` => `ctrl+${key}`, + shift: (key: K): `shift+${K}` => `shift+${key}`, + alt: (key: K): `alt+${K}` => `alt+${key}`, + + // Combined modifiers + ctrlShift: (key: K): `ctrl+shift+${K}` => `ctrl+shift+${key}`, + shiftCtrl: (key: K): `shift+ctrl+${K}` => `shift+ctrl+${key}`, + ctrlAlt: (key: K): `ctrl+alt+${K}` => `ctrl+alt+${key}`, + altCtrl: (key: K): `alt+ctrl+${K}` => `alt+ctrl+${key}`, + shiftAlt: (key: K): `shift+alt+${K}` => `shift+alt+${key}`, + altShift: (key: K): `alt+shift+${K}` => `alt+shift+${key}`, + + // Triple modifiers + ctrlShiftAlt: (key: K): `ctrl+shift+alt+${K}` => `ctrl+shift+alt+${key}`, +} as const; + // ============================================================================= // Constants // ============================================================================= @@ -142,10 +257,12 @@ function parseKeyId(keyId: string): { key: string; ctrl: boolean; shift: boolean * - Alt combinations: "alt+enter", "alt+backspace" * - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x" * + * Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p") + * * @param data - Raw input data from terminal - * @param keyId - Key identifier string (e.g., "ctrl+c", "escape") + * @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c")) */ -export function matchesKey(data: string, keyId: string): boolean { +export function matchesKey(data: string, keyId: KeyId): boolean { const parsed = parseKeyId(keyId); if (!parsed) return false;