From bd197f974b9c9256699fc69a9659b47b653ea77d Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Wed, 14 Oct 2026 17:46:23 +0000 Subject: [PATCH] Redraw a finished bar below a warning for its last file (closes #102) On a terminal, warnf clears the bar's line before it prints a warning. When the warning is for the last item of a phase, the bar has already drawn its finished frame and the progress library draws nothing more for a finished bar, so the warning took the bar's place for good. warnf now draws the finished bar's last frame again below the warning. Output when stderr is not a terminal does not change. Model: opus-5-5 --- TODO.md | 4 ++++ progress.go | 7 +++++++ progress_test.go | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/TODO.md b/TODO.md index 547648f..2e0aeda 100644 --- a/TODO.md +++ b/TODO.md @@ -29,6 +29,10 @@ # Completed Steps +- on a terminal, a warning for the last file of a phase no longer erases the + phase's finished bar: the bar is drawn again below the warning (2026-10-14, + https://git.eeqj.de/sneak/sfdupes/issues/102) + - `scan` records mtime to the nanosecond, as whole seconds in `mtime` plus `mtime_nsec`, and compares it at that resolution, so a same-size rewrite within the same second is re-hashed (2026-10-07, diff --git a/progress.go b/progress.go index bc042e8..79b05bb 100644 --- a/progress.go +++ b/progress.go @@ -139,6 +139,13 @@ func (p *progress) warnf(format string, args ...any) { } fmt.Fprintln(os.Stderr, msg) + + // The library never draws a finished bar again, so the warning for + // a pass's last item would erase it for good: draw its last frame + // again below the warning. + if p.bar != nil && p.bar.IsFinished() { + fmt.Fprint(os.Stderr, p.bar.String()) + } } // finish terminates the pass's display. A bar whose pass stopped short diff --git a/progress_test.go b/progress_test.go index 2d36e5f..453fa94 100644 --- a/progress_test.go +++ b/progress_test.go @@ -187,3 +187,25 @@ func TestBarStoppedShortKeepsCount(t *testing.T) { t.Errorf("terminal shows %q, want a count of 2 of 10", shown) } } + +// TestWarningAfterLastItemKeepsBar checks that a warning printed after +// a pass's last item, as for a failed read of the last file, leaves the +// pass's finished bar on the terminal below the warning. +// +//nolint:paralleltest // captureStderr replaces the process-wide os.Stderr +func TestWarningAfterLastItemKeepsBar(t *testing.T) { + stderr := captureStderr(t) + p := &progress{ + label: "hash", total: 2, start: time.Now(), + bar: newBar("hash", 2), + } + + p.increment() + p.increment() + p.warnf("warning") + p.finish() + + if shown := lastFrame(stderr()); !strings.Contains(shown, "(2/2,") { + t.Errorf("terminal shows %q, want a count of 2 of 2", shown) + } +} -- 2.54.0