feat(tui): standardize picker navigation keys (#22347)

## Why

Picker-style UI in the TUI has accumulated a mix of hardcoded navigation
keys. Some lists supported page movement, some did not; some accepted
Vim-like keys, while others only accepted arrows; and tabbed or
horizontally adjustable pickers had no shared keymap action for
left/right movement.

This PR makes picker/list navigation consistent and configurable so
users can rely on the same defaults across the TUI.

## What Changed

- Adds shared list keymap actions for:
  - vertical movement: `move_up`, `move_down`
  - horizontal movement: `move_left`, `move_right`
  - paging and jumps: `page_up`, `page_down`, `jump_top`, `jump_bottom`
- Adds defaults:
- Up/down: arrows, `Ctrl+P/N`, `Ctrl+K/J`, and plain `k/j` where text
input is not active
  - Page up/down: `PageUp/PageDown` and `Ctrl+B/F`
  - First/last: `Home/End`
  - Left/right: `Left/Right` and `Ctrl+H/L`
- Wires the shared list keymap through picker and list surfaces
including session resume, multi-select, tabbed selection lists,
settings-style lists, app-link selection, MCP elicitation,
request-user-input, and the OSS selection wizard.
- Keeps search behavior intact by reserving printable characters for
query text in searchable pickers.
- Updates keymap setup actions, config schema, snapshots, and focused
coverage for the new list actions.

## How to Test

1. Start Codex from this branch and open the session picker, for example
with an existing session history.
2. In the session list, verify that `Ctrl+J/K` moves the selection
down/up.
3. Verify that `Ctrl+F/B` pages down/up and `Home/End` jumps to the
first/last visible session.
4. Type printable search text such as `j` or `k` and confirm it updates
the query instead of navigating.
5. Focus a picker control that changes values horizontally, such as a
session picker toolbar control, and verify `Ctrl+H/L` changes the
focused value like left/right arrows.

Targeted tests run:

- `cargo test -p codex-tui keymap::tests::`
- `cargo test -p codex-tui keymap_setup::tests::`
- `cargo test -p codex-tui horizontal_list_keys`
- `cargo test -p codex-tui page_and_jump_navigation_use_list_keymap`
- `cargo test -p codex-tui ctrl_h_l_move_provider_selection`
- `cargo test -p codex-tui scroll_state::tests`
- `cargo test -p codex-tui
switching_tabs_changes_visible_items_and_clears_search`
- `cargo test -p codex-tui toggle_sort_key_reloads_with_new_sort`

Also ran `just write-config-schema`, `just fmt`, `just fix -p
codex-tui`, `just argument-comment-lint`, and `git diff --check`.

Note: `cargo test -p codex-tui` was attempted and still aborts in the
pre-existing
`tests::fork_last_filters_latest_session_by_cwd_unless_show_all` stack
overflow, which is unrelated to this branch.
This commit is contained in:
Felipe Coury
2026-05-13 12:33:27 -03:00
committed by GitHub
Unverified
parent 441c2f818f
commit 3d517fbd00
37 changed files with 1635 additions and 426 deletions
+1
View File
@@ -36,6 +36,7 @@ impl ChatWidget {
.show_view(Box::new(HooksBrowserView::from_entry(
entry,
self.app_event_tx.clone(),
self.bottom_pane.list_keymap(),
)));
self.request_redraw();
}
+5 -1
View File
@@ -94,7 +94,11 @@ impl ChatWidget {
})
.collect();
let view = SkillsToggleView::new(items, self.app_event_tx.clone());
let view = SkillsToggleView::new(
items,
self.app_event_tx.clone(),
self.bottom_pane.list_keymap(),
);
self.bottom_pane.show_view(Box::new(view));
}
@@ -17,4 +17,4 @@ expression: status_line_popup_snapshot(&mut chat)
[ ] current-dir Current working directory
my-project · feat/awesome-feature · thread title
Use ↑↓ to navigate, ←→ to move, space to select, enter to confirm, esc to cancel.
Press space to toggle;/→ to move; enter to confirm and close; esc to close
@@ -17,4 +17,4 @@ expression: status_line_popup_snapshot(&mut chat)
[ ] current-dir Current working directory
preview-live-root · feature/live-preview-branch · Live preview thread
Use ↑↓ to navigate, ←→ to move, space to select, enter to confirm, esc to cancel.
Press space to toggle;/→ to move; enter to confirm and close; esc to close
@@ -17,4 +17,4 @@ expression: status_line_popup_snapshot(&mut chat)
[ ] current-dir Current working directory
my-project · feature/mixed-preview · Mixed preview thread
Use ↑↓ to navigate, ←→ to move, space to select, enter to confirm, esc to cancel.
Press space to toggle;/→ to move; enter to confirm and close; esc to close
@@ -17,4 +17,4 @@ expression: terminal_title_popup_snapshot(&mut chat)
[ ] run-state Compact session run-state text (Ready, Working, Thinking)
thread title | feat/awesome-feature | Tasks 0/0
Use ↑↓ to navigate, ←→ to move, space to select, enter to confirm, esc to cancel.
Press space to toggle;/→ to move; enter to confirm and close; esc to close
@@ -17,4 +17,4 @@ expression: terminal_title_popup_snapshot(&mut chat)
[ ] run-state Compact session run-state text (Ready, Working, Thinking)
preview-live-root | Live preview thread | feature/live-preview-branch | Tasks 2/5
Use ↑↓ to navigate, ←→ to move, space to select, enter to confirm, esc to cancel.
Press space to toggle;/→ to move; enter to confirm and close; esc to close
@@ -17,4 +17,4 @@ expression: terminal_title_popup_snapshot(&mut chat)
[ ] git-branch Current Git branch (omitted when unavailable)
project | Mixed preview thread | Tasks 0/0
Use ↑↓ to navigate, ←→ to move, space to select, enter to confirm, esc to cancel.
Press space to toggle;/→ to move; enter to confirm and close; esc to close
@@ -2181,7 +2181,11 @@ async fn experimental_features_popup_snapshot() {
enabled: true,
},
];
let view = ExperimentalFeaturesView::new(features, chat.app_event_tx.clone());
let view = ExperimentalFeaturesView::new(
features,
chat.app_event_tx.clone(),
crate::keymap::RuntimeKeymap::defaults().list,
);
chat.bottom_pane.show_view(Box::new(view));
let popup = render_bottom_popup(&chat, /*width*/ 80);
@@ -2201,6 +2205,7 @@ async fn experimental_features_toggle_saves_on_exit() {
enabled: false,
}],
chat.app_event_tx.clone(),
crate::keymap::RuntimeKeymap::defaults().list,
);
chat.bottom_pane.show_view(Box::new(view));