import { type KeyId, matchesKey } from "./keys.js"; /** * Editor actions that can be bound to keys. */ export type EditorAction = // Cursor movement | "cursorUp" | "cursorDown" | "cursorLeft" | "cursorRight" | "cursorWordLeft" | "cursorWordRight" | "cursorLineStart" | "cursorLineEnd" // Deletion | "deleteCharBackward" | "deleteCharForward" | "deleteWordBackward" | "deleteToLineStart" | "deleteToLineEnd" // Text input | "newLine" | "submit" | "tab" // Selection/autocomplete | "selectUp" | "selectDown" | "selectPageUp" | "selectPageDown" | "selectConfirm" | "selectCancel" // Clipboard | "copy"; // Re-export KeyId from keys.ts export type { KeyId }; /** * Editor keybindings configuration. */ export type EditorKeybindingsConfig = { [K in EditorAction]?: KeyId | KeyId[]; }; /** * Default editor keybindings. */ export const DEFAULT_EDITOR_KEYBINDINGS: Required = { // Cursor movement cursorUp: "up", cursorDown: "down", cursorLeft: "left", cursorRight: "right", cursorWordLeft: ["alt+left", "ctrl+left"], cursorWordRight: ["alt+right", "ctrl+right"], cursorLineStart: ["home", "ctrl+a"], cursorLineEnd: ["end", "ctrl+e"], // Deletion deleteCharBackward: "backspace", deleteCharForward: "delete", deleteWordBackward: ["ctrl+w", "alt+backspace"], deleteToLineStart: "ctrl+u", deleteToLineEnd: "ctrl+k", // Text input newLine: "shift+enter", submit: "enter", tab: "tab", // Selection/autocomplete selectUp: "up", selectDown: "down", selectPageUp: "pageUp", selectPageDown: "pageDown", selectConfirm: "enter", selectCancel: ["escape", "ctrl+c"], // Clipboard copy: "ctrl+c", }; /** * Manages keybindings for the editor. */ export class EditorKeybindingsManager { private actionToKeys: Map; constructor(config: EditorKeybindingsConfig = {}) { this.actionToKeys = new Map(); this.buildMaps(config); } private buildMaps(config: EditorKeybindingsConfig): void { this.actionToKeys.clear(); // Start with defaults for (const [action, keys] of Object.entries(DEFAULT_EDITOR_KEYBINDINGS)) { const keyArray = Array.isArray(keys) ? keys : [keys]; this.actionToKeys.set(action as EditorAction, [...keyArray]); } // Override with user config for (const [action, keys] of Object.entries(config)) { if (keys === undefined) continue; const keyArray = Array.isArray(keys) ? keys : [keys]; this.actionToKeys.set(action as EditorAction, keyArray); } } /** * Check if input matches a specific action. */ matches(data: string, action: EditorAction): boolean { const keys = this.actionToKeys.get(action); if (!keys) return false; for (const key of keys) { if (matchesKey(data, key)) return true; } return false; } /** * Get keys bound to an action. */ getKeys(action: EditorAction): KeyId[] { return this.actionToKeys.get(action) ?? []; } /** * Update configuration. */ setConfig(config: EditorKeybindingsConfig): void { this.buildMaps(config); } } // Global instance let globalEditorKeybindings: EditorKeybindingsManager | null = null; export function getEditorKeybindings(): EditorKeybindingsManager { if (!globalEditorKeybindings) { globalEditorKeybindings = new EditorKeybindingsManager(); } return globalEditorKeybindings; } export function setEditorKeybindings(manager: EditorKeybindingsManager): void { globalEditorKeybindings = manager; }