Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2931294
Request user input: support is_other + reset freeform commits
charley-oai Jan 27, 2026
4b3feec
Delete scroll tooltip because it's obvious
charley-oai Jan 27, 2026
3af6e62
Lint
charley-oai Jan 27, 2026
61acfa6
Refine request user input options UI
charley-oai Jan 28, 2026
8db628d
Adjust ask-user-question overlay UI
charley-oai Jan 28, 2026
4b440a4
Tighten notes spacing in ask-user-question UI
charley-oai Jan 28, 2026
2a1a02d
Improve request user input commit flow
charley-oai Jan 28, 2026
3e1361c
Fix option shortcut committing
charley-oai Jan 28, 2026
16c571e
Fix clippy collapsible ifs
charley-oai Jan 28, 2026
cbfe0c1
Commit option when submitting empty notes
charley-oai Jan 28, 2026
47c9ec2
Keep notes and footer for long option lists
charley-oai Jan 28, 2026
f680d05
Add unanswered confirmation snapshot
charley-oai Jan 28, 2026
552d758
Document spacer rows in request user input
charley-oai Jan 28, 2026
0876819
Show option index in footer when scrolling
charley-oai Jan 28, 2026
5e2d969
Expand pending pastes via text elements
charley-oai Jan 28, 2026
486844c
Remove committed option state
charley-oai Jan 28, 2026
0bd3b2c
Refine ctrl+n footer hints
charley-oai Jan 28, 2026
b174e95
Add page up/down question navigation
charley-oai Jan 28, 2026
b177907
Extract notes spacer constant
charley-oai Jan 28, 2026
bc7598c
Explain spacer choice with notes
charley-oai Jan 28, 2026
2e39905
Require paste placeholders to have elements
charley-oai Jan 28, 2026
3a4169b
Commit selection on space
charley-oai Jan 28, 2026
ea5769f
Rename confirmation helpers
charley-oai Jan 28, 2026
2eeaf22
Deduplicate confirmation layout
charley-oai Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions codex-rs/tui/src/bottom_pane/bottom_pane_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ pub(crate) trait BottomPaneView: Renderable {
CancellationEvent::NotHandled
}

/// Return true if Esc should be routed through `handle_key_event` instead
/// of the `on_ctrl_c` cancellation path.
fn prefer_esc_to_handle_key_event(&self) -> bool {
false
}

/// Optional paste handler. Return true if the view modified its state and
/// needs a redraw.
fn handle_paste(&mut self, _pasted: String) -> bool {
Expand Down
14 changes: 13 additions & 1 deletion codex-rs/tui/src/bottom_pane/chat_composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,18 @@ impl ChatComposer {
text
}

pub(crate) fn pending_pastes(&self) -> Vec<(String, String)> {
self.pending_pastes.clone()
}

pub(crate) fn set_pending_pastes(&mut self, pending_pastes: Vec<(String, String)>) {
let text = self.textarea.text().to_string();
self.pending_pastes = pending_pastes
.into_iter()
.filter(|(placeholder, _)| text.contains(placeholder))
.collect();
}

/// Override the footer hint items displayed beneath the composer. Passing
/// `None` restores the default shortcut footer.
pub(crate) fn set_footer_hint_override(&mut self, items: Option<Vec<(String, String)>>) {
Expand Down Expand Up @@ -1409,7 +1421,7 @@ impl ChatComposer {
}

/// Expand large-paste placeholders using element ranges and rebuild other element spans.
fn expand_pending_pastes(
pub(crate) fn expand_pending_pastes(
text: &str,
mut elements: Vec<TextElement>,
pending_pastes: &[(String, String)],
Expand Down
66 changes: 66 additions & 0 deletions codex-rs/tui/src/bottom_pane/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,10 @@ impl BottomPane {
let (ctrl_c_completed, view_complete, view_in_paste_burst) = {
let last_index = self.view_stack.len() - 1;
let view = &mut self.view_stack[last_index];
let prefer_esc =
key_event.code == KeyCode::Esc && view.prefer_esc_to_handle_key_event();
let ctrl_c_completed = key_event.code == KeyCode::Esc
&& !prefer_esc
&& matches!(view.on_ctrl_c(), CancellationEvent::Handled)
&& view.is_complete();
if ctrl_c_completed {
Expand Down Expand Up @@ -338,6 +341,7 @@ impl BottomPane {
self.on_active_view_complete();
}
self.show_quit_shortcut_hint(key_hint::ctrl(KeyCode::Char('c')));
self.request_redraw();
}
event
} else if self.composer_is_empty() {
Expand All @@ -346,6 +350,7 @@ impl BottomPane {
self.view_stack.pop();
self.clear_composer_for_ctrl_c();
self.show_quit_shortcut_hint(key_hint::ctrl(KeyCode::Char('c')));
self.request_redraw();
CancellationEvent::Handled
}
}
Expand Down Expand Up @@ -815,7 +820,9 @@ mod tests {
use insta::assert_snapshot;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use std::cell::Cell;
use std::path::PathBuf;
use std::rc::Rc;
use tokio::sync::mpsc::unbounded_channel;

fn snapshot_buffer(buf: &Buffer) -> String {
Expand Down Expand Up @@ -1241,4 +1248,63 @@ mod tests {
"expected Esc to send Op::Interrupt while a task is running"
);
}

#[test]
fn esc_routes_to_handle_key_event_when_requested() {
#[derive(Default)]
struct EscRoutingView {
on_ctrl_c_calls: Rc<Cell<usize>>,
handle_calls: Rc<Cell<usize>>,
}

impl Renderable for EscRoutingView {
fn render(&self, _area: Rect, _buf: &mut Buffer) {}

fn desired_height(&self, _width: u16) -> u16 {
0
}
}

impl BottomPaneView for EscRoutingView {
fn handle_key_event(&mut self, _key_event: KeyEvent) {
self.handle_calls
.set(self.handle_calls.get().saturating_add(1));
}

fn on_ctrl_c(&mut self) -> CancellationEvent {
self.on_ctrl_c_calls
.set(self.on_ctrl_c_calls.get().saturating_add(1));
CancellationEvent::Handled
}

fn prefer_esc_to_handle_key_event(&self) -> bool {
true
}
}

let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let mut pane = BottomPane::new(BottomPaneParams {
app_event_tx: tx,
frame_requester: FrameRequester::test_dummy(),
has_input_focus: true,
enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
disable_paste_burst: false,
animations_enabled: true,
skills: Some(Vec::new()),
});

let on_ctrl_c_calls = Rc::new(Cell::new(0));
let handle_calls = Rc::new(Cell::new(0));
pane.push_view(Box::new(EscRoutingView {
on_ctrl_c_calls: Rc::clone(&on_ctrl_c_calls),
handle_calls: Rc::clone(&handle_calls),
}));

pane.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));

assert_eq!(on_ctrl_c_calls.get(), 0);
assert_eq!(handle_calls.get(), 1);
}
}
Loading
Loading