-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathspinner.go
More file actions
126 lines (104 loc) · 2.41 KB
/
spinner.go
File metadata and controls
126 lines (104 loc) · 2.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package cmd
import (
"fmt"
"io"
"os"
"time"
tea "github.com/charmbracelet/bubbletea"
"golang.org/x/term"
)
// spinnerFrames defines the animation frames for the spinner
var spinnerFrames = []string{"⢎ ", "⠎⠁", "⠊⠑", "⠈⠱", " ⡱", "⢀⡰", "⢄⡠", "⢆⡀"}
type Spinner struct {
// Populated when output is a TTY
program *tea.Program
// Populated when output is not a TTY
output io.Writer
model *spinnerModel
}
func NewSpinner(output io.Writer, message string, args ...any) *Spinner {
model := spinnerModel{
message: fmt.Sprintf(message, args...),
}
// If output is not a TTY, print each message on a new line
if !isTerminal(output) {
s := &Spinner{
output: output,
model: &model,
}
s.println()
return s
}
// If output is a TTY, use bubbletea to dynamically update the message
program := tea.NewProgram(
model,
tea.WithOutput(output),
)
// Start the program in a goroutine
go func() {
if _, err := program.Run(); err != nil {
fmt.Fprintf(output, "Error displaying output: %s\n", err)
}
}()
return &Spinner{
program: program,
}
}
func (s *Spinner) Update(message string, args ...any) {
message = fmt.Sprintf(message, args...)
if s.program != nil {
s.program.Send(updateMsg(message))
} else if message != s.model.message {
s.model.message = message
s.model.incFrame()
s.println()
}
}
func (s *Spinner) Stop() {
if s.program == nil {
return
}
s.program.Quit()
s.program.Wait()
}
func (s *Spinner) println() {
fmt.Fprintln(s.output, s.model.View())
}
func isTerminal(w io.Writer) bool {
if f, ok := w.(*os.File); ok {
return term.IsTerminal(int(f.Fd()))
}
return false
}
// Message types for the bubbletea model
type tickMsg struct{}
type updateMsg string
// spinnerModel is the bubbletea model for the spinner
type spinnerModel struct {
message string
frame int
}
func (m spinnerModel) Init() tea.Cmd {
return m.tick()
}
func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tickMsg:
m.incFrame()
return m, m.tick()
case updateMsg:
m.message = string(msg)
}
return m, nil
}
func (m spinnerModel) View() string {
return fmt.Sprintf("%s %s", spinnerFrames[m.frame], m.message)
}
func (m *spinnerModel) incFrame() {
m.frame = (m.frame + 1) % len(spinnerFrames)
}
func (m *spinnerModel) tick() tea.Cmd {
return tea.Tick(100*time.Millisecond, func(time.Time) tea.Msg {
return tickMsg{}
})
}