diff --git a/bar.go b/bar.go index b25e5e4..115f1bb 100644 --- a/bar.go +++ b/bar.go @@ -4,6 +4,7 @@ "io" "sync" "time" + "unicode/utf8" ) // Bar represents a progress Bar @@ -21,7 +22,7 @@ lastStatus int incrCh chan int - redrawReqCh chan chan []byte + redrawReqCh chan *redrawRequest currentReqCh chan chan int statusReqCh chan chan int decoratorCh chan *decorator @@ -33,7 +34,7 @@ // Statistics represents statistics of the progress bar // instance of this, sent to DecoratorFunc, as param type Statistics struct { - Total, Current int + Total, Current, TermWidth int TimeElapsed, TimePerItemEstimate time.Duration } @@ -52,7 +53,7 @@ width: width, incrCh: make(chan int), - redrawReqCh: make(chan chan []byte), + redrawReqCh: make(chan *redrawRequest), currentReqCh: make(chan chan int), statusReqCh: make(chan chan int), decoratorCh: make(chan *decorator), @@ -164,25 +165,40 @@ } // String returns the string representation of the bar -func (b *Bar) String() string { +// func (b *Bar) String() string { +// if b.isDone() { +// return string(b.lastFrame) +// } +// respCh := make(chan []byte) +// b.redrawReqCh <- respCh +// return string(<-respCh) +// } + +type redrawRequest struct { + width int + respCh chan []byte +} + +func (b *Bar) Bytes(width int) []byte { if b.isDone() { - return string(b.lastFrame) + return b.lastFrame } respCh := make(chan []byte) - b.redrawReqCh <- respCh - return string(<-respCh) + b.redrawReqCh <- &redrawRequest{width, respCh} + return <-respCh } func (b *Bar) server(wg *sync.WaitGroup, total int) { timeStarted := time.Now() blockStartTime := timeStarted - buf := make([]byte, b.width, b.width+24) + // buf := make([]byte, 0, b.width+32) var tpie time.Duration var timeElapsed time.Duration var appendFuncs []DecoratorFunc var prependFuncs []DecoratorFunc var completed bool var current int + var termWidth int for { select { case i := <-b.incrCh: @@ -208,15 +224,16 @@ } case respCh := <-b.currentReqCh: respCh <- current - case respCh := <-b.redrawReqCh: - stat := &Statistics{total, current, timeElapsed, tpie} - respCh <- b.draw(stat, buf, appendFuncs, prependFuncs) + case r := <-b.redrawReqCh: + termWidth = r.width + stat := &Statistics{total, current, termWidth, timeElapsed, tpie} + r.respCh <- b.draw(stat, appendFuncs, prependFuncs) case respCh := <-b.statusReqCh: respCh <- percentage(total, current, 100) case <-b.flushedCh: if completed && !b.isDone() { - stat := &Statistics{total, current, timeElapsed, tpie} - b.lastFrame = b.draw(stat, buf, appendFuncs, prependFuncs) + stat := &Statistics{total, current, termWidth, timeElapsed, tpie} + b.lastFrame = b.draw(stat, appendFuncs, prependFuncs) b.lastStatus = percentage(total, current, 100) close(b.done) wg.Done() @@ -232,35 +249,60 @@ } } -func (b *Bar) draw(stat *Statistics, buf []byte, appendFuncs, prependFuncs []DecoratorFunc) []byte { - completedWidth := percentage(stat.Total, stat.Current, b.width) - - for i := 0; i < completedWidth; i++ { +func (b *Bar) draw(stat *Statistics, appendFuncs, prependFuncs []DecoratorFunc) []byte { + + buf := make([]byte, 0, stat.TermWidth) + + barBlock := b.fillBar(stat.Total, stat.Current, b.width) + + // render append functions to the right of the bar + var appendBlock []byte + for _, f := range appendFuncs { + appendBlock = append(appendBlock, []byte(f(stat))...) + } + + // render prepend functions to the left of the bar + var prependBlock []byte + for _, f := range prependFuncs { + prependBlock = append(prependBlock, []byte(f(stat))...) + } + + prependCount := utf8.RuneCount(prependBlock) + barCount := utf8.RuneCount(barBlock) + appendCount := utf8.RuneCount(appendBlock) + totalCount := prependCount + barCount + appendCount + + if totalCount >= stat.TermWidth { + newWidth := stat.TermWidth - prependCount - appendCount + barBlock = b.fillBar(stat.Total, stat.Current, newWidth-1) + } + + for _, block := range [...][]byte{prependBlock, barBlock, appendBlock} { + buf = append(buf, block...) + } + + return buf +} + +func (b *Bar) fillBar(total, current, width int) []byte { + if width < 2 { + return []byte{b.leftEnd, b.rightEnd} + } + buf := make([]byte, width) + completedWidth := percentage(total, current, width) + + for i := 1; i < completedWidth; i++ { buf[i] = b.fill } - for i := completedWidth; i < b.width; i++ { + for i := completedWidth; i < width-1; i++ { buf[i] = b.empty } // set tip bit - if completedWidth > 0 && completedWidth < b.width { + if completedWidth > 0 && completedWidth < width { buf[completedWidth-1] = b.tip } - // set left and right ends bits - buf[0], buf[len(buf)-1] = b.leftEnd, b.rightEnd - - // render append functions to the right of the bar - for _, f := range appendFuncs { - buf = append(buf, ' ') - buf = append(buf, []byte(f(stat))...) - } - - // render prepend functions to the left of the bar - for _, f := range prependFuncs { - args := []byte(f(stat)) - args = append(args, ' ') - buf = append(args, buf...) - } + buf[0], buf[width-1] = b.leftEnd, b.rightEnd return buf } diff --git a/bar_test.go b/bar_test.go new file mode 100644 index 0000000..dd017a0 --- /dev/null +++ b/bar_test.go @@ -0,0 +1,45 @@ +package mpb + +import ( + "reflect" + "testing" +) + +func TestFillBar(t *testing.T) { + b := newTestBar(80).SetEmpty('-').SetFill('=').SetTip('>').SetLeftEnd('[').SetRightEnd(']') + tests := []struct { + width int + want []byte + }{ + { + width: 1, + want: []byte{'[', ']'}, + }, + { + width: 2, + want: []byte{'[', ']'}, + }, + { + width: 3, + want: []byte{'[', '>', ']'}, + }, + { + width: 4, + want: []byte{'[', '=', '>', ']'}, + }, + } + + for _, test := range tests { + got := b.fillBar(80, 60, test.width) + if !reflect.DeepEqual(test.want, got) { + t.Errorf("Want: %q, Got: %q\n", test.want, got) + } + } +} + +func newTestBar(width int) *Bar { + b := &Bar{ + width: width, + } + return b +} diff --git a/cwriter/writer_posix.go b/cwriter/writer_posix.go index 854b47b..4c3058d 100644 --- a/cwriter/writer_posix.go +++ b/cwriter/writer_posix.go @@ -4,7 +4,20 @@ import ( "fmt" + "os" + "syscall" + "unsafe" ) + +var tty *os.File + +func init() { + var err error + tty, err = os.Open("/dev/tty") + if err != nil { + tty = os.Stdin + } +} func (w *Writer) clearLines() { for i := 0; i < w.lineCount; i++ { @@ -12,3 +25,25 @@ fmt.Fprintf(w.out, "%c[2K\r", ESC) // clear the line } } + +// TerminalWidth returns width of the terminal. +func TerminalWidth() (int, error) { + w := new(window) + tio := syscall.TIOCGWINSZ + res, _, err := syscall.Syscall(syscall.SYS_IOCTL, + tty.Fd(), + uintptr(tio), + uintptr(unsafe.Pointer(w)), + ) + if int(res) == -1 { + return 0, err + } + return int(w.Col), nil +} + +type window struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} diff --git a/example/prependETA/main.go b/example/prependETA/main.go index cd26b6e..d242813 100644 --- a/example/prependETA/main.go +++ b/example/prependETA/main.go @@ -19,8 +19,8 @@ p := mpb.New().SetWidth(64) // p := mpb.New().RefreshRate(80 * time.Millisecond).SetWidth(64) - name1 := "Bar#1:" - bar1 := p.AddBar(50).AppendPercentage().PrependETA(4).PrependName(name1, len(name1)) + name1 := "Bar#1: " + bar1 := p.AddBar(50).AppendPercentage().PrependName(name1, len(name1)).PrependETA(4) wg.Add(1) go func() { defer wg.Done() @@ -32,7 +32,7 @@ } }() - bar2 := p.AddBar(100).AppendPercentage().PrependETA(4).PrependName("", 0-len(name1)) + bar2 := p.AddBar(100).AppendPercentage().PrependName("", 0-len(name1)).PrependETA(4) wg.Add(1) go func() { defer wg.Done() @@ -44,7 +44,7 @@ } }() - bar3 := p.AddBar(80).AppendPercentage().PrependETA(4).PrependName("Bar#3:", 0) + bar3 := p.AddBar(80).AppendPercentage().PrependName("Bar#3: ", 0).PrependETA(4) wg.Add(1) go func() { defer wg.Done() diff --git a/progress.go b/progress.go index 037445f..da4d5e3 100644 --- a/progress.go +++ b/progress.go @@ -2,7 +2,6 @@ import ( "errors" - "fmt" "io" "os" "sort" @@ -185,6 +184,8 @@ case respCh := <-p.countReqCh: respCh <- len(bars) case <-t.C: + width, _ := cwriter.TerminalWidth() + // fmt.Fprintf(os.Stderr, "twidth: %d\n", width) switch p.sort { case SortTop: sort.Sort(sort.Reverse(SortableBarSlice(bars))) @@ -192,7 +193,10 @@ sort.Sort(SortableBarSlice(bars)) } for _, b := range bars { - fmt.Fprintln(cw, b) + // fmt.Fprintln(cw, b) + buf := b.Bytes(width) + buf = append(buf, '\n') + cw.Write(buf) } cw.Flush() for _, b := range bars {