diff --git a/cmd/new.go b/cmd/new.go index 51aef07..a4fdf4a 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -5,6 +5,7 @@ import ( "os" "github.com/javoire/stackinator/internal/git" + "github.com/javoire/stackinator/internal/spinner" "github.com/javoire/stackinator/internal/stack" "github.com/javoire/stackinator/internal/ui" "github.com/spf13/cobra" @@ -78,21 +79,25 @@ func runNew(gitClient git.GitClient, branchName string, explicitParent string) e } } - fmt.Printf("Creating new branch %s from %s\n", ui.Branch(branchName), ui.Branch(parent)) - - // Create the new branch - if err := gitClient.CreateBranchAndCheckout(branchName, parent); err != nil { - return fmt.Errorf("failed to create branch: %w", err) - } - - // Set parent in git config - configKey := fmt.Sprintf("branch.%s.stackparent", branchName) - if err := gitClient.SetConfig(configKey, parent); err != nil { - return fmt.Errorf("failed to set parent config: %w", err) + // Create the new branch and set parent config + if err := spinner.WrapWithSuccess( + fmt.Sprintf("Creating branch %s from %s...", branchName, parent), + fmt.Sprintf("Created branch %s from %s", ui.Branch(branchName), ui.Branch(parent)), + func() error { + if err := gitClient.CreateBranchAndCheckout(branchName, parent); err != nil { + return fmt.Errorf("failed to create branch: %w", err) + } + configKey := fmt.Sprintf("branch.%s.stackparent", branchName) + if err := gitClient.SetConfig(configKey, parent); err != nil { + return fmt.Errorf("failed to set parent config: %w", err) + } + return nil + }, + ); err != nil { + return err } if !dryRun { - fmt.Println(ui.Success(fmt.Sprintf("Created branch %s with parent %s", ui.Branch(branchName), ui.Branch(parent)))) fmt.Println() // Show the local stack (fast, no PR fetching) diff --git a/cmd/sync.go b/cmd/sync.go index a75dde5..d943b84 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -414,7 +414,7 @@ func runSync(gitClient git.GitClient, githubClient github.GitHubClient) error { } // Wait for parallel network operations to complete - if err := spinner.WrapWithSuccess("Fetching from origin and loading PRs...", "✓ Fetched from origin and loaded PRs", func() error { + if err := spinner.WrapWithSuccess("Fetching from origin and loading PRs...", "Fetched from origin and loaded PRs", func() error { wg.Wait() return nil }); err != nil { diff --git a/internal/spinner/spinner.go b/internal/spinner/spinner.go index ac2bf42..10d0bc6 100644 --- a/internal/spinner/spinner.go +++ b/internal/spinner/spinner.go @@ -16,6 +16,7 @@ var Enabled = true // Spinner represents a loading spinner type Spinner struct { message string + indent string frames []string interval time.Duration writer io.Writer @@ -32,6 +33,20 @@ var defaultFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", " func New(message string) *Spinner { return &Spinner{ message: message, + indent: "", + frames: defaultFrames, + interval: 80 * time.Millisecond, + writer: os.Stdout, + stopChan: make(chan struct{}), + hideWhenDone: false, + } +} + +// NewWithIndent creates a new spinner with indent before the spinner frame +func NewWithIndent(indent, message string) *Spinner { + return &Spinner{ + message: message, + indent: indent, frames: defaultFrames, interval: 80 * time.Millisecond, writer: os.Stdout, @@ -110,7 +125,7 @@ func (s *Spinner) run() { case <-ticker.C: s.mu.Lock() frame := s.frames[frameIdx%len(s.frames)] - fmt.Fprintf(s.writer, "\r%s %s", frame, dim.Sprint(s.message)) + fmt.Fprintf(s.writer, "\r%s%s %s", s.indent, frame, dim.Sprint(s.message)) s.mu.Unlock() frameIdx++ } @@ -168,7 +183,7 @@ func WrapWithSuccessIndented(indent, message, successMessage string, fn func() e } return err } - sp := New(indent + message).Start() + sp := NewWithIndent(indent, message).Start() err := fn() if err != nil { sp.Stop(fmt.Sprintf("%s%s %s: %v", indent, red.Sprint("✗"), message, err))