mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
fix(tui): clear scrollback and redraw history on resize to avoid terminal glitches
- Clear scrollback and screen via ANSI/OSC 1337 before re-rendering history, reducing chopped or re-drawn text outside the scroll area on resize. - Handle resize explicitly, rebuilding transcript cells at the new width and redrawing, and treat onboarding resize like draw to ensure a full clear. - Add ClearScrollback command leveraging ESC[3J][H][2J plus iTerm’s 1337 code; works on Ghostty/WezTerm/Kitty, partial on Terminal.app, not on Alacritty. - Note: may introduce resize flicker; debouncing/notification parity still TBD.
This commit is contained in:
Generated
+5
-5
@@ -4450,7 +4450,7 @@ checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"indexmap 2.12.0",
|
||||
"quick-xml",
|
||||
"quick-xml 0.38.0",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
@@ -7093,7 +7093,7 @@ version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bitflags 2.10.0",
|
||||
"rustix 1.0.8",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
@@ -7105,7 +7105,7 @@ version = "0.32.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bitflags 2.10.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
@@ -7117,7 +7117,7 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bitflags 2.10.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -7726,7 +7726,7 @@ dependencies = [
|
||||
"os_pipe",
|
||||
"rustix 0.38.44",
|
||||
"tempfile",
|
||||
"thiserror 2.0.16",
|
||||
"thiserror 2.0.17",
|
||||
"tree_magic_mini",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
|
||||
@@ -268,6 +268,34 @@ impl App {
|
||||
},
|
||||
)?;
|
||||
}
|
||||
TuiEvent::Resize(size) => {
|
||||
// This code change addresses visual artifacts that can occur during terminal
|
||||
// resize events by completely redrawing the scrollback history instead of
|
||||
// attempting to incrementally adjust it. This approach helps ensure that the
|
||||
// display remains consistent and free of glitches as the terminal dimensions
|
||||
// change.
|
||||
//
|
||||
// TODO (joshka). This can cause flicker during resize, so we likely need to
|
||||
// investigate debouncing resize events or optimizing the redraw.
|
||||
//
|
||||
// TODO (joshka). I'm unsure whether this also needs to post notifications like
|
||||
// in the normal Draw case above.
|
||||
//
|
||||
// TODO (joshka). `size` is the size that comes from the event, which may differ
|
||||
// from the terminal's actual size. Some parts of the draw path use the terminal
|
||||
// size directly. We should standardize on one or the other so that there's no
|
||||
// mismatch, but debouncing first seems more important.
|
||||
tui.clear_scrollback()?;
|
||||
for cell in &self.transcript_cells {
|
||||
tui.insert_history_lines(cell.display_lines(size.width));
|
||||
}
|
||||
tui.draw(self.chat_widget.desired_height(size.width), |frame| {
|
||||
self.chat_widget.render(frame.area(), frame.buffer);
|
||||
if let Some((x, y)) = self.chat_widget.cursor_pos(frame.area()) {
|
||||
frame.set_cursor_position((x, y));
|
||||
}
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
|
||||
@@ -411,7 +411,7 @@ pub(crate) async fn run_onboarding_app(
|
||||
TuiEvent::Paste(text) => {
|
||||
onboarding_screen.handle_paste(text);
|
||||
}
|
||||
TuiEvent::Draw => {
|
||||
TuiEvent::Draw | TuiEvent::Resize(_) => {
|
||||
if !did_full_clear_after_success
|
||||
&& onboarding_screen.steps.iter().any(|step| {
|
||||
if let Step::Auth(w) = step {
|
||||
|
||||
+46
-2
@@ -12,6 +12,7 @@ use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use crossterm::Command;
|
||||
use crossterm::ExecutableCommand;
|
||||
use crossterm::SynchronizedUpdate;
|
||||
use crossterm::event::DisableBracketedPaste;
|
||||
use crossterm::event::DisableFocusChange;
|
||||
@@ -31,6 +32,7 @@ use ratatui::crossterm::execute;
|
||||
use ratatui::crossterm::terminal::disable_raw_mode;
|
||||
use ratatui::crossterm::terminal::enable_raw_mode;
|
||||
use ratatui::layout::Offset;
|
||||
use ratatui::layout::Size;
|
||||
use ratatui::text::Line;
|
||||
use tokio::select;
|
||||
use tokio_stream::Stream;
|
||||
@@ -152,6 +154,7 @@ pub enum TuiEvent {
|
||||
Key(KeyEvent),
|
||||
Paste(String),
|
||||
Draw,
|
||||
Resize(Size),
|
||||
}
|
||||
|
||||
pub struct Tui {
|
||||
@@ -270,8 +273,8 @@ impl Tui {
|
||||
}
|
||||
yield TuiEvent::Key(key_event);
|
||||
}
|
||||
Event::Resize(_, _) => {
|
||||
yield TuiEvent::Draw;
|
||||
Event::Resize(columns, rows) => {
|
||||
yield TuiEvent::Resize(Size::new(columns, rows));
|
||||
}
|
||||
Event::Paste(pasted) => {
|
||||
yield TuiEvent::Paste(pasted);
|
||||
@@ -436,6 +439,47 @@ impl Tui {
|
||||
})
|
||||
})?
|
||||
}
|
||||
|
||||
pub(crate) fn clear_scrollback(&mut self) -> Result<()> {
|
||||
self.terminal.backend_mut().execute(ClearScrollback)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Command that emits an OSC 1337 ClearScrollback sequence.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct ClearScrollback;
|
||||
|
||||
impl Command for ClearScrollback {
|
||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||
// This sequence clears the scrollback and the screen. It aligns to the standard that the clear command currently outputs.
|
||||
// - ESC[3J clears the scrollback buffer
|
||||
// - ESC[H moves the cursor to the home position
|
||||
// - ESC[2J clears the screen
|
||||
//
|
||||
// works on ghostty, wezterm, kitty
|
||||
// does not work on alacritty, iterm
|
||||
// sort of works on terminal.app (leftover content above the viewport)
|
||||
write!(f, "\x1b[3J\x1b[H\x1b[2J")?;
|
||||
|
||||
// iterm specific sequence to clear scrollback
|
||||
// see https://iterm2.com/documentation-escape-codes.html
|
||||
write!(f, "\x1b]1337;ClearScrollback=yes\x07")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> Result<()> {
|
||||
Err(std::io::Error::other(
|
||||
"tried to execute ClearScrollback using WinAPI; use ANSI instead",
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_ansi_code_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn background scheduler to coalesce frame requests and emit draws at deadlines.
|
||||
|
||||
Reference in New Issue
Block a user