spinner.go

 1package format
 2
 3import (
 4	"context"
 5	"errors"
 6	"fmt"
 7	"os"
 8
 9	tea "github.com/charmbracelet/bubbletea/v2"
10	"github.com/charmbracelet/crush/internal/tui/components/anim"
11	"github.com/charmbracelet/crush/internal/tui/styles"
12	"github.com/charmbracelet/x/ansi"
13)
14
15// Spinner wraps the bubbles spinner for non-interactive mode
16type Spinner struct {
17	done chan struct{}
18	prog *tea.Program
19}
20
21type model struct {
22	cancel context.CancelFunc
23	anim   anim.Anim
24}
25
26func (m model) Init() tea.Cmd { return m.anim.Init() }
27func (m model) View() string  { return m.anim.View() }
28
29// Update implements tea.Model.
30func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
31	switch msg := msg.(type) {
32	case tea.KeyPressMsg:
33		switch msg.String() {
34		case "ctrl+c", "esc":
35			m.cancel()
36			return m, tea.Quit
37		}
38	}
39	var cmd tea.Cmd
40	m.anim, cmd = m.anim.Update(msg)
41	return m, cmd
42}
43
44// NewSpinner creates a new spinner with the given message
45func NewSpinner(ctx context.Context, cancel context.CancelFunc, message string) *Spinner {
46	t := styles.CurrentTheme()
47	model := model{
48		anim: anim.New(anim.Settings{
49			Size:        10,
50			Label:       message,
51			LabelColor:  t.FgBase,
52			GradColorA:  t.Primary,
53			GradColorB:  t.Secondary,
54			CycleColors: true,
55		}),
56		cancel: cancel,
57	}
58
59	prog := tea.NewProgram(
60		model,
61		tea.WithOutput(os.Stderr),
62		tea.WithContext(ctx),
63	)
64
65	return &Spinner{
66		prog: prog,
67		done: make(chan struct{}, 1),
68	}
69}
70
71// Start begins the spinner animation
72func (s *Spinner) Start() {
73	go func() {
74		defer close(s.done)
75		_, err := s.prog.Run()
76		// ensures line is cleared
77		fmt.Fprint(os.Stderr, ansi.EraseEntireLine)
78		if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, tea.ErrInterrupted) {
79			fmt.Fprintf(os.Stderr, "Error running spinner: %v\n", err)
80		}
81	}()
82}
83
84// Stop ends the spinner animation
85func (s *Spinner) Stop() {
86	s.prog.Quit()
87	<-s.done
88}