diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 005f2e86f7012b265fb619580c7cc2eec2e4de03..bc7667208082070604c2416acd42e217a0d40909 100644 --- a/internal/cmd/root.go +++ b/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") +} diff --git a/internal/tui/tui.go b/internal/tui/tui.go index c53e3395285ad321a6e2fd1dc7b6ff7ed7d39edc..6377bf92fa07f0b488b64901ef985fe6ce34018a 100644 --- a/internal/tui/tui.go +++ b/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()