You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TerminalUI.display() resets JLine's Display every redraw, defeating frame diffing (full-screen rewrite per frame) #1361
In the JLine TUI layer, TerminalUI.display() calls display.clear() + display.reset() on every redraw in the fullScreen path, immediately before display.update(newLines, …). org.jline.utils.Display.reset() discards the cached previous frame, so update() has no diff base and rewrites the entire screen on every frame instead of only the changed cells. JLine's cell-level diffing is effectively disabled for every redraw.
This is invisible for static frames (the redundant bytes render identical pixels), but very visible during a fast window resize, where each redraw re-lays out content: every frame becomes a full, non-atomic screen rewrite, producing tearing/flicker on terminals without synchronized output (e.g. macOS Terminal.app).
Environment
spring-shell-jline 4.0.3-SNAPSHOT
JLine 3.30.x (FFM PTY provider, Java 25)
macOS Terminal.app (no DECSET 2026 synchronized output)
Evidence
org/springframework/shell/jline/tui/component/view/TerminalUI.java, display() (the body invoked for every redraw()):
privatesynchronizedvoiddisplay() {
...
if (fullScreen) {
display.clear();
display.reset(); // wipes JLine's previous-frame cache (the diff base)display.resize(size.getRows(), size.getColumns());
...
render(rect);
}
...
display.update(newLines, targetCursorPos); // would cell-diff, but reset() just nulled its base
}
Because reset() runs before update() on every frame, update() always performs a full redraw.
Impact
Full-screen rewrite per frame regardless of how little changed.
Fast resize: tearing / blank-growth / flicker on terminals lacking synchronized output.
Higher TTY write volume than necessary at any repaint cadence.
Proposed fix
Only clear()/reset()/resize() when the terminal size actually changed (track the last applied size); for same-size redraws, go straight to render(rect) + display.update(...) and let JLine diff. Keeps resize correctness while restoring incremental updates for the common case.
Optionally emit synchronized output (DECSET 2026 / BSU-ESU) around the write when the terminal advertises it, so even a full rewrite is presented atomically.
(1) is the higher-value change — it makes both the steady-state and resize paths write only what changed.
Notes
Found while diagnosing resize tearing in a downstream Compose-style TUI built on spring-shell-jline. Consumer-side fixes (view-rect sync, resize-event coalescing) reduce the symptom but cannot remove the per-frame full rewrite, which lives in TerminalUI.display().
TerminalUI.display() calls display.reset() before every display.update() in full screen mode. reset() empties JLine's cached previous frame (oldLines), so the per-line diff has nothing to compare against and re-emits the whole screen on every redraw.
Verified with a test that measures bytes written for a second, identical frame: current code rewrites everything, whereas keeping JLine's cache writes ~0 bytes. It's purely about bytes written, so it's platform-independent - the test passes on Windows. The visible tearing/flicker additionally depends on the terminal's synchronized-output support.
Fix: only clear()/reset()/resize() when the terminal size actually changes; otherwise let JLine diff. PR to follow. (Scoped to full screen, as reported; the non-full-screen path uses relative output where the per-frame reset is harder to remove safely.)
Activity
czpilar commentedon Jun 23, 2026
Reproduced, and confirmed it affects Windows too.
TerminalUI.display()callsdisplay.reset()before everydisplay.update()in full screen mode.reset()empties JLine's cached previous frame (oldLines), so the per-line diff has nothing to compare against and re-emits the whole screen on every redraw.Verified with a test that measures bytes written for a second, identical frame: current code rewrites everything, whereas keeping JLine's cache writes ~0 bytes. It's purely about bytes written, so it's platform-independent - the test passes on Windows. The visible tearing/flicker additionally depends on the terminal's synchronized-output support.
Fix: only
clear()/reset()/resize()when the terminal size actually changes; otherwise let JLine diff. PR to follow. (Scoped to full screen, as reported; the non-full-screen path uses relative output where the per-frame reset is harder to remove safely.)Fixed in PR #1362
Here is a reproducer, just use
tuicommand to get to full screen mode:Preserve frame diffing on full screen redraw