Skip to content

Commit 0e5d72c

Browse files
tui: bring the transcript closer to display mode (#4848)
before <img width="1161" height="836" alt="Screenshot 2025-10-06 at 3 06 52 PM" src="https://github.com/user-attachments/assets/7622fd6b-9d37-402f-8651-61c2c55dcbc6" /> after <img width="1161" height="858" alt="Screenshot 2025-10-06 at 3 07 02 PM" src="https://github.com/user-attachments/assets/1498f327-1d1a-4630-951f-7ca371ab0139" />
1 parent 60f9e85 commit 0e5d72c

File tree

13 files changed

+233
-187
lines changed

13 files changed

+233
-187
lines changed

codex-rs/tui/src/app_backtrack.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,9 @@ impl App {
134134
/// Useful when switching sessions to ensure prior history remains visible.
135135
pub(crate) fn render_transcript_once(&mut self, tui: &mut tui::Tui) {
136136
if !self.transcript_cells.is_empty() {
137+
let width = tui.terminal.last_known_screen_size.width;
137138
for cell in &self.transcript_cells {
138-
tui.insert_history_lines(cell.transcript_lines());
139+
tui.insert_history_lines(cell.display_lines(width));
139140
}
140141
}
141142
}

codex-rs/tui/src/bottom_pane/chat_composer.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ use crate::bottom_pane::prompt_args::prompt_has_numeric_placeholders;
3838
use crate::slash_command::SlashCommand;
3939
use crate::slash_command::built_in_slash_commands;
4040
use crate::style::user_message_style;
41-
use crate::terminal_palette;
4241
use codex_protocol::custom_prompts::CustomPrompt;
4342
use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX;
4443

@@ -1533,7 +1532,7 @@ impl WidgetRef for ChatComposer {
15331532
}
15341533
}
15351534
}
1536-
let style = user_message_style(terminal_palette::default_bg());
1535+
let style = user_message_style();
15371536
let mut block_rect = composer_rect;
15381537
block_rect.y = composer_rect.y.saturating_sub(1);
15391538
block_rect.height = composer_rect.height.saturating_add(1);

codex-rs/tui/src/bottom_pane/list_selection_view.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use crate::render::RectExt as _;
2020
use crate::render::renderable::ColumnRenderable;
2121
use crate::render::renderable::Renderable;
2222
use crate::style::user_message_style;
23-
use crate::terminal_palette;
2423

2524
use super::CancellationEvent;
2625
use super::bottom_pane_view::BottomPaneView;
@@ -350,7 +349,7 @@ impl Renderable for ListSelectionView {
350349
.areas(area);
351350

352351
Block::default()
353-
.style(user_message_style(terminal_palette::default_bg()))
352+
.style(user_message_style())
354353
.render(content_area, buf);
355354

356355
let header_height = self

codex-rs/tui/src/custom_terminal.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ where
120120
/// Last known position of the cursor. Used to find the new area when the viewport is inlined
121121
/// and the terminal resized.
122122
pub last_known_cursor_pos: Position,
123+
124+
use_custom_flush: bool,
123125
}
124126

125127
impl<B> Drop for Terminal<B>
@@ -158,6 +160,7 @@ where
158160
viewport_area: Rect::new(0, cursor_pos.y, 0, 0),
159161
last_known_screen_size: screen_size,
160162
last_known_cursor_pos: cursor_pos,
163+
use_custom_flush: true,
161164
})
162165
}
163166

@@ -190,15 +193,24 @@ where
190193
pub fn flush(&mut self) -> io::Result<()> {
191194
let previous_buffer = &self.buffers[1 - self.current];
192195
let current_buffer = &self.buffers[self.current];
193-
let updates = diff_buffers(previous_buffer, current_buffer);
194-
if let Some(DrawCommand::Put { x, y, .. }) = updates
195-
.iter()
196-
.rev()
197-
.find(|cmd| matches!(cmd, DrawCommand::Put { .. }))
198-
{
199-
self.last_known_cursor_pos = Position { x: *x, y: *y };
196+
197+
if self.use_custom_flush {
198+
let updates = diff_buffers(previous_buffer, current_buffer);
199+
if let Some(DrawCommand::Put { x, y, .. }) = updates
200+
.iter()
201+
.rev()
202+
.find(|cmd| matches!(cmd, DrawCommand::Put { .. }))
203+
{
204+
self.last_known_cursor_pos = Position { x: *x, y: *y };
205+
}
206+
draw(&mut self.backend, updates.into_iter())
207+
} else {
208+
let updates = previous_buffer.diff(current_buffer);
209+
if let Some((x, y, _)) = updates.last() {
210+
self.last_known_cursor_pos = Position { x: *x, y: *y };
211+
}
212+
self.backend.draw(updates.into_iter())
200213
}
201-
draw(&mut self.backend, updates.into_iter())
202214
}
203215

204216
/// Updates the Terminal so that internal buffers match the requested area.
@@ -408,11 +420,13 @@ fn diff_buffers<'a>(a: &'a Buffer, b: &'a Buffer) -> Vec<DrawCommand<'a>> {
408420

409421
let x = row
410422
.iter()
411-
.rposition(|cell| cell.symbol() != " " || cell.bg != bg)
423+
.rposition(|cell| {
424+
cell.symbol() != " " || cell.bg != bg || cell.modifier != Modifier::empty()
425+
})
412426
.unwrap_or(0);
413427
last_nonblank_column[y as usize] = x as u16;
414-
let (x_abs, y_abs) = a.pos_of(row_start + x + 1);
415428
if x < (a.area.width as usize).saturating_sub(1) {
429+
let (x_abs, y_abs) = a.pos_of(row_start + x + 1);
416430
updates.push(DrawCommand::ClearToEnd {
417431
x: x_abs,
418432
y: y_abs,

codex-rs/tui/src/diff_render.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl From<DiffSummary> for Box<dyn Renderable> {
6767
rows.push(Box::new(path));
6868
rows.push(Box::new(RtLine::from("")));
6969
rows.push(Box::new(InsetRenderable::new(
70-
Box::new(row.change),
70+
row.change,
7171
Insets::tlbr(0, 2, 0, 0),
7272
)));
7373
}

codex-rs/tui/src/exec_cell/render.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::render::line_utils::push_owned_lines;
1111
use crate::shimmer::shimmer_spans;
1212
use crate::wrapping::RtOptions;
1313
use crate::wrapping::word_wrap_line;
14+
use crate::wrapping::word_wrap_lines;
1415
use codex_ansi_escape::ansi_escape_line;
1516
use codex_common::elapsed::format_duration;
1617
use codex_protocol::parse_command::ParsedCommand;
@@ -138,17 +139,25 @@ impl HistoryCell for ExecCell {
138139
}
139140
}
140141

141-
fn transcript_lines(&self) -> Vec<Line<'static>> {
142+
fn desired_transcript_height(&self, width: u16) -> u16 {
143+
self.transcript_lines(width).len() as u16
144+
}
145+
146+
fn transcript_lines(&self, width: u16) -> Vec<Line<'static>> {
142147
let mut lines: Vec<Line<'static>> = vec![];
143-
for call in self.iter_calls() {
144-
let cmd_display = strip_bash_lc_and_escape(&call.command);
145-
for (i, part) in cmd_display.lines().enumerate() {
146-
if i == 0 {
147-
lines.push(vec!["$ ".magenta(), part.to_string().into()].into());
148-
} else {
149-
lines.push(vec![" ".into(), part.to_string().into()].into());
150-
}
148+
for (i, call) in self.iter_calls().enumerate() {
149+
if i > 0 {
150+
lines.push("".into());
151151
}
152+
let script = strip_bash_lc_and_escape(&call.command);
153+
let highlighted_script = highlight_bash_to_lines(&script);
154+
let cmd_display = word_wrap_lines(
155+
&highlighted_script,
156+
RtOptions::new(width as usize)
157+
.initial_indent("$ ".magenta().into())
158+
.subsequent_indent(" ".into()),
159+
);
160+
lines.extend(cmd_display);
152161

153162
if let Some(output) = call.output.as_ref() {
154163
lines.extend(output.formatted_output.lines().map(ansi_escape_line));
@@ -167,7 +176,6 @@ impl HistoryCell for ExecCell {
167176
result.push_span(format!(" • {duration}").dim());
168177
lines.push(result);
169178
}
170-
lines.push("".into());
171179
}
172180
lines
173181
}

0 commit comments

Comments
 (0)