|
5 | 5 | "os" |
6 | 6 | "strings" |
7 | 7 |
|
| 8 | + "github.com/charmbracelet/bubbles/spinner" |
| 9 | + tea "github.com/charmbracelet/bubbletea" |
8 | 10 | "github.com/charmbracelet/huh" |
9 | 11 | "github.com/charmbracelet/lipgloss" |
10 | 12 | "github.com/charmbracelet/x/ansi" |
@@ -181,3 +183,86 @@ func (g Group) Confirm(cmd *cli.Command, flagName, title, description string, de |
181 | 183 | func (g Group) Field(field huh.Field) error { |
182 | 184 | return huh.NewForm(huh.NewGroup(field)).WithTheme(GetFormTheme(g.indent)).Run() |
183 | 185 | } |
| 186 | + |
| 187 | +// spinnerModel handles the spinner UI while executing an operation |
| 188 | +type spinnerModel struct { |
| 189 | + spinner spinner.Model |
| 190 | + message string |
| 191 | + indent int |
| 192 | + execute func() error |
| 193 | + err error |
| 194 | + done bool |
| 195 | +} |
| 196 | + |
| 197 | +type operationCompleteMsg struct { |
| 198 | + err error |
| 199 | +} |
| 200 | + |
| 201 | +func (m spinnerModel) Init() tea.Cmd { |
| 202 | + return tea.Batch(m.spinner.Tick, m.runOperation) |
| 203 | +} |
| 204 | + |
| 205 | +func (m spinnerModel) runOperation() tea.Msg { |
| 206 | + err := m.execute() |
| 207 | + return operationCompleteMsg{err: err} |
| 208 | +} |
| 209 | + |
| 210 | +func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { |
| 211 | + switch msg := msg.(type) { |
| 212 | + case tea.KeyMsg: |
| 213 | + if msg.String() == "ctrl+c" { |
| 214 | + return m, tea.Quit |
| 215 | + } |
| 216 | + case operationCompleteMsg: |
| 217 | + m.done = true |
| 218 | + m.err = msg.err |
| 219 | + return m, tea.Quit |
| 220 | + case spinner.TickMsg: |
| 221 | + var cmd tea.Cmd |
| 222 | + m.spinner, cmd = m.spinner.Update(msg) |
| 223 | + return m, cmd |
| 224 | + } |
| 225 | + return m, nil |
| 226 | +} |
| 227 | + |
| 228 | +func (m spinnerModel) View() string { |
| 229 | + if m.done { |
| 230 | + return "" |
| 231 | + } |
| 232 | + indentStr := strings.Repeat(" ", m.indent) |
| 233 | + return indentStr + m.spinner.View() + " " + m.message |
| 234 | +} |
| 235 | + |
| 236 | +// Spinner runs the given operation with a spinner and message |
| 237 | +func Spinner(message string, operation func() error) error { |
| 238 | + return spinnerWithIndent(0, message, operation) |
| 239 | +} |
| 240 | + |
| 241 | +// Spinner runs the given operation with a spinner and message, respecting the group's indent |
| 242 | +func (g Group) Spinner(message string, operation func() error) error { |
| 243 | + return spinnerWithIndent(g.indent, message, operation) |
| 244 | +} |
| 245 | + |
| 246 | +func spinnerWithIndent(indent int, message string, operation func() error) error { |
| 247 | + s := spinner.New() |
| 248 | + s.Spinner = spinner.MiniDot |
| 249 | + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("14")) |
| 250 | + |
| 251 | + model := spinnerModel{ |
| 252 | + spinner: s, |
| 253 | + message: message, |
| 254 | + indent: indent, |
| 255 | + execute: operation, |
| 256 | + } |
| 257 | + |
| 258 | + finalModel, err := tea.NewProgram(model).Run() |
| 259 | + if err != nil { |
| 260 | + return err |
| 261 | + } |
| 262 | + |
| 263 | + if m, ok := finalModel.(spinnerModel); ok && m.err != nil { |
| 264 | + return m.err |
| 265 | + } |
| 266 | + |
| 267 | + return nil |
| 268 | +} |
0 commit comments