show diff output in the pager (#2568)

this shows `/diff` output in an overlay like the transcript, instead of
dumping it into history.



https://github.com/user-attachments/assets/48e79b65-7f66-45dd-97b3-d5c627ac7349
This commit is contained in:
Jeremy Rose
2025-08-22 08:24:13 -07:00
committed by GitHub
parent e4c275d615
commit 76dc3f6054
4 changed files with 44 additions and 22 deletions

View File

@@ -5,6 +5,7 @@ use crate::file_search::FileSearchManager;
use crate::transcript_app::TranscriptApp; use crate::transcript_app::TranscriptApp;
use crate::tui; use crate::tui;
use crate::tui::TuiEvent; use crate::tui::TuiEvent;
use codex_ansi_escape::ansi_escape_line;
use codex_core::ConversationManager; use codex_core::ConversationManager;
use codex_core::config::Config; use codex_core::config::Config;
use codex_core::protocol::TokenUsage; use codex_core::protocol::TokenUsage;
@@ -17,6 +18,7 @@ use crossterm::terminal::EnterAlternateScreen;
use crossterm::terminal::LeaveAlternateScreen; use crossterm::terminal::LeaveAlternateScreen;
use crossterm::terminal::supports_keyboard_enhancement; use crossterm::terminal::supports_keyboard_enhancement;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui::style::Stylize;
use ratatui::text::Line; use ratatui::text::Line;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@@ -126,8 +128,8 @@ impl App {
tui.insert_history_lines(lines); tui.insert_history_lines(lines);
} }
self.transcript_overlay = None; self.transcript_overlay = None;
tui.frame_requester().schedule_frame();
} }
tui.frame_requester().schedule_frame();
} else { } else {
match event { match event {
TuiEvent::Key(key_event) => { TuiEvent::Key(key_event) => {
@@ -238,7 +240,29 @@ impl App {
} }
AppEvent::CodexOp(op) => self.chat_widget.submit_op(op), AppEvent::CodexOp(op) => self.chat_widget.submit_op(op),
AppEvent::DiffResult(text) => { 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<ratatui::text::Line<'static>> = 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) => { AppEvent::StartFileSearch(query) => {
if !query.is_empty() { if !query.is_empty() {

View File

@@ -818,9 +818,8 @@ impl ChatWidget {
self.request_redraw(); 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.bottom_pane.set_task_running(false);
self.add_to_history(history_cell::new_diff_output(diff_output));
self.mark_needs_redraw(); self.mark_needs_redraw();
} }

View File

@@ -506,20 +506,6 @@ pub(crate) fn new_completed_mcp_tool_call(
Box::new(PlainHistoryCell { lines }) Box::new(PlainHistoryCell { lines })
} }
pub(crate) fn new_diff_output(message: String) -> PlainHistoryCell {
let mut lines: Vec<Line<'static>> = 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( pub(crate) fn new_status_output(
config: &Config, config: &Config,
usage: &TokenUsage, usage: &TokenUsage,

View File

@@ -21,6 +21,7 @@ pub(crate) struct TranscriptApp {
pub(crate) transcript_lines: Vec<Line<'static>>, pub(crate) transcript_lines: Vec<Line<'static>>,
pub(crate) scroll_offset: usize, pub(crate) scroll_offset: usize,
pub(crate) is_done: bool, pub(crate) is_done: bool,
title: String,
} }
impl TranscriptApp { impl TranscriptApp {
@@ -29,6 +30,16 @@ impl TranscriptApp {
transcript_lines, transcript_lines,
scroll_offset: 0, scroll_offset: 0,
is_done: false, is_done: false,
title: "T R A N S C R I P T".to_string(),
}
}
pub(crate) fn with_title(transcript_lines: Vec<Line<'static>>, title: String) -> Self {
Self {
transcript_lines,
scroll_offset: 0,
is_done: false,
title,
} }
} }
@@ -114,8 +125,11 @@ impl TranscriptApp {
} => { } => {
self.scroll_offset = usize::MAX; self.scroll_offset = usize::MAX;
} }
_ => {} _ => {
return;
}
} }
tui.frame_requester().schedule_frame();
} }
fn scroll_area(&self, area: Rect) -> Rect { fn scroll_area(&self, area: Rect) -> Rect {
@@ -130,9 +144,8 @@ impl TranscriptApp {
Span::from("/ ".repeat(area.width as usize / 2)) Span::from("/ ".repeat(area.width as usize / 2))
.dim() .dim()
.render_ref(area, buf); .render_ref(area, buf);
Span::from("/ T R A N S C R I P T") let header = format!("/ {}", self.title);
.dim() Span::from(header).dim().render_ref(area, buf);
.render_ref(area, buf);
// Main content area (excludes header and bottom status section) // Main content area (excludes header and bottom status section)
let content_area = self.scroll_area(area); let content_area = self.scroll_area(area);