package progress import ( "fmt" "sync" "time" ) // Spinner displays an animated spinner with a message type Spinner struct { message string frames []string interval time.Duration stop chan struct{} done chan struct{} mu sync.Mutex running bool } // NewSpinner creates a new spinner with the given message func NewSpinner(message string) *Spinner { return &Spinner{ message: message, frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, interval: 80 * time.Millisecond, stop: make(chan struct{}), done: make(chan struct{}), } } // Start begins the spinner animation func (s *Spinner) Start() { s.mu.Lock() if s.running { s.mu.Unlock() return } s.running = true s.mu.Unlock() go func() { i := 0 for { select { case <-s.stop: // Clear the line and signal done fmt.Print("\r\033[K") close(s.done) return default: fmt.Printf("\r%s %s", s.frames[i%len(s.frames)], s.message) i++ time.Sleep(s.interval) } } }() } // Stop stops the spinner and clears the line func (s *Spinner) Stop() { s.mu.Lock() if !s.running { s.mu.Unlock() return } s.running = false s.mu.Unlock() close(s.stop) <-s.done } // StopWithMessage stops the spinner and prints a final message func (s *Spinner) StopWithMessage(message string) { s.Stop() fmt.Println(message) } // UpdateMessage updates the spinner message while running func (s *Spinner) UpdateMessage(message string) { s.mu.Lock() defer s.mu.Unlock() s.message = message }