spinner.go 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. package format
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "os"
  7. tea "charm.land/bubbletea/v2"
  8. "github.com/charmbracelet/crush/internal/ui/anim"
  9. "github.com/charmbracelet/x/ansi"
  10. )
  11. // Spinner wraps the bubbles spinner for non-interactive mode
  12. type Spinner struct {
  13. done chan struct{}
  14. prog *tea.Program
  15. }
  16. type model struct {
  17. cancel context.CancelFunc
  18. anim *anim.Anim
  19. }
  20. func (m model) Init() tea.Cmd { return m.anim.Start() }
  21. func (m model) View() tea.View { return tea.NewView(m.anim.Render()) }
  22. // Update implements tea.Model.
  23. func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  24. switch msg := msg.(type) {
  25. case tea.KeyPressMsg:
  26. switch msg.String() {
  27. case "ctrl+c", "esc":
  28. m.cancel()
  29. return m, tea.Quit
  30. }
  31. case anim.StepMsg:
  32. cmd := m.anim.Animate(msg)
  33. return m, cmd
  34. }
  35. return m, nil
  36. }
  37. // NewSpinner creates a new spinner with the given message
  38. func NewSpinner(ctx context.Context, cancel context.CancelFunc, animSettings anim.Settings) *Spinner {
  39. m := model{
  40. anim: anim.New(animSettings),
  41. cancel: cancel,
  42. }
  43. p := tea.NewProgram(m, tea.WithOutput(os.Stderr), tea.WithContext(ctx))
  44. return &Spinner{
  45. prog: p,
  46. done: make(chan struct{}, 1),
  47. }
  48. }
  49. // Start begins the spinner animation
  50. func (s *Spinner) Start() {
  51. go func() {
  52. defer close(s.done)
  53. _, err := s.prog.Run()
  54. // ensures line is cleared
  55. fmt.Fprint(os.Stderr, ansi.EraseEntireLine)
  56. if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, tea.ErrInterrupted) {
  57. fmt.Fprintf(os.Stderr, "Error running spinner: %v\n", err)
  58. }
  59. }()
  60. }
  61. // Stop ends the spinner animation
  62. func (s *Spinner) Stop() {
  63. s.prog.Quit()
  64. <-s.done
  65. }