From c3ecb557d37a3410308ff4ed12ccebbb4d9f0f9b Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Fri, 17 Apr 2026 08:38:47 -0700 Subject: [PATCH] Support Ctrl+P/Ctrl+N in resume picker (#18267) Fixes #18179. ## Why The fullscreen `/resume` picker accepted Up/Down navigation but ignored Ctrl+P/Ctrl+N, which made it inconsistent with other TUI selection flows such as `ListSelectionView`-backed pickers and composer navigation. ## What Changed Updated `codex-rs/tui/src/resume_picker.rs` so the resume picker treats Ctrl+P/Ctrl+N as aliases for Up/Down, including the raw `^P`/`^N` control-character events some terminals emit without a CONTROL modifier. --- codex-rs/tui/src/resume_picker.rs | 82 ++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index 6703a606c..93828afe8 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -31,6 +31,7 @@ use color_eyre::eyre::Result; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyEventKind; +use crossterm::event::KeyModifiers; use ratatui::layout::Constraint; use ratatui::layout::Layout; use ratatui::layout::Rect; @@ -617,16 +618,21 @@ impl PickerState { async fn handle_key(&mut self, key: KeyEvent) -> Result> { self.inline_error = None; - match key.code { - KeyCode::Esc => return Ok(Some(SessionSelection::StartFresh)), - KeyCode::Char('c') - if key - .modifiers - .contains(crossterm::event::KeyModifiers::CONTROL) => - { + match key { + KeyEvent { + code: KeyCode::Esc, .. + } => return Ok(Some(SessionSelection::StartFresh)), + KeyEvent { + code: KeyCode::Char('c'), + modifiers, + .. + } if modifiers.contains(KeyModifiers::CONTROL) => { return Ok(Some(SessionSelection::Exit)); } - KeyCode::Enter => { + KeyEvent { + code: KeyCode::Enter, + .. + } => { if let Some(row) = self.filtered_rows.get(self.selected) { let path = row.path.clone(); let thread_id = match row.thread_id { @@ -656,14 +662,39 @@ impl PickerState { self.request_frame(); } } - KeyCode::Up => { + KeyEvent { + code: KeyCode::Up, .. + } + | KeyEvent { + code: KeyCode::Char('p'), + modifiers: KeyModifiers::CONTROL, + .. + } + | KeyEvent { + code: KeyCode::Char('\u{0010}'), + modifiers: KeyModifiers::NONE, + .. + } /* ^P */ => { if self.selected > 0 { self.selected -= 1; self.ensure_selected_visible(); } self.request_frame(); } - KeyCode::Down => { + KeyEvent { + code: KeyCode::Down, + .. + } + | KeyEvent { + code: KeyCode::Char('n'), + modifiers: KeyModifiers::CONTROL, + .. + } + | KeyEvent { + code: KeyCode::Char('\u{000e}'), + modifiers: KeyModifiers::NONE, + .. + } /* ^N */ => { if self.selected + 1 < self.filtered_rows.len() { self.selected += 1; self.ensure_selected_visible(); @@ -671,7 +702,10 @@ impl PickerState { self.maybe_load_more_for_scroll(); self.request_frame(); } - KeyCode::PageUp => { + KeyEvent { + code: KeyCode::PageUp, + .. + } => { let step = self.view_rows.unwrap_or(10).max(1); if self.selected > 0 { self.selected = self.selected.saturating_sub(step); @@ -679,7 +713,10 @@ impl PickerState { self.request_frame(); } } - KeyCode::PageDown => { + KeyEvent { + code: KeyCode::PageDown, + .. + } => { if !self.filtered_rows.is_empty() { let step = self.view_rows.unwrap_or(10).max(1); let max_index = self.filtered_rows.len().saturating_sub(1); @@ -689,21 +726,28 @@ impl PickerState { self.request_frame(); } } - KeyCode::Tab => { + KeyEvent { + code: KeyCode::Tab, .. + } => { self.toggle_sort_key(); self.request_frame(); } - KeyCode::Backspace => { + KeyEvent { + code: KeyCode::Backspace, + .. + } => { let mut new_query = self.query.clone(); new_query.pop(); self.set_query(new_query); } - KeyCode::Char(c) => { + KeyEvent { + code: KeyCode::Char(c), + modifiers, + .. + } => { // basic text input for search - if !key - .modifiers - .contains(crossterm::event::KeyModifiers::CONTROL) - && !key.modifiers.contains(crossterm::event::KeyModifiers::ALT) + if !modifiers.contains(KeyModifiers::CONTROL) + && !modifiers.contains(KeyModifiers::ALT) { let mut new_query = self.query.clone(); new_query.push(c);