Skip to content

Commit e196f4a

Browse files
committed
feat: add custom keybindings support with VSCode-style configuration
Refactor keybindings into a declarative action registry with user override support via keybindings.json. Includes JSON schema, config sidebar entry, documentation, and Go backend support for reading keybindings config.
1 parent e4e77e7 commit e196f4a

13 files changed

Lines changed: 583 additions & 224 deletions

File tree

docs/docs/keybindings.mdx

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,74 @@ Chords are shown with a + between the keys. You have 2 seconds to hit the 2nd ch
105105
| <Kbd k="Shift:PageUp"/> | Scroll up one page |
106106
| <Kbd k="Shift:PageDown"/>| Scroll down one page |
107107

108-
## Customizeable Systemwide Global Hotkey
108+
## Customizing Keybindings
109+
110+
You can override, remap, or disable any default keybinding by editing `keybindings.json` in the Wave config directory (`~/.config/waveterm/keybindings.json`). You can also edit this file from within Wave by opening the Config editor and selecting "Keybindings" in the sidebar.
111+
112+
The file uses a VS Code-style array format. Each entry maps a key combination to an action ID. Only overrides are needed — all defaults apply automatically.
113+
114+
### Key Syntax
115+
116+
Key combinations use colon-separated format:
117+
118+
- **Modifiers:** `Cmd` (macOS Command / Windows-Linux Meta), `Ctrl`, `Shift`, `Alt` (macOS Option), `Meta`
119+
- **Special keys:** `ArrowUp`, `ArrowDown`, `ArrowLeft`, `ArrowRight`, `Home`, `End`, `Escape`, `Enter`, `Tab`, `Space`, `Backspace`, `Delete`
120+
- **Letters and digits:** Lowercase (`a``z`), digits (`0``9`)
121+
122+
### Examples
123+
124+
**Rebind a key:** Change "new tab" from <Kbd k="Cmd:t"/> to <Kbd k="Cmd:Shift:t"/>:
125+
```json
126+
[
127+
{ "key": "Cmd:Shift:t", "command": "tab:new" }
128+
]
129+
```
130+
131+
**Disable a keybinding:** Remove <Kbd k="Cmd:w"/> close block:
132+
```json
133+
[
134+
{ "key": null, "command": "-block:close" }
135+
]
136+
```
137+
138+
**Swap two keys:**
139+
```json
140+
[
141+
{ "key": "Cmd:d", "command": "block:splitDown" },
142+
{ "key": "Cmd:Shift:d", "command": "block:splitRight" }
143+
]
144+
```
145+
146+
### Action IDs
147+
148+
| Action ID | Default Key | Description |
149+
| --- | --- | --- |
150+
| `tab:new` | <Kbd k="Cmd:t"/> | Open a new tab |
151+
| `tab:close` | <Kbd k="Cmd:Shift:w"/> | Close the current tab |
152+
| `tab:prev` | <Kbd k="Cmd:["/> | Switch to previous tab |
153+
| `tab:next` | <Kbd k="Cmd:]"/> | Switch to next tab |
154+
| `tab:switchTo1``tab:switchTo9` | <Kbd k="Cmd:1"/>–<Kbd k="Cmd:9"/> | Switch to tab N |
155+
| `block:new` | <Kbd k="Cmd:n"/> | Open a new block |
156+
| `block:close` | <Kbd k="Cmd:w"/> | Close the current block |
157+
| `block:splitRight` | <Kbd k="Cmd:d"/> | Split right |
158+
| `block:splitDown` | <Kbd k="Cmd:Shift:d"/> | Split down |
159+
| `block:magnify` | <Kbd k="Cmd:m"/> | Magnify/unmagnify block |
160+
| `block:refocus` | <Kbd k="Cmd:i"/> | Refocus the current block |
161+
| `block:navUp/Down/Left/Right` | <Kbd k="Ctrl:Shift:Arrows"/> | Navigate between blocks |
162+
| `block:switchTo1``block:switchTo9` | <Kbd k="Ctrl:Shift:1"/>–<Kbd k="Ctrl:Shift:9"/> | Switch to block N |
163+
| `block:switchToAI` | <Kbd k="Ctrl:Shift:0"/> | Focus WaveAI input |
164+
| `block:replaceWithLauncher` | <Kbd k="Ctrl:Shift:x"/> | Replace block with launcher |
165+
| `app:search` | <Kbd k="Cmd:f"/> | Find/search |
166+
| `app:openConnection` | <Kbd k="Cmd:g"/> | Open connection switcher |
167+
| `app:toggleAIPanel` | <Kbd k="Cmd:Shift:a"/> | Toggle WaveAI panel |
168+
| `app:toggleWidgetsSidebar` | <Kbd k="Cmd:b"/> | Toggle widgets sidebar |
169+
| `term:toggleMultiInput` | <Kbd k="Ctrl:Shift:i"/> | Toggle terminal multi-input |
170+
| `generic:cancel` | <Kbd k="Escape"/> | Close modals/search |
171+
| `block:splitChord` | <Kbd k="Ctrl:Shift:s"/> | Initiate split chord |
172+
173+
Changes take effect immediately — no restart required.
174+
175+
## Customizable Systemwide Global Hotkey
109176

110177
Wave allows setting a custom global hotkey to focus your most recent window from anywhere in your computer. For more information on this, see [the config docs](./config#customizable-systemwide-global-hotkey).
111178

frontend/app/monaco/schemaendpoints.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import aipresetsSchema from "../../../schema/aipresets.json";
55
import backgroundsSchema from "../../../schema/backgrounds.json";
66
import connectionsSchema from "../../../schema/connections.json";
7+
import keybindingsSchema from "../../../schema/keybindings.json";
78
import settingsSchema from "../../../schema/settings.json";
89
import waveaiSchema from "../../../schema/waveai.json";
910
import widgetsSchema from "../../../schema/widgets.json";
@@ -45,6 +46,11 @@ const MonacoSchemas: SchemaInfo[] = [
4546
fileMatch: ["*/WAVECONFIGPATH/widgets.json"],
4647
schema: widgetsSchema,
4748
},
49+
{
50+
uri: "wave://schema/keybindings.json",
51+
fileMatch: ["*/WAVECONFIGPATH/keybindings.json"],
52+
schema: keybindingsSchema,
53+
},
4854
];
4955

5056
export { MonacoSchemas };

frontend/app/store/global-atoms.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
5959
const settingsAtom = atom((get) => {
6060
return get(fullConfigAtom)?.settings ?? {};
6161
}) as Atom<SettingsType>;
62+
const keybindingsAtom = atom((get) => {
63+
const fullConfig = get(fullConfigAtom);
64+
if (!fullConfig?.keybindings) return [];
65+
try {
66+
const raw = fullConfig.keybindings;
67+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
68+
return Array.isArray(parsed) ? parsed : [];
69+
} catch {
70+
console.warn("Failed to parse keybindings.json");
71+
return [];
72+
}
73+
});
6274
const hasCustomAIPresetsAtom = atom((get) => {
6375
const fullConfig = get(fullConfigAtom);
6476
if (!fullConfig?.presets) {
@@ -136,6 +148,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
136148
fullConfigAtom,
137149
waveaiModeConfigAtom,
138150
settingsAtom,
151+
keybindingsAtom,
139152
hasCustomAIPresetsAtom,
140153
hasConfigErrors,
141154
staticTabId: staticTabIdAtom,

0 commit comments

Comments
 (0)