diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index e706fa72..8737347f 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -5,6 +5,7 @@ use crate::file_search::FileSearchManager; use crate::transcript_app::TranscriptApp; use crate::tui; use crate::tui::TuiEvent; +use codex_ansi_escape::ansi_escape_line; use codex_core::ConversationManager; use codex_core::config::Config; use codex_core::protocol::TokenUsage; @@ -17,6 +18,7 @@ use crossterm::terminal::EnterAlternateScreen; use crossterm::terminal::LeaveAlternateScreen; use crossterm::terminal::supports_keyboard_enhancement; use ratatui::layout::Rect; +use ratatui::style::Stylize; use ratatui::text::Line; use std::path::PathBuf; use std::sync::Arc; @@ -126,8 +128,8 @@ impl App { tui.insert_history_lines(lines); } self.transcript_overlay = None; + tui.frame_requester().schedule_frame(); } - tui.frame_requester().schedule_frame(); } else { match event { TuiEvent::Key(key_event) => { @@ -238,7 +240,29 @@ impl App { } AppEvent::CodexOp(op) => self.chat_widget.submit_op(op), AppEvent::DiffResult(text) => { - self.chat_widget.add_diff_output(text); + // Clear the in-progress state in the bottom pane + self.chat_widget.on_diff_complete(); + + // Enter alternate screen and set viewport to full size. + let _ = execute!(tui.terminal.backend_mut(), EnterAlternateScreen); + if let Ok(size) = tui.terminal.size() { + self.transcript_saved_viewport = Some(tui.terminal.viewport_area); + tui.terminal + .set_viewport_area(Rect::new(0, 0, size.width, size.height)); + let _ = tui.terminal.clear(); + } + + // Build pager lines directly without the "/diff" header + let pager_lines: Vec> = if text.trim().is_empty() { + vec!["No changes detected.".italic().into()] + } else { + text.lines().map(ansi_escape_line).collect() + }; + self.transcript_overlay = Some(TranscriptApp::with_title( + pager_lines, + "D I F F".to_string(), + )); + tui.frame_requester().schedule_frame(); } AppEvent::StartFileSearch(query) => { if !query.is_empty() { diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index fa18342b..6e6f8de8 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -818,9 +818,8 @@ impl ChatWidget { self.request_redraw(); } - pub(crate) fn add_diff_output(&mut self, diff_output: String) { + pub(crate) fn on_diff_complete(&mut self) { self.bottom_pane.set_task_running(false); - self.add_to_history(history_cell::new_diff_output(diff_output)); self.mark_needs_redraw(); } diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 9f236d78..0cbb4888 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -506,20 +506,6 @@ pub(crate) fn new_completed_mcp_tool_call( Box::new(PlainHistoryCell { lines }) } -pub(crate) fn new_diff_output(message: String) -> PlainHistoryCell { - let mut lines: Vec> = Vec::new(); - lines.push(Line::from("/diff".magenta())); - - if message.trim().is_empty() { - lines.push(Line::from("No changes detected.".italic())); - } else { - lines.extend(message.lines().map(ansi_escape_line)); - } - - lines.push(Line::from("")); - PlainHistoryCell { lines } -} - pub(crate) fn new_status_output( config: &Config, usage: &TokenUsage, diff --git a/codex-rs/tui/src/transcript_app.rs b/codex-rs/tui/src/transcript_app.rs index 059e2a07..0c84ad55 100644 --- a/codex-rs/tui/src/transcript_app.rs +++ b/codex-rs/tui/src/transcript_app.rs @@ -21,6 +21,7 @@ pub(crate) struct TranscriptApp { pub(crate) transcript_lines: Vec>, pub(crate) scroll_offset: usize, pub(crate) is_done: bool, + title: String, } impl TranscriptApp { @@ -29,6 +30,16 @@ impl TranscriptApp { transcript_lines, scroll_offset: 0, is_done: false, + title: "T R A N S C R I P T".to_string(), + } + } + + pub(crate) fn with_title(transcript_lines: Vec>, title: String) -> Self { + Self { + transcript_lines, + scroll_offset: 0, + is_done: false, + title, } } @@ -114,8 +125,11 @@ impl TranscriptApp { } => { self.scroll_offset = usize::MAX; } - _ => {} + _ => { + return; + } } + tui.frame_requester().schedule_frame(); } fn scroll_area(&self, area: Rect) -> Rect { @@ -130,9 +144,8 @@ impl TranscriptApp { Span::from("/ ".repeat(area.width as usize / 2)) .dim() .render_ref(area, buf); - Span::from("/ T R A N S C R I P T") - .dim() - .render_ref(area, buf); + let header = format!("/ {}", self.title); + Span::from(header).dim().render_ref(area, buf); // Main content area (excludes header and bottom status section) let content_area = self.scroll_area(area);