diff --git a/internal/app/app.go b/internal/app/app.go index fef0fa1639c8f2ded4176141aca04e93f520ca16..87e0a80186a8f837f47ad3a3cffe14e3112a825f 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -28,6 +28,7 @@ import ( "github.com/charmbracelet/crush/internal/permission" "github.com/charmbracelet/crush/internal/pubsub" "github.com/charmbracelet/crush/internal/session" + "github.com/charmbracelet/crush/internal/term" "github.com/charmbracelet/crush/internal/tui/components/anim" "github.com/charmbracelet/crush/internal/tui/styles" "github.com/charmbracelet/lipgloss/v2" @@ -191,9 +192,12 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt messageEvents := app.Messages.Subscribe(ctx) messageReadBytes := make(map[string]int) + supportsProgressBar := term.SupportsProgressBar() defer func() { - _, _ = fmt.Printf(ansi.ResetProgressBar) + if supportsProgressBar { + _, _ = fmt.Fprintf(os.Stderr, ansi.ResetProgressBar) + } // Always print a newline at the end. If output is a TTY this will // 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 }() for { - // HACK: Reinitialize the terminal progress bar on every iteration so - // it doesn't get hidden by the terminal due to inactivity. - _, _ = fmt.Printf(ansi.SetIndeterminateProgressBar) + if supportsProgressBar { + // HACK: Reinitialize the terminal progress bar on every iteration so + // it doesn't get hidden by the terminal due to inactivity. + _, _ = fmt.Fprintf(os.Stderr, ansi.SetIndeterminateProgressBar) + } select { case result := <-done: diff --git a/internal/cmd/root.go b/internal/cmd/root.go index bc7667208082070604c2416acd42e217a0d40909..0a1f10837b5e73ddbb51de08839a094fba5e0768 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -18,11 +18,13 @@ import ( "github.com/charmbracelet/crush/internal/config" "github.com/charmbracelet/crush/internal/db" "github.com/charmbracelet/crush/internal/event" + termutil "github.com/charmbracelet/crush/internal/term" "github.com/charmbracelet/crush/internal/tui" "github.com/charmbracelet/crush/internal/version" "github.com/charmbracelet/fang" "github.com/charmbracelet/lipgloss/v2" uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/exp/charmtone" "github.com/charmbracelet/x/term" "github.com/spf13/cobra" @@ -32,7 +34,6 @@ func init() { rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory") rootCmd.PersistentFlags().StringP("data-dir", "D", "", "Custom crush data directory") rootCmd.PersistentFlags().BoolP("debug", "d", false, "Debug") - rootCmd.Flags().BoolP("help", "h", false, "Help") rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)") @@ -74,7 +75,7 @@ crush run "Explain the use of context in Go" crush -y `, RunE: func(cmd *cobra.Command, args []string) error { - app, err := setupApp(cmd) + app, err := setupAppWithProgressBar(cmd) if err != nil { return err } @@ -92,7 +93,6 @@ crush -y tea.WithEnvironment(env), tea.WithContext(cmd.Context()), tea.WithFilter(tui.MouseEventFilter)) // Filter mouse events based on focus state - go app.Subscribe(program) if _, err := program.Run(); err != nil { @@ -150,6 +150,15 @@ func Execute() { } } +func setupAppWithProgressBar(cmd *cobra.Command) (*app.App, error) { + if termutil.SupportsProgressBar() { + _, _ = fmt.Fprintf(os.Stderr, ansi.SetIndeterminateProgressBar) + defer func() { _, _ = fmt.Fprintf(os.Stderr, ansi.ResetProgressBar) }() + } + + return setupApp(cmd) +} + // setupApp handles the common setup logic for both interactive and non-interactive modes. // It returns the app instance, config, cleanup function, and any error. func setupApp(cmd *cobra.Command) (*app.App, error) { diff --git a/internal/term/term.go b/internal/term/term.go new file mode 100644 index 0000000000000000000000000000000000000000..8f86ea241a226c6db389091ca0683cab7f0ac436 --- /dev/null +++ b/internal/term/term.go @@ -0,0 +1,15 @@ +package term + +import ( + "os" + "strings" +) + +// SupportsProgressBar tries to determine whether the current terminal supports +// progress bars by looking into environment variables. +func SupportsProgressBar() bool { + termProg := os.Getenv("TERM_PROGRAM") + _, isWindowsTerminal := os.LookupEnv("WT_SESSION") + + return isWindowsTerminal || strings.Contains(strings.ToLower(termProg), "ghostty") +}