Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

progress: auto-trim output to fit terminal width #290

Merged
merged 5 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17

# Download all the tools used in the steps that follow
- name: Set up Tools
run: |
go get -u github.com/fzipp/gocyclo/cmd/gocyclo
go get -u github.com/mattn/goveralls
go install github.com/fzipp/gocyclo/cmd/gocyclo@v0.6.0
go install github.com/mattn/goveralls@v0.0.12

# Run all the unit-tests
- name: Test
Expand Down
4 changes: 2 additions & 2 deletions cmd/demo-progress/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ func main() {
// instantiate a Progress Writer and set up the options
pw := progress.NewWriter()
pw.SetAutoStop(*flagAutoStop)
pw.SetTrackerLength(25)
pw.SetMessageWidth(24)
pw.SetMessageLength(24)
pw.SetNumTrackersExpected(*flagNumTrackers)
pw.SetSortBy(progress.SortByPercentDsc)
pw.SetStyle(progress.StyleDefault)
pw.SetTrackerLength(25)
pw.SetTrackerPosition(progress.PositionRight)
pw.SetUpdateFrequency(time.Millisecond * 100)
pw.Style().Colors = progress.StyleColorsExample
Expand Down
20 changes: 15 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
module github.com/jedib0t/go-pretty/v6

go 1.16
go 1.17

require (
github.com/mattn/go-runewidth v0.0.13
github.com/pkg/profile v1.6.0
github.com/stretchr/testify v1.7.4
golang.org/x/sys v0.1.0
github.com/mattn/go-runewidth v0.0.15
github.com/pkg/profile v1.7.0
github.com/stretchr/testify v1.8.4
golang.org/x/sys v0.16.0
golang.org/x/term v0.16.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
29 changes: 21 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
81 changes: 69 additions & 12 deletions progress/progress.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package progress

import (
"context"
"fmt"
"io"
"os"
"sync"
"time"
"unicode/utf8"

"github.com/jedib0t/go-pretty/v6/text"
"golang.org/x/term"
)

var (
Expand All @@ -23,24 +24,28 @@ var (
// Progress helps track progress for one or more tasks.
type Progress struct {
autoStop bool
done chan bool
lengthMessage int
lengthProgress int
lengthProgressOverall int
lengthTracker int
logsToRender []string
logsToRenderMutex sync.RWMutex
messageWidth int
numTrackersExpected int64
outputWriter io.Writer
overallTracker *Tracker
overallTrackerMutex sync.RWMutex
pinnedMessages []string
pinnedMessageMutex sync.RWMutex
pinnedMessageNumLines int
renderContext context.Context
renderContextCancel context.CancelFunc
renderInProgress bool
renderInProgressMutex sync.RWMutex
sortBy SortBy
style *Style
terminalWidth int
terminalWidthMutex sync.RWMutex
terminalWidthOverride int
trackerPosition Position
trackersActive []*Tracker
trackersActiveMutex sync.RWMutex
Expand Down Expand Up @@ -168,11 +173,19 @@ func (p *Progress) SetAutoStop(autoStop bool) {
p.autoStop = autoStop
}

// SetMessageWidth sets the (printed) length of the tracker message. Any message
// longer the specified width will be snipped abruptly. Any message shorter than
// SetMessageLength sets the (printed) length of the tracker message. Any
// message longer the specified length will be snipped. Any message shorter than
// the specified width will be padded with spaces.
func (p *Progress) SetMessageLength(length int) {
p.lengthMessage = length
}

// SetMessageWidth sets the (printed) length of the tracker message. Any message
// longer the specified width will be snipped. Any message shorter than the
// specified width will be padded with spaces.
// Deprecated: in favor of SetMessageLength(length)
func (p *Progress) SetMessageWidth(width int) {
p.messageWidth = width
p.lengthMessage = width
}

// SetNumTrackersExpected sets the expected number of trackers to be tracked.
Expand Down Expand Up @@ -209,6 +222,12 @@ func (p *Progress) SetStyle(style Style) {
p.style = &style
}

// SetTerminalWidth sets up a sticky terminal width and prevents the Progress
// Writer from polling for the real width during render.
func (p *Progress) SetTerminalWidth(width int) {
p.terminalWidthOverride = width
}

// SetTrackerLength sets the text-length of all the Trackers.
func (p *Progress) SetTrackerLength(length int) {
p.lengthTracker = length
Expand Down Expand Up @@ -266,7 +285,7 @@ func (p *Progress) ShowValue(show bool) {
// Stop stops the Render() logic that is in progress.
func (p *Progress) Stop() {
if p.IsRenderInProgress() {
p.done <- true
p.renderContextCancel()
}
}

Expand All @@ -279,6 +298,16 @@ func (p *Progress) Style() *Style {
return p.style
}

func (p *Progress) getTerminalWidth() int {
p.terminalWidthMutex.RLock()
defer p.terminalWidthMutex.RUnlock()

if p.terminalWidthOverride > 0 {
return p.terminalWidthOverride
}
return p.terminalWidth
}

func (p *Progress) initForRender() {
// pick a default style
p.Style()
Expand All @@ -287,7 +316,7 @@ func (p *Progress) initForRender() {
}

// reset the signals
p.done = make(chan bool, 1)
p.renderContext, p.renderContextCancel = context.WithCancel(context.Background())

// pick default lengths if no valid ones set
if p.lengthTracker <= 0 {
Expand All @@ -297,13 +326,15 @@ func (p *Progress) initForRender() {
// calculate length of the actual progress bar by discounting the left/right
// border/box chars
p.lengthProgress = p.lengthTracker -
utf8.RuneCountInString(p.style.Chars.BoxLeft) -
utf8.RuneCountInString(p.style.Chars.BoxRight)
p.lengthProgressOverall = p.messageWidth +
text.RuneWidthWithoutEscSequences(p.style.Chars.BoxLeft) -
text.RuneWidthWithoutEscSequences(p.style.Chars.BoxRight)
p.lengthProgressOverall = p.lengthMessage +
text.RuneWidthWithoutEscSequences(p.style.Options.Separator) +
p.lengthProgress + 1
if p.style.Visibility.Percentage {
p.lengthProgressOverall += text.RuneWidthWithoutEscSequences(fmt.Sprintf(p.style.Options.PercentFormat, 0.0))
p.lengthProgressOverall += text.RuneWidthWithoutEscSequences(
fmt.Sprintf(p.style.Options.PercentFormat, 0.0),
)
}

// if not output write has been set, output to STDOUT
Expand All @@ -315,6 +346,32 @@ func (p *Progress) initForRender() {
if p.updateFrequency <= 0 {
p.updateFrequency = DefaultUpdateFrequency
}

// get the current terminal size for preventing roll-overs, and do this in a
// background loop until end of render
go p.watchTerminalSize() // needs p.updateFrequency
}

func (p *Progress) updateTerminalSize() {
p.terminalWidthMutex.Lock()
defer p.terminalWidthMutex.Unlock()

p.terminalWidth, _, _ = term.GetSize(int(os.Stdout.Fd()))
}

func (p *Progress) watchTerminalSize() {
// once
p.updateTerminalSize()
// until end of time
ticker := time.NewTicker(time.Second / 10)
for {
select {
case <-ticker.C:
p.updateTerminalSize()
case <-p.renderContext.Done():
return
}
}
}

// renderHint has hints for the Render*() logic
Expand Down
17 changes: 13 additions & 4 deletions progress/progress_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package progress

import (
"context"
"math"
"os"
"testing"
Expand Down Expand Up @@ -143,6 +144,16 @@ func TestProgress_SetStyle(t *testing.T) {
assert.Equal(t, StyleCircle.Name, p.Style().Name)
}

func TestProgress_SetMessageLength(t *testing.T) {
p := Progress{}
assert.Equal(t, 0, p.lengthMessage)

p.SetMessageLength(80)
assert.Equal(t, 80, p.lengthMessage)
p.SetMessageWidth(81)
assert.Equal(t, 81, p.lengthMessage)
}

func TestProgress_SetTrackerLength(t *testing.T) {
p := Progress{}
assert.Equal(t, 0, p.lengthTracker)
Expand Down Expand Up @@ -222,13 +233,11 @@ func TestProgress_ShowValue(t *testing.T) {
}

func TestProgress_Stop(t *testing.T) {
doneChannel := make(chan bool, 1)

p := Progress{}
p.done = doneChannel
p.renderContext, p.renderContextCancel = context.WithCancel(context.Background())
p.renderInProgress = true
p.Stop()
assert.True(t, <-doneChannel)
assert.NotNil(t, <-p.renderContext.Done())
}

func TestProgress_Style(t *testing.T) {
Expand Down
Loading
Loading