fix(ui): force resize-event size during post-resize draw (macOS Terminal.app, ConHost) (#993)
Some terminal emulators (macOS Terminal.app, Windows ConHost) briefly report stale dimensions via `crossterm::terminal::size()` after a resize. ratatui's `draw()` calls `autoresize()` internally, queries the backend, and shrinks the viewport back to the stale dimension — leaving the newly-expanded area filled with stale content from the previous frame (the duplicate-panel symptom users have reported). The fix adds `force_size` / `clear_forced_size` to `ColorCompatBackend` and forces the resize-event size for the post-resize draw, then clears the forcing for subsequent frames. Same class of fix as #582 but covers an additional emulator family. Thanks to @ArronAI007 for tracking down the autoresize→stale-size→short-buffer interaction.
This commit is contained in:
@@ -21,6 +21,11 @@ pub(crate) struct ColorCompatBackend<W: Write> {
|
||||
inner: CrosstermBackend<W>,
|
||||
depth: ColorDepth,
|
||||
palette_mode: PaletteMode,
|
||||
/// During a resize event the terminal emulator may report stale dimensions
|
||||
/// for a brief window (observed on macOS Terminal.app and Windows ConHost).
|
||||
/// Forcing the expected size prevents ratatui's internal `autoresize` from
|
||||
/// shrinking the viewport back to the stale dimension inside `draw()`.
|
||||
forced_size: Option<Size>,
|
||||
}
|
||||
|
||||
impl<W: Write> ColorCompatBackend<W> {
|
||||
@@ -29,8 +34,17 @@ impl<W: Write> ColorCompatBackend<W> {
|
||||
inner: CrosstermBackend::new(writer),
|
||||
depth,
|
||||
palette_mode,
|
||||
forced_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn force_size(&mut self, size: Size) {
|
||||
self.forced_size = Some(size);
|
||||
}
|
||||
|
||||
pub(crate) fn clear_forced_size(&mut self) {
|
||||
self.forced_size = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Write for ColorCompatBackend<W> {
|
||||
@@ -88,7 +102,10 @@ impl<W: Write> Backend for ColorCompatBackend<W> {
|
||||
}
|
||||
|
||||
fn size(&self) -> io::Result<Size> {
|
||||
self.inner.size()
|
||||
match self.forced_size {
|
||||
Some(size) => Ok(size),
|
||||
None => self.inner.size(),
|
||||
}
|
||||
}
|
||||
|
||||
fn window_size(&mut self) -> io::Result<WindowSize> {
|
||||
|
||||
@@ -18,7 +18,7 @@ use crossterm::{
|
||||
};
|
||||
use ratatui::{
|
||||
Frame, Terminal,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
layout::{Constraint, Direction, Layout, Rect, Size},
|
||||
prelude::Widget,
|
||||
style::Style,
|
||||
text::Span,
|
||||
@@ -1643,11 +1643,29 @@ async fn run_event_loop(
|
||||
|
||||
terminal.clear()?;
|
||||
app.handle_resize(final_w, final_h);
|
||||
// #macos-resize: some terminals (macOS Terminal.app, Windows
|
||||
// ConHost) briefly report stale dimensions via
|
||||
// `terminal::size()` after a resize. ratatui's `draw()` calls
|
||||
// `autoresize()` internally, which queries the backend size;
|
||||
// if it sees the old dimension it shrinks the viewport back,
|
||||
// leaving the newly-expanded area filled with stale content
|
||||
// from the previous frame (duplicate UI panels).
|
||||
//
|
||||
// We force the backend to report the resize-event size for
|
||||
// this single draw so the buffer matches the real viewport.
|
||||
{
|
||||
let backend = terminal.backend_mut();
|
||||
backend.force_size(Size::new(final_w, final_h));
|
||||
}
|
||||
// Draw immediately so the cleared screen gets repainted before
|
||||
// any other events can interleave. Without this, the next
|
||||
// iteration's draw can race against fast follow-up input and
|
||||
// leave the user staring at a blank/partial frame.
|
||||
terminal.draw(|f| render(f, app))?;
|
||||
{
|
||||
let backend = terminal.backend_mut();
|
||||
backend.clear_forced_size();
|
||||
}
|
||||
app.needs_redraw = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user