Skip to content

Commit efecf4b

Browse files
committed
ui: erase spinner line before redraw, hide cursor during animation
Two more sources of primary-bar flicker: 1. The spinner library writes \r+content without erasing the line first. When the label shrinks (e.g. [10/10] -> [1/1]), leftover characters from the previous longer frame remain visible for one tick. Fix: replace the leading \r in Write with \r\033[2K so the line is always cleared before the new frame is painted. 2. The cursor is visible during animation and its per-tick repositioning creates visual noise, especially in terminals with slower cursor rendering. Fix: hide the cursor in startAnimator (\033[?25l) and restore it unconditionally in stopAnimator (\033[?25h), so it stays hidden for exactly the duration of the animation and can't be left permanently invisible if the process exits early.
1 parent 4d63793 commit efecf4b

1 file changed

Lines changed: 13 additions & 1 deletion

File tree

internal/ui/ui.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,11 @@ func (sw *spinnerWriter) Write(p []byte) (n int, err error) {
389389
// synchronized write so the terminal never shows a partial frame.
390390
var buf strings.Builder
391391
buf.WriteString("\033[?2026h") // begin synchronized output
392-
buf.Write(p)
392+
// Replace the leading \r with \r\033[2K (go-to-col-0 + erase line)
393+
// so that when the label shrinks between ticks the old longer text
394+
// is fully cleared instead of leaving leftover characters visible.
395+
buf.WriteString("\r\033[2K")
396+
buf.Write(p[1:]) // p[0] is the \r we already emitted above
393397
sw.buildWorkerFrameLocked(&buf)
394398
buf.WriteString("\033[?2026l") // end synchronized output
395399
_, err = sw.w.Write([]byte(buf.String()))
@@ -502,6 +506,12 @@ func (sw *spinnerWriter) startAnimator() {
502506
done := sw.done
503507
sw.mu.Unlock()
504508

509+
// Hide the cursor for the duration of the animation so per-tick
510+
// cursor repositioning doesn't create visual noise. Restored in
511+
// stopAnimator unconditionally so a crash or early-stop can't leave
512+
// the cursor permanently invisible.
513+
fmt.Fprint(sw.w, "\033[?25l")
514+
505515
go func() {
506516
defer close(done)
507517
t := time.NewTicker(workerFrameInterval)
@@ -542,6 +552,8 @@ func (sw *spinnerWriter) stopAnimator() {
542552
}
543553
close(stop)
544554
<-done
555+
// Restore the cursor that startAnimator hid.
556+
fmt.Fprint(sw.w, "\033[?25h")
545557
}
546558

547559
// setDetail is a backward-compat shim that sets a single worker slot (slot 0).

0 commit comments

Comments
 (0)