mirror of
https://github.com/openai/codex.git
synced 2026-10-26 18:38:47 +00:00
wip(tui): avoid blank prefill during resize replay
This commit is contained in:
@@ -46,7 +46,10 @@ use ratatui::text::Span;
|
||||
pub enum InsertHistoryMode {
|
||||
Standard,
|
||||
Zellij,
|
||||
FullScreenReplay,
|
||||
FullScreenReplayPrefill,
|
||||
// Kept available while validating terminal-specific replay behavior.
|
||||
#[allow(dead_code)]
|
||||
FullScreenReplayDirect,
|
||||
}
|
||||
|
||||
impl InsertHistoryMode {
|
||||
@@ -78,9 +81,10 @@ where
|
||||
/// emits newlines at the screen bottom to create space (since Zellij ignores scroll
|
||||
/// region escapes) and writes lines at computed absolute positions. Both modes
|
||||
/// update `terminal.viewport_area` so subsequent draw passes know where the
|
||||
/// viewport moved to. In `FullScreenReplay` mode every line is replayed through
|
||||
/// normal full-screen scrolling so terminals that drop scroll-region output from
|
||||
/// scrollback can still rebuild deep history after a resize.
|
||||
/// viewport moved to. In `FullScreenReplayPrefill` mode blank rows are used to
|
||||
/// create scrollback space before replaying content, matching xterm.js behavior.
|
||||
/// `FullScreenReplayDirect` keeps the no-prefill strategy isolated for terminals
|
||||
/// where the prefilled blank rows become user-visible scrollback.
|
||||
pub fn insert_history_lines_with_mode<B>(
|
||||
terminal: &mut crate::custom_terminal::Terminal<B>,
|
||||
lines: Vec<Line>,
|
||||
@@ -126,88 +130,103 @@ where
|
||||
}
|
||||
let wrapped_lines = wrapped_rows as u16;
|
||||
|
||||
if matches!(
|
||||
mode,
|
||||
InsertHistoryMode::Zellij | InsertHistoryMode::FullScreenReplay
|
||||
) {
|
||||
let space_below = screen_size.height.saturating_sub(area.bottom());
|
||||
let shift_down = if matches!(mode, InsertHistoryMode::Zellij) {
|
||||
wrapped_lines.min(space_below)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let scroll_up_amount = wrapped_lines.saturating_sub(shift_down);
|
||||
match mode {
|
||||
InsertHistoryMode::Zellij | InsertHistoryMode::FullScreenReplayPrefill => {
|
||||
let space_below = screen_size.height.saturating_sub(area.bottom());
|
||||
let shift_down = if matches!(mode, InsertHistoryMode::Zellij) {
|
||||
wrapped_lines.min(space_below)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let scroll_up_amount = wrapped_lines.saturating_sub(shift_down);
|
||||
|
||||
if scroll_up_amount > 0 {
|
||||
// Scroll the entire screen up by emitting \n at the bottom
|
||||
queue!(writer, MoveTo(0, screen_size.height.saturating_sub(1)))?;
|
||||
for _ in 0..scroll_up_amount {
|
||||
queue!(writer, Print("\n"))?;
|
||||
if scroll_up_amount > 0 {
|
||||
// Scroll the entire screen up by emitting \n at the bottom
|
||||
queue!(
|
||||
writer,
|
||||
MoveTo(/*x*/ 0, screen_size.height.saturating_sub(1))
|
||||
)?;
|
||||
for _ in 0..scroll_up_amount {
|
||||
queue!(writer, Print("\n"))?;
|
||||
}
|
||||
}
|
||||
|
||||
if shift_down > 0 {
|
||||
area.y += shift_down;
|
||||
should_update_area = true;
|
||||
}
|
||||
|
||||
let cursor_top = area.top().saturating_sub(scroll_up_amount + shift_down);
|
||||
queue!(writer, MoveTo(/*x*/ 0, cursor_top))?;
|
||||
|
||||
for (i, line) in wrapped.iter().enumerate() {
|
||||
if i > 0 {
|
||||
queue!(writer, Print("\r\n"))?;
|
||||
}
|
||||
write_history_line(writer, line, wrap_width)?;
|
||||
}
|
||||
}
|
||||
InsertHistoryMode::FullScreenReplayDirect => {
|
||||
// Rebuild scrollback with transcript content itself. Pre-scrolling with blank
|
||||
// newlines creates visible empty scrollback in terminals such as Terminal.app.
|
||||
queue!(writer, MoveTo(/*x*/ 0, /*y*/ 0))?;
|
||||
|
||||
if shift_down > 0 {
|
||||
area.y += shift_down;
|
||||
should_update_area = true;
|
||||
for (i, line) in wrapped.iter().enumerate() {
|
||||
if i > 0 {
|
||||
queue!(writer, Print("\r\n"))?;
|
||||
}
|
||||
write_history_line(writer, line, wrap_width)?;
|
||||
}
|
||||
}
|
||||
InsertHistoryMode::Standard => {
|
||||
let cursor_top = if area.bottom() < screen_size.height {
|
||||
let scroll_amount = wrapped_lines.min(screen_size.height - area.bottom());
|
||||
|
||||
let cursor_top = area.top().saturating_sub(scroll_up_amount + shift_down);
|
||||
queue!(writer, MoveTo(0, cursor_top))?;
|
||||
let top_1based = area.top() + 1;
|
||||
queue!(writer, SetScrollRegion(top_1based..screen_size.height))?;
|
||||
queue!(writer, MoveTo(/*x*/ 0, area.top()))?;
|
||||
for _ in 0..scroll_amount {
|
||||
queue!(writer, Print("\x1bM"))?;
|
||||
}
|
||||
queue!(writer, ResetScrollRegion)?;
|
||||
|
||||
for (i, line) in wrapped.iter().enumerate() {
|
||||
if i > 0 {
|
||||
let cursor_top = area.top().saturating_sub(1);
|
||||
area.y += scroll_amount;
|
||||
should_update_area = true;
|
||||
cursor_top
|
||||
} else {
|
||||
area.top().saturating_sub(1)
|
||||
};
|
||||
|
||||
// Limit the scroll region to the lines from the top of the screen to the
|
||||
// top of the viewport. With this in place, when we add lines inside this
|
||||
// area, only the lines in this area will be scrolled. We place the cursor
|
||||
// at the end of the scroll region, and add lines starting there.
|
||||
//
|
||||
// ┌─Screen───────────────────────┐
|
||||
// │┌╌Scroll region╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐│
|
||||
// │┆ ┆│
|
||||
// │┆ ┆│
|
||||
// │┆ ┆│
|
||||
// │█╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘│
|
||||
// │╭─Viewport───────────────────╮│
|
||||
// ││ ││
|
||||
// │╰────────────────────────────╯│
|
||||
// └──────────────────────────────┘
|
||||
queue!(writer, SetScrollRegion(1..area.top()))?;
|
||||
|
||||
// NB: we are using MoveTo instead of set_cursor_position here to avoid messing with the
|
||||
// terminal's last_known_cursor_position, which hopefully will still be accurate after we
|
||||
// fetch/restore the cursor position. insert_history_lines should be cursor-position-neutral :)
|
||||
queue!(writer, MoveTo(/*x*/ 0, cursor_top))?;
|
||||
|
||||
for line in &wrapped {
|
||||
queue!(writer, Print("\r\n"))?;
|
||||
write_history_line(writer, line, wrap_width)?;
|
||||
}
|
||||
write_history_line(writer, line, wrap_width)?;
|
||||
}
|
||||
} else {
|
||||
let cursor_top = if area.bottom() < screen_size.height {
|
||||
let scroll_amount = wrapped_lines.min(screen_size.height - area.bottom());
|
||||
|
||||
let top_1based = area.top() + 1;
|
||||
queue!(writer, SetScrollRegion(top_1based..screen_size.height))?;
|
||||
queue!(writer, MoveTo(0, area.top()))?;
|
||||
for _ in 0..scroll_amount {
|
||||
queue!(writer, Print("\x1bM"))?;
|
||||
}
|
||||
queue!(writer, ResetScrollRegion)?;
|
||||
|
||||
let cursor_top = area.top().saturating_sub(1);
|
||||
area.y += scroll_amount;
|
||||
should_update_area = true;
|
||||
cursor_top
|
||||
} else {
|
||||
area.top().saturating_sub(1)
|
||||
};
|
||||
|
||||
// Limit the scroll region to the lines from the top of the screen to the
|
||||
// top of the viewport. With this in place, when we add lines inside this
|
||||
// area, only the lines in this area will be scrolled. We place the cursor
|
||||
// at the end of the scroll region, and add lines starting there.
|
||||
//
|
||||
// ┌─Screen───────────────────────┐
|
||||
// │┌╌Scroll region╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐│
|
||||
// │┆ ┆│
|
||||
// │┆ ┆│
|
||||
// │┆ ┆│
|
||||
// │█╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘│
|
||||
// │╭─Viewport───────────────────╮│
|
||||
// ││ ││
|
||||
// │╰────────────────────────────╯│
|
||||
// └──────────────────────────────┘
|
||||
queue!(writer, SetScrollRegion(1..area.top()))?;
|
||||
|
||||
// NB: we are using MoveTo instead of set_cursor_position here to avoid messing with the
|
||||
// terminal's last_known_cursor_position, which hopefully will still be accurate after we
|
||||
// fetch/restore the cursor position. insert_history_lines should be cursor-position-neutral :)
|
||||
queue!(writer, MoveTo(0, cursor_top))?;
|
||||
|
||||
for line in &wrapped {
|
||||
queue!(writer, Print("\r\n"))?;
|
||||
write_history_line(writer, line, wrap_width)?;
|
||||
}
|
||||
|
||||
queue!(writer, ResetScrollRegion)?;
|
||||
}
|
||||
|
||||
// Restore the cursor position to where it was before we started.
|
||||
@@ -857,8 +876,12 @@ mod tests {
|
||||
let lines: Vec<Line<'static>> = (0..12)
|
||||
.map(|idx| Line::from(format!("reflow line {idx:02}")))
|
||||
.collect();
|
||||
insert_history_lines_with_mode(&mut term, lines, InsertHistoryMode::FullScreenReplay)
|
||||
.expect("insert reflow history");
|
||||
insert_history_lines_with_mode(
|
||||
&mut term,
|
||||
lines,
|
||||
InsertHistoryMode::FullScreenReplayPrefill,
|
||||
)
|
||||
.expect("insert reflow history");
|
||||
|
||||
let start_row = 0;
|
||||
let rows: Vec<String> = term
|
||||
|
||||
@@ -833,7 +833,7 @@ impl Tui {
|
||||
crate::insert_history::insert_history_lines_with_mode(
|
||||
terminal,
|
||||
pending_reflow_history_lines.clone(),
|
||||
crate::insert_history::InsertHistoryMode::FullScreenReplay,
|
||||
crate::insert_history::InsertHistoryMode::FullScreenReplayPrefill,
|
||||
)?;
|
||||
pending_reflow_history_lines.clear();
|
||||
Ok(true)
|
||||
|
||||
Reference in New Issue
Block a user