Allow to change the message for the spinner (#1973)

This commit is contained in:
Chris Suszynski 2024-10-28 12:12:48 +01:00 committed by GitHub
parent b3b6b86030
commit 117a365d93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 46 additions and 6 deletions

View File

@ -33,6 +33,7 @@ import (
) )
func TestProgress(t *testing.T) { func TestProgress(t *testing.T) {
t.Parallel()
ctx := context.TestContext(t) ctx := context.TestContext(t)
prt := output.NewTestPrinter() prt := output.NewTestPrinter()
ctx = output.WithContext(ctx, prt) ctx = output.WithContext(ctx, prt)

View File

@ -30,7 +30,7 @@ import (
const spinnerColor = lipgloss.Color("205") const spinnerColor = lipgloss.Color("205")
type Spinner interface { type Spinner interface {
Runnable[Spinner] Runnable[SpinnerControl]
} }
func (w *widgets) NewSpinner(message string) Spinner { func (w *widgets) NewSpinner(message string) Spinner {
@ -44,17 +44,27 @@ type BubbleSpinner struct {
output.InputOutput output.InputOutput
Message Message
*updater
spin spinner.Model spin spinner.Model
tea *tea.Program tea *tea.Program
quitChan chan struct{} quitChan chan struct{}
teaErr error teaErr error
} }
func (b *BubbleSpinner) With(fn func(Spinner) error) error { // SpinnerControl allows one to control the spinner, for example, to change the
// message.
type SpinnerControl interface {
UpdateMessage(message string)
}
// With will start the spinner and perform long operation within the
// provided fn. The spinner will be automatically shutdown when the provided
// function exits.
func (b *BubbleSpinner) With(fn func(SpinnerControl) error) error {
b.start() b.start()
err := func() error { err := func() error {
defer b.stop() defer b.stop()
return fn(b) return fn(b.updater)
}() }()
return multierr.Combine(err, b.teaErr) return multierr.Combine(err, b.teaErr)
} }
@ -70,10 +80,20 @@ func (b *BubbleSpinner) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
func (b *BubbleSpinner) View() string { func (b *BubbleSpinner) View() string {
select {
case m := <-b.updater.messages:
// nil on channel close
if m != nil {
b.Message.Text = *m
}
default:
// nothing
}
return fmt.Sprintf("%s %s", b.Message.Text, b.spin.View()) return fmt.Sprintf("%s %s", b.Message.Text, b.spin.View())
} }
func (b *BubbleSpinner) start() { func (b *BubbleSpinner) start() {
b.updater = &updater{make(chan *string)}
b.spin = spinner.New( b.spin = spinner.New(
spinner.WithSpinner(spinner.Meter), spinner.WithSpinner(spinner.Meter),
spinner.WithStyle(spinnerStyle()), spinner.WithStyle(spinnerStyle()),
@ -94,6 +114,7 @@ func (b *BubbleSpinner) stop() {
return return
} }
close(b.updater.messages)
b.tea.Quit() b.tea.Quit()
<-b.quitChan <-b.quitChan
@ -111,3 +132,11 @@ func (b *BubbleSpinner) stop() {
func spinnerStyle() lipgloss.Style { func spinnerStyle() lipgloss.Style {
return lipgloss.NewStyle().Foreground(spinnerColor) return lipgloss.NewStyle().Foreground(spinnerColor)
} }
type updater struct {
messages chan *string
}
func (u updater) UpdateMessage(message string) {
u.messages <- &message
}

View File

@ -26,6 +26,7 @@ import (
) )
func TestSpinner(t *testing.T) { func TestSpinner(t *testing.T) {
t.Parallel()
ctx := context.TestContext(t) ctx := context.TestContext(t)
prt := output.NewTestPrinter() prt := output.NewTestPrinter()
ctx = output.WithContext(ctx, prt) ctx = output.WithContext(ctx, prt)
@ -35,14 +36,21 @@ func TestSpinner(t *testing.T) {
if s == nil { if s == nil {
t.Errorf("want spinner, got nil") t.Errorf("want spinner, got nil")
} }
if err := s.With(func(spinner tui.Spinner) error { if err := s.With(func(sc tui.SpinnerControl) error {
time.Sleep(20 * time.Millisecond) time.Sleep(3 * time.Millisecond)
sc.UpdateMessage("msg-1")
time.Sleep(3 * time.Millisecond)
sc.UpdateMessage("msg-2")
time.Sleep(3 * time.Millisecond)
return nil return nil
}); err != nil { }); err != nil {
t.Errorf("want nil, got %v", err) t.Errorf("want nil, got %v", err)
} }
got := prt.Outputs().Out.String() got := prt.Outputs().Out.String()
want := "\x1b[?25lmessage ▰▱▱\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006lmessage Done\n" want := "\x1b[?25lmessage ▰▱▱\x1b[0D" +
"\x1b[0D\x1b[2Kmsg-1 ▰▰▱\x1b[0D" +
"\x1b[0D\x1b[2Kmsg-2 ▰▰▰\x1b[0D" +
"\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006lmsg-2 Done\n"
if got != want { if got != want {
t.Errorf("text missmatch\nwant %q,\n got %q", want, got) t.Errorf("text missmatch\nwant %q,\n got %q", want, got)
} }

View File

@ -24,6 +24,7 @@ import (
) )
func TestNewWidgets(t *testing.T) { func TestNewWidgets(t *testing.T) {
t.Parallel()
ctx := context.TestContext(t) ctx := context.TestContext(t)
w := tui.NewWidgets(ctx) w := tui.NewWidgets(ctx)
@ -33,6 +34,7 @@ func TestNewWidgets(t *testing.T) {
} }
func TestNewInteractiveWidgets(t *testing.T) { func TestNewInteractiveWidgets(t *testing.T) {
t.Parallel()
ctx := context.TestContext(t) ctx := context.TestContext(t)
w, err := tui.NewInteractiveWidgets(ctx) w, err := tui.NewInteractiveWidgets(ctx)