@@ -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:
@@ -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) {
@@ -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")
+}