Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 1 addition & 22 deletions cmd/root/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,7 @@ func runTUI(ctx context.Context, rt runtime.Runtime, sess *session.Session, spaw
wd, _ := os.Getwd()
model := tui.New(ctx, spawner, a, wd, cleanup)

programOpts := []tea.ProgramOption{tea.WithContext(ctx), tea.WithFilter(filter)}

// Terminal multiplexers can drop frames at the default 60 FPS, making
// spinners appear static. Cap the renderer at 30 FPS when detected.
if inMultiplexer() {
programOpts = append(programOpts, tea.WithFPS(30))
}

p := tea.NewProgram(model, programOpts...)
p := tea.NewProgram(model, tea.WithContext(ctx), tea.WithFilter(filter))
coalescer.SetSender(p.Send)

if m, ok := model.(interface{ SetProgram(p *tea.Program) }); ok {
Expand All @@ -126,16 +118,3 @@ func runTUI(ctx context.Context, rt runtime.Runtime, sess *session.Session, spaw
_, err := p.Run()
return err
}

// inMultiplexer reports whether the process is running inside a terminal
// multiplexer (tmux, screen).
func inMultiplexer() bool {
if os.Getenv("TMUX") != "" {
return true
}
if os.Getenv("STY") != "" {
return true
}
term := os.Getenv("TERM")
return strings.HasPrefix(term, "tmux") || strings.HasPrefix(term, "screen")
}
2 changes: 1 addition & 1 deletion pkg/tui/components/scrollbar/scrollbar.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func New() *Model {
return &Model{
width: Width,
trackChar: "⎪",
thumbChar: "",
thumbChar: "",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scrollbar thumb and track use identical character

The scrollbar thumb and track now both use the same character () after changing from thumbChar: "┃" to thumbChar: "⎪".

The thumb and track are now only distinguished by their styles:

  • Track: BorderSecondary foreground color
  • Thumb: Info foreground color with BackgroundAlt background and bold styling

Issue: If these colors are similar in a particular theme, or if the terminal doesn't render the bold/background styling clearly, the thumb will be difficult or impossible to see against the track since they use the same glyph.

The original (box drawings heavy vertical, U+2503) was a visually heavier/bolder character that provided clear visual distinction even with similar colors. The new (right vertical box line, U+23AA) is the same as the track character, reducing visual distinction to style-only.

Recommendation: Consider using a visually distinct character for the thumb (like the original or another heavier glyph) to ensure the scrollbar remains usable across different terminal themes and configurations.

}
}

Expand Down
33 changes: 1 addition & 32 deletions pkg/tui/components/spinner/spinner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package spinner

import (
"math/rand/v2"
"os"
"strings"

tea "charm.land/bubbletea/v2"
Expand Down Expand Up @@ -138,37 +137,7 @@ func (s *spinner) Stop() {
}

// spinnerFrames holds the animation frames for the current terminal.
// Braille characters are used by default; inside tmux they don't render
// correctly, so we fall back to ASCII.
var spinnerFrames = selectFrames(inMultiplexer())

// inMultiplexer reports whether the process is running inside a terminal
// multiplexer (tmux, screen). Detection checks multiple env vars because
// some of them may be stripped in containers or sudo sessions.
func inMultiplexer() bool {
if os.Getenv("TMUX") != "" {
return true
}
if os.Getenv("STY") != "" { // GNU screen
return true
}
term := os.Getenv("TERM")
return strings.HasPrefix(term, "tmux") || strings.HasPrefix(term, "screen")
}

var (
brailleFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
asciiFrames = []string{"|", "/", "-", "\\"}
)

// selectFrames returns ASCII spinner frames when inTmux is true,
// braille frames otherwise.
func selectFrames(inTmux bool) []string {
if inTmux {
return asciiFrames
}
return brailleFrames
}
var spinnerFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}

// Frame returns the spinner character for the given animation frame.
func Frame(index int) string {
Expand Down
32 changes: 0 additions & 32 deletions pkg/tui/components/spinner/spinner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,38 +28,6 @@ func TestFrameWrapsAround(t *testing.T) {
require.Equal(t, spinnerFrames[0], Frame(n), "Frame should wrap around")
}

func TestSelectFrames(t *testing.T) {
require.Equal(t, brailleFrames, selectFrames(false))
require.Equal(t, asciiFrames, selectFrames(true))
}

func TestInMultiplexer(t *testing.T) {
tests := []struct {
name string
env map[string]string
want bool
}{
{"plain terminal", map[string]string{"TERM": "xterm-256color"}, false},
{"TMUX set", map[string]string{"TMUX": "/tmp/tmux-1000/default,123,0"}, true},
{"STY set", map[string]string{"STY": "12345.pts-0"}, true},
{"TERM=tmux-256color", map[string]string{"TERM": "tmux-256color"}, true},
{"TERM=screen-256color", map[string]string{"TERM": "screen-256color"}, true},
{"TERM=screen", map[string]string{"TERM": "screen"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear all multiplexer env vars
t.Setenv("TMUX", "")
t.Setenv("STY", "")
t.Setenv("TERM", "")
for k, v := range tt.env {
t.Setenv(k, v)
}
require.Equal(t, tt.want, inMultiplexer())
})
}
}

func BenchmarkSpinner_ModeSpinnerOnly(b *testing.B) {
s := New(ModeSpinnerOnly, lipgloss.NewStyle())
for b.Loop() {
Expand Down
Loading