startSpinner displays a spinner with rotating messages and returns a stop function
(ctx context.Context, messages []string)
| 61 | |
| 62 | // startSpinner displays a spinner with rotating messages and returns a stop function |
| 63 | func startSpinner(ctx context.Context, messages []string) func() { |
| 64 | var mu gosync.Mutex |
| 65 | var currentMessage string |
| 66 | var stopped bool |
| 67 | |
| 68 | // Create a progressbar for the spinner |
| 69 | bar := progressbar.NewOptions(-1, |
| 70 | progressbar.OptionSetDescription(""), |
| 71 | progressbar.OptionSetWidth(50), |
| 72 | progressbar.OptionClearOnFinish(), |
| 73 | progressbar.OptionSetRenderBlankState(true), |
| 74 | ) |
| 75 | |
| 76 | // Create a context for the spinner that can be cancelled |
| 77 | spinnerCtx, spinnerCancel := context.WithCancel(ctx) |
| 78 | |
| 79 | // Channel to signal when cleanup is complete |
| 80 | cleanupDone := make(chan struct{}) |
| 81 | |
| 82 | // Start the message rotation |
| 83 | go func() { |
| 84 | ticker := time.NewTicker(4 * time.Second) |
| 85 | defer ticker.Stop() |
| 86 | |
| 87 | messageIndex := 0 |
| 88 | for { |
| 89 | select { |
| 90 | case <-spinnerCtx.Done(): |
| 91 | return |
| 92 | case <-ticker.C: |
| 93 | mu.Lock() |
| 94 | if !stopped { |
| 95 | currentMessage = messages[messageIndex%len(messages)] |
| 96 | messageIndex++ |
| 97 | } |
| 98 | mu.Unlock() |
| 99 | } |
| 100 | } |
| 101 | }() |
| 102 | |
| 103 | // Start the spinner animation |
| 104 | spinnerChars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} |
| 105 | spinnerIndex := 0 |
| 106 | |
| 107 | go func() { |
| 108 | defer close(cleanupDone) |
| 109 | ticker := time.NewTicker(100 * time.Millisecond) |
| 110 | defer ticker.Stop() |
| 111 | |
| 112 | for { |
| 113 | select { |
| 114 | case <-spinnerCtx.Done(): |
| 115 | mu.Lock() |
| 116 | stopped = true |
| 117 | mu.Unlock() |
| 118 | _ = bar.Clear() |
| 119 | return |
| 120 | case <-ticker.C: |