mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Preserve inline TUI scrollback in Windows Terminal (#39619)
## Why Windows Terminal can discard rows scrolled through a partial DEC scroll region instead of retaining them in terminal scrollback. ## What changed - Detect Windows Terminal, including via `WT_SESSION`, and use full-screen scrolling for inline viewport growth and history insertion. - Keep the existing terminal-wrapped history behavior for Zellij while centralizing terminal-specific scrollback selection. ## Testing Add VT100-backed tests covering strategy selection and preservation of scrollback during history insertion and viewport growth. GitOrigin-RevId: 209a7a8053a1fcf911d6e0aee764e563b8725bd1
This commit is contained in:
1 parent
4bb7804a23
commit
da6e68951b
5 files changed
+284
-34
No files matched your search
@@ -48,13 +48,12 @@ pub enum HistoryLineWrapPolicy {
|
|||||||
|
|
||||||
/// Selects the terminal escape strategy used when writing history above the viewport.
|
/// Selects the terminal escape strategy used when writing history above the viewport.
|
||||||
///
|
///
|
||||||
/// Raw lines intentionally remain unbroken so terminal selection copies their source faithfully.
|
/// Full-screen insertion preserves terminal-native scrollback when partial scroll regions are
|
||||||
/// Zellij does not constrain soft-wrapped continuation rows to Codex's scroll region, so its raw
|
/// unreliable and keeps terminal-managed soft wrapping intact for Zellij.
|
||||||
/// path appends history through the terminal and reserves blank rows for the next viewport draw.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) enum InsertHistoryMode {
|
pub(crate) enum InsertHistoryMode {
|
||||||
Standard,
|
Standard,
|
||||||
ZellijRaw,
|
FullScreen,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert `lines` above the viewport using the terminal's backend writer
|
/// Insert `lines` above the viewport using the terminal's backend writer
|
||||||
@@ -133,7 +132,7 @@ where
|
|||||||
let (wrapped, wrapped_rows) = wrap_history_hyperlink_lines(lines, wrap_width, wrap_policy);
|
let (wrapped, wrapped_rows) = wrap_history_hyperlink_lines(lines, wrap_width, wrap_policy);
|
||||||
let wrapped_lines = wrapped_rows as u16;
|
let wrapped_lines = wrapped_rows as u16;
|
||||||
match mode {
|
match mode {
|
||||||
InsertHistoryMode::ZellijRaw => {
|
InsertHistoryMode::FullScreen => {
|
||||||
// The existing viewport is immediately replaced in the same draw pass. Clear it
|
// The existing viewport is immediately replaced in the same draw pass. Clear it
|
||||||
// before terminal scrolling can move composer contents into scrollback.
|
// before terminal scrolling can move composer contents into scrollback.
|
||||||
terminal.clear_after_position(area.as_position())?;
|
terminal.clear_after_position(area.as_position())?;
|
||||||
@@ -1033,7 +1032,7 @@ mod tests {
|
|||||||
insert_history_lines_with_mode_and_wrap_policy(
|
insert_history_lines_with_mode_and_wrap_policy(
|
||||||
&mut term,
|
&mut term,
|
||||||
vec![line],
|
vec![line],
|
||||||
InsertHistoryMode::ZellijRaw,
|
InsertHistoryMode::FullScreen,
|
||||||
HistoryLineWrapPolicy::Terminal,
|
HistoryLineWrapPolicy::Terminal,
|
||||||
)
|
)
|
||||||
.expect("insert Zellij raw history");
|
.expect("insert Zellij raw history");
|
||||||
@@ -1069,7 +1068,7 @@ mod tests {
|
|||||||
insert_history_lines_with_mode_and_wrap_policy(
|
insert_history_lines_with_mode_and_wrap_policy(
|
||||||
&mut term,
|
&mut term,
|
||||||
vec![line],
|
vec![line],
|
||||||
InsertHistoryMode::ZellijRaw,
|
InsertHistoryMode::FullScreen,
|
||||||
HistoryLineWrapPolicy::Terminal,
|
HistoryLineWrapPolicy::Terminal,
|
||||||
)
|
)
|
||||||
.expect("replay Zellij raw history");
|
.expect("replay Zellij raw history");
|
||||||
|
|||||||
+23
-21
@@ -48,7 +48,6 @@ use self::input_boundary::terminal_input_is_readable;
|
|||||||
use crate::custom_terminal;
|
use crate::custom_terminal;
|
||||||
use crate::custom_terminal::Terminal as CustomTerminal;
|
use crate::custom_terminal::Terminal as CustomTerminal;
|
||||||
use crate::insert_history::HistoryLineWrapPolicy;
|
use crate::insert_history::HistoryLineWrapPolicy;
|
||||||
use crate::insert_history::InsertHistoryMode;
|
|
||||||
use crate::notifications::DesktopNotificationBackend;
|
use crate::notifications::DesktopNotificationBackend;
|
||||||
use crate::notifications::detect_backend;
|
use crate::notifications::detect_backend;
|
||||||
use crate::terminal_hyperlinks::HyperlinkLine;
|
use crate::terminal_hyperlinks::HyperlinkLine;
|
||||||
@@ -58,6 +57,7 @@ use crate::tui::event_stream::TuiEventStream;
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use crate::tui::job_control::SuspendContext;
|
use crate::tui::job_control::SuspendContext;
|
||||||
use crate::tui::screen_size::ScreenSizePolicy;
|
use crate::tui::screen_size::ScreenSizePolicy;
|
||||||
|
use crate::tui::scrollback::ScrollbackStrategy;
|
||||||
use codex_config::types::NotificationCondition;
|
use codex_config::types::NotificationCondition;
|
||||||
use codex_config::types::NotificationMethod;
|
use codex_config::types::NotificationMethod;
|
||||||
|
|
||||||
@@ -70,6 +70,7 @@ mod input_boundary;
|
|||||||
mod job_control;
|
mod job_control;
|
||||||
mod keyboard_modes;
|
mod keyboard_modes;
|
||||||
mod screen_size;
|
mod screen_size;
|
||||||
|
mod scrollback;
|
||||||
#[cfg(all(test, unix))]
|
#[cfg(all(test, unix))]
|
||||||
#[path = "tui_startup_tests.rs"]
|
#[path = "tui_startup_tests.rs"]
|
||||||
mod startup_tests;
|
mod startup_tests;
|
||||||
@@ -594,8 +595,7 @@ pub struct Tui {
|
|||||||
enhanced_keys_supported: bool,
|
enhanced_keys_supported: bool,
|
||||||
notification_backend: Option<DesktopNotificationBackend>,
|
notification_backend: Option<DesktopNotificationBackend>,
|
||||||
notification_condition: NotificationCondition,
|
notification_condition: NotificationCondition,
|
||||||
// Raw terminal-wrapped history needs a non-scroll-region insertion path in Zellij.
|
scrollback: ScrollbackStrategy,
|
||||||
is_zellij: bool,
|
|
||||||
// When false, enter_alt_screen() becomes a no-op.
|
// When false, enter_alt_screen() becomes a no-op.
|
||||||
alt_screen_enabled: bool,
|
alt_screen_enabled: bool,
|
||||||
// Keeps unmanaged process stderr writes out of the inline viewport.
|
// Keeps unmanaged process stderr writes out of the inline viewport.
|
||||||
@@ -631,7 +631,7 @@ impl Tui {
|
|||||||
// Cache this to avoid contention with the event reader.
|
// Cache this to avoid contention with the event reader.
|
||||||
supports_color::on_cached(supports_color::Stream::Stdout);
|
supports_color::on_cached(supports_color::Stream::Stdout);
|
||||||
let _ = crate::terminal_palette::default_colors();
|
let _ = crate::terminal_palette::default_colors();
|
||||||
let is_zellij = codex_terminal_detection::terminal_info().is_zellij();
|
let scrollback = ScrollbackStrategy::detect(&codex_terminal_detection::terminal_info());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
frame_requester,
|
frame_requester,
|
||||||
@@ -650,7 +650,7 @@ impl Tui {
|
|||||||
enhanced_keys_supported,
|
enhanced_keys_supported,
|
||||||
notification_backend: Some(detect_backend(NotificationMethod::default())),
|
notification_backend: Some(detect_backend(NotificationMethod::default())),
|
||||||
notification_condition: NotificationCondition::default(),
|
notification_condition: NotificationCondition::default(),
|
||||||
is_zellij,
|
scrollback,
|
||||||
alt_screen_enabled: true,
|
alt_screen_enabled: true,
|
||||||
_stderr_guard: stderr_guard,
|
_stderr_guard: stderr_guard,
|
||||||
}
|
}
|
||||||
@@ -893,6 +893,7 @@ impl Tui {
|
|||||||
terminal: &mut Terminal,
|
terminal: &mut Terminal,
|
||||||
height: u16,
|
height: u16,
|
||||||
screen_size: Size,
|
screen_size: Size,
|
||||||
|
scrollback: ScrollbackStrategy,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let terminal_height_shrank = screen_size.height < terminal.last_known_screen_size.height;
|
let terminal_height_shrank = screen_size.height < terminal.last_known_screen_size.height;
|
||||||
let terminal_height_grew = screen_size.height > terminal.last_known_screen_size.height;
|
let terminal_height_grew = screen_size.height > terminal.last_known_screen_size.height;
|
||||||
@@ -908,9 +909,7 @@ impl Tui {
|
|||||||
if area.bottom() > screen_size.height {
|
if area.bottom() > screen_size.height {
|
||||||
let scroll_by = area.bottom() - screen_size.height;
|
let scroll_by = area.bottom() - screen_size.height;
|
||||||
if !terminal_height_shrank {
|
if !terminal_height_shrank {
|
||||||
terminal
|
scrollback.grow_viewport(terminal, area.top(), screen_size, scroll_by)?;
|
||||||
.backend_mut()
|
|
||||||
.scroll_region_up(0..area.top(), scroll_by)?;
|
|
||||||
}
|
}
|
||||||
area.y = screen_size.height - area.height;
|
area.y = screen_size.height - area.height;
|
||||||
} else if terminal_height_grew && viewport_was_bottom_aligned {
|
} else if terminal_height_grew && viewport_was_bottom_aligned {
|
||||||
@@ -931,7 +930,7 @@ impl Tui {
|
|||||||
fn flush_pending_history_lines(
|
fn flush_pending_history_lines(
|
||||||
terminal: &mut Terminal,
|
terminal: &mut Terminal,
|
||||||
pending_history_lines: &mut Vec<PendingHistoryLines>,
|
pending_history_lines: &mut Vec<PendingHistoryLines>,
|
||||||
is_zellij: bool,
|
scrollback: ScrollbackStrategy,
|
||||||
screen_size: Size,
|
screen_size: Size,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if pending_history_lines.is_empty() {
|
if pending_history_lines.is_empty() {
|
||||||
@@ -939,11 +938,7 @@ impl Tui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for batch in pending_history_lines.iter() {
|
for batch in pending_history_lines.iter() {
|
||||||
let mode = if is_zellij && batch.wrap_policy == HistoryLineWrapPolicy::Terminal {
|
let mode = scrollback.history_insertion_mode(batch.wrap_policy);
|
||||||
InsertHistoryMode::ZellijRaw
|
|
||||||
} else {
|
|
||||||
InsertHistoryMode::Standard
|
|
||||||
};
|
|
||||||
crate::insert_history::insert_history_hyperlink_lines_with_mode_and_wrap_policy(
|
crate::insert_history::insert_history_hyperlink_lines_with_mode_and_wrap_policy(
|
||||||
terminal,
|
terminal,
|
||||||
&batch.lines,
|
&batch.lines,
|
||||||
@@ -992,9 +987,12 @@ impl Tui {
|
|||||||
area.width = screen_size.width;
|
area.width = screen_size.width;
|
||||||
// If the viewport has expanded, scroll everything else up to make room.
|
// If the viewport has expanded, scroll everything else up to make room.
|
||||||
if area.bottom() > screen_size.height {
|
if area.bottom() > screen_size.height {
|
||||||
terminal
|
self.scrollback.grow_viewport(
|
||||||
.backend_mut()
|
terminal,
|
||||||
.scroll_region_up(0..area.top(), area.bottom() - screen_size.height)?;
|
area.top(),
|
||||||
|
screen_size,
|
||||||
|
area.bottom() - screen_size.height,
|
||||||
|
)?;
|
||||||
area.y = screen_size.height - area.height;
|
area.y = screen_size.height - area.height;
|
||||||
}
|
}
|
||||||
if area != terminal.viewport_area {
|
if area != terminal.viewport_area {
|
||||||
@@ -1007,7 +1005,7 @@ impl Tui {
|
|||||||
Self::flush_pending_history_lines(
|
Self::flush_pending_history_lines(
|
||||||
terminal,
|
terminal,
|
||||||
&mut self.pending_history_lines,
|
&mut self.pending_history_lines,
|
||||||
self.is_zellij,
|
self.scrollback,
|
||||||
screen_size,
|
screen_size,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -1114,8 +1112,12 @@ impl Tui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let terminal = &mut self.terminal;
|
let terminal = &mut self.terminal;
|
||||||
let needs_full_repaint =
|
let needs_full_repaint = Self::update_inline_viewport_for_resize_reflow(
|
||||||
Self::update_inline_viewport_for_resize_reflow(terminal, height, screen_size)?;
|
terminal,
|
||||||
|
height,
|
||||||
|
screen_size,
|
||||||
|
self.scrollback,
|
||||||
|
)?;
|
||||||
// A zero- or one-row history region cannot isolate raw history writes from the
|
// A zero- or one-row history region cannot isolate raw history writes from the
|
||||||
// viewport, so replayed rows can leave stale cells inside the composer.
|
// viewport, so replayed rows can leave stale cells inside the composer.
|
||||||
let history_can_overlap_viewport =
|
let history_can_overlap_viewport =
|
||||||
@@ -1123,7 +1125,7 @@ impl Tui {
|
|||||||
Self::flush_pending_history_lines(
|
Self::flush_pending_history_lines(
|
||||||
terminal,
|
terminal,
|
||||||
&mut self.pending_history_lines,
|
&mut self.pending_history_lines,
|
||||||
self.is_zellij,
|
self.scrollback,
|
||||||
screen_size,
|
screen_size,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,10 @@ impl Tui {
|
|||||||
Self::flush_pending_history_lines(
|
Self::flush_pending_history_lines(
|
||||||
&mut self.terminal,
|
&mut self.terminal,
|
||||||
&mut self.pending_history_lines,
|
&mut self.pending_history_lines,
|
||||||
self.is_zellij,
|
self.scrollback,
|
||||||
screen_size,
|
screen_size,
|
||||||
)?;
|
)?;
|
||||||
let mode = if self.is_zellij && wrap_policy == HistoryLineWrapPolicy::Terminal {
|
let mode = self.scrollback.history_insertion_mode(wrap_policy);
|
||||||
InsertHistoryMode::ZellijRaw
|
|
||||||
} else {
|
|
||||||
InsertHistoryMode::Standard
|
|
||||||
};
|
|
||||||
let replaced = replace_visible_terminal_history_tail(
|
let replaced = replace_visible_terminal_history_tail(
|
||||||
&mut self.terminal,
|
&mut self.terminal,
|
||||||
previous_lines,
|
previous_lines,
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
//! Choose terminal-safe strategies for growing the viewport and inserting history.
|
||||||
|
|
||||||
|
use crate::custom_terminal::Terminal;
|
||||||
|
use crate::insert_history::HistoryLineWrapPolicy;
|
||||||
|
use crate::insert_history::InsertHistoryMode;
|
||||||
|
use codex_terminal_detection::TerminalInfo;
|
||||||
|
use codex_terminal_detection::TerminalName;
|
||||||
|
use crossterm::cursor::MoveTo;
|
||||||
|
use crossterm::queue;
|
||||||
|
use crossterm::style::Print;
|
||||||
|
use ratatui::backend::Backend;
|
||||||
|
use ratatui::layout::Position;
|
||||||
|
use ratatui::layout::Size;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) enum ScrollbackStrategy {
|
||||||
|
Standard,
|
||||||
|
Zellij,
|
||||||
|
FullScreen,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollbackStrategy {
|
||||||
|
pub(super) fn detect(terminal: &TerminalInfo) -> Self {
|
||||||
|
if terminal.is_zellij() {
|
||||||
|
Self::Zellij
|
||||||
|
} else if terminal.name == TerminalName::WindowsTerminal
|
||||||
|
|| std::env::var_os("WT_SESSION").is_some()
|
||||||
|
{
|
||||||
|
Self::FullScreen
|
||||||
|
} else {
|
||||||
|
Self::Standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn history_insertion_mode(
|
||||||
|
self,
|
||||||
|
wrap_policy: HistoryLineWrapPolicy,
|
||||||
|
) -> InsertHistoryMode {
|
||||||
|
match self {
|
||||||
|
Self::FullScreen => InsertHistoryMode::FullScreen,
|
||||||
|
Self::Zellij if wrap_policy == HistoryLineWrapPolicy::Terminal => {
|
||||||
|
InsertHistoryMode::FullScreen
|
||||||
|
}
|
||||||
|
Self::Standard | Self::Zellij => InsertHistoryMode::Standard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn grow_viewport<B>(
|
||||||
|
self,
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
viewport_top: u16,
|
||||||
|
screen_size: Size,
|
||||||
|
scroll_by: u16,
|
||||||
|
) -> io::Result<()>
|
||||||
|
where
|
||||||
|
B: Backend<Error = io::Error> + Write,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::FullScreen => {
|
||||||
|
// Partial DEC scroll regions can discard rows instead of moving them into Windows
|
||||||
|
// Terminal's scrollback. Clear the stale composer, then scroll the entire screen.
|
||||||
|
terminal.clear_after_position(Position::new(/*x*/ 0, viewport_top))?;
|
||||||
|
let writer = terminal.backend_mut();
|
||||||
|
queue!(
|
||||||
|
writer,
|
||||||
|
MoveTo(/*x*/ 0, screen_size.height.saturating_sub(/*rhs*/ 1))
|
||||||
|
)?;
|
||||||
|
for _ in 0..scroll_by {
|
||||||
|
queue!(writer, Print("\r\n"))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Self::Standard | Self::Zellij => terminal
|
||||||
|
.backend_mut()
|
||||||
|
.scroll_region_up(0..viewport_top, scroll_by),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "scrollback_tests.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
use super::ScrollbackStrategy;
|
||||||
|
use crate::custom_terminal::Terminal;
|
||||||
|
use crate::insert_history::HistoryLineWrapPolicy;
|
||||||
|
use crate::insert_history::InsertHistoryMode;
|
||||||
|
use crate::insert_history::insert_history_lines_with_mode_and_wrap_policy;
|
||||||
|
use crate::test_backend::VT100Backend;
|
||||||
|
use codex_terminal_detection::Multiplexer;
|
||||||
|
use codex_terminal_detection::TerminalInfo;
|
||||||
|
use codex_terminal_detection::TerminalName;
|
||||||
|
use crossterm::cursor::MoveTo;
|
||||||
|
use crossterm::queue;
|
||||||
|
use crossterm::style::Print;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use ratatui::layout::Rect;
|
||||||
|
use ratatui::layout::Size;
|
||||||
|
use ratatui::text::Line;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn windows_terminal_uses_full_screen_unless_zellij_is_active() {
|
||||||
|
let mut terminal = TerminalInfo {
|
||||||
|
name: TerminalName::WindowsTerminal,
|
||||||
|
term_program: None,
|
||||||
|
version: None,
|
||||||
|
term: None,
|
||||||
|
multiplexer: None,
|
||||||
|
};
|
||||||
|
let mut strategy = ScrollbackStrategy::detect(&terminal);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
[
|
||||||
|
strategy.history_insertion_mode(HistoryLineWrapPolicy::PreWrap),
|
||||||
|
strategy.history_insertion_mode(HistoryLineWrapPolicy::Terminal),
|
||||||
|
],
|
||||||
|
[InsertHistoryMode::FullScreen, InsertHistoryMode::FullScreen]
|
||||||
|
);
|
||||||
|
|
||||||
|
terminal.multiplexer = Some(Multiplexer::Zellij { version: None });
|
||||||
|
strategy = ScrollbackStrategy::detect(&terminal);
|
||||||
|
assert_eq!(strategy, ScrollbackStrategy::Zellij);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zellij_only_uses_full_screen_insertion_for_terminal_wrapped_history() {
|
||||||
|
assert_eq!(
|
||||||
|
[
|
||||||
|
ScrollbackStrategy::Zellij.history_insertion_mode(HistoryLineWrapPolicy::PreWrap),
|
||||||
|
ScrollbackStrategy::Zellij.history_insertion_mode(HistoryLineWrapPolicy::Terminal),
|
||||||
|
],
|
||||||
|
[InsertHistoryMode::Standard, InsertHistoryMode::FullScreen]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_screen_history_insertion_preserves_terminal_scrollback() {
|
||||||
|
let width = 24;
|
||||||
|
let height = 6;
|
||||||
|
let backend = VT100Backend::with_scrollback(width, height, /*scrollback_len*/ 32);
|
||||||
|
let mut terminal = Terminal::with_options(backend).expect("terminal with scrollback");
|
||||||
|
terminal.set_viewport_area(Rect::new(
|
||||||
|
/*x*/ 0,
|
||||||
|
/*y*/ height - 2,
|
||||||
|
width,
|
||||||
|
/*height*/ 2,
|
||||||
|
));
|
||||||
|
|
||||||
|
for (row, line) in [
|
||||||
|
"oldest-history-row",
|
||||||
|
"history-row-2",
|
||||||
|
"history-row-3",
|
||||||
|
"history-row-4",
|
||||||
|
"stale-composer-1",
|
||||||
|
"stale-composer-2",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
queue!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
MoveTo(/*x*/ 0, row as u16),
|
||||||
|
Print(line)
|
||||||
|
)
|
||||||
|
.expect("seed terminal row");
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_history_lines_with_mode_and_wrap_policy(
|
||||||
|
&mut terminal,
|
||||||
|
vec![Line::from("new-history-row")],
|
||||||
|
ScrollbackStrategy::FullScreen.history_insertion_mode(HistoryLineWrapPolicy::PreWrap),
|
||||||
|
HistoryLineWrapPolicy::PreWrap,
|
||||||
|
)
|
||||||
|
.expect("insert history through the full screen");
|
||||||
|
|
||||||
|
let visible = terminal.backend().vt100().screen().contents();
|
||||||
|
let mut scrollback_screen = terminal.backend().vt100().screen().clone();
|
||||||
|
scrollback_screen.set_scrollback(/*rows*/ usize::MAX);
|
||||||
|
let scrollback = scrollback_screen.contents();
|
||||||
|
|
||||||
|
insta::assert_snapshot!(format!("SCROLLBACK:\n{scrollback}\nVISIBLE:\n{visible}"), @r"
|
||||||
|
SCROLLBACK:
|
||||||
|
oldest-history-row
|
||||||
|
history-row-2
|
||||||
|
history-row-3
|
||||||
|
history-row-4
|
||||||
|
new-history-row
|
||||||
|
VISIBLE:
|
||||||
|
history-row-2
|
||||||
|
history-row-3
|
||||||
|
history-row-4
|
||||||
|
new-history-row
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_screen_viewport_growth_preserves_terminal_scrollback() {
|
||||||
|
let width = 24;
|
||||||
|
let height = 6;
|
||||||
|
let backend = VT100Backend::with_scrollback(width, height, /*scrollback_len*/ 32);
|
||||||
|
let mut terminal = Terminal::with_options(backend).expect("terminal with scrollback");
|
||||||
|
terminal.set_viewport_area(Rect::new(
|
||||||
|
/*x*/ 0,
|
||||||
|
/*y*/ height - 2,
|
||||||
|
width,
|
||||||
|
/*height*/ 2,
|
||||||
|
));
|
||||||
|
|
||||||
|
for (row, line) in [
|
||||||
|
"oldest-history-row",
|
||||||
|
"history-row-2",
|
||||||
|
"history-row-3",
|
||||||
|
"history-row-4",
|
||||||
|
"stale-composer-1",
|
||||||
|
"stale-composer-2",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
queue!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
MoveTo(/*x*/ 0, row as u16),
|
||||||
|
Print(line)
|
||||||
|
)
|
||||||
|
.expect("seed terminal row");
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollbackStrategy::FullScreen
|
||||||
|
.grow_viewport(
|
||||||
|
&mut terminal,
|
||||||
|
/*viewport_top*/ height - 2,
|
||||||
|
Size::new(width, height),
|
||||||
|
/*scroll_by*/ 2,
|
||||||
|
)
|
||||||
|
.expect("grow viewport through full-screen scrolling");
|
||||||
|
|
||||||
|
let visible = terminal.backend().vt100().screen().contents();
|
||||||
|
let mut scrollback_screen = terminal.backend().vt100().screen().clone();
|
||||||
|
scrollback_screen.set_scrollback(/*rows*/ usize::MAX);
|
||||||
|
let scrollback = scrollback_screen.contents();
|
||||||
|
|
||||||
|
insta::assert_snapshot!(format!("SCROLLBACK:\n{scrollback}\nVISIBLE:\n{visible}"), @r"
|
||||||
|
SCROLLBACK:
|
||||||
|
oldest-history-row
|
||||||
|
history-row-2
|
||||||
|
history-row-3
|
||||||
|
history-row-4
|
||||||
|
VISIBLE:
|
||||||
|
history-row-3
|
||||||
|
history-row-4
|
||||||
|
");
|
||||||
|
}
|
||||||
Reference in new issue
Block a user