Skip to content

Commit 7aeb3df

Browse files
authored
Fix flickering screen for most Terminals (#164)
frame() correctly implements DEC 2026 synchronized output, which allows for "clear screen" sequences to be used while redrawing the screen without flicker. However, two issues prevent it from working. First, the clear sequence is *outside of the sync frame*. Second, when moved within the frame, the write() method detects ``x1b[2J`` and auto triggers a buffer flush, bypassing the intended synchronization. Finally, terminals that do not support DEC 2026 will always flicker with clear sequence at every frame. But to me, it seems to me that the clear sequence is not needed during this kind of draw, as each line is written with explicit cursor positioning, please review! I suggest also to include HOME to ensure (0,0) position before and after clear, as some terminals can have indeterminate cursor position.
1 parent 450ddfa commit 7aeb3df

File tree

3 files changed

+15
-11
lines changed

3 files changed

+15
-11
lines changed

pytermgui/ansi_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def clear(what: str = "screen") -> None:
116116
commands = {
117117
"eos": "\x1b[0J",
118118
"bos": "\x1b[1J",
119-
"screen": "\x1b[2J",
119+
"screen": "\x1b[H\x1b[2J",
120120
"eol": "\x1b[0K",
121121
"bol": "\x1b[1K",
122122
"line": "\x1b[2K",

pytermgui/term.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ def _update_size(self, *_: Any) -> None:
358358
self._call_listener(self.RESIZE, self.size)
359359

360360
# Wipe the screen in case anything got messed up
361-
self.write("\x1b[2J")
361+
self.write("\x1b[H\x1b[2J")
362362

363363
@property
364364
def width(self) -> int:
@@ -454,15 +454,15 @@ def frame(self) -> Generator[StringIO, None, None]:
454454
buffer = StringIO()
455455

456456
try:
457-
with self.no_record():
458-
self.write("\x1b[?2026h")
457+
# Write directly to stream to avoid write()'s auto-clear behavior
458+
self._stream.write("\x1b[?2026h")
459459
yield buffer
460460

461461
finally:
462-
self.write(buffer.getvalue())
463-
with self.no_record():
464-
self.write("\x1b[?2026l")
465-
self.flush()
462+
# Write buffer directly to stream - bypasses write()'s \x1b[2J detection
463+
self._stream.write(buffer.getvalue())
464+
self._stream.write("\x1b[?2026l")
465+
self._stream.flush()
466466

467467
@staticmethod
468468
def isatty() -> bool:
@@ -532,6 +532,7 @@ def _slice(line: str, maximum: int) -> str:
532532

533533
return sliced
534534

535+
# Truncate pending buffer on clear (may help on Windows)
535536
if "\x1b[2J" in data:
536537
self.clear_stream()
537538

@@ -565,7 +566,11 @@ def _slice(line: str, maximum: int) -> str:
565566
self._stream.flush()
566567

567568
def clear_stream(self) -> None:
568-
"""Clears (truncates) the terminal's stream."""
569+
"""Clears the terminal screen.
570+
571+
Attempts to truncate any buffered stream data (may work on Windows),
572+
then moves cursor to home position and clears the entire screen.
573+
"""
569574

570575
try:
571576
self._stream.truncate(0)
@@ -574,7 +579,7 @@ def clear_stream(self) -> None:
574579
if error.errno != errno.EINVAL and os.name != "nt":
575580
raise
576581

577-
self._stream.write("\x1b[2J")
582+
self._stream.write("\x1b[H\x1b[2J")
578583

579584
def print(
580585
self,

pytermgui/window_manager/compositor.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,6 @@ def draw(self, force: bool = False) -> None:
255255
if not force and self._previous == lines:
256256
return
257257

258-
self.terminal.clear_stream()
259258
with self.terminal.frame() as frame:
260259
frame_write = frame.write
261260

0 commit comments

Comments
 (0)