feat(tui): add keymap debug inspector (#20794)

## Why

We constantly get bug reports about keys not being recognized by Codex
when the terminal is not handling the key press. Running `/keymap debug`
or `/keymap` and going to the Debug tab, we can allow the user to either
understand that the key being pressed is not being recognized or to
check what it's being recognized as and report or reassign that key.

| Menu | Inspector | Hint |
|---|---|---|
| <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57 12"
src="https://github.com/user-attachments/assets/512b6faa-344e-4aee-9c00-b4bdc633a662"
/> | <img width="1261" height="754" alt="CleanShot 2026-05-02 at 12 56
36"
src="https://github.com/user-attachments/assets/a6ddae7d-e174-4ee4-893f-e6bec4fff4ab"
/> | <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57
30"
src="https://github.com/user-attachments/assets/db507784-f40a-4cff-ac23-a61d9703769b"
/> |
## Summary
- add a Debug tab to `/keymap` and support `/keymap debug` for direct
access
- show what key Codex receives, the config key representation, raw event
details, and matching actions
- add a progressive missing-key hint that escalates after a few seconds
with no detected keypress

## Validation
- `just fmt`
- `cargo test -p codex-tui keymap_setup::tests::debug_view`
- `cargo test -p codex-tui keymap_setup::tests`
- `cargo test -p codex-tui slash_keymap`
- `cargo test -p codex-tui` (unit tests passed; integration test
`suite::model_availability_nux::resume_startup_does_not_consume_model_availability_nux_count`
failed locally by itself with `codex resume` exiting 1 and terminal
probe escape output)
- `just fix -p codex-tui`
- `just argument-comment-lint`
- `cargo insta pending-snapshots`
- `git diff --check`
This commit is contained in:
Felipe Coury authored and GitHub committed 2026-05-04 14:40:50 -03:00
1 parent 5b80f87c97
commit 94800ecbbf
21 files changed
+710 -23

No files matched your search

+3
View File
@@ -1926,6 +1926,9 @@ impl App {
self.chat_widget
.open_keymap_capture(context, action, intent, &self.keymap);
}
AppEvent::OpenKeymapDebug => {
self.chat_widget.open_keymap_debug(&self.keymap);
}
AppEvent::KeymapCaptured {
context,
action,
+30 -4
View File
@@ -122,12 +122,14 @@ impl App {
return;
}
if self.keymap.app.toggle_vim_mode.is_pressed(key_event) {
let app_keymap_shortcuts_available = self.app_keymap_shortcuts_available();
if app_keymap_shortcuts_available && self.keymap.app.toggle_vim_mode.is_pressed(key_event) {
self.chat_widget.toggle_vim_mode_and_notify();
return;
}
if self.keymap.app.open_transcript.is_pressed(key_event) {
if app_keymap_shortcuts_available && self.keymap.app.open_transcript.is_pressed(key_event) {
// Enter alternate screen and set viewport to full size.
let _ = tui.enter_alt_screen();
self.overlay = Some(Overlay::new_transcript(
@@ -138,7 +140,9 @@ impl App {
return;
}
if self.keymap.app.open_external_editor.is_pressed(key_event) {
if app_keymap_shortcuts_available
&& self.keymap.app.open_external_editor.is_pressed(key_event)
{
// Only launch the external editor if there is no overlay and the bottom pane is not in use.
// Note that it can be launched while a task is running to enable editing while the previous turn is ongoing.
if self.overlay.is_none()
@@ -166,7 +170,9 @@ impl App {
}
match key_event {
_ if self.keymap.app.clear_terminal.is_pressed(key_event) => {
_ if app_keymap_shortcuts_available
&& self.keymap.app.clear_terminal.is_pressed(key_event) =>
{
if !self.chat_widget.can_run_ctrl_l_clear_now() {
return;
}
@@ -217,7 +223,27 @@ impl App {
&& !self.chat_widget.should_handle_vim_insert_escape(key_event)
}
fn app_keymap_shortcuts_available(&self) -> bool {
self.overlay.is_none() && self.chat_widget.no_modal_or_popup_active()
}
pub(super) fn refresh_status_line(&mut self) {
self.chat_widget.refresh_status_line();
}
}
#[cfg(test)]
mod tests {
use super::super::test_support::make_test_app;
#[tokio::test]
async fn app_keymap_shortcuts_are_disabled_while_keymap_view_is_active() {
let mut app = make_test_app().await;
assert!(app.app_keymap_shortcuts_available());
let keymap = app.keymap.clone();
app.chat_widget.open_keymap_debug(&keymap);
assert!(!app.app_keymap_shortcuts_available());
}
}