mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
69 lines
1.7 KiB
Go
69 lines
1.7 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Spinner provides an animated loading indicator that displays while
|
|
// long-running operations are in progress. It writes directly to stderr
|
|
// using a goroutine-based animation loop, avoiding Bubble Tea's terminal
|
|
// capability queries that can leak escape sequences (mode 2026 DECRPM).
|
|
//
|
|
// The KITT-style frames are generated by knightRiderFrames() in stream.go
|
|
// (same package) and use the active theme colors.
|
|
type Spinner struct {
|
|
frames []string
|
|
fps time.Duration
|
|
done chan struct{}
|
|
finished chan struct{} // closed by run() after cleanup
|
|
once sync.Once
|
|
}
|
|
|
|
// NewSpinner creates a new animated KITT-style spinner using theme colors.
|
|
func NewSpinner() *Spinner {
|
|
return &Spinner{
|
|
frames: knightRiderFrames(),
|
|
fps: time.Second / 14,
|
|
done: make(chan struct{}),
|
|
finished: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Start begins the spinner animation in a separate goroutine. The spinner
|
|
// will continue animating until Stop is called.
|
|
func (s *Spinner) Start() {
|
|
go s.run()
|
|
}
|
|
|
|
// Stop halts the spinner animation and blocks until the animation goroutine
|
|
// has exited and the line is cleared. Safe to call multiple times.
|
|
func (s *Spinner) Stop() {
|
|
s.once.Do(func() { close(s.done) })
|
|
<-s.finished
|
|
}
|
|
|
|
// run is the animation loop that renders spinner frames to stderr.
|
|
func (s *Spinner) run() {
|
|
defer close(s.finished) // unblock Stop()
|
|
|
|
ticker := time.NewTicker(s.fps)
|
|
defer ticker.Stop()
|
|
|
|
var frame int
|
|
for {
|
|
select {
|
|
case <-s.done:
|
|
// Clear the spinner line and return.
|
|
fmt.Fprint(os.Stderr, "\r\033[K")
|
|
return
|
|
case <-ticker.C:
|
|
f := s.frames[frame%len(s.frames)]
|
|
fmt.Fprintf(os.Stderr, "\r %s", f)
|
|
frame++
|
|
}
|
|
}
|
|
}
|