package tui import ( "fmt" "os" "git.jingrow.com/charmbracelet/bubbles/spinner" tea "git.jingrow.com/charmbracelet/bubbletea" "git.jingrow.com/charmbracelet/lipgloss" ) type spinnerModel struct { spinner spinner.Model message string quitting bool err error onQuit func() } func ShowSpinner(message string, onQuit func()) *SpinnerUI { s := spinner.New() s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) m := spinnerModel{ spinner: s, message: message, onQuit: onQuit, } // Create program without alt screen p := tea.NewProgram(m, tea.WithOutput(os.Stderr)) doneChan := make(chan struct{}) go func() { if _, err := p.Run(); err != nil { m.err = fmt.Errorf("spinner error: %w", err) } close(doneChan) }() return &SpinnerUI{ program: p, doneChan: doneChan, } } type SpinnerUI struct { program *tea.Program doneChan chan struct{} } func (s *SpinnerUI) Done() { s.program.Send(tea.KeyMsg{Type: tea.KeyCtrlQ}) <-s.doneChan } func (m spinnerModel) Init() tea.Cmd { return tea.Batch( tea.EnterAltScreen, // Start with alt screen tea.HideCursor, // Hide cursor m.spinner.Tick, ) } func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "ctrl+q": m.quitting = true m.onQuit() return m, tea.Sequence(tea.ShowCursor, tea.Quit) } case tea.QuitMsg: return m, tea.Sequence(tea.ShowCursor, tea.Quit) } var cmd tea.Cmd m.spinner, cmd = m.spinner.Update(msg) return m, cmd } func (m spinnerModel) View() string { if m.quitting { return "" } if m.err != nil { return fmt.Sprintf(" %s Error: %v", m.spinner.View(), m.err) } return fmt.Sprintf(" %s %s", m.spinner.View(), m.message) + "\n\n jcloude to quit" }