feat(tui): add configurable Vim keymap and startup default

Expose Vim bindings through the TUI keymap config and add a startup
toggle so Vim mode can be enabled by default.

Wire runtime keymap handling for Vim actions, add
`tui.vim_mode_default` to config parsing/runtime config, apply the
default in ChatWidget startup paths, and update docs/default
keymap/schema accordingly.
This commit is contained in:
Felipe Coury
2026-04-23 10:35:17 -03:00
committed by Felipe Coury
parent d49e77a7c2
commit cb3e5baa73
17 changed files with 1450 additions and 243 deletions
+23 -1
View File
@@ -6,6 +6,18 @@ For advanced configuration instructions, see [this documentation](https://develo
For a full configuration reference, see [this documentation](https://developers.openai.com/codex/config-reference).
## TUI behavior
Use `[tui]` to configure startup behavior for the terminal UI.
```toml
[tui]
# Start the composer in Vim "Normal" mode on launch.
vim_mode_default = true
```
`vim_mode_default` defaults to `false` (composer starts in insert mode).
## TUI keymap
The TUI supports rebinding shortcuts via `[tui.keymap]` in `~/.codex/config.toml`.
@@ -33,13 +45,23 @@ When defaults change in the future, a new version (for example `v2`) is added an
### Supported actions
- `global`: `open_transcript`, `open_external_editor`, `edit_previous_message`,
`confirm_edit_previous_message`, `submit`, `queue`, `toggle_shortcuts`
`confirm_edit_previous_message`, `submit`, `queue`, `toggle_shortcuts`,
`toggle_vim_mode`
- `chat`: `edit_previous_message`, `confirm_edit_previous_message`
- `composer`: `submit`, `queue`, `toggle_shortcuts`
- `editor`: `insert_newline`, `move_left`, `move_right`, `move_up`, `move_down`,
`move_word_left`, `move_word_right`, `move_line_start`, `move_line_end`,
`delete_backward`, `delete_forward`, `delete_backward_word`, `delete_forward_word`,
`kill_line_start`, `kill_line_end`, `yank`
- `vim_normal`: `enter_insert`, `append_after_cursor`, `append_line_end`,
`insert_line_start`, `open_line_below`, `open_line_above`, `move_left`,
`move_right`, `move_up`, `move_down`, `move_word_forward`,
`move_word_backward`, `move_word_end`, `move_line_start`, `move_line_end`,
`delete_char`, `delete_to_line_end`, `yank_line`, `paste_after`,
`start_delete_operator`, `start_yank_operator`, `cancel_operator`
- `vim_operator`: `delete_line`, `yank_line`, `motion_left`, `motion_right`,
`motion_up`, `motion_down`, `motion_word_forward`, `motion_word_backward`,
`motion_word_end`, `motion_line_start`, `motion_line_end`, `cancel`
- `pager`: `scroll_up`, `scroll_down`, `page_up`, `page_down`, `half_page_up`,
`half_page_down`, `jump_top`, `jump_bottom`, `close`, `close_transcript`,
`edit_previous_message`, `edit_next_message`, `confirm_edit_message`
+43
View File
@@ -37,6 +37,9 @@ queue = "tab"
# Toggle composer shortcut overlay.
# Include both forms because some terminals report `?` with SHIFT and others don't.
toggle_shortcuts = ["?", "shift-?"]
# Toggle Vim mode for the composer input.
# Empty list keeps this unbound by default.
toggle_vim_mode = []
[tui.keymap.chat]
# Overrides [tui.keymap.global] for chat-only behavior.
@@ -68,6 +71,46 @@ kill_line_start = ["ctrl-u"]
kill_line_end = ["ctrl-k"]
yank = ["ctrl-y"]
[tui.keymap.vim_normal]
# Vim normal-mode actions for modal editing.
enter_insert = "i"
append_after_cursor = "a"
append_line_end = "shift-a"
insert_line_start = "shift-i"
open_line_below = "o"
open_line_above = "shift-o"
move_left = "h"
move_right = "l"
move_up = "k"
move_down = "j"
move_word_forward = "w"
move_word_backward = "b"
move_word_end = "e"
move_line_start = "0"
move_line_end = "$"
delete_char = "x"
delete_to_line_end = "shift-d"
yank_line = "shift-y"
paste_after = "p"
start_delete_operator = "d"
start_yank_operator = "y"
cancel_operator = "esc"
[tui.keymap.vim_operator]
# Vim operator-pending actions for delete/yank motions.
delete_line = "d"
yank_line = "y"
motion_left = "h"
motion_right = "l"
motion_up = "k"
motion_down = "j"
motion_word_forward = "w"
motion_word_backward = "b"
motion_word_end = "e"
motion_line_start = "0"
motion_line_end = "$"
cancel = "esc"
[tui.keymap.pager]
# Transcript/static overlay navigation.
scroll_up = ["up", "k"]
+3
View File
@@ -5,6 +5,9 @@ For a full sample configuration, see [this documentation](https://developers.ope
## Keymap snippet
```toml
[tui]
vim_mode_default = false
[tui.keymap]
preset = "latest" # currently points to v1 defaults
+27
View File
@@ -38,6 +38,7 @@ For runtime behavior, safety invariants, and testing guidance, see
- `submit`: submit current draft
- `queue`: queue current draft while a task is running
- `toggle_shortcuts`: toggle composer shortcut overlay
- `toggle_vim_mode`: toggle Vim mode for composer input
### `chat`
@@ -61,6 +62,32 @@ For runtime behavior, safety invariants, and testing guidance, see
- `kill_line_start` / `kill_line_end`: kill to line boundary
- `yank`: paste kill-buffer contents
### `vim_normal`
- `enter_insert`: switch to insert mode
- `append_after_cursor`: move right and switch to insert mode
- `append_line_end`: move to end of line and switch to insert mode
- `insert_line_start`: move to beginning of line and switch to insert mode
- `open_line_below` / `open_line_above`: open line and switch to insert mode
- `move_left` / `move_right` / `move_up` / `move_down`: cursor movement
- `move_word_forward` / `move_word_backward` / `move_word_end`: word motions
- `move_line_start` / `move_line_end`: line boundary motions
- `delete_char`: delete character at cursor
- `delete_to_line_end`: delete from cursor to line end
- `yank_line`: copy current line
- `paste_after`: paste after cursor
- `start_delete_operator` / `start_yank_operator`: enter operator-pending state
- `cancel_operator`: clear pending operator
### `vim_operator`
- `delete_line`: apply delete operator to full line
- `yank_line`: apply yank operator to full line
- `motion_left` / `motion_right` / `motion_up` / `motion_down`: motion targets
- `motion_word_forward` / `motion_word_backward` / `motion_word_end`: word targets
- `motion_line_start` / `motion_line_end`: line targets
- `cancel`: cancel pending operator
### `pager`
- `scroll_up` / `scroll_down`: row scroll
+8 -5
View File
@@ -70,10 +70,12 @@ Supported contexts:
2. `chat`
3. `composer`
4. `editor`
5. `pager`
6. `list`
7. `approval`
8. `onboarding`
5. `vim_normal`
6. `vim_operator`
7. `pager`
8. `list`
9. `approval`
10. `onboarding`
Action inventory by context is documented in `docs/config.md` and the template
at `https://github.com/openai/codex/blob/main/docs/default-keymap.toml`.
@@ -124,7 +126,8 @@ Current conflict passes in `RuntimeKeymap::validate_conflicts` enforce:
2. App/composer shadowing prevention, because app handlers execute before
forwarding to composer handlers.
3. Composer-local uniqueness for submit/queue/shortcut-toggle.
4. Context-local uniqueness in editor, pager, list, approval, and onboarding.
4. Context-local uniqueness in editor, vim_normal, vim_operator, pager, list,
approval, and onboarding.
Intentionally allowed: