on main help sign in
fix(tui): disable progress bar for unsupported terminals (#1329)
05577deAyman Bagabas
internal/cmd/root.go+22−1
@@ -10,6 +10,7 @@ import (
10 "os"
10 "os"
11 "path/filepath"
11 "path/filepath"
12 "strconv"
12 "strconv"
13+ "strings"
13
14
14 tea "github.com/charmbracelet/bubbletea/v2"
15 tea "github.com/charmbracelet/bubbletea/v2"
15 "github.com/charmbracelet/colorprofile"
16 "github.com/charmbracelet/colorprofile"
@@ -21,6 +22,7 @@ import (
21 "github.com/charmbracelet/crush/internal/version"
22 "github.com/charmbracelet/crush/internal/version"
22 "github.com/charmbracelet/fang"
23 "github.com/charmbracelet/fang"
23 "github.com/charmbracelet/lipgloss/v2"
24 "github.com/charmbracelet/lipgloss/v2"
25+ uv "github.com/charmbracelet/ultraviolet"
24 "github.com/charmbracelet/x/exp/charmtone"
26 "github.com/charmbracelet/x/exp/charmtone"
25 "github.com/charmbracelet/x/term"
27 "github.com/charmbracelet/x/term"
26 "github.com/spf13/cobra"
28 "github.com/spf13/cobra"
@@ -81,8 +83,13 @@ crush -y
81 event.AppInitialized()
83 event.AppInitialized()
82
84
83 // Set up the TUI.
85 // Set up the TUI.
86+ var env uv.Environ = os.Environ()
87+ ui := tui.New(app)
88+ ui.QueryVersion = shouldQueryTerminalVersion(env)
89+
84 program := tea.NewProgram(
90 program := tea.NewProgram(
85− tui.New(app),
91+ ui,
92+ tea.WithEnvironment(env),
86 tea.WithContext(cmd.Context()),
93 tea.WithContext(cmd.Context()),
87 tea.WithFilter(tui.MouseEventFilter)) // Filter mouse events based on focus state
94 tea.WithFilter(tui.MouseEventFilter)) // Filter mouse events based on focus state
88
95
@@ -250,3 +257,17 @@ func createDotCrushDir(dir string) error {
250
257
251 return nil
258 return nil
252}
259}
260+
261+func shouldQueryTerminalVersion(env uv.Environ) bool {
262+ termType := env.Getenv("TERM")
263+ termProg, okTermProg := env.LookupEnv("TERM_PROGRAM")
264+ _, okSSHTTY := env.LookupEnv("SSH_TTY")
265+ return (!okTermProg && !okSSHTTY) ||
266+ (!strings.Contains(termProg, "Apple") && !okSSHTTY) ||
267+ // Terminals that do support XTVERSION.
268+ strings.Contains(termType, "ghostty") ||
269+ strings.Contains(termType, "wezterm") ||
270+ strings.Contains(termType, "alacritty") ||
271+ strings.Contains(termType, "kitty") ||
272+ strings.Contains(termType, "rio")
273+}
internal/tui/tui.go+26−2
@@ -4,6 +4,7 @@ import (
4 "context"
4 "context"
5 "fmt"
5 "fmt"
6 "math/rand"
6 "math/rand"
7+ "slices"
7 "strings"
8 "strings"
8 "time"
9 "time"
9
10
@@ -72,6 +73,14 @@ type appModel struct {
72
73
73 // Chat Page Specific
74 // Chat Page Specific
74 selectedSessionID string // The ID of the currently selected session
75 selectedSessionID string // The ID of the currently selected session
76+
77+ // sendProgressBar instructs the TUI to send progress bar updates to the
78+ // terminal.
79+ sendProgressBar bool
80+
81+ // QueryVersion instructs the TUI to query for the terminal version when it
82+ // starts.
83+ QueryVersion bool
75}
84}
76
85
77// Init initializes the application model and returns initial commands.
86// Init initializes the application model and returns initial commands.
@@ -88,6 +97,9 @@ func (a appModel) Init() tea.Cmd {
88
97
89 cmd = a.status.Init()
98 cmd = a.status.Init()
90 cmds = append(cmds, cmd)
99 cmds = append(cmds, cmd)
100+ if a.QueryVersion {
101+ cmds = append(cmds, tea.RequestTerminalVersion)
102+ }
91
103
92 return tea.Batch(cmds...)
104 return tea.Batch(cmds...)
93}
105}
@@ -99,6 +111,18 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
99 a.isConfigured = config.HasInitialDataConfig()
111 a.isConfigured = config.HasInitialDataConfig()
100
112
101 switch msg := msg.(type) {
113 switch msg := msg.(type) {
114+ case tea.EnvMsg:
115+ // Is this Windows Terminal?
116+ if !a.sendProgressBar {
117+ a.sendProgressBar = slices.Contains(msg, "WT_SESSION")
118+ }
119+ case tea.TerminalVersionMsg:
120+ termVersion := strings.ToLower(string(msg))
121+ // Only enable progress bar for the following terminals.
122+ if !a.sendProgressBar {
123+ a.sendProgressBar = strings.Contains(termVersion, "ghostty")
124+ }
125+ return a, nil
102 case tea.KeyboardEnhancementsMsg:
126 case tea.KeyboardEnhancementsMsg:
103 for id, page := range a.pages {
127 for id, page := range a.pages {
104 m, pageCmd := page.Update(msg)
128 m, pageCmd := page.Update(msg)
@@ -555,7 +579,7 @@ func (a *appModel) View() tea.View {
555 view.MouseMode = tea.MouseModeCellMotion
579 view.MouseMode = tea.MouseModeCellMotion
556 view.AltScreen = true
580 view.AltScreen = true
557
581
558− if a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
582+ if a.sendProgressBar && a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
559 // HACK: use a random percentage to prevent ghostty from hiding it
583 // HACK: use a random percentage to prevent ghostty from hiding it
560 // after a timeout.
584 // after a timeout.
561 view.ProgressBar = tea.NewProgressBar(tea.ProgressBarIndeterminate, rand.Intn(100))
585 view.ProgressBar = tea.NewProgressBar(tea.ProgressBarIndeterminate, rand.Intn(100))
@@ -564,7 +588,7 @@ func (a *appModel) View() tea.View {
564}
588}
565
589
566// New creates and initializes a new TUI application model.
590// New creates and initializes a new TUI application model.
567−func New(app *app.App) tea.Model {
591+func New(app *app.App) *appModel {
568 chatPage := chat.New(app)
592 chatPage := chat.New(app)
569 keyMap := DefaultKeyMap()
593 keyMap := DefaultKeyMap()
570 keyMap.pageBindings = chatPage.Bindings()
594 keyMap.pageBindings = chatPage.Bindings()