From a92ce848e150bafa7816d705e3569c50ee4c35cc Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Tue, 10 Mar 2026 21:32:20 -0700 Subject: [PATCH] custom keybinding documentation! --- docs/reference/keyboard-shortcuts.md | 227 +++++++++++------- packages/cli/src/ui/key/keyBindings.ts | 1 + scripts/generate-keybindings-doc.ts | 8 +- .../tests/generate-keybindings-doc.test.ts | 16 +- 4 files changed, 153 insertions(+), 99 deletions(-) diff --git a/docs/reference/keyboard-shortcuts.md b/docs/reference/keyboard-shortcuts.md index 3529ead3ec..97aad0f5b2 100644 --- a/docs/reference/keyboard-shortcuts.md +++ b/docs/reference/keyboard-shortcuts.md @@ -8,126 +8,173 @@ available combinations. #### Basic Controls -| Action | Keys | -| --------------------------------------------------------------- | ------------------- | -| Confirm the current selection or choice. | `Enter` | -| Dismiss dialogs or cancel the current focus. | `Esc`
`Ctrl+[` | -| Cancel the current request or quit the CLI when input is empty. | `Ctrl+C` | -| Exit the CLI when the input buffer is empty. | `Ctrl+D` | +| Command | Action | Keys | +| --------------- | --------------------------------------------------------------- | ------------------- | +| `basic.confirm` | Confirm the current selection or choice. | `Enter` | +| `basic.cancel` | Dismiss dialogs or cancel the current focus. | `Esc`
`Ctrl+[` | +| `basic.quit` | Cancel the current request or quit the CLI when input is empty. | `Ctrl+C` | +| `basic.exit` | Exit the CLI when the input buffer is empty. | `Ctrl+D` | #### Cursor Movement -| Action | Keys | -| ------------------------------------------- | ------------------------------------------ | -| Move the cursor to the start of the line. | `Ctrl+A`
`Home` | -| Move the cursor to the end of the line. | `Ctrl+E`
`End` | -| Move the cursor up one line. | `Up` | -| Move the cursor down one line. | `Down` | -| Move the cursor one character to the left. | `Left` | -| Move the cursor one character to the right. | `Right`
`Ctrl+F` | -| Move the cursor one word to the left. | `Ctrl+Left`
`Alt+Left`
`Alt+B` | -| Move the cursor one word to the right. | `Ctrl+Right`
`Alt+Right`
`Alt+F` | +| Command | Action | Keys | +| ------------------ | ------------------------------------------- | ------------------------------------------ | +| `cursor.home` | Move the cursor to the start of the line. | `Ctrl+A`
`Home` | +| `cursor.end` | Move the cursor to the end of the line. | `Ctrl+E`
`End` | +| `cursor.up` | Move the cursor up one line. | `Up` | +| `cursor.down` | Move the cursor down one line. | `Down` | +| `cursor.left` | Move the cursor one character to the left. | `Left` | +| `cursor.right` | Move the cursor one character to the right. | `Right`
`Ctrl+F` | +| `cursor.wordLeft` | Move the cursor one word to the left. | `Ctrl+Left`
`Alt+Left`
`Alt+B` | +| `cursor.wordRight` | Move the cursor one word to the right. | `Ctrl+Right`
`Alt+Right`
`Alt+F` | #### Editing -| Action | Keys | -| ------------------------------------------------ | -------------------------------------------------------- | -| Delete from the cursor to the end of the line. | `Ctrl+K` | -| Delete from the cursor to the start of the line. | `Ctrl+U` | -| Clear all text in the input field. | `Ctrl+C` | -| Delete the previous word. | `Ctrl+Backspace`
`Alt+Backspace`
`Ctrl+W` | -| Delete the next word. | `Ctrl+Delete`
`Alt+Delete`
`Alt+D` | -| Delete the character to the left. | `Backspace`
`Ctrl+H` | -| Delete the character to the right. | `Delete`
`Ctrl+D` | -| Undo the most recent text edit. | `Cmd/Win+Z`
`Alt+Z` | -| Redo the most recent undone text edit. | `Ctrl+Shift+Z`
`Shift+Cmd/Win+Z`
`Alt+Shift+Z` | +| Command | Action | Keys | +| ---------------------- | ------------------------------------------------ | -------------------------------------------------------- | +| `edit.deleteRightAll` | Delete from the cursor to the end of the line. | `Ctrl+K` | +| `edit.deleteLeftAll` | Delete from the cursor to the start of the line. | `Ctrl+U` | +| `edit.clear` | Clear all text in the input field. | `Ctrl+C` | +| `edit.deleteWordLeft` | Delete the previous word. | `Ctrl+Backspace`
`Alt+Backspace`
`Ctrl+W` | +| `edit.deleteWordRight` | Delete the next word. | `Ctrl+Delete`
`Alt+Delete`
`Alt+D` | +| `edit.deleteLeft` | Delete the character to the left. | `Backspace`
`Ctrl+H` | +| `edit.deleteRight` | Delete the character to the right. | `Delete`
`Ctrl+D` | +| `edit.undo` | Undo the most recent text edit. | `Cmd/Win+Z`
`Alt+Z` | +| `edit.redo` | Redo the most recent undone text edit. | `Ctrl+Shift+Z`
`Shift+Cmd/Win+Z`
`Alt+Shift+Z` | #### Scrolling -| Action | Keys | -| ------------------------ | ----------------------------- | -| Scroll content up. | `Shift+Up` | -| Scroll content down. | `Shift+Down` | -| Scroll to the top. | `Ctrl+Home`
`Shift+Home` | -| Scroll to the bottom. | `Ctrl+End`
`Shift+End` | -| Scroll up by one page. | `Page Up` | -| Scroll down by one page. | `Page Down` | +| Command | Action | Keys | +| ----------------- | ------------------------ | ----------------------------- | +| `scroll.up` | Scroll content up. | `Shift+Up` | +| `scroll.down` | Scroll content down. | `Shift+Down` | +| `scroll.home` | Scroll to the top. | `Ctrl+Home`
`Shift+Home` | +| `scroll.end` | Scroll to the bottom. | `Ctrl+End`
`Shift+End` | +| `scroll.pageUp` | Scroll up by one page. | `Page Up` | +| `scroll.pageDown` | Scroll down by one page. | `Page Down` | #### History & Search -| Action | Keys | -| -------------------------------------------- | -------- | -| Show the previous entry in history. | `Ctrl+P` | -| Show the next entry in history. | `Ctrl+N` | -| Start reverse search through history. | `Ctrl+R` | -| Submit the selected reverse-search match. | `Enter` | -| Accept a suggestion while reverse searching. | `Tab` | +| Command | Action | Keys | +| ----------------------- | -------------------------------------------- | -------- | +| `history.previous` | Show the previous entry in history. | `Ctrl+P` | +| `history.next` | Show the next entry in history. | `Ctrl+N` | +| `history.search.start` | Start reverse search through history. | `Ctrl+R` | +| `history.search.submit` | Submit the selected reverse-search match. | `Enter` | +| `history.search.accept` | Accept a suggestion while reverse searching. | `Tab` | #### Navigation -| Action | Keys | -| -------------------------------------------------- | --------------- | -| Move selection up in lists. | `Up` | -| Move selection down in lists. | `Down` | -| Move up within dialog options. | `Up`
`K` | -| Move down within dialog options. | `Down`
`J` | -| Move to the next item or question in a dialog. | `Tab` | -| Move to the previous item or question in a dialog. | `Shift+Tab` | +| Command | Action | Keys | +| --------------------- | -------------------------------------------------- | --------------- | +| `nav.up` | Move selection up in lists. | `Up` | +| `nav.down` | Move selection down in lists. | `Down` | +| `nav.dialog.up` | Move up within dialog options. | `Up`
`K` | +| `nav.dialog.down` | Move down within dialog options. | `Down`
`J` | +| `nav.dialog.next` | Move to the next item or question in a dialog. | `Tab` | +| `nav.dialog.previous` | Move to the previous item or question in a dialog. | `Shift+Tab` | #### Suggestions & Completions -| Action | Keys | -| --------------------------------------- | -------------------- | -| Accept the inline suggestion. | `Tab`
`Enter` | -| Move to the previous completion option. | `Up`
`Ctrl+P` | -| Move to the next completion option. | `Down`
`Ctrl+N` | -| Expand an inline suggestion. | `Right` | -| Collapse an inline suggestion. | `Left` | +| Command | Action | Keys | +| ----------------------- | --------------------------------------- | -------------------- | +| `suggest.accept` | Accept the inline suggestion. | `Tab`
`Enter` | +| `suggest.focusPrevious` | Move to the previous completion option. | `Up`
`Ctrl+P` | +| `suggest.focusNext` | Move to the next completion option. | `Down`
`Ctrl+N` | +| `suggest.expand` | Expand an inline suggestion. | `Right` | +| `suggest.collapse` | Collapse an inline suggestion. | `Left` | #### Text Input -| Action | Keys | -| ---------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| Submit the current prompt. | `Enter` | -| Insert a newline without submitting. | `Ctrl+Enter`
`Cmd/Win+Enter`
`Alt+Enter`
`Shift+Enter`
`Ctrl+J` | -| Open the current prompt or the plan in an external editor. | `Ctrl+X` | -| Paste from the clipboard. | `Ctrl+V`
`Cmd/Win+V`
`Alt+V` | +| Command | Action | Keys | +| -------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `input.submit` | Submit the current prompt. | `Enter` | +| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`
`Cmd/Win+Enter`
`Alt+Enter`
`Shift+Enter`
`Ctrl+J` | +| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+X` | +| `input.paste` | Paste from the clipboard. | `Ctrl+V`
`Cmd/Win+V`
`Alt+V` | #### App Controls -| Action | Keys | -| -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| Toggle detailed error information. | `F12` | -| Toggle the full TODO list. | `Ctrl+T` | -| Show IDE context details. | `Ctrl+G` | -| Toggle Markdown rendering. | `Alt+M` | -| Toggle copy mode when in alternate buffer mode. | `Ctrl+S` | -| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl+Y` | -| Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). Plan mode is skipped when the agent is busy. | `Shift+Tab` | -| Expand and collapse blocks of content when not in alternate buffer mode. | `Ctrl+O` | -| Expand or collapse a paste placeholder when cursor is over placeholder. | `Ctrl+O` | -| Move focus from Gemini to the active shell. | `Tab` | -| Move focus from the shell back to Gemini. | `Shift+Tab` | -| Clear the terminal screen and redraw the UI. | `Ctrl+L` | -| Restart the application. | `R`
`Shift+R` | -| Suspend the CLI and move it to the background. | `Ctrl+Z` | -| Show warning when trying to move focus away from shell input. | `Tab` | +| Command | Action | Keys | +| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `app.showErrorDetails` | Toggle detailed error information. | `F12` | +| `app.showFullTodos` | Toggle the full TODO list. | `Ctrl+T` | +| `app.showIdeContextDetail` | Show IDE context details. | `Ctrl+G` | +| `app.toggleMarkdown` | Toggle Markdown rendering. | `Alt+M` | +| `app.toggleCopyMode` | Toggle copy mode when in alternate buffer mode. | `Ctrl+S` | +| `app.toggleYolo` | Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl+Y` | +| `app.cycleApprovalMode` | Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). Plan mode is skipped when the agent is busy. | `Shift+Tab` | +| `app.showMoreLines` | Expand and collapse blocks of content when not in alternate buffer mode. | `Ctrl+O` | +| `app.expandPaste` | Expand or collapse a paste placeholder when cursor is over placeholder. | `Ctrl+O` | +| `app.focusShellInput` | Move focus from Gemini to the active shell. | `Tab` | +| `app.unfocusShellInput` | Move focus from the shell back to Gemini. | `Shift+Tab` | +| `app.clearScreen` | Clear the terminal screen and redraw the UI. | `Ctrl+L` | +| `app.restart` | Restart the application. | `R`
`Shift+R` | +| `app.suspend` | Suspend the CLI and move it to the background. | `Ctrl+Z` | +| `app.showShellUnfocusWarning` | Show warning when trying to move focus away from shell input. | `Tab` | #### Background Shell Controls -| Action | Keys | -| ------------------------------------------------------------------ | ----------- | -| Dismiss background shell list. | `Esc` | -| Confirm selection in background shell list. | `Enter` | -| Toggle current background shell visibility. | `Ctrl+B` | -| Toggle background shell list. | `Ctrl+L` | -| Kill the active background shell. | `Ctrl+K` | -| Move focus from background shell to Gemini. | `Shift+Tab` | -| Move focus from background shell list to Gemini. | `Tab` | -| Show warning when trying to move focus away from background shell. | `Tab` | +| Command | Action | Keys | +| --------------------------- | ------------------------------------------------------------------ | ----------- | +| `background.escape` | Dismiss background shell list. | `Esc` | +| `background.select` | Confirm selection in background shell list. | `Enter` | +| `background.toggle` | Toggle current background shell visibility. | `Ctrl+B` | +| `background.toggleList` | Toggle background shell list. | `Ctrl+L` | +| `background.kill` | Kill the active background shell. | `Ctrl+K` | +| `background.unfocus` | Move focus from background shell to Gemini. | `Shift+Tab` | +| `background.unfocusList` | Move focus from background shell list to Gemini. | `Tab` | +| `background.unfocusWarning` | Show warning when trying to move focus away from background shell. | `Tab` | +## Customizing Keybindings + +You can customize your keybindings by creating or modifying the +`keybindings.json` file in your home gemini directory (typically +`~/.gemini/keybindings.json`). The CLI allows you to bind specific commands to +custom key combinations. + +### Configuration Format + +The configuration uses a JSON array of objects, similar to VS Code's keybinding +schema. Each object must specify a `command` from the reference tables above and +a `key` combination. + +```json +[ + { + "command": "input.submit", + "key": "ctrl+enter" + }, + { + "command": "edit.clear", + "key": "ctrl+l" + } +] +``` + +### Keyboard Rules + +- **Explicit Modifiers**: Key matching is explicit. For example, a binding for + `ctrl+f` will only trigger on exactly `ctrl+f`, not `ctrl+shift+f` or + `alt+ctrl+f`. You must specify the exact modifier keys (`ctrl`, `shift`, + `alt`/`opt`/`option`, `cmd`/`meta`). +- **Literal Characters**: Terminals often translate complex key combinations + (especially on macOS with the `Option` key) into special characters. To handle + this reliably across all operating systems and SSH sessions, you can bind + directly to the literal character produced. For example, instead of trying to + bind `shift+5`, bind directly to `%`. +- **Special Keys**: Supported special keys include: + - **Navigation**: `up`, `down`, `left`, `right`, `home`, `end`, `pageup`, + `pagedown` + - **Actions**: `enter`, `escape`, `tab`, `space`, `backspace`, `delete`, + `clear`, `insert`, `printscreen` + - **Toggles**: `capslock`, `numlock`, `scrolllock`, `pausebreak` + - **Function Keys**: `f1` through `f35` + - **Numpad**: `numpad0` through `numpad9`, `numpad_add`, `numpad_subtract`, + `numpad_multiply`, `numpad_divide`, `numpad_decimal`, `numpad_separator` + ## Additional context-specific shortcuts - `Option+B/F/M` (macOS only): Are interpreted as `Cmd+B/F/M` even if your diff --git a/packages/cli/src/ui/key/keyBindings.ts b/packages/cli/src/ui/key/keyBindings.ts index cd234022fc..b151ad8ee3 100644 --- a/packages/cli/src/ui/key/keyBindings.ts +++ b/packages/cli/src/ui/key/keyBindings.ts @@ -134,6 +134,7 @@ export class KeyBinding { 'insert', 'numlock', 'scrolllock', + 'printscreen', 'numpad_multiply', 'numpad_add', 'numpad_separator', diff --git a/scripts/generate-keybindings-doc.ts b/scripts/generate-keybindings-doc.ts index ee908ff25d..10c95d9649 100644 --- a/scripts/generate-keybindings-doc.ts +++ b/scripts/generate-keybindings-doc.ts @@ -27,6 +27,7 @@ const OUTPUT_RELATIVE_PATH = ['docs', 'reference', 'keyboard-shortcuts.md']; import { formatKeyBinding } from '../packages/cli/src/ui/key/keybindingUtils.js'; export interface KeybindingDocCommand { + command: string; description: string; bindings: readonly KeyBinding[]; } @@ -81,6 +82,7 @@ export function buildDefaultDocSections(): readonly KeybindingDocSection[] { return commandCategories.map((category) => ({ title: category.title, commands: category.commands.map((command) => ({ + command: command, description: commandDescriptions[command], bindings: defaultKeyBindingConfig.get(command) ?? [], })), @@ -94,14 +96,14 @@ export function renderDocumentation( const rows = section.commands.map((command) => { const formattedBindings = formatBindings(command.bindings); const keysCell = formattedBindings.join('
'); - return `| ${command.description} | ${keysCell} |`; + return `| \`${command.command}\` | ${command.description} | ${keysCell} |`; }); return [ `#### ${section.title}`, '', - '| Action | Keys |', - '| --- | --- |', + '| Command | Action | Keys |', + '| --- | --- | --- |', ...rows, ].join('\n'); }); diff --git a/scripts/tests/generate-keybindings-doc.test.ts b/scripts/tests/generate-keybindings-doc.test.ts index 19ba2e0f98..e6319e03fe 100644 --- a/scripts/tests/generate-keybindings-doc.test.ts +++ b/scripts/tests/generate-keybindings-doc.test.ts @@ -10,6 +10,7 @@ import { renderDocumentation, type KeybindingDocSection, } from '../generate-keybindings-doc.ts'; +import { KeyBinding } from '../../packages/cli/src/ui/key/keyBindings.js'; describe('generate-keybindings-doc', () => { it('keeps keyboard shortcut documentation in sync in check mode', async () => { @@ -31,12 +32,14 @@ describe('generate-keybindings-doc', () => { title: 'Custom Controls', commands: [ { + command: 'custom.trigger', description: 'Trigger custom action.', - bindings: [{ key: 'x', ctrl: true }], + bindings: [new KeyBinding('ctrl+x')], }, { + command: 'custom.submit', description: 'Submit with Enter if no modifiers are held.', - bindings: [{ key: 'enter', shift: false, ctrl: false }], + bindings: [new KeyBinding('enter')], }, ], }, @@ -44,11 +47,9 @@ describe('generate-keybindings-doc', () => { title: 'Navigation', commands: [ { + command: 'nav.up', description: 'Move up through results.', - bindings: [ - { key: 'up', shift: false }, - { key: 'p', shift: false, ctrl: true }, - ], + bindings: [new KeyBinding('up'), new KeyBinding('ctrl+p')], }, ], }, @@ -56,11 +57,14 @@ describe('generate-keybindings-doc', () => { const markdown = renderDocumentation(sections); expect(markdown).toContain('#### Custom Controls'); + expect(markdown).toContain('`custom.trigger`'); expect(markdown).toContain('Trigger custom action.'); expect(markdown).toContain('`Ctrl+X`'); + expect(markdown).toContain('`custom.submit`'); expect(markdown).toContain('Submit with Enter if no modifiers are held.'); expect(markdown).toContain('`Enter`'); expect(markdown).toContain('#### Navigation'); + expect(markdown).toContain('`nav.up`'); expect(markdown).toContain('Move up through results.'); expect(markdown).toContain('`Up`
`Ctrl+P`'); });