ollama/progress/spinner.go

108 lines
1.8 KiB
Go

package progress
import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
)
type Spinner struct {
mu sync.Mutex
message atomic.Value
messageWidth int
parts []string
value int
done chan struct{}
stopOnce sync.Once
started time.Time
stopped time.Time
}
func NewSpinner(message string) *Spinner {
s := &Spinner{
parts: []string{
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",
},
started: time.Now(),
done: make(chan struct{}),
}
s.SetMessage(message)
go s.start()
return s
}
func (s *Spinner) SetMessage(message string) {
s.message.Store(message)
}
func (s *Spinner) String() string {
var sb strings.Builder
if message, ok := s.message.Load().(string); ok && len(message) > 0 {
message := strings.TrimSpace(message)
if s.messageWidth > 0 && len(message) > s.messageWidth {
message = message[:s.messageWidth]
}
fmt.Fprintf(&sb, "%s", message)
if padding := s.messageWidth - sb.Len(); padding > 0 {
sb.WriteString(strings.Repeat(" ", padding))
}
sb.WriteString(" ")
}
s.mu.Lock()
value := s.value
stopped := !s.stopped.IsZero()
s.mu.Unlock()
if !stopped {
spinner := s.parts[value]
sb.WriteString(spinner)
sb.WriteString(" ")
}
return sb.String()
}
func (s *Spinner) start() {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
s.mu.Lock()
if !s.stopped.IsZero() {
s.mu.Unlock()
return
}
s.mu.Unlock()
for {
select {
case <-s.done:
return
case <-ticker.C:
s.mu.Lock()
if !s.stopped.IsZero() {
s.mu.Unlock()
return
}
s.value = (s.value + 1) % len(s.parts)
s.mu.Unlock()
}
}
}
func (s *Spinner) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
if s.stopped.IsZero() {
s.stopped = time.Now()
}
s.stopOnce.Do(func() {
close(s.done)
})
}