Skip to content

feat(tui): add configurable keymap support#18593

Merged
fcoury-oai merged 13 commits intomainfrom
fcoury/keymap-core
Apr 28, 2026
Merged

feat(tui): add configurable keymap support#18593
fcoury-oai merged 13 commits intomainfrom
fcoury/keymap-core

Conversation

@fcoury-oai
Copy link
Copy Markdown
Contributor

@fcoury-oai fcoury-oai commented Apr 19, 2026

Why

The TUI currently handles keyboard shortcuts as hard-coded event matches spread across app, composer, pager, list, approval, and navigation code. That makes shortcuts hard to customize, makes displayed hints easy to drift from actual behavior, and makes future keymap work riskier because there is no central action inventory.

This PR adds the foundation for configurable, action-based keymaps without adding the interactive remapping UI yet. Onboarding intentionally stays on fixed startup shortcuts because users cannot reasonably configure keymaps before completing onboarding.

This is PR1 in the keymap stack:

Design Notes

The new model resolves named actions into concrete runtime bindings once from config, then passes those bindings to the UI surfaces that handle input or render shortcut hints.

The main concepts are:

  • Context: a scope where an action is active, such as global, chat, composer, editor, pager, list, or approval.
  • Action: a named operation inside a context, such as global.open_transcript, composer.submit, or pager.close.
  • Binding: one or more single-key shortcuts assigned to an action, written as config strings such as ctrl-t, alt-backspace, or page-down. Multi-step sequences such as ctrl-x ctrl-s, g g, or leader-key flows are not part of this PR.
  • Resolution order: context-specific config wins first, supported global fallbacks come next, and built-in defaults fill in anything unset.
  • Explicit unbinding: an empty array removes an action binding in that scope and does not fall through to a fallback binding.
  • Conflict validation: a resolved keymap rejects duplicate active bindings inside the same scope so one keypress cannot dispatch two actions.

What Changed

  • Added TuiKeymap config support under [tui.keymap], including typed contexts/actions, key alias normalization, generated schema coverage, and user-facing config errors.
  • Added RuntimeKeymap resolution in codex-rs/tui/src/keymap.rs, including fallback precedence, built-in defaults, explicit unbinding, and per-context conflict validation.
  • Rewired existing TUI handlers to consume resolved keymap actions instead of directly matching hard-coded keys in each component.
  • Updated key hint rendering and footer/pager/list surfaces so displayed shortcuts follow the resolved keymap.
  • Kept onboarding shortcuts fixed in codex-rs/tui/src/onboarding/keys.rs instead of exposing them through [tui.keymap].

Validation

The branch includes focused coverage for config parsing, key normalization, runtime fallback resolution, explicit unbinding, duplicate-key conflict validation, default keymap consistency, onboarding startup key behavior, and UI hint snapshots affected by resolved key bindings.

@fcoury-oai fcoury-oai marked this pull request as ready for review April 19, 2026 21:20
chatgpt-codex-connector[bot]

This comment was marked as resolved.

Comment thread docs/config.md Outdated
@fcoury-oai fcoury-oai force-pushed the fcoury/keymap-core branch 2 times, most recently from e9358e0 to 057921d Compare April 20, 2026 16:13
@fcoury-oai fcoury-oai requested a review from a team as a code owner April 20, 2026 22:23
@fcoury-oai fcoury-oai force-pushed the fcoury/keymap-core branch 11 times, most recently from 18c791a to 03229de Compare April 24, 2026 14:44
@canvrno-oai
Copy link
Copy Markdown
Contributor

Smoke tested the new [tui.keymap] support with local config overrides across the main TUI surfaces, using a mix of contexts, bindings, and modifiers. Confirmed remapped shortcuts behave as expected in the composer, transcript, and approval flows.

Add typed `tui.keymap` config, schema coverage, default keymap docs,
and runtime keymap resolution for existing TUI shortcuts.

Wire the resolved bindings through app, composer, list, pager, approval,
and onboarding input handlers so users can reassign built-in keys.
Add argument comments to keymap-related TUI callsites that pass opaque
literal values, keeping them compliant with the anonymous argument lint.
Remove the legacy Enter/Tab submission fallback so composer submit and
queue actions are dispatched only through the resolved keymap bindings.

Add regression coverage for remapped submit and queue keys to ensure
unconfigured Enter/Tab presses no longer bypass `tui.keymap`.
Document keymap preset stability, single-key binding semantics, explicit
unbind behavior, and the runtime snapshot contract used by TUI handlers.

Clarify how resolved keymaps propagate into active bottom-pane surfaces so
future remap changes keep cached bindings synchronized.
Remove the keymap preset config surface from PR1 so runtime resolution
falls back directly to built-in defaults after user overrides.

This keeps the initial keymap capability focused on action remapping and
leaves versioned default migrations out of the first PR.
Remove onboarding from the configurable keymap surface and keep its
startup shortcuts hard-coded. Users cannot remap keys before onboarding,
so exposing those actions in `[tui.keymap]` adds config surface without a
practical customization path.
## Why

PR1 (#18593) makes keymaps configurable, but manually editing
`[tui.keymap]` still requires users to know context names, action names,
supported key syntax, and which bindings are currently coming from
defaults versus custom config. The goal of this PR is to turn that model
into a guided TUI workflow: users can open `/keymap`, browse supported
actions, see current/default/custom state, capture a replacement key,
and let Codex validate and persist the edit safely.

This is PR2 in the keymap stack:

- PR1: #18593: configurable keymap foundation
- PR2: #18594: `/keymap` picker and guided remapping UI
- PR3: #18595: Vim composer mode and the remap option

## How It Works


https://github.com/user-attachments/assets/d40feb02-7f9e-4478-811a-d00f7f955133

The `/keymap` command opens a tabbed shortcut picker backed by the
runtime keymap introduced in #18593. The picker organizes actions by
common shortcuts, custom overrides, unbound actions, and
context-specific tabs such as app, composer, editor, navigation, and
approval.

Selecting an action opens an action menu that explains the current
binding, its config path, whether it comes from a root-level override or
the default keymap, and the available operations. Depending on the
current state, users can set a key, replace one binding, replace all
bindings, add an alternate binding, or clear the custom override.

Captured keys are serialized back to canonical
`tui.keymap.<context>.<action>` config entries. Before writing, the
edited config is re-resolved through `RuntimeKeymap`, so the same parser
and conflict validation used at startup also protect interactive edits.
If the new binding conflicts with another active action, Codex shows the
conflict instead of persisting an invalid config.

## Reviewer Entry Points

-
[`codex-rs/tui/src/keymap_setup.rs`](https://github.com/openai/codex/blob/fcoury/keymap-command/codex-rs/tui/src/keymap_setup.rs):
guided remapping flow, capture view, edit outcomes, and conflict
handling.
-
[`codex-rs/tui/src/keymap_setup/actions.rs`](https://github.com/openai/codex/blob/fcoury/keymap-command/codex-rs/tui/src/keymap_setup/actions.rs):
action catalog and config/runtime accessors for editable actions.
-
[`codex-rs/tui/src/keymap_setup/picker.rs`](https://github.com/openai/codex/blob/fcoury/keymap-command/codex-rs/tui/src/keymap_setup/picker.rs):
tabbed picker construction and row rendering.
-
[`codex-rs/core/src/config/edit.rs`](https://github.com/openai/codex/blob/fcoury/keymap-command/codex-rs/core/src/config/edit.rs):
config edit helpers used to write keymap overrides atomically.
-
[`codex-rs/tui/src/chatwidget/slash_dispatch.rs`](https://github.com/openai/codex/blob/fcoury/keymap-command/codex-rs/tui/src/chatwidget/slash_dispatch.rs):
`/keymap` dispatch and queue-drain behavior.

## What Changed

- Added the `/keymap` slash command.
- Added a reusable keymap setup module with action picker, action menu,
replace-one menu, capture view, and conflict surface.
- Added keymap edit events so app-level code can apply, validate,
persist, and refresh runtime bindings after a user remaps an action.
- Added config edit helpers and tests for setting, replacing, and
clearing `tui.keymap` entries.
- Added snapshot coverage for the picker, action menu, capture view, and
keymap search/index behavior.

This PR intentionally does not add Vim mode or the remap option; those
are isolated in #18595.

## Validation

- `cargo check -p codex-tui` on this branch after rebasing onto #18593

## Documentation Follow-Up

When the stack ships, the Codex CLI documentation on
`developers.openai.com/codex` should mention `/keymap` as the guided way
to inspect and edit shortcuts, alongside the lower-level `[tui.keymap]`
config documentation added in #18593.
@fcoury-oai fcoury-oai merged commit 5e73737 into main Apr 28, 2026
25 checks passed
@fcoury-oai fcoury-oai deleted the fcoury/keymap-core branch April 28, 2026 15:52
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants