mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-13 15:44:56 +00:00
introduce opentui keymap as sole key/cmd engine (#26053)
This commit is contained in:
@@ -525,17 +525,27 @@ You can also define commands using markdown files in `~/.config/opencode/command
|
||||
|
||||
---
|
||||
|
||||
### Keybinds
|
||||
### Keymap
|
||||
|
||||
Customize keybinds in `tui.json`.
|
||||
Customize TUI keyboard shortcuts in `tui.json` with `keymap`.
|
||||
|
||||
```json title="tui.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"keybinds": {}
|
||||
"keymap": {
|
||||
"sections": {
|
||||
"global": {
|
||||
"command.palette.show": "ctrl+p"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`keymap` is merged with built-in defaults, so you only need to configure the shortcuts you want to change.
|
||||
|
||||
The older `keybinds` field is deprecated and only applies when `keymap` is not present.
|
||||
|
||||
[Learn more here](/docs/keybinds).
|
||||
|
||||
---
|
||||
|
||||
@@ -1,144 +1,317 @@
|
||||
---
|
||||
title: Keybinds
|
||||
description: Customize your keybinds.
|
||||
description: Customize your keyboard shortcuts.
|
||||
---
|
||||
|
||||
OpenCode has a list of keybinds that you can customize through `tui.json`.
|
||||
OpenCode customizes TUI keyboard shortcuts with `keymap` in `tui.json`.
|
||||
|
||||
```json title="tui.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"keybinds": {
|
||||
"leader": "ctrl+x",
|
||||
"app_exit": "ctrl+c,ctrl+d,<leader>q",
|
||||
"editor_open": "<leader>e",
|
||||
"theme_list": "<leader>t",
|
||||
"sidebar_toggle": "<leader>b",
|
||||
"scrollbar_toggle": "none",
|
||||
"username_toggle": "none",
|
||||
"status_view": "<leader>s",
|
||||
"tool_details": "none",
|
||||
"session_export": "<leader>x",
|
||||
"session_new": "<leader>n",
|
||||
"session_list": "<leader>l",
|
||||
"session_timeline": "<leader>g",
|
||||
"session_fork": "none",
|
||||
"session_rename": "ctrl+r",
|
||||
"session_share": "none",
|
||||
"session_unshare": "none",
|
||||
"session_interrupt": "escape",
|
||||
"session_compact": "<leader>c",
|
||||
"session_child_first": "<leader>down",
|
||||
"session_child_cycle": "right",
|
||||
"session_child_cycle_reverse": "left",
|
||||
"session_parent": "up",
|
||||
"messages_page_up": "pageup,ctrl+alt+b",
|
||||
"messages_page_down": "pagedown,ctrl+alt+f",
|
||||
"messages_line_up": "ctrl+alt+y",
|
||||
"messages_line_down": "ctrl+alt+e",
|
||||
"messages_half_page_up": "ctrl+alt+u",
|
||||
"messages_half_page_down": "ctrl+alt+d",
|
||||
"messages_first": "ctrl+g,home",
|
||||
"messages_last": "ctrl+alt+g,end",
|
||||
"messages_next": "none",
|
||||
"messages_previous": "none",
|
||||
"messages_copy": "<leader>y",
|
||||
"messages_undo": "<leader>u",
|
||||
"messages_redo": "<leader>r",
|
||||
"messages_last_user": "none",
|
||||
"messages_toggle_conceal": "<leader>h",
|
||||
"model_list": "<leader>m",
|
||||
"model_cycle_recent": "f2",
|
||||
"model_cycle_recent_reverse": "shift+f2",
|
||||
"model_cycle_favorite": "none",
|
||||
"model_cycle_favorite_reverse": "none",
|
||||
"variant_cycle": "ctrl+t",
|
||||
"variant_list": "none",
|
||||
"command_list": "ctrl+p",
|
||||
"agent_list": "<leader>a",
|
||||
"agent_cycle": "tab",
|
||||
"agent_cycle_reverse": "shift+tab",
|
||||
"input_clear": "ctrl+c",
|
||||
"input_paste": "ctrl+v",
|
||||
"input_submit": "return",
|
||||
"input_newline": "shift+return,ctrl+return,alt+return,ctrl+j",
|
||||
"input_move_left": "left,ctrl+b",
|
||||
"input_move_right": "right,ctrl+f",
|
||||
"input_move_up": "up",
|
||||
"input_move_down": "down",
|
||||
"input_select_left": "shift+left",
|
||||
"input_select_right": "shift+right",
|
||||
"input_select_up": "shift+up",
|
||||
"input_select_down": "shift+down",
|
||||
"input_line_home": "ctrl+a",
|
||||
"input_line_end": "ctrl+e",
|
||||
"input_select_line_home": "ctrl+shift+a",
|
||||
"input_select_line_end": "ctrl+shift+e",
|
||||
"input_visual_line_home": "alt+a",
|
||||
"input_visual_line_end": "alt+e",
|
||||
"input_select_visual_line_home": "alt+shift+a",
|
||||
"input_select_visual_line_end": "alt+shift+e",
|
||||
"input_buffer_home": "home",
|
||||
"input_buffer_end": "end",
|
||||
"input_select_buffer_home": "shift+home",
|
||||
"input_select_buffer_end": "shift+end",
|
||||
"input_delete_line": "ctrl+shift+d",
|
||||
"input_delete_to_line_end": "ctrl+k",
|
||||
"input_delete_to_line_start": "ctrl+u",
|
||||
"input_backspace": "backspace,shift+backspace",
|
||||
"input_delete": "ctrl+d,delete,shift+delete",
|
||||
"input_undo": "ctrl+-,super+z",
|
||||
"input_redo": "ctrl+.,super+shift+z",
|
||||
"input_word_forward": "alt+f,alt+right,ctrl+right",
|
||||
"input_word_backward": "alt+b,alt+left,ctrl+left",
|
||||
"input_select_word_forward": "alt+shift+f,alt+shift+right",
|
||||
"input_select_word_backward": "alt+shift+b,alt+shift+left",
|
||||
"input_delete_word_forward": "alt+d,alt+delete,ctrl+delete",
|
||||
"input_delete_word_backward": "ctrl+w,ctrl+backspace,alt+backspace",
|
||||
"history_previous": "up",
|
||||
"history_next": "down",
|
||||
"terminal_suspend": "ctrl+z",
|
||||
"terminal_title_toggle": "none",
|
||||
"tips_toggle": "<leader>h",
|
||||
"display_thinking": "none"
|
||||
}
|
||||
}
|
||||
```
|
||||
The older `keybinds` field is still accepted as a migration fallback, but it is deprecated and will be removed in OpenCode v2.0. If `keymap` is present, OpenCode ignores `keybinds` for shortcut resolution.
|
||||
|
||||
:::note
|
||||
On Windows, the defaults for `input_undo` and `terminal_suspend` are different:
|
||||
|
||||
- `input_undo` defaults to `ctrl+z,ctrl+-,super+z` (the `ctrl+z` binding is added because Windows terminals do not support POSIX suspend).
|
||||
- `terminal_suspend` is forced to `none` because native Windows terminals do not support POSIX suspend.
|
||||
:::
|
||||
`keymap` is merged with built-in defaults, so you only need to configure the shortcuts you want to change.
|
||||
|
||||
---
|
||||
|
||||
## Leader key
|
||||
|
||||
OpenCode uses a `leader` key for most keybinds. This avoids conflicts in your terminal.
|
||||
OpenCode uses a `leader` key for many shortcuts. This avoids conflicts in your terminal.
|
||||
|
||||
By default, `ctrl+x` is the leader key and most actions require you to first press the leader key and then the shortcut. For example, to start a new session you first press `ctrl+x` and then press `n`.
|
||||
By default, `ctrl+x` is the leader key and leader shortcuts require you to first press the leader key and then the shortcut. For example, to start a new session you first press `ctrl+x` and then press `n`.
|
||||
|
||||
You don't need to use a leader key for your keybinds but we recommend doing so.
|
||||
|
||||
Some navigation keybinds intentionally do not use the leader key by default. For subagent sessions, the defaults are `session_child_first` = `\<leader>down`, `session_child_cycle` = `right`, `session_child_cycle_reverse` = `left`, and `session_parent` = `up`.
|
||||
You do not need to use a leader key, but we recommend doing so.
|
||||
|
||||
---
|
||||
|
||||
## Disable keybind
|
||||
## Minimal example
|
||||
|
||||
You can disable a keybind by adding the key to `tui.json` with a value of "none".
|
||||
```json title="tui.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"keymap": {
|
||||
"leader": "ctrl+x",
|
||||
"leader_timeout": 2000,
|
||||
"sections": {
|
||||
"global": {
|
||||
"command.palette.show": "ctrl+p",
|
||||
"session.new": "<leader>n",
|
||||
"session.list": "<leader>l"
|
||||
},
|
||||
"session": {
|
||||
"session.compact": "<leader>c",
|
||||
"session.undo": "<leader>u",
|
||||
"session.redo": "<leader>r"
|
||||
},
|
||||
"input": {
|
||||
"input.submit": "return",
|
||||
"input.newline": ["shift+return", "ctrl+return", "alt+return", "ctrl+j"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Keymap structure
|
||||
|
||||
`keymap.sections` is grouped by semantic area. Each section contains command names and the key sequence that triggers them.
|
||||
|
||||
| Field | Description |
|
||||
| ----- | ----------- |
|
||||
| `leader` | The key used by `<leader>` sequences. Defaults to `ctrl+x`. |
|
||||
| `leader_timeout` | How long OpenCode waits for the next key after the leader key, in milliseconds. Defaults to `2000`. |
|
||||
| `sections` | A map of TUI areas to command bindings. |
|
||||
|
||||
---
|
||||
|
||||
## Binding values
|
||||
|
||||
A string can contain one shortcut or multiple comma-separated shortcuts. You can also use an array for multiple shortcuts, or `"none"`/`false` to disable a command.
|
||||
|
||||
```json title="tui.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"keymap": {
|
||||
"sections": {
|
||||
"session": {
|
||||
"session.compact": "none",
|
||||
"session.export": "<leader>x,ctrl+shift+x",
|
||||
"session.copy": ["<leader>y", "ctrl+shift+c"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For advanced cases, use an object with `key`, `event`, `preventDefault`, or `fallthrough`.
|
||||
|
||||
```json title="tui.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"keymap": {
|
||||
"sections": {
|
||||
"prompt": {
|
||||
"prompt.paste": {
|
||||
"key": "ctrl+v",
|
||||
"preventDefault": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete keymap reference
|
||||
|
||||
This example lists the built-in sections, command names, and default fallback bindings. Commands set to `"none"` are available to bind but disabled by default.
|
||||
|
||||
```json title="tui.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"keymap": {
|
||||
"leader": "ctrl+x",
|
||||
"leader_timeout": 2000,
|
||||
"sections": {
|
||||
"global": {
|
||||
"command.palette.show": "ctrl+p",
|
||||
"session.list": "<leader>l",
|
||||
"session.new": "<leader>n",
|
||||
"model.list": "<leader>m",
|
||||
"model.cycle_recent": "f2",
|
||||
"model.cycle_recent_reverse": "shift+f2",
|
||||
"model.cycle_favorite": "none",
|
||||
"model.cycle_favorite_reverse": "none",
|
||||
"agent.list": "<leader>a",
|
||||
"mcp.list": "none",
|
||||
"agent.cycle": "tab",
|
||||
"agent.cycle.reverse": "shift+tab",
|
||||
"variant.cycle": "ctrl+t",
|
||||
"variant.list": "none",
|
||||
"provider.connect": "none",
|
||||
"console.org.switch": "none",
|
||||
"opencode.status": "<leader>s",
|
||||
"theme.switch": "<leader>t",
|
||||
"theme.switch_mode": "none",
|
||||
"theme.mode.lock": "none",
|
||||
"help.show": "none",
|
||||
"docs.open": "none",
|
||||
"app.exit": "ctrl+c,ctrl+d,<leader>q",
|
||||
"app.debug": "none",
|
||||
"app.console": "none",
|
||||
"app.heap_snapshot": "none",
|
||||
"app.toggle.animations": "none",
|
||||
"app.toggle.file_context": "none",
|
||||
"app.toggle.diffwrap": "none",
|
||||
"app.toggle.paste_summary": "none",
|
||||
"app.toggle.session_directory_filter": "none",
|
||||
"terminal.suspend": "ctrl+z",
|
||||
"terminal.title.toggle": "none"
|
||||
},
|
||||
"session": {
|
||||
"session.share": "none",
|
||||
"session.rename": "ctrl+r",
|
||||
"session.timeline": "<leader>g",
|
||||
"session.fork": "none",
|
||||
"session.compact": "<leader>c",
|
||||
"session.unshare": "none",
|
||||
"session.undo": "<leader>u",
|
||||
"session.redo": "<leader>r",
|
||||
"session.sidebar.toggle": "<leader>b",
|
||||
"session.toggle.conceal": "<leader>h",
|
||||
"session.toggle.timestamps": "none",
|
||||
"session.toggle.thinking": "none",
|
||||
"session.toggle.actions": "none",
|
||||
"session.toggle.scrollbar": "none",
|
||||
"session.toggle.generic_tool_output": "none",
|
||||
"session.page.up": "pageup,ctrl+alt+b",
|
||||
"session.page.down": "pagedown,ctrl+alt+f",
|
||||
"session.line.up": "ctrl+alt+y",
|
||||
"session.line.down": "ctrl+alt+e",
|
||||
"session.half.page.up": "ctrl+alt+u",
|
||||
"session.half.page.down": "ctrl+alt+d",
|
||||
"session.first": "ctrl+g,home",
|
||||
"session.last": "ctrl+alt+g,end",
|
||||
"session.messages_last_user": "none",
|
||||
"session.message.next": "none",
|
||||
"session.message.previous": "none",
|
||||
"messages.copy": "<leader>y",
|
||||
"session.copy": "none",
|
||||
"session.export": "<leader>x",
|
||||
"session.child.first": "<leader>down",
|
||||
"session.parent": "up",
|
||||
"session.child.next": "right",
|
||||
"session.child.previous": "left"
|
||||
},
|
||||
"prompt": {
|
||||
"prompt.submit": "none",
|
||||
"prompt.editor": "<leader>e",
|
||||
"prompt.editor_context.clear": "none",
|
||||
"prompt.skills": "none",
|
||||
"prompt.stash": "none",
|
||||
"prompt.stash.pop": "none",
|
||||
"prompt.stash.list": "none",
|
||||
"workspace.set": "none",
|
||||
"session.interrupt": "escape",
|
||||
"prompt.clear": "ctrl+c",
|
||||
"prompt.paste": {
|
||||
"key": "ctrl+v",
|
||||
"preventDefault": false
|
||||
},
|
||||
"prompt.history.previous": "up",
|
||||
"prompt.history.next": "down"
|
||||
},
|
||||
"autocomplete": {
|
||||
"prompt.autocomplete.prev": "up,ctrl+p",
|
||||
"prompt.autocomplete.next": "down,ctrl+n",
|
||||
"prompt.autocomplete.hide": "escape",
|
||||
"prompt.autocomplete.select": "return",
|
||||
"prompt.autocomplete.complete": "tab"
|
||||
},
|
||||
"input": {
|
||||
"input.submit": "return",
|
||||
"input.newline": "shift+return,ctrl+return,alt+return,ctrl+j",
|
||||
"input.move.left": "left,ctrl+b",
|
||||
"input.move.right": "right,ctrl+f",
|
||||
"input.move.up": "up",
|
||||
"input.move.down": "down",
|
||||
"input.select.left": "shift+left",
|
||||
"input.select.right": "shift+right",
|
||||
"input.select.up": "shift+up",
|
||||
"input.select.down": "shift+down",
|
||||
"input.line.home": "ctrl+a",
|
||||
"input.line.end": "ctrl+e",
|
||||
"input.select.line.home": "ctrl+shift+a",
|
||||
"input.select.line.end": "ctrl+shift+e",
|
||||
"input.visual.line.home": "alt+a",
|
||||
"input.visual.line.end": "alt+e",
|
||||
"input.select.visual.line.home": "alt+shift+a",
|
||||
"input.select.visual.line.end": "alt+shift+e",
|
||||
"input.buffer.home": "home",
|
||||
"input.buffer.end": "end",
|
||||
"input.select.buffer.home": "shift+home",
|
||||
"input.select.buffer.end": "shift+end",
|
||||
"input.delete.line": "ctrl+shift+d",
|
||||
"input.delete.to.line.end": "ctrl+k",
|
||||
"input.delete.to.line.start": "ctrl+u",
|
||||
"input.backspace": "backspace,shift+backspace",
|
||||
"input.delete": "ctrl+d,delete,shift+delete",
|
||||
"input.undo": "ctrl+-,super+z",
|
||||
"input.redo": "ctrl+.,super+shift+z",
|
||||
"input.word.forward": "alt+f,alt+right,ctrl+right",
|
||||
"input.word.backward": "alt+b,alt+left,ctrl+left",
|
||||
"input.select.word.forward": "alt+shift+f,alt+shift+right",
|
||||
"input.select.word.backward": "alt+shift+b,alt+shift+left",
|
||||
"input.delete.word.forward": "alt+d,alt+delete,ctrl+delete",
|
||||
"input.delete.word.backward": "ctrl+w,ctrl+backspace,alt+backspace",
|
||||
"input.select.all": "super+a"
|
||||
},
|
||||
"dialog_select": {
|
||||
"dialog.select.prev": "up,ctrl+p",
|
||||
"dialog.select.next": "down,ctrl+n",
|
||||
"dialog.select.page_up": "pageup",
|
||||
"dialog.select.page_down": "pagedown",
|
||||
"dialog.select.home": "home",
|
||||
"dialog.select.end": "end",
|
||||
"dialog.select.submit": "return"
|
||||
},
|
||||
"dialog_actions": {
|
||||
"dialog.action.toggle": "space",
|
||||
"dialog.action.delete": "ctrl+d",
|
||||
"dialog.action.rename": "ctrl+r"
|
||||
},
|
||||
"model": {
|
||||
"model.dialog.provider": "ctrl+a",
|
||||
"model.dialog.favorite": "ctrl+f"
|
||||
},
|
||||
"permission": {
|
||||
"permission.reject.cancel": "ctrl+c,ctrl+d,<leader>q",
|
||||
"permission.prompt.escape": "ctrl+c,ctrl+d,<leader>q",
|
||||
"permission.prompt.fullscreen": "ctrl+f"
|
||||
},
|
||||
"question": {
|
||||
"question.reject": "ctrl+c,ctrl+d,<leader>q",
|
||||
"question.edit.clear": "ctrl+c"
|
||||
},
|
||||
"plugins": {
|
||||
"plugins.list": "none",
|
||||
"plugins.install": "none",
|
||||
"plugin.dialog.install": "shift+i"
|
||||
},
|
||||
"home_tips": {
|
||||
"tips.toggle": "<leader>h"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Legacy keybinds
|
||||
|
||||
`keybinds` is deprecated. It is kept so existing configs continue to work while users migrate to `keymap`.
|
||||
|
||||
Only use `keybinds` when `keymap` is not present. If both fields are set, `keymap` wins and `keybinds` are ignored for shortcut resolution.
|
||||
|
||||
```json title="tui.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"keybinds": {
|
||||
"session_compact": "none"
|
||||
"command_list": "ctrl+p",
|
||||
"session_new": "<leader>n",
|
||||
"session_compact": "<leader>c"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
On native Windows, the defaults for undo and terminal suspend are different for both `keymap` and legacy `keybinds`:
|
||||
|
||||
- `input.undo` defaults to `ctrl+z,ctrl+-,super+z` when it is not explicitly configured (the `ctrl+z` binding is added because Windows terminals do not support POSIX suspend).
|
||||
- `terminal.suspend` is disabled because native Windows terminals do not support POSIX suspend.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Desktop prompt shortcuts
|
||||
|
||||
@@ -63,7 +63,7 @@ When using the OpenCode TUI, you can type `/` followed by a command name to quic
|
||||
/help
|
||||
```
|
||||
|
||||
Most commands also have keybind using `ctrl+x` as the leader key, where `ctrl+x` is the default leader key. [Learn more](/docs/keybinds).
|
||||
Most commands also have keyboard shortcuts using `ctrl+x` as the default leader key. [Learn more](/docs/keybinds).
|
||||
|
||||
Here are all available slash commands:
|
||||
|
||||
@@ -353,8 +353,14 @@ You can customize TUI behavior through `tui.json` (or `tui.jsonc`).
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"theme": "opencode",
|
||||
"keybinds": {
|
||||
"leader": "ctrl+x"
|
||||
"keymap": {
|
||||
"leader": "ctrl+x",
|
||||
"leader_timeout": 2000,
|
||||
"sections": {
|
||||
"global": {
|
||||
"command.palette.show": "ctrl+p"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scroll_speed": 3,
|
||||
"scroll_acceleration": {
|
||||
@@ -367,10 +373,13 @@ You can customize TUI behavior through `tui.json` (or `tui.jsonc`).
|
||||
|
||||
This is separate from `opencode.json`, which configures server/runtime behavior.
|
||||
|
||||
`keymap` is merged with built-in defaults, so you only need to configure the shortcuts you want to change.
|
||||
|
||||
### Options
|
||||
|
||||
- `theme` - Sets your UI theme. [Learn more](/docs/themes).
|
||||
- `keybinds` - Customizes keyboard shortcuts. [Learn more](/docs/keybinds).
|
||||
- `keymap` - Customizes keyboard shortcuts. [Learn more](/docs/keybinds).
|
||||
- `keybinds` - Deprecated legacy shortcut config. This only applies when `keymap` is not present.
|
||||
- `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration for smooth, natural scrolling. When enabled, scroll speed increases with rapid scrolling gestures and stays precise for slower movements. **This setting takes precedence over `scroll_speed` and overrides it when enabled.**
|
||||
- `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (minimum: `0.001`, supports decimal values). Defaults to `3`. **Note: This is ignored if `scroll_acceleration.enabled` is set to `true`.**
|
||||
- `diff_style` - Controls diff rendering. `"auto"` adapts to terminal width, `"stacked"` always shows a single-column layout.
|
||||
|
||||
Reference in New Issue
Block a user