on main help sign in
feat: show progress bar on boot for feedback (#1371)

Co-authored-by: Ayman Bagabas <ayman.bagabas@gmail.com>

commit0182d943911ceba6fef0c7444d3e6acdf5b2ef50 authorAndrey Nering <andreynering@users.noreply.github.com> dateMon 3 Nov 2025, 18:22 (1 year ago) parent449de92
3 files changed+37−7
internal/app/app.go+10−4 internal/cmd/root.go+12−3 internal/term/term.go+15
internal/app/app.go+10−4
@@ -28,6 +28,7 @@ import (
2828 "github.com/charmbracelet/crush/internal/permission"
2929 "github.com/charmbracelet/crush/internal/pubsub"
3030 "github.com/charmbracelet/crush/internal/session"
31+ "github.com/charmbracelet/crush/internal/term"
3132 "github.com/charmbracelet/crush/internal/tui/components/anim"
3233 "github.com/charmbracelet/crush/internal/tui/styles"
3334 "github.com/charmbracelet/lipgloss/v2"
@@ -191,9 +192,12 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt
191192
192193 messageEvents := app.Messages.Subscribe(ctx)
193194 messageReadBytes := make(map[string]int)
195+ supportsProgressBar := term.SupportsProgressBar()
194196
195197 defer func() {
196− _, _ = fmt.Printf(ansi.ResetProgressBar)
198+ if supportsProgressBar {
199+ _, _ = fmt.Fprintf(os.Stderr, ansi.ResetProgressBar)
200+ }
197201
198202 // Always print a newline at the end. If output is a TTY this will
199203 // prevent the prompt from overwriting the last line of output.
@@ -201,9 +205,11 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt
201205 }()
202206
203207 for {
204− // HACK: Reinitialize the terminal progress bar on every iteration so
205− // it doesn't get hidden by the terminal due to inactivity.
206− _, _ = fmt.Printf(ansi.SetIndeterminateProgressBar)
208+ if supportsProgressBar {
209+ // HACK: Reinitialize the terminal progress bar on every iteration so
210+ // it doesn't get hidden by the terminal due to inactivity.
211+ _, _ = fmt.Fprintf(os.Stderr, ansi.SetIndeterminateProgressBar)
212+ }
207213
208214 select {
209215 case result := <-done:
internal/cmd/root.go+12−3
@@ -18,11 +18,13 @@ import (
1818 "github.com/charmbracelet/crush/internal/config"
1919 "github.com/charmbracelet/crush/internal/db"
2020 "github.com/charmbracelet/crush/internal/event"
21+ termutil "github.com/charmbracelet/crush/internal/term"
2122 "github.com/charmbracelet/crush/internal/tui"
2223 "github.com/charmbracelet/crush/internal/version"
2324 "github.com/charmbracelet/fang"
2425 "github.com/charmbracelet/lipgloss/v2"
2526 uv "github.com/charmbracelet/ultraviolet"
27+ "github.com/charmbracelet/x/ansi"
2628 "github.com/charmbracelet/x/exp/charmtone"
2729 "github.com/charmbracelet/x/term"
2830 "github.com/spf13/cobra"
@@ -32,7 +34,6 @@ func init() {
3234 rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
3335 rootCmd.PersistentFlags().StringP("data-dir", "D", "", "Custom crush data directory")
3436 rootCmd.PersistentFlags().BoolP("debug", "d", false, "Debug")
35−
3637 rootCmd.Flags().BoolP("help", "h", false, "Help")
3738 rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)")
3839
@@ -74,7 +75,7 @@ crush run "Explain the use of context in Go"
7475crush -y
7576 `,
7677 RunE: func(cmd *cobra.Command, args []string) error {
77− app, err := setupApp(cmd)
78+ app, err := setupAppWithProgressBar(cmd)
7879 if err != nil {
7980 return err
8081 }
@@ -92,7 +93,6 @@ crush -y
9293 tea.WithEnvironment(env),
9394 tea.WithContext(cmd.Context()),
9495 tea.WithFilter(tui.MouseEventFilter)) // Filter mouse events based on focus state
95−
9696 go app.Subscribe(program)
9797
9898 if _, err := program.Run(); err != nil {
@@ -150,6 +150,15 @@ func Execute() {
150150 }
151151}
152152
153+func setupAppWithProgressBar(cmd *cobra.Command) (*app.App, error) {
154+ if termutil.SupportsProgressBar() {
155+ _, _ = fmt.Fprintf(os.Stderr, ansi.SetIndeterminateProgressBar)
156+ defer func() { _, _ = fmt.Fprintf(os.Stderr, ansi.ResetProgressBar) }()
157+ }
158+
159+ return setupApp(cmd)
160+}
161+
153162// setupApp handles the common setup logic for both interactive and non-interactive modes.
154163// It returns the app instance, config, cleanup function, and any error.
155164func setupApp(cmd *cobra.Command) (*app.App, error) {
internal/term/term.gonew file+15
@@ -0,0 +1,15 @@
1+package term
2+
3+import (
4+ "os"
5+ "strings"
6+)
7+
8+// SupportsProgressBar tries to determine whether the current terminal supports
9+// progress bars by looking into environment variables.
10+func SupportsProgressBar() bool {
11+ termProg := os.Getenv("TERM_PROGRAM")
12+ _, isWindowsTerminal := os.LookupEnv("WT_SESSION")
13+
14+ return isWindowsTerminal || strings.Contains(strings.ToLower(termProg), "ghostty")
15+}