@@ -2,101 +2,63 @@ package format
8− "github.com/charmbracelet/bubbles/v2/spinner"
9 tea "github.com/charmbracelet/bubbletea/v2"
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"
12// Spinner wraps the bubbles spinner for non-interactive mode
15// Spinner wraps the bubbles spinner for non-interactive mode
13type Spinner struct {
16type Spinner struct {
15− done chan struct{}
17+ done chan struct{}
16− prog *tea.Program
18+ prog *tea.Program
18− cancel context.CancelFunc
21−// spinnerModel is the tea.Model for the spinner
22−type spinnerModel struct {
23− spinner spinner.Model
28−func (m spinnerModel) Init() tea.Cmd {
29− return m.spinner.Tick
32−func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
33− switch msg := msg.(type) {
34− case tea.KeyPressMsg:
37− case spinner.TickMsg:
39− m.spinner, cmd = m.spinner.Update(msg)
49−func (m spinnerModel) View() string {
53− return fmt.Sprintf("%s %s", m.spinner.View(), m.message)
56−// quitMsg is sent when we want to quit the spinner
59// NewSpinner creates a new spinner with the given message
21// NewSpinner creates a new spinner with the given message
60−func NewSpinner(message string) *Spinner {
22+func NewSpinner(ctx context.Context, message string) *Spinner {
61− s := spinner.New()
23+ t := styles.CurrentTheme()
62− s.Spinner = spinner.Dot
24+ model := anim.New(anim.Settings{
63− s.Style = s.Style.Foreground(s.Style.GetForeground())
25+ Size: 10,
65− ctx, cancel := context.WithCancel(context.Background())
27+ LabelColor: t.FgBase,
66−
28+ GradColorA: t.Primary,
67− model := spinnerModel{
29+ GradColorB: t.Secondary,
68− spinner: s,
30+ CycleColors: true,
33+ prog := tea.NewProgram(
72− prog := tea.NewProgram(model, tea.WithOutput(os.Stderr), tea.WithoutCatchPanics())
36+ tea.WithOutput(os.Stderr),
37+ tea.WithContext(ctx),
38+ tea.WithoutCatchPanics(),
74 return &Spinner{
41 return &Spinner{
75− model: s,
42+ prog: prog,
76− done: make(chan struct{}),
43+ done: make(chan struct{}, 1),
83// Start begins the spinner animation
47// Start begins the spinner animation
84func (s *Spinner) Start() {
48func (s *Spinner) Start() {
85 go func() {
49 go func() {
89− s.prog.Send(quitMsg{})
91 _, err := s.prog.Run()
50 _, 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) {
93 fmt.Fprintf(os.Stderr, "Error running spinner: %v\n", err)
54 fmt.Fprintf(os.Stderr, "Error running spinner: %v\n", err)
98// Stop ends the spinner animation
60// Stop ends the spinner animation
99func (s *Spinner) Stop() {
61func (s *Spinner) Stop() {
100− s.cancel()
62+ s.prog.Quit()