fix(tui): accept Alt+G as Ctrl+G fallback in VSCode/Cursor terminals

VSCode and Cursor bind Ctrl+G to "Find Next" at the editor level, so
the keystroke never reaches the embedded terminal — Ctrl+G to open
\$EDITOR was effectively dead inside those IDEs.

Alt+G is unbound in both editors and reaches the TUI cleanly as
`\x1bg` → `key.meta && ch === 'g'` after parse-keypress. Accept it
alongside the existing isAction(key, ch, 'g') check, and document the
fallback in README + the hotkeys panel.
This commit is contained in:
Brooklyn Nicholson 2026-04-25 19:57:17 -05:00
parent 3944b22506
commit c58956a9a2
3 changed files with 7 additions and 4 deletions

View file

@ -110,7 +110,7 @@ Current input behavior is split across `app.tsx`, `components/textInput.tsx`, an
| `\` + `Enter` | Append the line to the multiline buffer (fallback for terminals without modifier support) | | `\` + `Enter` | Append the line to the multiline buffer (fallback for terminals without modifier support) |
| `Ctrl+C` | Interrupt active run, or clear the current draft, or exit if nothing is pending | | `Ctrl+C` | Interrupt active run, or clear the current draft, or exit if nothing is pending |
| `Ctrl+D` | Exit | | `Ctrl+D` | Exit |
| `Ctrl+G` | Open `$EDITOR` with the current draft | | `Ctrl+G` / `Alt+G` | Open `$EDITOR` with the current draft (use `Alt+G` in VSCode/Cursor — they bind `Ctrl+G` to Find Next) |
| `Ctrl+L` | New session (same as `/clear`) | | `Ctrl+L` | New session (same as `/clear`) |
| `Ctrl+V` / `Alt+V` | Paste text first, then fall back to image/path attachment when applicable | | `Ctrl+V` / `Alt+V` | Paste text first, then fall back to image/path attachment when applicable |
| `Tab` | Apply the active completion | | `Tab` | Apply the active completion |
@ -169,7 +169,7 @@ Notes:
- If you load a queued item into the input and resubmit plain text, that queue item is replaced, removed from the queue preview, and promoted to send next. If the agent is still busy, the edited item is moved to the front of the queue and sent after the current run completes. - If you load a queued item into the input and resubmit plain text, that queue item is replaced, removed from the queue preview, and promoted to send next. If the agent is still busy, the edited item is moved to the front of the queue and sent after the current run completes.
- Completion requests are debounced by 60 ms. Input starting with `/` uses `complete.slash`. A trailing token that starts with `./`, `../`, `~/`, `/`, or `@` uses `complete.path`. - Completion requests are debounced by 60 ms. Input starting with `/` uses `complete.slash`. A trailing token that starts with `./`, `../`, `~/`, `/`, or `@` uses `complete.path`.
- Text pastes are inserted inline directly into the draft. Nothing is newline-flattened. - Text pastes are inserted inline directly into the draft. Nothing is newline-flattened.
- `Ctrl+G` writes the current draft, including any multiline buffer, to a temp file, temporarily swaps screen buffers, launches `$EDITOR`, then restores the TUI and submits the saved text if the editor exits cleanly. - `Ctrl+G` (or `Alt+G` in VSCode/Cursor, which intercept `Ctrl+G` for Find Next) writes the current draft, including any multiline buffer, to a temp file, suspends Ink, launches `$EDITOR`, then restores the TUI and submits the saved text if the editor exits cleanly.
- Input history is stored in `~/.hermes/.hermes_history` or under `HERMES_HOME`. - Input history is stored in `~/.hermes/.hermes_history` or under `HERMES_HOME`.
## Rendering ## Rendering

View file

@ -366,7 +366,10 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
return voiceRecordToggle() return voiceRecordToggle()
} }
if (isAction(key, ch, 'g')) { // Alt+G is the escape hatch for terminals that swallow Ctrl+G — VSCode and
// Cursor bind it to "Find Next" by default, so the keystroke never reaches
// the embedded TUI. Alt+G arrives as `\x1bg` → meta+g across platforms.
if (isAction(key, ch, 'g') || (key.meta && ch.toLowerCase() === 'g')) {
return cActions.openEditor() return cActions.openEditor()
} }

View file

@ -18,7 +18,7 @@ const copyHotkeys: [string, string][] = isMac
export const HOTKEYS: [string, string][] = [ export const HOTKEYS: [string, string][] = [
...copyHotkeys, ...copyHotkeys,
[action + '+D', 'exit'], [action + '+D', 'exit'],
[action + '+G', 'open $EDITOR for prompt'], [action + '+G / Alt+G', 'open $EDITOR for prompt (Alt+G in VSCode/Cursor)'],
[action + '+L', 'new session (clear)'], [action + '+L', 'new session (clear)'],
[paste + '+V / /paste', 'paste text; /paste attaches clipboard image'], [paste + '+V / /paste', 'paste text; /paste attaches clipboard image'],
['Tab', 'apply completion'], ['Tab', 'apply completion'],