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::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<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) => {
if !query.is_empty() {

View File

@@ -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();
}

View File

@@ -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<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(
config: &Config,
usage: &TokenUsage,

View File

@@ -21,6 +21,7 @@ pub(crate) struct TranscriptApp {
pub(crate) transcript_lines: Vec<Line<'static>>,
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<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;
}
_ => {}
_ => {
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);