Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9b69e911b |
@@ -770,9 +770,7 @@ Additional requirements:
|
|||||||
gives way to the spinner. Only when those four alone are too wide, as on a
|
gives way to the spinner. Only when those four alone are too wide, as on a
|
||||||
terminal narrower than about 70 columns hours into a scan of millions of
|
terminal narrower than about 70 columns hours into a scan of millions of
|
||||||
files, is the line cut short at the terminal's edge, so the end of the ETA is
|
files, is the line cut short at the terminal's edge, so the end of the ETA is
|
||||||
lost first. A terminal made narrower while a phase runs gets the same: the
|
lost first.
|
||||||
next draw erases the line within the new width and fits the line to it, and
|
|
||||||
the display goes on redrawing in place.
|
|
||||||
- When stderr is not a terminal (a pipe, a file, `/dev/null`), do not emit ANSI
|
- When stderr is not a terminal (a pipe, a file, `/dev/null`), do not emit ANSI
|
||||||
redraws: print the line the moment each phase starts, every 5 seconds while it
|
redraws: print the line the moment each phase starts, every 5 seconds while it
|
||||||
runs (whether or not an item has completed, and never more often), and once
|
runs (whether or not an item has completed, and never more often), and once
|
||||||
|
|||||||
@@ -37,12 +37,12 @@
|
|||||||
until the first one is returned. On a terminal a phase with an exact total
|
until the first one is returned. On a terminal a phase with an exact total
|
||||||
draws its line followed by the library's bar, and any other a spinner followed
|
draws its line followed by the library's bar, and any other a spinner followed
|
||||||
by the line; every display is drawn again several times a second. No line
|
by the line; every display is drawn again several times a second. No line
|
||||||
reaches the terminal's last column, however narrow, also after the terminal is
|
reaches the terminal's last column, however narrow: a line too wide leaves out
|
||||||
made narrower during a phase: a line too wide leaves out its rate, then its
|
its rate, then its bar, keeping the phase, progress, elapsed time and ETA
|
||||||
bar, keeping the phase, progress, elapsed time and ETA whole, and is cut short
|
whole, and is cut short only when those alone do not fit, as below about 70
|
||||||
only when those alone do not fit, as below about 70 columns late in a large
|
columns late in a large scan. In plain mode the line is printed every 5
|
||||||
scan. In plain mode the line is printed every 5 seconds whether or not an item
|
seconds whether or not an item completed (2026-10-16,
|
||||||
completed (2026-10-16, https://git.eeqj.de/sneak/sfdupes/issues/112)
|
https://git.eeqj.de/sneak/sfdupes/issues/112)
|
||||||
|
|
||||||
- `scan`, `report` and `trees` first print a line on stderr naming the database
|
- `scan`, `report` and `trees` first print a line on stderr naming the database
|
||||||
they use, as an absolute path, and whether that path is the default or comes
|
they use, as an absolute path, and whether that path is the default or comes
|
||||||
|
|||||||
+10
-24
@@ -5,7 +5,6 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -99,7 +98,6 @@ type progress struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
bar *progressbar.ProgressBar // what the library draws; nil until then
|
bar *progressbar.ProgressBar // what the library draws; nil until then
|
||||||
withBar bool // bar is the library's bar, not its spinner
|
withBar bool // bar is the library's bar, not its spinner
|
||||||
widest int // the widest line bar has drawn, in columns
|
|
||||||
label string
|
label string
|
||||||
total int64
|
total int64
|
||||||
estimate bool
|
estimate bool
|
||||||
@@ -178,20 +176,15 @@ func (p *progress) draw() {
|
|||||||
text, withBar = p.line(width-barMargin, false)
|
text, withBar = p.line(width-barMargin, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
margin := barMargin
|
|
||||||
if !withBar {
|
if !withBar {
|
||||||
margin = spinnerMargin
|
text, _ = p.line(width-spinnerMargin, true)
|
||||||
text, _ = p.line(width-margin, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The library makes a bar or a spinner, and before each draw erases
|
// The library makes a bar or a spinner. A display that turns from
|
||||||
// its line with as many spaces as the widest line it has drawn. A
|
// one to the other, as its line grows or the terminal is resized,
|
||||||
// display gets a new one when it turns from one to the other, as its
|
// gets a new one.
|
||||||
// line grows or the terminal is resized, and when the terminal has
|
if p.bar == nil || p.withBar != withBar {
|
||||||
// been made too narrow for that erase, which would wrap and leave a
|
p.newBar(withBar)
|
||||||
// line behind at every draw.
|
|
||||||
if p.bar == nil || p.withBar != withBar || p.widest >= width {
|
|
||||||
p.newBar(withBar, width)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if withBar {
|
if withBar {
|
||||||
@@ -199,23 +192,16 @@ func (p *progress) draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.bar.Describe(text)
|
p.bar.Describe(text)
|
||||||
|
|
||||||
// The library drew the text and its margin, less the terminal's last
|
|
||||||
// column, which the margin keeps clear.
|
|
||||||
p.widest = max(p.widest, len(text)+margin-1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newBar has the library make what draw draws: a bar for the phase's
|
// newBar has the library make what draw draws: a bar for the phase's
|
||||||
// total when withBar is set, and a spinner when not. Either shows the
|
// total when withBar is set, and a spinner when not. Either shows the
|
||||||
// text draw gives it, and nothing of its own besides the spinner, or
|
// text draw gives it, and nothing of its own besides the spinner, or
|
||||||
// the bar and its percent. p.mu must be held.
|
// the bar and its percent. p.mu must be held.
|
||||||
func (p *progress) newBar(withBar bool, width int) {
|
func (p *progress) newBar(withBar bool) {
|
||||||
if p.bar != nil {
|
if p.bar != nil {
|
||||||
// Erase what was drawn before, short of the last column of a
|
// Erase what was drawn before.
|
||||||
// terminal that may have been made narrower since: the library's
|
_ = p.bar.Clear()
|
||||||
// own erase is as wide as the widest line it drew.
|
|
||||||
blank := strings.Repeat(" ", min(p.widest, width-1))
|
|
||||||
fmt.Fprint(os.Stderr, "\r"+blank+"\r")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
total := int64(-1) // the library's spinner
|
total := int64(-1) // the library's spinner
|
||||||
@@ -234,7 +220,7 @@ func (p *progress) newBar(withBar bool, width int) {
|
|||||||
progressbar.OptionSetElapsedTime(false),
|
progressbar.OptionSetElapsedTime(false),
|
||||||
progressbar.OptionSetPredictTime(false),
|
progressbar.OptionSetPredictTime(false),
|
||||||
)
|
)
|
||||||
p.withBar, p.widest = withBar, 0
|
p.withBar = withBar
|
||||||
}
|
}
|
||||||
|
|
||||||
// setTotal gives a phase that started without a total its total, once
|
// setTotal gives a phase that started without a total its total, once
|
||||||
|
|||||||
@@ -410,60 +410,6 @@ func TestTerminalLinesFitWidth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTerminalNarrowedRedrawsInPlace draws a large scan's display, two
|
|
||||||
// and a half hours into the phase, on a terminal 120 columns wide, then
|
|
||||||
// makes the terminal narrower than that line and draws it again a few
|
|
||||||
// times. Nothing written after that, the erase before each redraw
|
|
||||||
// included, may reach the narrower terminal's last column: it would
|
|
||||||
// wrap, and every redraw would leave a line behind. At these widths the
|
|
||||||
// spinner stays a spinner and the bar stays a bar.
|
|
||||||
//
|
|
||||||
//nolint:paralleltest // captureStderr replaces the process-wide os.Stderr
|
|
||||||
func TestTerminalNarrowedRedrawsInPlace(t *testing.T) {
|
|
||||||
stderr := captureStderr(t)
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
params progressParams
|
|
||||||
narrow int
|
|
||||||
}{
|
|
||||||
{progressParams{label: scanPhase("walk"), total: -1}, 60},
|
|
||||||
{progressParams{label: scanPhase("hash"), total: 9876543}, 90},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
width := 120
|
|
||||||
|
|
||||||
// No timed draws: the test draws.
|
|
||||||
p := startProgress(tc.params, func() int { return width }, time.Hour)
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
p.count = 1234567
|
|
||||||
p.start = p.start.Add(-150 * time.Minute)
|
|
||||||
p.rateStart = p.start
|
|
||||||
p.mu.Unlock()
|
|
||||||
|
|
||||||
p.draw()
|
|
||||||
|
|
||||||
width = tc.narrow
|
|
||||||
before := len(stderr())
|
|
||||||
|
|
||||||
p.draw()
|
|
||||||
p.draw()
|
|
||||||
p.finish()
|
|
||||||
|
|
||||||
lines := strings.FieldsFuncSeq(stderr()[before:], func(r rune) bool {
|
|
||||||
return r == '\r' || r == '\n'
|
|
||||||
})
|
|
||||||
|
|
||||||
for line := range lines {
|
|
||||||
if n := utf8.RuneCountInString(line); n >= width {
|
|
||||||
t.Errorf("%d columns written on a terminal made %d wide: %q",
|
|
||||||
n, width, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestProgressWarningsOnOwnLines drives the terminal displays, spinner
|
// TestProgressWarningsOnOwnLines drives the terminal displays, spinner
|
||||||
// and bar, through a run of warnings with no items between them, as when
|
// and bar, through a run of warnings with no items between them, as when
|
||||||
// the walk meets many unreadable paths, for many redraws: every warning
|
// the walk meets many unreadable paths, for many redraws: every warning
|
||||||
|
|||||||
Reference in New Issue
Block a user