package progress import ( "strings" "time" "github.com/jedib0t/go-pretty/v6/text" ) // IndeterminateIndicator defines the structure for the indicator to indicate // indeterminate progress. Ex.: {0, <=>} type IndeterminateIndicator struct { Position int Text string } // IndeterminateIndicatorGenerator is a function that takes the maximum length // of the progress bar and returns an IndeterminateIndicator telling the // indicator string, and the location of the same in the progress bar. // // Technically, this could generate and return the entire progress bar string to // override the full display of the same - this is done by the Dominoes and // Pac-Man examples below. type IndeterminateIndicatorGenerator func(maxLen int) IndeterminateIndicator // IndeterminateIndicatorDominoes simulates a bunch of dominoes falling back and // forth. func IndeterminateIndicatorDominoes(duration time.Duration) IndeterminateIndicatorGenerator { return timedIndeterminateIndicatorGenerator(indeterminateIndicatorDominoes(), duration) } // IndeterminateIndicatorMovingBackAndForth incrementally moves from the left to // right and back for each specified duration. If duration is 0, then every // single invocation moves the indicator. func IndeterminateIndicatorMovingBackAndForth(indicator string, duration time.Duration) IndeterminateIndicatorGenerator { return timedIndeterminateIndicatorGenerator(indeterminateIndicatorMovingBackAndForth(indicator), duration) } // IndeterminateIndicatorMovingLeftToRight incrementally moves from the left to // right and starts from left again for each specified duration. If duration is // 0, then every single invocation moves the indicator. func IndeterminateIndicatorMovingLeftToRight(indicator string, duration time.Duration) IndeterminateIndicatorGenerator { return timedIndeterminateIndicatorGenerator(indeterminateIndicatorMovingLeftToRight(indicator), duration) } // IndeterminateIndicatorMovingRightToLeft incrementally moves from the right to // left and starts from right again for each specified duration. If duration is // 0, then every single invocation moves the indicator. func IndeterminateIndicatorMovingRightToLeft(indicator string, duration time.Duration) IndeterminateIndicatorGenerator { return timedIndeterminateIndicatorGenerator(indeterminateIndicatorMovingRightToLeft(indicator), duration) } // IndeterminateIndicatorPacMan simulates a Pac-Man character chomping through // the progress bar back and forth. func IndeterminateIndicatorPacMan(duration time.Duration) IndeterminateIndicatorGenerator { return timedIndeterminateIndicatorGenerator(indeterminateIndicatorPacMan(), duration) } func indeterminateIndicatorDominoes() IndeterminateIndicatorGenerator { direction := 1 // positive == left to right; negative == right to left nextPosition := 0 out := strings.Builder{} generateIndicator := func(currentPosition int, maxLen int) string { out.Reset() out.WriteString(strings.Repeat("/", currentPosition)) out.WriteString(strings.Repeat("\\", maxLen-currentPosition)) return out.String() } return func(maxLen int) IndeterminateIndicator { currentPosition := nextPosition if currentPosition == 0 { direction = 1 } else if currentPosition == maxLen { direction = -1 } nextPosition += direction return IndeterminateIndicator{ Position: 0, Text: generateIndicator(currentPosition, maxLen), } } } func indeterminateIndicatorMovingBackAndForth(indicator string) IndeterminateIndicatorGenerator { direction := 1 // positive == left to right; negative == right to left nextPosition := 0 return func(maxLen int) IndeterminateIndicator { currentPosition := nextPosition if currentPosition == 0 { direction = 1 } else if currentPosition+text.RuneWidthWithoutEscSequences(indicator) == maxLen { direction = -1 } nextPosition += direction return IndeterminateIndicator{ Position: currentPosition, Text: indicator, } } } func indeterminateIndicatorMovingLeftToRight(indicator string) IndeterminateIndicatorGenerator { nextPosition := 0 return func(maxLen int) IndeterminateIndicator { currentPosition := nextPosition nextPosition++ if nextPosition+text.RuneWidthWithoutEscSequences(indicator) > maxLen { nextPosition = 0 } return IndeterminateIndicator{ Position: currentPosition, Text: indicator, } } } func indeterminateIndicatorMovingRightToLeft(indicator string) IndeterminateIndicatorGenerator { nextPosition := -1 return func(maxLen int) IndeterminateIndicator { if nextPosition == -1 { nextPosition = maxLen - text.RuneWidthWithoutEscSequences(indicator) } currentPosition := nextPosition nextPosition-- return IndeterminateIndicator{ Position: currentPosition, Text: indicator, } } } func indeterminateIndicatorPacMan() IndeterminateIndicatorGenerator { pacManMovingRight, pacManMovingLeft := "ᗧ", "ᗤ" direction := 1 // positive == left to right; negative == right to left indicator := pacManMovingRight nextPosition := 0 out := strings.Builder{} generateIndicator := func(currentPosition int, maxLen int) string { out.Reset() if currentPosition > 0 { out.WriteString(strings.Repeat(" ", currentPosition)) } out.WriteString(indicator) out.WriteString(strings.Repeat(" ", maxLen-currentPosition-1)) return out.String() } return func(maxLen int) IndeterminateIndicator { currentPosition := nextPosition currentText := generateIndicator(currentPosition, maxLen) if currentPosition == 0 { direction = 1 indicator = pacManMovingRight } else if currentPosition+text.RuneWidthWithoutEscSequences(indicator) == maxLen { direction = -1 indicator = pacManMovingLeft } nextPosition += direction return IndeterminateIndicator{ Position: 0, Text: currentText, } } } // timedIndeterminateIndicatorGenerator ticks based on the given duration. If // duration is 0, it ticks for every invocation. func timedIndeterminateIndicatorGenerator(indicatorGenerator IndeterminateIndicatorGenerator, duration time.Duration) IndeterminateIndicatorGenerator { var indeterminateIndicator *IndeterminateIndicator lastRenderTime := time.Now() return func(maxLen int) IndeterminateIndicator { currRenderTime := time.Now() if indeterminateIndicator == nil || duration == 0 || currRenderTime.Sub(lastRenderTime) > duration { tmpIndeterminateIndicator := indicatorGenerator(maxLen) indeterminateIndicator = &tmpIndeterminateIndicator lastRenderTime = currRenderTime } return *indeterminateIndicator } }