Files
l0ng-ai a723e71c9a fix(keybindings): give Alt+1..9 a way out, and stop one bad line resetting the file (#901)
Alt+1..9 are vim's tab keys, and tty7 takes all nine for Go to Tab.
Two things stood between the reporter and getting them back.

**Nothing in the app could leave an action unbound.** Backspace on a
Keybindings row that has recorded nothing *reset* the row — dropped the
override so the action gets its shipped chord back. On a row nobody had
overridden, which is every row the first time it is looked at, that is a
no-op: pressing it over Alt+1 left Alt+1 sitting exactly where it was,
which reads as the default restoring itself. `config.json` has spelled
"no chord" as `[]` since #868, but no gesture wrote it.

Backspace now writes that empty list. The row falls to `—` and grows the
**Reset** button every overridden row has, which is the way back to the
default. The capture hint names the key, and the docs say what it is for.

**A keybinding line serde could not read failed the whole `Config`.**
`keybindings` is a hand-edited map and was strict, so `"ActivateTab1":
null` — or a number, or an object — quarantined `config.json` and started
the app on built-in defaults. Every rebinding in the file then read as
its shipped default, and the next settings write persisted those
defaults over what the user had written. It now reads one entry at a
time, like every other hand-edited nested key here: the lines that name
a shortcut bind, a line that does not is logged and skipped.

Tests, each failing on the unfixed code:

- `ui::app::keybinding_gpui_tests::backspace_on_a_row_unbinds_the_action_rather_than_restoring_its_default`
- `core::config::tests::a_keybinding_line_that_cannot_be_read_does_not_take_the_config_with_it`

and `ui::keymap::gpui_tests::alt_digits_can_be_moved_off_the_tab_actions_for_good`
pins the merge and a save/reload round trip: a list replaces the shipped
Alt+1, `[]` leaves nothing, and neither comes back after a restart.

Fixes #901

Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM
2026-09-21 22:42:45 +08:00

138 lines
5.3 KiB
Plaintext

---
title: "Keybindings"
description: "Rebinding anything, chord sequences, and the tmux preset."
---
**Settings → Keybindings** (<kbd>⌘ ,</kbd>) lists every shortcut in the app,
grouped the same way the command palette is. The search box at the top of
Settings reaches this page too: type what a feature is called — `split`,
`commit`, `sftp` — and the page narrows to the shortcuts for it. Action names
work as well as labels, so a binding you read about in `keybindings.json` is
one search away from the row that sets it.
## Rebinding
Click a shortcut and press the new keys. It saves after a brief pause.
| | |
|---|---|
| Press keys | Set the binding |
| Press more keys | Chain a sequence — <kbd>⌃ B</kbd> then <kbd>X</kbd> |
| <kbd>Esc</kbd> | Cancel |
| <kbd>⌫</kbd> | Remove the last key — or, pressed first, leave the action with **no shortcut** |
Leaving an action unbound is how you hand a key back to whatever is running in
the terminal: <kbd>⌥ 1</kbd>…<kbd>⌥ 9</kbd> jump between tabs by default, and
vim wants them for its own tabs. Clear the nine **Go to Tab** rows and the
digits go straight through. A cleared row shows `—` and grows a **Reset**
button, which puts the default back.
**Restore all defaults** at the bottom undoes every rebinding at once. There is
no undo for that one.
<Frame caption="Placeholder — screenshot: the Keybindings page mid-capture, showing “Press keys…”">
<img src="/images/placeholder.svg" alt="Rebinding a shortcut" />
</Frame>
## Actions with no default key
Some actions ship deliberately unbound, because there is no obvious key left to
take: pane resize and swap, workspace selection, most git commands, SFTP, and
the panel tabs. They are all in the command palette, and all bindable here.
## Editing `config.json` instead
```json
{
"keybindings": {
"NextTab": "cmd-shift-]",
"SplitRight": ["cmd-d"],
"ResizePaneLeft": "ctrl-alt-left",
"ToggleFullscreen": ""
}
}
```
An action's value takes one of two shapes:
| Value | Means |
|---|---|
| `"cmd-shift-]"` | **Add** this shortcut. The default keeps working — <kbd>⌃ ⇥</kbd> still switches tabs |
| `["cmd-d"]` | **Replace**: exactly these shortcuts, nothing else. List several to have several |
| `""` or `[]` | **Unbind** the action |
Recording a shortcut on the Settings page replaces, so it writes the list
shape. A shortcut you add that another action already uses is the one that
runs. The exception is the terminal's own shortcuts: copy, paste, find, clear
scrollback and insert newline keep theirs while a terminal has focus.
Up to 26.9.2 a string replaced the default, and the Settings page wrote
strings. So a shortcut you recorded in those versions now works *beside* the
default it was meant to replace. To drop the default again, record the shortcut again
or wrap it in a list: `"NewTab": ["cmd-shift-n"]`.
The syntax is modifiers joined by `-`, then the key. Chords are separated by a
space.
| Token | Means |
|---|---|
| `secondary` | <kbd>⌘</kbd> on macOS, <kbd>Ctrl</kbd> elsewhere |
| `cmd` · `ctrl` · `alt` · `shift` | Literal modifiers |
| `ctrl-b n` | A two-key sequence |
To keep a key for the program running in the terminal, unbind the action that
holds it. vim's tab keys, for instance:
```json
{
"keybindings": {
"ActivateTab1": [], "ActivateTab2": [], "ActivateTab3": [],
"ActivateTab4": [], "ActivateTab5": [], "ActivateTab6": [],
"ActivateTab7": [], "ActivateTab8": [], "ActivateTab9": []
}
}
```
An unknown action name, an invalid keystroke, or a line that is neither a
shortcut nor a list of them is skipped with a warning in the log rather than
breaking the rest of your bindings.
The full action list is on the [keyboard shortcuts](/reference/keyboard-shortcuts)
page.
## The tmux preset
**Settings → Keybindings → Preset → tmux** remaps pane and tab actions onto a
prefix — <kbd>⌃ B</kbd> by default, changeable in the **Prefix** field beside
it.
| | |
|---|---|
| <kbd>⌃ B</kbd> <kbd>C</kbd> · <kbd>X</kbd> | New tab · close tab |
| <kbd>⌃ B</kbd> <kbd>%</kbd> · <kbd>"</kbd> | Split right · split down |
| <kbd>⌃ B</kbd> <kbd>←→↑↓</kbd> | Move focus |
| <kbd>⌃ B</kbd> <kbd>⌃ ←→↑↓</kbd> | Resize the pane |
| <kbd>⌃ B</kbd> <kbd>O</kbd> · <kbd>;</kbd> | Next pane · previous pane |
| <kbd>⌃ B</kbd> <kbd>&#123;</kbd> · <kbd>&#125;</kbd> | Swap with the previous · next pane |
| <kbd>⌃ B</kbd> <kbd>Z</kbd> | Zoom the pane |
| <kbd>⌃ B</kbd> <kbd>N</kbd> · <kbd>P</kbd> | Next tab · previous tab |
| <kbd>⌃ B</kbd> <kbd>1</kbd>…<kbd>9</kbd> | Jump to a tab |
Two details that make it livable:
- A **bare prefix** reaches the shell after about a second, so <kbd>⌃ B</kbd>
still works as "back one character" when you meant it.
- **Prefix plus an unbound key** is passed straight through to the terminal, so
a tmux binding you did not remap still lands in whatever is running.
## Some non-obvious defaults
| | |
|---|---|
| <kbd>⇧ ⏎</kbd> · <kbd>⌥ ⏎</kbd> | Insert a newline at the prompt instead of submitting (`InsertNewline`) |
| <kbd>⌘ ⇧ ⏎</kbd> | Zoom the focused pane |
| <kbd>⌘ ⇧ E</kbd> | Toggle the code panel |
| <kbd>⌘ ⇧ R</kbd> | Restart the SSH session in this pane |
| <kbd>⌘ ⇧ O</kbd> | Workspace switcher |
| <kbd>⌘ ⇧ N</kbd> | New workspace |