@@ -2,101 +2,63 @@ package format
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "os"
78
8− "github.com/charmbracelet/bubbles/v2/spinner"
99 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"
1013)
1114
1215// Spinner wraps the bubbles spinner for non-interactive mode
1316type Spinner struct {
14− model spinner.Model
15− done chan struct{}
16− prog *tea.Program
17− ctx context.Context
18− cancel context.CancelFunc
17+ done chan struct{}
18+ prog *tea.Program
1919}
2020
21−// spinnerModel is the tea.Model for the spinner
22−type spinnerModel struct {
23− spinner spinner.Model
24− message string
25− quitting bool
26−}
27−
28−func (m spinnerModel) Init() tea.Cmd {
29− return m.spinner.Tick
30−}
31−
32−func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
33− switch msg := msg.(type) {
34− case tea.KeyPressMsg:
35− m.quitting = true
36− return m, tea.Quit
37− case spinner.TickMsg:
38− var cmd tea.Cmd
39− m.spinner, cmd = m.spinner.Update(msg)
40− return m, cmd
41− case quitMsg:
42− m.quitting = true
43− return m, tea.Quit
44− default:
45− return m, nil
46− }
47−}
48−
49−func (m spinnerModel) View() string {
50− if m.quitting {
51− return ""
52− }
53− return fmt.Sprintf("%s %s", m.spinner.View(), m.message)
54−}
55−
56−// quitMsg is sent when we want to quit the spinner
57−type quitMsg struct{}
58−
5921// NewSpinner creates a new spinner with the given message
60−func NewSpinner(message string) *Spinner {
61− s := spinner.New()
62− s.Spinner = spinner.Dot
63− s.Style = s.Style.Foreground(s.Style.GetForeground())
64−
65− ctx, cancel := context.WithCancel(context.Background())
66−
67− model := spinnerModel{
68− spinner: s,
69− message: message,
70− }
22+func NewSpinner(ctx context.Context, message string) *Spinner {
23+ t := styles.CurrentTheme()
24+ model := anim.New(anim.Settings{
25+ Size: 10,
26+ Label: message,
27+ LabelColor: t.FgBase,
28+ GradColorA: t.Primary,
29+ GradColorB: t.Secondary,
30+ CycleColors: true,
31+ })
7132
72− prog := tea.NewProgram(model, tea.WithOutput(os.Stderr), tea.WithoutCatchPanics())
33+ prog := tea.NewProgram(
34+ model,
35+ tea.WithInput(nil),
36+ tea.WithOutput(os.Stderr),
37+ tea.WithContext(ctx),
38+ tea.WithoutCatchPanics(),
39+ )
7340
7441 return &Spinner{
75− model: s,
76− done: make(chan struct{}),
77− prog: prog,
78− ctx: ctx,
79− cancel: cancel,
42+ prog: prog,
43+ done: make(chan struct{}, 1),
8044 }
8145}
8246
8347// Start begins the spinner animation
8448func (s *Spinner) Start() {
8549 go func() {
86− defer close(s.done)
87− go func() {
88− <-s.ctx.Done()
89− s.prog.Send(quitMsg{})
90− }()
9150 _, err := s.prog.Run()
92− if err != nil {
51+ // ensures line is cleared
52+ fmt.Fprint(os.Stderr, ansi.EraseEntireLine)
53+ if err != nil && !errors.Is(err, context.Canceled) {
9354 fmt.Fprintf(os.Stderr, "Error running spinner: %v\n", err)
9455 }
56+ close(s.done)
9557 }()
9658}
9759
9860// Stop ends the spinner animation
9961func (s *Spinner) Stop() {
100− s.cancel()
62+ s.prog.Quit()
10163 <-s.done
10264}