Skip to content

Commit 462678c

Browse files
Cleanup
1 parent e2b792e commit 462678c

File tree

1 file changed

+47
-26
lines changed

1 file changed

+47
-26
lines changed

internal/tiger/cmd/spinner.go

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,43 @@ import (
1414
var spinnerFrames = []string{"⢎ ", "⠎⠁", "⠊⠑", "⠈⠱", " ⡱", "⢀⡰", "⢄⡠", "⢆⡀"}
1515

1616
type Spinner struct {
17-
// Populated when output is a TTY
17+
// Populated when output is a terminal
1818
program *tea.Program
1919

20-
// Populated when output is not a TTY
20+
// Populated when output is not a terminal
2121
output io.Writer
2222
model *spinnerModel
2323
}
2424

25+
// NewSpinner creates and returns a new [Spinner] for displaying animated
26+
// status messages. If output is a terminal, it uses bubbletea to dynamically
27+
// update the spinner in place. If output is not a terminal, it prints each
28+
// message on a new line without animation. The message parameter supports
29+
// fmt.Sprintf-style formatting with optional args.
2530
func NewSpinner(output io.Writer, message string, args ...any) *Spinner {
26-
model := spinnerModel{
27-
message: fmt.Sprintf(message, args...),
31+
if isTerminal(output) {
32+
return newAnimatedSpinner(output, message, args...)
2833
}
34+
return newManualSpinner(output, message, args...)
35+
}
2936

30-
// If output is not a TTY, print each message on a new line
31-
if !isTerminal(output) {
32-
s := &Spinner{
33-
output: output,
34-
model: &model,
35-
}
36-
s.println()
37-
return s
37+
// isTerminal is a helper method for detecting whether an [io.Writer] is a
38+
// terminal.
39+
func isTerminal(w io.Writer) bool {
40+
if f, ok := w.(*os.File); ok {
41+
return term.IsTerminal(int(f.Fd()))
3842
}
43+
return false
44+
}
3945

40-
// If output is a TTY, use bubbletea to dynamically update the message
46+
func newAnimatedSpinner(output io.Writer, message string, args ...any) *Spinner {
4147
program := tea.NewProgram(
42-
model,
48+
spinnerModel{
49+
message: fmt.Sprintf(message, args...),
50+
},
4351
tea.WithOutput(output),
4452
)
4553

46-
// Start the program in a goroutine
4754
go func() {
4855
if _, err := program.Run(); err != nil {
4956
fmt.Fprintf(output, "Error displaying output: %s\n", err)
@@ -55,6 +62,20 @@ func NewSpinner(output io.Writer, message string, args ...any) *Spinner {
5562
}
5663
}
5764

65+
func newManualSpinner(output io.Writer, message string, args ...any) *Spinner {
66+
s := &Spinner{
67+
output: output,
68+
model: &spinnerModel{
69+
message: fmt.Sprintf(message, args...),
70+
},
71+
}
72+
s.println()
73+
return s
74+
}
75+
76+
// Update changes the spinner's displayed message. If the output is a terminal,
77+
// the message is updated in place via bubbletea. Otherwise, the message is
78+
// printed on a new line if it differs from the previous one.
5879
func (s *Spinner) Update(message string, args ...any) {
5980
message = fmt.Sprintf(message, args...)
6081
if s.program != nil {
@@ -66,6 +87,8 @@ func (s *Spinner) Update(message string, args ...any) {
6687
}
6788
}
6889

90+
// Stop terminates the spinner program and waits for it to finish.
91+
// This method is a no-op if the output is not a terminal.
6992
func (s *Spinner) Stop() {
7093
if s.program == nil {
7194
return
@@ -75,22 +98,20 @@ func (s *Spinner) Stop() {
7598
s.program.Wait()
7699
}
77100

101+
// println prints the current state of the model to the configured output on a
102+
// new line. It is used when the output is not a terminal, and we therefore
103+
// don't want to write terminal control characters.
78104
func (s *Spinner) println() {
79105
fmt.Fprintln(s.output, s.model.View())
80106
}
81107

82-
func isTerminal(w io.Writer) bool {
83-
if f, ok := w.(*os.File); ok {
84-
return term.IsTerminal(int(f.Fd()))
85-
}
86-
return false
87-
}
88-
89-
// Message types for the bubbletea model
90-
type tickMsg struct{}
91-
type updateMsg string
108+
// Message types for the [tea.Model].
109+
type (
110+
tickMsg struct{}
111+
updateMsg string
112+
)
92113

93-
// spinnerModel is the bubbletea model for the spinner
114+
// spinnerModel is the [tea.Model] for the spinner.
94115
type spinnerModel struct {
95116
message string
96117
frame int

0 commit comments

Comments
 (0)