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
+23
-21
@@ -48,7 +48,6 @@ use self::input_boundary::terminal_input_is_readable;
|
||||
use crate::custom_terminal;
|
||||
use crate::custom_terminal::Terminal as CustomTerminal;
|
||||
use crate::insert_history::HistoryLineWrapPolicy;
|
||||
use crate::insert_history::InsertHistoryMode;
|
||||
use crate::notifications::DesktopNotificationBackend;
|
||||
use crate::notifications::detect_backend;
|
||||
use crate::terminal_hyperlinks::HyperlinkLine;
|
||||
@@ -58,6 +57,7 @@ use crate::tui::event_stream::TuiEventStream;
|
||||
#[cfg(unix)]
|
||||
use crate::tui::job_control::SuspendContext;
|
||||
use crate::tui::screen_size::ScreenSizePolicy;
|
||||
use crate::tui::scrollback::ScrollbackStrategy;
|
||||
use codex_config::types::NotificationCondition;
|
||||
use codex_config::types::NotificationMethod;
|
||||
|
||||
@@ -70,6 +70,7 @@ mod input_boundary;
|
||||
mod job_control;
|
||||
mod keyboard_modes;
|
||||
mod screen_size;
|
||||
mod scrollback;
|
||||
#[cfg(all(test, unix))]
|
||||
#[path = "tui_startup_tests.rs"]
|
||||
mod startup_tests;
|
||||
@@ -594,8 +595,7 @@ pub struct Tui {
|
||||
enhanced_keys_supported: bool,
|
||||
notification_backend: Option<DesktopNotificationBackend>,
|
||||
notification_condition: NotificationCondition,
|
||||
// Raw terminal-wrapped history needs a non-scroll-region insertion path in Zellij.
|
||||
is_zellij: bool,
|
||||
scrollback: ScrollbackStrategy,
|
||||
// When false, enter_alt_screen() becomes a no-op.
|
||||
alt_screen_enabled: bool,
|
||||
// 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.
|
||||
supports_color::on_cached(supports_color::Stream::Stdout);
|
||||
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 {
|
||||
frame_requester,
|
||||
@@ -650,7 +650,7 @@ impl Tui {
|
||||
enhanced_keys_supported,
|
||||
notification_backend: Some(detect_backend(NotificationMethod::default())),
|
||||
notification_condition: NotificationCondition::default(),
|
||||
is_zellij,
|
||||
scrollback,
|
||||
alt_screen_enabled: true,
|
||||
_stderr_guard: stderr_guard,
|
||||
}
|
||||
@@ -893,6 +893,7 @@ impl Tui {
|
||||
terminal: &mut Terminal,
|
||||
height: u16,
|
||||
screen_size: Size,
|
||||
scrollback: ScrollbackStrategy,
|
||||
) -> Result<bool> {
|
||||
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;
|
||||
@@ -908,9 +909,7 @@ impl Tui {
|
||||
if area.bottom() > screen_size.height {
|
||||
let scroll_by = area.bottom() - screen_size.height;
|
||||
if !terminal_height_shrank {
|
||||
terminal
|
||||
.backend_mut()
|
||||
.scroll_region_up(0..area.top(), scroll_by)?;
|
||||
scrollback.grow_viewport(terminal, area.top(), screen_size, scroll_by)?;
|
||||
}
|
||||
area.y = screen_size.height - area.height;
|
||||
} else if terminal_height_grew && viewport_was_bottom_aligned {
|
||||
@@ -931,7 +930,7 @@ impl Tui {
|
||||
fn flush_pending_history_lines(
|
||||
terminal: &mut Terminal,
|
||||
pending_history_lines: &mut Vec<PendingHistoryLines>,
|
||||
is_zellij: bool,
|
||||
scrollback: ScrollbackStrategy,
|
||||
screen_size: Size,
|
||||
) -> Result<()> {
|
||||
if pending_history_lines.is_empty() {
|
||||
@@ -939,11 +938,7 @@ impl Tui {
|
||||
}
|
||||
|
||||
for batch in pending_history_lines.iter() {
|
||||
let mode = if is_zellij && batch.wrap_policy == HistoryLineWrapPolicy::Terminal {
|
||||
InsertHistoryMode::ZellijRaw
|
||||
} else {
|
||||
InsertHistoryMode::Standard
|
||||
};
|
||||
let mode = scrollback.history_insertion_mode(batch.wrap_policy);
|
||||
crate::insert_history::insert_history_hyperlink_lines_with_mode_and_wrap_policy(
|
||||
terminal,
|
||||
&batch.lines,
|
||||
@@ -992,9 +987,12 @@ impl Tui {
|
||||
area.width = screen_size.width;
|
||||
// If the viewport has expanded, scroll everything else up to make room.
|
||||
if area.bottom() > screen_size.height {
|
||||
terminal
|
||||
.backend_mut()
|
||||
.scroll_region_up(0..area.top(), area.bottom() - screen_size.height)?;
|
||||
self.scrollback.grow_viewport(
|
||||
terminal,
|
||||
area.top(),
|
||||
screen_size,
|
||||
area.bottom() - screen_size.height,
|
||||
)?;
|
||||
area.y = screen_size.height - area.height;
|
||||
}
|
||||
if area != terminal.viewport_area {
|
||||
@@ -1007,7 +1005,7 @@ impl Tui {
|
||||
Self::flush_pending_history_lines(
|
||||
terminal,
|
||||
&mut self.pending_history_lines,
|
||||
self.is_zellij,
|
||||
self.scrollback,
|
||||
screen_size,
|
||||
)?;
|
||||
|
||||
@@ -1114,8 +1112,12 @@ impl Tui {
|
||||
}
|
||||
|
||||
let terminal = &mut self.terminal;
|
||||
let needs_full_repaint =
|
||||
Self::update_inline_viewport_for_resize_reflow(terminal, height, screen_size)?;
|
||||
let needs_full_repaint = Self::update_inline_viewport_for_resize_reflow(
|
||||
terminal,
|
||||
height,
|
||||
screen_size,
|
||||
self.scrollback,
|
||||
)?;
|
||||
// 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.
|
||||
let history_can_overlap_viewport =
|
||||
@@ -1123,7 +1125,7 @@ impl Tui {
|
||||
Self::flush_pending_history_lines(
|
||||
terminal,
|
||||
&mut self.pending_history_lines,
|
||||
self.is_zellij,
|
||||
self.scrollback,
|
||||
screen_size,
|
||||
)?;
|
||||
|
||||
|
||||
Reference in new issue
Block a user