fix(tui): disable progress bar for unsupported terminals (#1329)

Ayman Bagabas created

Change summary

internal/cmd/root.go | 23 ++++++++++++++++++++++-
internal/tui/tui.go  | 28 ++++++++++++++++++++++++++--
2 files changed, 48 insertions(+), 3 deletions(-)

Detailed changes

internal/cmd/root.go 🔗

@@ -10,6 +10,7 @@ import (
 	"os"
 	"path/filepath"
 	"strconv"
+	"strings"
 
 	tea "github.com/charmbracelet/bubbletea/v2"
 	"github.com/charmbracelet/colorprofile"
@@ -21,6 +22,7 @@ import (
 	"github.com/charmbracelet/crush/internal/version"
 	"github.com/charmbracelet/fang"
 	"github.com/charmbracelet/lipgloss/v2"
+	uv "github.com/charmbracelet/ultraviolet"
 	"github.com/charmbracelet/x/exp/charmtone"
 	"github.com/charmbracelet/x/term"
 	"github.com/spf13/cobra"
@@ -81,8 +83,13 @@ crush -y
 		event.AppInitialized()
 
 		// Set up the TUI.
+		var env uv.Environ = os.Environ()
+		ui := tui.New(app)
+		ui.QueryVersion = shouldQueryTerminalVersion(env)
+
 		program := tea.NewProgram(
-			tui.New(app),
+			ui,
+			tea.WithEnvironment(env),
 			tea.WithContext(cmd.Context()),
 			tea.WithFilter(tui.MouseEventFilter)) // Filter mouse events based on focus state
 
@@ -250,3 +257,17 @@ func createDotCrushDir(dir string) error {
 
 	return nil
 }
+
+func shouldQueryTerminalVersion(env uv.Environ) bool {
+	termType := env.Getenv("TERM")
+	termProg, okTermProg := env.LookupEnv("TERM_PROGRAM")
+	_, okSSHTTY := env.LookupEnv("SSH_TTY")
+	return (!okTermProg && !okSSHTTY) ||
+		(!strings.Contains(termProg, "Apple") && !okSSHTTY) ||
+		// Terminals that do support XTVERSION.
+		strings.Contains(termType, "ghostty") ||
+		strings.Contains(termType, "wezterm") ||
+		strings.Contains(termType, "alacritty") ||
+		strings.Contains(termType, "kitty") ||
+		strings.Contains(termType, "rio")
+}

internal/tui/tui.go 🔗

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"math/rand"
+	"slices"
 	"strings"
 	"time"
 
@@ -72,6 +73,14 @@ type appModel struct {
 
 	// Chat Page Specific
 	selectedSessionID string // The ID of the currently selected session
+
+	// sendProgressBar instructs the TUI to send progress bar updates to the
+	// terminal.
+	sendProgressBar bool
+
+	// QueryVersion instructs the TUI to query for the terminal version when it
+	// starts.
+	QueryVersion bool
 }
 
 // Init initializes the application model and returns initial commands.
@@ -88,6 +97,9 @@ func (a appModel) Init() tea.Cmd {
 
 	cmd = a.status.Init()
 	cmds = append(cmds, cmd)
+	if a.QueryVersion {
+		cmds = append(cmds, tea.RequestTerminalVersion)
+	}
 
 	return tea.Batch(cmds...)
 }
@@ -99,6 +111,18 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	a.isConfigured = config.HasInitialDataConfig()
 
 	switch msg := msg.(type) {
+	case tea.EnvMsg:
+		// Is this Windows Terminal?
+		if !a.sendProgressBar {
+			a.sendProgressBar = slices.Contains(msg, "WT_SESSION")
+		}
+	case tea.TerminalVersionMsg:
+		termVersion := strings.ToLower(string(msg))
+		// Only enable progress bar for the following terminals.
+		if !a.sendProgressBar {
+			a.sendProgressBar = strings.Contains(termVersion, "ghostty")
+		}
+		return a, nil
 	case tea.KeyboardEnhancementsMsg:
 		for id, page := range a.pages {
 			m, pageCmd := page.Update(msg)
@@ -555,7 +579,7 @@ func (a *appModel) View() tea.View {
 	view.MouseMode = tea.MouseModeCellMotion
 	view.AltScreen = true
 
-	if a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
+	if a.sendProgressBar && a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
 		// HACK: use a random percentage to prevent ghostty from hiding it
 		// after a timeout.
 		view.ProgressBar = tea.NewProgressBar(tea.ProgressBarIndeterminate, rand.Intn(100))
@@ -564,7 +588,7 @@ func (a *appModel) View() tea.View {
 }
 
 // New creates and initializes a new TUI application model.
-func New(app *app.App) tea.Model {
+func New(app *app.App) *appModel {
 	chatPage := chat.New(app)
 	keyMap := DefaultKeyMap()
 	keyMap.pageBindings = chatPage.Bindings()