spinner.go 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package format
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "github.com/charmbracelet/bubbles/v2/spinner"
  7. tea "github.com/charmbracelet/bubbletea/v2"
  8. )
  9. // Spinner wraps the bubbles spinner for non-interactive mode
  10. type Spinner struct {
  11. model spinner.Model
  12. done chan struct{}
  13. prog *tea.Program
  14. ctx context.Context
  15. cancel context.CancelFunc
  16. }
  17. // spinnerModel is the tea.Model for the spinner
  18. type spinnerModel struct {
  19. spinner spinner.Model
  20. message string
  21. quitting bool
  22. }
  23. func (m spinnerModel) Init() tea.Cmd {
  24. return m.spinner.Tick
  25. }
  26. func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  27. switch msg := msg.(type) {
  28. case tea.KeyPressMsg:
  29. m.quitting = true
  30. return m, tea.Quit
  31. case spinner.TickMsg:
  32. var cmd tea.Cmd
  33. m.spinner, cmd = m.spinner.Update(msg)
  34. return m, cmd
  35. case quitMsg:
  36. m.quitting = true
  37. return m, tea.Quit
  38. default:
  39. return m, nil
  40. }
  41. }
  42. func (m spinnerModel) View() string {
  43. if m.quitting {
  44. return ""
  45. }
  46. return fmt.Sprintf("%s %s", m.spinner.View(), m.message)
  47. }
  48. // quitMsg is sent when we want to quit the spinner
  49. type quitMsg struct{}
  50. // NewSpinner creates a new spinner with the given message
  51. func NewSpinner(message string) *Spinner {
  52. s := spinner.New()
  53. s.Spinner = spinner.Dot
  54. s.Style = s.Style.Foreground(s.Style.GetForeground())
  55. ctx, cancel := context.WithCancel(context.Background())
  56. model := spinnerModel{
  57. spinner: s,
  58. message: message,
  59. }
  60. prog := tea.NewProgram(model, tea.WithOutput(os.Stderr), tea.WithoutCatchPanics())
  61. return &Spinner{
  62. model: s,
  63. done: make(chan struct{}),
  64. prog: prog,
  65. ctx: ctx,
  66. cancel: cancel,
  67. }
  68. }
  69. // Start begins the spinner animation
  70. func (s *Spinner) Start() {
  71. go func() {
  72. defer close(s.done)
  73. go func() {
  74. <-s.ctx.Done()
  75. s.prog.Send(quitMsg{})
  76. }()
  77. _, err := s.prog.Run()
  78. if err != nil {
  79. fmt.Fprintf(os.Stderr, "Error running spinner: %v\n", err)
  80. }
  81. }()
  82. }
  83. // Stop ends the spinner animation
  84. func (s *Spinner) Stop() {
  85. s.cancel()
  86. <-s.done
  87. }