Skip to content

Commit 0dd1938

Browse files
committed
Fix terminal auto-refresh: replace queue/flush-thread with direct page.update()
The two-thread queue design (worker fills queue, flush-thread drains it) caused repaints to be dropped until user interaction. Root cause: Flutter desktop only reliably repaints when page.update() is called from the same thread that owns the subprocess stdout loop. New design: single worker thread reads lines, appends them directly to the ListView, and calls page.update() every 100ms. No queue, no second thread. Also removes the now-unused `queue` import.
1 parent a8d43e6 commit 0dd1938

1 file changed

Lines changed: 32 additions & 48 deletions

File tree

gui.py

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import json
1212
import os
13-
import queue
1413
import re
1514
import subprocess
1615
import sys
@@ -436,42 +435,18 @@ def run(self, cmd: list[str], on_done: callable | None = None) -> None:
436435
self.write(f"$ {display_cmd}\n",
437436
color=ft.Colors.with_opacity(0.40, ft.Colors.WHITE))
438437

439-
# Line queue: (text, color). _flush_thread drains it every 50 ms.
440-
_line_q: queue.Queue = queue.Queue()
441-
_FLUSH_INTERVAL = 0.05 # seconds between UI updates
442-
443-
def _flush_thread() -> None:
444-
"""Drain the line queue and do a single page.update() per interval."""
445-
default_color = ft.Colors.with_opacity(0.88, ft.Colors.WHITE)
446-
while True:
447-
batch: list[tuple[str, str | None]] = []
448-
try:
449-
# Block until the first item arrives
450-
batch.append(_line_q.get(timeout=1.0))
451-
except queue.Empty:
452-
if not state.running:
453-
break
454-
continue
455-
# Drain everything else that arrived in this window
456-
deadline = time.monotonic() + _FLUSH_INTERVAL
457-
while time.monotonic() < deadline:
458-
try:
459-
batch.append(_line_q.get_nowait())
460-
except queue.Empty:
461-
break
462-
# Append all lines, one page.update() for the whole batch
463-
for text, color in batch:
464-
for line in text.splitlines():
465-
self._lines.controls.append(
466-
ft.Text(
467-
line, size=11, font_family=MONO,
468-
color=color or default_color,
469-
no_wrap=False, selectable=True,
470-
)
471-
)
472-
self._lines.update() # push the ListView itself
473-
self.page.update()
474-
time.sleep(0.02) # yield to Flet event loop so repaint fires
438+
_UPDATE_INTERVAL = 0.1 # seconds between UI refreshes while streaming
439+
_default_color = ft.Colors.with_opacity(0.88, ft.Colors.WHITE)
440+
441+
def _append_line(text: str, color: str | None = None) -> None:
442+
for line in text.splitlines():
443+
self._lines.controls.append(
444+
ft.Text(
445+
line, size=11, font_family=MONO,
446+
color=color or _default_color,
447+
no_wrap=False, selectable=True,
448+
)
449+
)
475450

476451
def _worker() -> None:
477452
try:
@@ -485,8 +460,9 @@ def _worker() -> None:
485460
bufsize=1,
486461
env=env,
487462
)
463+
_last_update = time.monotonic()
464+
_pending = False
488465
for line in state.proc.stdout:
489-
# Filter only the noisiest frozen-importlib bootstrap lines
490466
stripped = line.strip()
491467
if stripped.startswith("<frozen importlib"):
492468
continue
@@ -496,30 +472,38 @@ def _worker() -> None:
496472
color = C_ERROR
497473
elif low.startswith("warning"):
498474
color = C_WARN
499-
_line_q.put((line.rstrip(), color))
475+
_append_line(line.rstrip(), color)
476+
_pending = True
477+
now = time.monotonic()
478+
if now - _last_update >= _UPDATE_INTERVAL:
479+
self.page.update()
480+
_last_update = now
481+
_pending = False
482+
483+
if _pending:
484+
self.page.update()
485+
500486
state.proc.wait()
501487
rc = state.proc.returncode
502488

503489
if rc == 0:
504-
_line_q.put(("\n✓ Completed successfully.", C_SUCCESS))
490+
_append_line("\n✓ Completed successfully.", C_SUCCESS)
505491
self.set_status("✓ done", C_SUCCESS)
506-
elif rc == -15: # SIGTERM from our stop button
507-
pass # already handled in _on_stop
492+
elif rc == -15:
493+
pass
508494
else:
509-
_line_q.put((f"\n✗ Exited with code {rc}.", C_ERROR))
495+
_append_line(f"\n✗ Exited with code {rc}.", C_ERROR)
510496
self.set_status(f"✗ code {rc}", C_ERROR)
511497
except Exception as exc:
512-
_line_q.put((f"\n{exc}", C_ERROR))
498+
_append_line(f"\n{exc}", C_ERROR)
513499
self.set_status("✗ error", C_ERROR)
514500
finally:
515-
state.running = False
516-
state.proc = None
501+
state.running = False
502+
state.proc = None
517503
self._stop_btn.visible = False
518504
self.page.update()
519505
if on_done:
520506
on_done()
521-
522-
threading.Thread(target=_flush_thread, daemon=True).start()
523507
threading.Thread(target=_worker, daemon=True).start()
524508

525509
# ── UI helpers ────────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)