/* Every keyboard shortcut the shell owns, as data. ── Why a registry ── Ported in shape from the owner's `vimazing` (`vim-maze/src/gameInfo.ts`), where the controls are declared once and the help screen renders them. The alternative is what this replaced: chords spelled out inside three separate key handlers, and a help text nobody updates because it lives somewhere else. Two things follow from having it. `Ctrl+?` can render an accurate list without anybody maintaining prose. And when shortcuts become configurable — the next big piece — this is the table a binding UI edits, rather than a grep across handlers. ── What it is NOT, yet ── Declarative but not authoritative: the handlers still contain their own conditions, so a chord changed here does not move. Making this the single source is part of the configurable-shortcuts work, and doing it now would mean a rewrite of three handlers for no visible gain. Where a chord appears in both places the file that owns it is named in `implementedIn`, so the pair can be found in one search. */ export type Shortcut = { /** As a person reads it — `Ctrl+Shift+Space`, not a KeyboardEvent spelling. */ keys: string; description: string; /** The file whose handler actually implements it, until this registry is authoritative. */ implementedIn: string; }; export type ShortcutSection = { title: string; /** Shown under the title when the section needs a caveat that a per-row description cannot carry. */ note?: string; shortcuts: Shortcut[]; }; export const SHORTCUT_SECTIONS: ShortcutSection[] = [ { title: 'Navigation', shortcuts: [ { keys: 'Shift+Space', description: 'Spotlight — go to any screen, plugin or setting by name', implementedIn: 'Layout/Spotlight/Spotlight.tsx', }, { keys: 'Ctrl+?', description: 'This list', implementedIn: 'Layout/ShortcutsModal.tsx' }, ], }, { title: 'Themes', note: 'The browser applies as you move, so you watch the app change rather than reading names.', shortcuts: [ { keys: 'Ctrl+Shift+Space', description: 'Open the theme browser', implementedIn: 'theme/useThemeKeys.ts', }, { keys: 'Ctrl+Shift+← / →', description: 'Previous / next theme, without opening anything', implementedIn: 'theme/useThemeKeys.ts', }, { keys: 'Ctrl+Shift+F', description: 'Star or unstar the current theme, from anywhere', implementedIn: 'theme/useThemeKeys.ts', }, ], }, { title: 'In the theme browser', shortcuts: [ { keys: 'j / k ↑ / ↓', description: 'Move through the list', implementedIn: 'theme/useThemeKeys.ts' }, { keys: 'l →', description: 'Apply the focused theme and keep browsing', implementedIn: 'theme/useThemeKeys.ts', }, { keys: 'h ←', description: 'Put back the theme you started on', implementedIn: 'theme/useThemeKeys.ts' }, { keys: 'f', description: 'Star or unstar the focused theme', implementedIn: 'theme/useThemeKeys.ts' }, { keys: 'Enter', description: 'Keep what you are looking at', implementedIn: 'theme/useThemeKeys.ts' }, { keys: 'Esc', description: 'Cancel and restore', implementedIn: 'theme/useThemeKeys.ts' }, ], }, { title: 'In Spotlight', shortcuts: [ { keys: '↑ / ↓', description: 'Move through the results', implementedIn: 'Layout/Spotlight/Spotlight.tsx' }, { keys: 'Enter', description: 'Go there', implementedIn: 'Layout/Spotlight/Spotlight.tsx' }, { keys: 'Esc', description: 'Close', implementedIn: 'Layout/Spotlight/Spotlight.tsx' }, ], }, { title: 'Chat', note: 'Recall only takes the arrow key from an EMPTY composer, and any other key ends a recall — so a draft is never overwritten.', shortcuts: [ { keys: '↑ / ↓', description: 'Previous / next prompt you sent, shell-style', implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts', }, { keys: 'Enter', description: 'Send · on an empty composer with something queued, send that now', implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts', }, { keys: 'Shift+Enter', description: 'Newline', implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts' }, { keys: 'Esc', description: 'Stop the turn that is generating', implementedIn: 'Chat/EmbeddableChat/useEmbeddableChat.ts', }, ], }, ]; /** * Is this the `Ctrl+?` chord? * * `?` is `Shift+/` on most layouts and its own key on some, so both spellings are accepted rather than * guessing which keyboard is attached. `code === 'Slash'` is the physical key, which is stable where `key` * is not. */ export function isShortcutsChord(ev: KeyboardEvent): boolean { if (!ev.ctrlKey) return false; return ev.key === '?' || (ev.shiftKey && ev.code === 'Slash'); }