Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spinner): improve action #292

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 deletions examples/burger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type Burger struct {

func main() {
var burger Burger
var order = Order{Burger: burger}
order := Order{Burger: burger}

// Should we run in accessible mode?
accessible, _ := strconv.ParseBool(os.Getenv("ACCESSIBLE"))
Expand Down Expand Up @@ -152,14 +152,14 @@ func main() {
).WithAccessible(accessible)

err := form.Run()

if err != nil {
fmt.Println("Uh oh:", err)
os.Exit(1)
}

prepareBurger := func() {
prepareBurger := func() error {
time.Sleep(2 * time.Second)
return nil
}

_ = spinner.New().Title("Preparing your burger...").Accessible(accessible).Action(prepareBurger).Run()
Expand Down
29 changes: 29 additions & 0 deletions spinner/examples/context-and-action/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"context"
"fmt"
"log"
"math/rand"
"time"

"github.com/charmbracelet/huh/spinner"
)

func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

err := spinner.New().
Context(ctx).
Action(func() error {
time.Sleep(time.Minute)
return nil
}).
Accessible(rand.Int()%2 == 0).
Run()
if err != nil {
log.Fatalln(err)
}
fmt.Println("Done!")
}
10 changes: 7 additions & 3 deletions spinner/examples/loading/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import (
)

func main() {
action := func() {
time.Sleep(2 * time.Second)
action := func() error {
caarlos0 marked this conversation as resolved.
Show resolved Hide resolved
time.Sleep(1 * time.Second)
return nil
}
if err := spinner.New().Title("Preparing your burger...").Action(action).Run(); err != nil {
fmt.Println("Failed:", err)
return
}
_ = spinner.New().Title("Preparing your burger...").Action(action).Run()
fmt.Println("Order up!")
}
75 changes: 41 additions & 34 deletions spinner/spinner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"os"
"strings"
"time"

"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
Expand All @@ -23,12 +22,14 @@ import (
// ⣾ Loading...
type Spinner struct {
spinner spinner.Model
action func()
action func() error
caarlos0 marked this conversation as resolved.
Show resolved Hide resolved
ctx context.Context
accessible bool
output *termenv.Output
title string
titleStyle lipgloss.Style

err error
}

type Type spinner.Spinner
Expand Down Expand Up @@ -61,7 +62,7 @@ func (s *Spinner) Title(title string) *Spinner {
}

// Action sets the action of the spinner.
func (s *Spinner) Action(action func()) *Spinner {
func (s *Spinner) Action(action func() error) *Spinner {
caarlos0 marked this conversation as resolved.
Show resolved Hide resolved
s.action = action
return s
}
Expand Down Expand Up @@ -98,24 +99,29 @@ func New() *Spinner {
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#F780E2"))

return &Spinner{
action: func() { time.Sleep(time.Second) },
spinner: s,
title: "Loading...",
titleStyle: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"}),
output: termenv.NewOutput(os.Stdout),
ctx: nil,
}
}

// Init initializes the spinner.
func (s *Spinner) Init() tea.Cmd {
return s.spinner.Tick
return tea.Batch(s.spinner.Tick, func() tea.Msg {
if s.action != nil {
return doneMsg{err: s.action()}
}
return nil
})
}

// Update updates the spinner.
func (s *Spinner) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case spinner.TickMsg:
case doneMsg:
s.err = msg.err
return s, tea.Quit
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c":
Expand All @@ -139,34 +145,31 @@ func (s *Spinner) View() string {

// Run runs the spinner.
func (s *Spinner) Run() error {
if s.accessible {
return s.runAccessible()
}

hasCtx := s.ctx != nil
hasCtxErr := hasCtx && s.ctx.Err() != nil

if hasCtxErr {
if hasCtx && s.ctx.Err() != nil {
if errors.Is(s.ctx.Err(), context.Canceled) {
return nil
}
return s.ctx.Err()
}

p := tea.NewProgram(s, tea.WithContext(s.ctx), tea.WithOutput(os.Stderr))
if s.ctx == nil {
go func() {
s.action()
p.Quit()
}()
// sets a dummy action if the spinner does not have a context nor an action.
if !hasCtx && s.action == nil {
// there's nothing to do!
return nil
}

_, err := p.Run()
if errors.Is(err, tea.ErrProgramKilled) {
return nil
} else {
return err
if s.accessible {
return s.runAccessible()
}

m, err := tea.NewProgram(s, tea.WithContext(s.ctx), tea.WithOutput(os.Stderr)).Run()
mm := m.(*Spinner)
if mm.err != nil {
return mm.err
}
return err
}

// runAccessible runs the spinner in an accessible mode (statically).
Expand All @@ -177,29 +180,33 @@ func (s *Spinner) runAccessible() error {
fmt.Println(title + frame)

if s.ctx == nil {
s.action()
err := s.action()
s.output.ShowCursor()
s.output.CursorBack(len(frame) + len(title))
return nil
return err
}

actionDone := make(chan struct{})

go func() {
s.action()
actionDone <- struct{}{}
}()
actionDone := make(chan error)
if s.action != nil {
go func() {
actionDone <- s.action()
}()
}

for {
select {
case <-s.ctx.Done():
s.output.ShowCursor()
s.output.CursorBack(len(frame) + len(title))
return s.ctx.Err()
case <-actionDone:
case err := <-actionDone:
s.output.ShowCursor()
s.output.CursorBack(len(frame) + len(title))
return nil
return err
}
}
}

type doneMsg struct {
err error
}
Loading