|
1 | 1 | /** |
2 | | - * OpenTUI shell keybinding catalog (pure data) — the help overlay's source |
3 | | - * of truth. Every row must be a chord that really works: a handler in |
4 | | - * `src/tui/shell.ts`, or a default prompt binding from @opentui/core. Do |
5 | | - * not hand-transcribe from docs; `keybindings.test.ts` drives every row's |
6 | | - * chord through a live shell and asserts its effect. |
| 2 | + * Help rows. Application lines come from the live `@opentui/keymap` |
| 3 | + * registration (`bindAppHelp`). Widget-default rows are the Textarea chords |
| 4 | + * `defaultTextareaKeyBindings` / `PROMPT_KEY_BINDINGS` already perform, plus |
| 5 | + * the kill-ring yank chords the textarea does not implement. |
7 | 6 | */ |
8 | 7 |
|
9 | 8 | export interface ShellShortcut { |
10 | 9 | readonly keys: string; |
11 | 10 | readonly description: string; |
12 | 11 | } |
13 | 12 |
|
14 | | -export const SHELL_SHORTCUTS: readonly ShellShortcut[] = [ |
| 13 | +/** |
| 14 | + * Prompt-local rows. Cursor, delete, word motion, and newline/submit are |
| 15 | + * Textarea defaults (`renderables/Textarea.d.ts` `TextareaAction`). Yank is |
| 16 | + * not a Textarea action; it stays listed because the kill ring records it. |
| 17 | + */ |
| 18 | +export const WIDGET_DEFAULT_SHORTCUTS: readonly ShellShortcut[] = [ |
15 | 19 | { |
16 | 20 | keys: "Enter", |
17 | 21 | description: |
18 | 22 | "soft-steer at the next tool boundary while busy (held above the prompt); send straight through when idle", |
19 | 23 | }, |
20 | | - { |
21 | | - keys: "Alt+Enter", |
22 | | - description: |
23 | | - "queue a follow-up delivered only when the run goes idle; does nothing unless a run is busy", |
24 | | - }, |
25 | | - { |
26 | | - keys: "Up / Down / Enter / Ctrl+X", |
27 | | - description: |
28 | | - "on held items above the prompt: select, send now, drop (Esc backs out)", |
29 | | - }, |
30 | | - { |
31 | | - keys: "Ctrl+C", |
32 | | - description: |
33 | | - "pause a busy run and hold the queue, or clear the prompt and attachments when idle; Ctrl+C again to exit — with live sub-agents, a 2nd press stops them, a 3rd quits", |
34 | | - }, |
35 | | - { |
36 | | - keys: "Ctrl+G", |
37 | | - description: |
38 | | - "pop the most recently queued or steered message back into the prompt for editing", |
39 | | - }, |
40 | | - { |
41 | | - keys: "Alt+C", |
42 | | - description: |
43 | | - "copy mode: pick a message, tool output, or diff; press again to close it", |
44 | | - }, |
45 | | - { |
46 | | - keys: "Alt+M", |
47 | | - description: |
48 | | - "toggle DEC mouse capture (on by default: wheel scroll, click-to-expand, drag-to-copy); off restores native terminal drag-select", |
49 | | - }, |
50 | | - { |
51 | | - keys: "Alt+E", |
52 | | - description: |
53 | | - "expand or collapse every collapsible row (tool call, diff, skill, reasoning)", |
54 | | - }, |
55 | | - { |
56 | | - keys: "Alt+T", |
57 | | - description: |
58 | | - "show or hide the task list above the prompt (hidden by default)", |
59 | | - }, |
60 | | - { |
61 | | - keys: "Alt+O", |
62 | | - description: |
63 | | - "observe a live subagent session; a system row says so when there is none", |
64 | | - }, |
65 | | - { |
66 | | - keys: "Tab", |
67 | | - description: "move focus between the prompt and the transcript", |
68 | | - }, |
69 | | - { |
70 | | - keys: "Shift+Tab", |
71 | | - description: "cycle reasoning effort for the current model", |
72 | | - }, |
73 | | - { |
74 | | - keys: "Esc", |
75 | | - description: "close the open overlay, or leave subagent observe", |
76 | | - }, |
77 | 24 | { |
78 | 25 | keys: "Ctrl+B / Ctrl+F", |
79 | 26 | description: "move the cursor back / forward one character", |
@@ -126,12 +73,37 @@ export const SHELL_SHORTCUTS: readonly ShellShortcut[] = [ |
126 | 73 | description: |
127 | 74 | "insert a newline instead of sending (Shift+Enter also works on terminals that report the modifier)", |
128 | 75 | }, |
129 | | -] as const; |
| 76 | +]; |
| 77 | + |
| 78 | +let appRows: readonly ShellShortcut[] = []; |
| 79 | +let readAppHelp: () => readonly string[] = () => |
| 80 | + appRows.map((row) => `${row.keys} — ${row.description}`); |
| 81 | + |
| 82 | +export function setAppShortcutRows(rows: readonly ShellShortcut[]): void { |
| 83 | + appRows = rows; |
| 84 | +} |
| 85 | + |
| 86 | +export function bindAppHelp(read: () => readonly string[]): () => void { |
| 87 | + const previous = readAppHelp; |
| 88 | + readAppHelp = read; |
| 89 | + return () => { |
| 90 | + readAppHelp = previous; |
| 91 | + }; |
| 92 | +} |
| 93 | + |
| 94 | +export function shellShortcuts(): readonly ShellShortcut[] { |
| 95 | + return [...appRows, ...WIDGET_DEFAULT_SHORTCUTS]; |
| 96 | +} |
| 97 | + |
| 98 | +function formatShortcut(row: ShellShortcut): string { |
| 99 | + return `${row.keys} — ${row.description}`; |
| 100 | +} |
130 | 101 |
|
131 | | -/** Help rows from the shell's own catalog, so they cannot drift from it. */ |
| 102 | +/** Keymap command lines, then widget-default rows. */ |
132 | 103 | export function helpItems(): readonly string[] { |
133 | 104 | return [ |
134 | | - ...SHELL_SHORTCUTS.map((s) => `${s.keys} — ${s.description}`), |
| 105 | + ...readAppHelp(), |
| 106 | + ...WIDGET_DEFAULT_SHORTCUTS.map(formatShortcut), |
135 | 107 | "Close help", |
136 | 108 | ]; |
137 | 109 | } |
0 commit comments