From c58956a9a282afcbe88210272710165b13ccf853 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sat, 25 Apr 2026 19:57:17 -0500 Subject: [PATCH] fix(tui): accept Alt+G as Ctrl+G fallback in VSCode/Cursor terminals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- ui-tui/README.md | 4 ++-- ui-tui/src/app/useInputHandlers.ts | 5 ++++- ui-tui/src/content/hotkeys.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ui-tui/README.md b/ui-tui/README.md index 4d7090d5ac..988448718f 100644 --- a/ui-tui/README.md +++ b/ui-tui/README.md @@ -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) | | `Ctrl+C` | Interrupt active run, or clear the current draft, or exit if nothing is pending | | `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+V` / `Alt+V` | Paste text first, then fall back to image/path attachment when applicable | | `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. - 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. -- `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`. ## Rendering diff --git a/ui-tui/src/app/useInputHandlers.ts b/ui-tui/src/app/useInputHandlers.ts index d7ac30d932..88d065feeb 100644 --- a/ui-tui/src/app/useInputHandlers.ts +++ b/ui-tui/src/app/useInputHandlers.ts @@ -366,7 +366,10 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult { 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() } diff --git a/ui-tui/src/content/hotkeys.ts b/ui-tui/src/content/hotkeys.ts index 4944a19a6a..f14444691d 100644 --- a/ui-tui/src/content/hotkeys.ts +++ b/ui-tui/src/content/hotkeys.ts @@ -18,7 +18,7 @@ const copyHotkeys: [string, string][] = isMac export const HOTKEYS: [string, string][] = [ ...copyHotkeys, [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)'], [paste + '+V / /paste', 'paste text; /paste attaches clipboard image'], ['Tab', 'apply completion'],