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

Feature scrolling detail #194

Merged
merged 6 commits into from
Sep 15, 2024
82 changes: 82 additions & 0 deletions progressbar.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ type state struct {
finished bool
exit bool // Progress bar exit halfway

details []string // details to show,only used when detail row is set to more than 0

rendered string
}

Expand Down Expand Up @@ -123,6 +125,9 @@ type config struct {
// showDescriptionAtLineEnd specifies whether description should be written at line end instead of line start
showDescriptionAtLineEnd bool

// specifies how many rows of details to show,default value is 0 and no details will be shown
maxDetailRow int

stdBuffer bytes.Buffer
}

Expand Down Expand Up @@ -308,6 +313,14 @@ func OptionShowDescriptionAtLineEnd() Option {
}
}

// OptionSetMaxDetailRow sets the max row of details
// the row count should be less than the terminal height, otherwise it will not give you the output you want
func OptionSetMaxDetailRow(row int) Option {
return func(p *ProgressBar) {
p.config.maxDetailRow = row
}
}

var defaultTheme = Theme{Saucer: "█", SaucerPadding: " ", BarStart: "|", BarEnd: "|"}

// NewOptions constructs a new instance of ProgressBar, with any options you specify
Expand Down Expand Up @@ -341,6 +354,10 @@ func NewOptions64(max int64, options ...Option) *ProgressBar {
panic("invalid spinner type, must be between 0 and 75")
}

if b.config.maxDetailRow < 0 {
panic("invalid max detail row, must be greater than 0")
}

// ignoreLength if max bytes not known
if b.config.max == -1 {
b.config.ignoreLength = true
Expand Down Expand Up @@ -596,6 +613,66 @@ func (p *ProgressBar) Add64(num int64) error {
return nil
}

// AddDetail adds a detail to the progress bar. Only used when maxDetailRow is set to a value greater than 0
func (p *ProgressBar) AddDetail(detail string) error {
if p.config.maxDetailRow == 0 {
return errors.New("maxDetailRow is set to 0, cannot add detail")
}
if p.IsFinished() {
return errors.New("cannot add detail to a finished progress bar")
}

p.lock.Lock()
defer p.lock.Unlock()
if p.state.details == nil {
// if we add a detail before the first add, it will be weird that we have detail but don't have the progress bar in the top.
// so when we add the first detail, we will render the progress bar first.
if err := p.render(); err != nil {
return err
}
}
p.state.details = append(p.state.details, detail)
if len(p.state.details) > p.config.maxDetailRow {
p.state.details = p.state.details[1:]
}
if err := p.renderDetails(); err != nil {
return err
}
return nil
}

// renderDetails renders the details of the progress bar
func (p *ProgressBar) renderDetails() error {
if p.config.invisible {
return nil
}
if p.state.finished {
return nil
}
if p.config.maxDetailRow == 0 {
return nil
}

b := strings.Builder{}
b.WriteString("\n")

// render the details row
for _, detail := range p.state.details {
b.WriteString(fmt.Sprintf("\u001B[K\r%s\n", detail))
}
// add empty lines to fill the maxDetailRow
for i := len(p.state.details); i < p.config.maxDetailRow; i++ {
b.WriteString("\u001B[K\n")
}

// move the cursor up to the start of the details row
b.WriteString(fmt.Sprintf("\u001B[%dF", p.config.maxDetailRow+1))

writeString(p.config, b.String())

return nil
}

// Clear erases the progress bar from the current line
func (p *ProgressBar) Clear() error {
return clearProgressBar(p.config, p.state)
Expand Down Expand Up @@ -695,6 +772,11 @@ func (p *ProgressBar) render() error {
io.Copy(p.config.writer, &p.config.stdBuffer)
renderProgressBar(p.config, &p.state)
}
if p.config.maxDetailRow > 0 {
p.renderDetails()
// put the cursor back to the last line of the details
writeString(p.config, fmt.Sprintf("\u001B[%dB\r\u001B[%dC", p.config.maxDetailRow, len(p.state.details[len(p.state.details)-1])))
}
if p.config.onCompletion != nil {
p.config.onCompletion()
}
Expand Down
Loading