From 95a79d82b711d494a4dd154222d775ff50de05d2 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Sun, 24 Aug 2025 21:39:49 -0700 Subject: [PATCH] fix esc --- codex-rs/tui/src/app.rs | 12 ++++++++++-- codex-rs/tui/src/bottom_pane/chat_composer.rs | 12 ++++++++++++ codex-rs/tui/src/bottom_pane/mod.rs | 7 +++++++ codex-rs/tui/src/chatwidget.rs | 7 +++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index bda66cba85..9f33c25f99 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -303,13 +303,21 @@ impl App { self.transcript_overlay = Some(TranscriptApp::new(self.transcript_lines.clone())); tui.frame_requester().schedule_frame(); } - // Esc primes/advances backtracking when composer is empty. + // Esc primes/advances backtracking only in normal (not working) mode + // with an empty composer. In any other state, forward Esc so the + // active UI (e.g. status indicator, modals, popups) handles it. KeyEvent { code: KeyCode::Esc, kind: KeyEventKind::Press | KeyEventKind::Repeat, .. } => { - self.handle_backtrack_esc_key(tui); + if self.chat_widget.is_normal_backtrack_mode() + && self.chat_widget.composer_is_empty() + { + self.handle_backtrack_esc_key(tui); + } else { + self.chat_widget.handle_key_event(key_event); + } } // Enter confirms backtrack when primed + count > 0. Otherwise pass to widget. KeyEvent { diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 0d8a34a809..29c252dda2 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -293,6 +293,11 @@ impl ChatComposer { result } + /// Return true if either the slash-command popup or the file-search popup is active. + pub(crate) fn popup_active(&self) -> bool { + !matches!(self.active_popup, ActivePopup::None) + } + /// Handle key event when the slash-command popup is visible. fn handle_key_event_with_slash_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool) { let ActivePopup::Command(popup) = &mut self.active_popup else { @@ -313,6 +318,13 @@ impl ChatComposer { popup.move_down(); (InputResult::None, true) } + KeyEvent { + code: KeyCode::Esc, .. + } => { + // Dismiss the slash popup; keep the current input untouched. + self.active_popup = ActivePopup::None; + (InputResult::None, true) + } KeyEvent { code: KeyCode::Tab, .. } => { diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index 8c4802eaa5..74c106f0ce 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -323,6 +323,13 @@ impl BottomPane { self.is_task_running } + /// Return true when the pane is in the regular composer state without any + /// overlays or popups and not running a task. This is the safe context to + /// use Esc-Esc for backtracking from the main view. + pub(crate) fn is_normal_backtrack_mode(&self) -> bool { + !self.is_task_running && self.active_view.is_none() && !self.composer.popup_active() + } + /// Update the *context-window remaining* indicator in the composer. This /// is forwarded directly to the underlying `ChatComposer`. pub(crate) fn set_token_usage( diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 1fb5c6dfe5..980c0ca7b7 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1083,6 +1083,13 @@ impl ChatWidget { self.bottom_pane.composer_is_empty() } + /// True when the UI is in the regular composer state with no running task, + /// no modal overlay (e.g. approvals or status indicator), and no composer popups. + /// In this state Esc-Esc backtracking is enabled. + pub(crate) fn is_normal_backtrack_mode(&self) -> bool { + self.bottom_pane.is_normal_backtrack_mode() + } + pub(crate) fn insert_str(&mut self, text: &str) { self.bottom_pane.insert_str(text); }