spinner.go 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  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/tui/components/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.Init() }
  21. func (m model) View() tea.View { return tea.NewView(m.anim.View()) }
  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. }
  32. mm, cmd := m.anim.Update(msg)
  33. m.anim = mm.(*anim.Anim)
  34. return m, cmd
  35. }
  36. // NewSpinner creates a new spinner with the given message
  37. func NewSpinner(ctx context.Context, cancel context.CancelFunc, animSettings anim.Settings) *Spinner {
  38. m := model{
  39. anim: anim.New(animSettings),
  40. cancel: cancel,
  41. }
  42. p := tea.NewProgram(m, tea.WithOutput(os.Stderr), tea.WithContext(ctx))
  43. return &Spinner{
  44. prog: p,
  45. done: make(chan struct{}, 1),
  46. }
  47. }
  48. // Start begins the spinner animation
  49. func (s *Spinner) Start() {
  50. go func() {
  51. defer close(s.done)
  52. _, err := s.prog.Run()
  53. // ensures line is cleared
  54. fmt.Fprint(os.Stderr, ansi.EraseEntireLine)
  55. if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, tea.ErrInterrupted) {
  56. fmt.Fprintf(os.Stderr, "Error running spinner: %v\n", err)
  57. }
  58. }()
  59. }
  60. // Stop ends the spinner animation
  61. func (s *Spinner) Stop() {
  62. s.prog.Quit()
  63. <-s.done
  64. }