From 89403c5e116da1c561d1b8fba4b8660f7108c29a Mon Sep 17 00:00:00 2001 From: xl-openai Date: Tue, 13 Jan 2026 15:02:44 -0800 Subject: [PATCH] Allow close skill popup with esc. (#9165) image image You can now esc to quit the skill popup and submit the input as it is. --- codex-rs/tui/src/bottom_pane/chat_composer.rs | 10 +---- codex-rs/tui/src/bottom_pane/skill_popup.rs | 40 ++++++++++++++++++- .../tui2/src/bottom_pane/chat_composer.rs | 10 +---- codex-rs/tui2/src/bottom_pane/skill_popup.rs | 40 ++++++++++++++++++- 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 9d01d7429..27e191d8e 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -983,15 +983,7 @@ impl ChatComposer { if self.handle_shortcut_overlay_key(&key_event) { return (InputResult::None, true); } - if key_event.code == KeyCode::Esc { - let next_mode = esc_hint_mode(self.footer_mode, self.is_task_running); - if next_mode != self.footer_mode { - self.footer_mode = next_mode; - return (InputResult::None, true); - } - } else { - self.footer_mode = reset_mode_after_activity(self.footer_mode); - } + self.footer_mode = reset_mode_after_activity(self.footer_mode); let ActivePopup::Skill(popup) = &mut self.active_popup else { unreachable!(); diff --git a/codex-rs/tui/src/bottom_pane/skill_popup.rs b/codex-rs/tui/src/bottom_pane/skill_popup.rs index fc4fba911..8e894de45 100644 --- a/codex-rs/tui/src/bottom_pane/skill_popup.rs +++ b/codex-rs/tui/src/bottom_pane/skill_popup.rs @@ -1,11 +1,17 @@ +use crossterm::event::KeyCode; use ratatui::buffer::Buffer; +use ratatui::layout::Constraint; +use ratatui::layout::Layout; use ratatui::layout::Rect; +use ratatui::text::Line; +use ratatui::widgets::Widget; use ratatui::widgets::WidgetRef; use super::popup_consts::MAX_POPUP_ROWS; use super::scroll_state::ScrollState; use super::selection_popup_common::GenericDisplayRow; use super::selection_popup_common::render_rows_single_line; +use crate::key_hint; use crate::render::Insets; use crate::render::RectExt; use codex_common::fuzzy_match::fuzzy_match; @@ -41,7 +47,7 @@ impl SkillPopup { pub(crate) fn calculate_required_height(&self, _width: u16) -> u16 { let rows = self.rows_from_matches(self.filtered()); let visible = rows.len().clamp(1, MAX_POPUP_ROWS); - visible as u16 + (visible as u16).saturating_add(2) } pub(crate) fn move_up(&mut self) { @@ -130,14 +136,44 @@ impl SkillPopup { impl WidgetRef for SkillPopup { fn render_ref(&self, area: Rect, buf: &mut Buffer) { + let (list_area, hint_area) = if area.height > 2 { + let [list_area, _spacer_area, hint_area] = Layout::vertical([ + Constraint::Length(area.height - 2), + Constraint::Length(1), + Constraint::Length(1), + ]) + .areas(area); + (list_area, Some(hint_area)) + } else { + (area, None) + }; let rows = self.rows_from_matches(self.filtered()); render_rows_single_line( - area.inset(Insets::tlbr(0, 2, 0, 0)), + list_area.inset(Insets::tlbr(0, 2, 0, 0)), buf, &rows, &self.state, MAX_POPUP_ROWS, "no skills", ); + if let Some(hint_area) = hint_area { + let hint_area = Rect { + x: hint_area.x + 2, + y: hint_area.y, + width: hint_area.width.saturating_sub(2), + height: hint_area.height, + }; + skill_popup_hint_line().render(hint_area, buf); + } } } + +fn skill_popup_hint_line() -> Line<'static> { + Line::from(vec![ + "Press ".into(), + key_hint::plain(KeyCode::Enter).into(), + " to select or ".into(), + key_hint::plain(KeyCode::Esc).into(), + " to close".into(), + ]) +} diff --git a/codex-rs/tui2/src/bottom_pane/chat_composer.rs b/codex-rs/tui2/src/bottom_pane/chat_composer.rs index c9693567b..df8016a16 100644 --- a/codex-rs/tui2/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui2/src/bottom_pane/chat_composer.rs @@ -916,15 +916,7 @@ impl ChatComposer { if self.handle_shortcut_overlay_key(&key_event) { return (InputResult::None, true); } - if key_event.code == KeyCode::Esc { - let next_mode = esc_hint_mode(self.footer_mode, self.is_task_running); - if next_mode != self.footer_mode { - self.footer_mode = next_mode; - return (InputResult::None, true); - } - } else { - self.footer_mode = reset_mode_after_activity(self.footer_mode); - } + self.footer_mode = reset_mode_after_activity(self.footer_mode); let ActivePopup::Skill(popup) = &mut self.active_popup else { unreachable!(); diff --git a/codex-rs/tui2/src/bottom_pane/skill_popup.rs b/codex-rs/tui2/src/bottom_pane/skill_popup.rs index 594e43e71..faf386e29 100644 --- a/codex-rs/tui2/src/bottom_pane/skill_popup.rs +++ b/codex-rs/tui2/src/bottom_pane/skill_popup.rs @@ -1,11 +1,17 @@ +use crossterm::event::KeyCode; use ratatui::buffer::Buffer; +use ratatui::layout::Constraint; +use ratatui::layout::Layout; use ratatui::layout::Rect; +use ratatui::text::Line; +use ratatui::widgets::Widget; use ratatui::widgets::WidgetRef; use super::popup_consts::MAX_POPUP_ROWS; use super::scroll_state::ScrollState; use super::selection_popup_common::GenericDisplayRow; use super::selection_popup_common::render_rows_single_line; +use crate::key_hint; use crate::render::Insets; use crate::render::RectExt; use codex_common::fuzzy_match::fuzzy_match; @@ -41,7 +47,7 @@ impl SkillPopup { pub(crate) fn calculate_required_height(&self, _width: u16) -> u16 { let rows = self.rows_from_matches(self.filtered()); let visible = rows.len().clamp(1, MAX_POPUP_ROWS); - visible as u16 + (visible as u16).saturating_add(2) } pub(crate) fn move_up(&mut self) { @@ -129,14 +135,44 @@ impl SkillPopup { impl WidgetRef for SkillPopup { fn render_ref(&self, area: Rect, buf: &mut Buffer) { + let (list_area, hint_area) = if area.height > 2 { + let [list_area, _spacer_area, hint_area] = Layout::vertical([ + Constraint::Length(area.height - 2), + Constraint::Length(1), + Constraint::Length(1), + ]) + .areas(area); + (list_area, Some(hint_area)) + } else { + (area, None) + }; let rows = self.rows_from_matches(self.filtered()); render_rows_single_line( - area.inset(Insets::tlbr(0, 2, 0, 0)), + list_area.inset(Insets::tlbr(0, 2, 0, 0)), buf, &rows, &self.state, MAX_POPUP_ROWS, "no skills", ); + if let Some(hint_area) = hint_area { + let hint_area = Rect { + x: hint_area.x + 2, + y: hint_area.y, + width: hint_area.width.saturating_sub(2), + height: hint_area.height, + }; + skill_popup_hint_line().render(hint_area, buf); + } } } + +fn skill_popup_hint_line() -> Line<'static> { + Line::from(vec![ + "Press ".into(), + key_hint::plain(KeyCode::Enter).into(), + " to select or ".into(), + key_hint::plain(KeyCode::Esc).into(), + " to close".into(), + ]) +}