diff --git a/go.sum b/go.sum index d4c1a58f5a5cc390ccd97203613af06ba9c96eb4..31a850af4bacf3f32312fba17157a3897a7d4fec 100644 --- a/go.sum +++ b/go.sum @@ -78,16 +78,10 @@ github.com/charlievieth/fastwalk v1.0.12 h1:pwfxe1LajixViQqo7EFLXU2+mQxb6OaO0CeN github.com/charlievieth/fastwalk v1.0.12/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250807144536-86f332629539 h1:ptsaiaVl07xdCCosnjs04J7qTymdV0GkQ42qf404dC0= github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250807144536-86f332629539/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw= -github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250813191918-4ea1703d4181 h1:JVO5KiuVuoyCxfUJC3xs+1hvz89S8ziPih5rEqgPWAs= -github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250813191918-4ea1703d4181/go.mod h1:rNVGP9g4DZiJprF5jr52Xtmq+DiLu0CPXl8nvddz/Y4= -github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250813201422-d4d69f63338d h1:My+32EIDUjemBI17YRTfK7W999nhESxX8/gZQq2IevE= -github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250813201422-d4d69f63338d/go.mod h1:rNVGP9g4DZiJprF5jr52Xtmq+DiLu0CPXl8nvddz/Y4= github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250813213544-5cc219db8892 h1:lqoYD2DrKhSdC9xCr59JMXtbbdR5/AZ6xfd/G8eOQJM= github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250813213544-5cc219db8892/go.mod h1:TUpoECaG4/3CwFx5lTlXNpR87Yo7gOwGqucnHGfAm20= github.com/charmbracelet/catwalk v0.4.6 h1:Y0JDq5V4agK8oO3lKC/hhInrYXePGwZPNo8I1Lk08jc= github.com/charmbracelet/catwalk v0.4.6/go.mod h1:WnKgNPmQHuMyk7GtwAQwl+ezHusfH40IvzML2qwUGwc= -github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= -github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0= github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= github.com/charmbracelet/fang v0.3.1-0.20250711140230-d5ebb8c1d674 h1:+Cz+VfxD5DO+JT1LlswXWhre0HYLj6l2HW8HVGfMuC0= @@ -98,8 +92,6 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250721205738-ea66aa652ee0/go.mod h1:XIuqKpZTUXtVyeyiN1k9Tc/U7EzfaDnVc34feFHfBws= github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mSIGnGuSdKl9qDSyfbYK50z2wc2gGMggegE= github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM= -github.com/charmbracelet/ultraviolet v0.0.0-20250811142928-f69b0392d22a h1:uxbFdz1bP7G/aDuKg047p+6nzqh1AI/1HgkyhWjI1NY= -github.com/charmbracelet/ultraviolet v0.0.0-20250811142928-f69b0392d22a/go.mod h1:tvF2zaoXYOmuLtUMLJSWcNfgGfe3302CTKkRf+vYZqo= github.com/charmbracelet/ultraviolet v0.0.0-20250813213450-50737e162af5 h1:7FlxuSTw5paY5Km8AK1WwfSVjAIOW4UiZI6Okva83pY= github.com/charmbracelet/ultraviolet v0.0.0-20250813213450-50737e162af5/go.mod h1:uQXXTlOPWiN05pLfSdajBj5FaaszPUrrr9qRFmmQ79M= github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= @@ -384,8 +376,6 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= diff --git a/internal/tui/components/chat/header/header.go b/internal/tui/components/chat/header/header.go index 4eac0c2444321a59c06d2e83d328fd1ea9e8512c..edcdc6960123056fc61df7a4332b106d1f417ab0 100644 --- a/internal/tui/components/chat/header/header.go +++ b/internal/tui/components/chat/header/header.go @@ -14,6 +14,7 @@ import ( "github.com/charmbracelet/crush/internal/tui/styles" "github.com/charmbracelet/crush/internal/tui/util" "github.com/charmbracelet/lipgloss/v2" + "github.com/charmbracelet/x/ansi" ) type Header interface { @@ -42,56 +43,65 @@ func (h *header) Init() tea.Cmd { return nil } -func (p *header) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (h *header) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case pubsub.Event[session.Session]: if msg.Type == pubsub.UpdatedEvent { - if p.session.ID == msg.Payload.ID { - p.session = msg.Payload + if h.session.ID == msg.Payload.ID { + h.session = msg.Payload } } } - return p, nil + return h, nil } -func (p *header) View() string { - if p.session.ID == "" { +func (h *header) View() string { + if h.session.ID == "" { return "" } + const ( + gap = " " + diag = "╱" + minDiags = 3 + leftPadding = 1 + rightPadding = 1 + ) + t := styles.CurrentTheme() - details := p.details() - parts := []string{ - t.S().Base.Foreground(t.Secondary).Render("Charm™"), - " ", - styles.ApplyBoldForegroundGrad("CRUSH", t.Secondary, t.Primary), - " ", - } - remainingWidth := p.width - lipgloss.Width(strings.Join(parts, "")) - lipgloss.Width(details) - 2 + var b strings.Builder + + b.WriteString(t.S().Base.Foreground(t.Secondary).Render("Charm™")) + b.WriteString(gap) + b.WriteString(styles.ApplyBoldForegroundGrad("CRUSH", t.Secondary, t.Primary)) + b.WriteString(gap) + + availDetailWidth := h.width - leftPadding - rightPadding - lipgloss.Width(b.String()) - minDiags + details := h.details(availDetailWidth) + + remainingWidth := h.width - + lipgloss.Width(b.String()) - + lipgloss.Width(details) - + leftPadding - + rightPadding + if remainingWidth > 0 { - char := "╱" - lines := strings.Repeat(char, remainingWidth) - parts = append(parts, t.S().Base.Foreground(t.Primary).Render(lines), " ") + b.WriteString(t.S().Base.Foreground(t.Primary).Render( + strings.Repeat(diag, max(minDiags, remainingWidth)), + )) + b.WriteString(gap) } - parts = append(parts, details) + b.WriteString(details) - content := t.S().Base.Padding(0, 1).Render( - lipgloss.JoinHorizontal( - lipgloss.Left, - parts..., - ), - ) - return content + return t.S().Base.Padding(0, rightPadding, 0, leftPadding).Render(b.String()) } -func (h *header) details() string { - t := styles.CurrentTheme() - cwd := fsext.DirTrim(fsext.PrettyPath(config.Get().WorkingDir()), 4) - parts := []string{ - t.S().Muted.Render(cwd), - } +func (h *header) details(availWidth int) string { + s := styles.CurrentTheme().S() + + var parts []string errorCount := 0 for _, l := range h.lspClients { @@ -105,22 +115,33 @@ func (h *header) details() string { } if errorCount > 0 { - parts = append(parts, t.S().Error.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount))) + parts = append(parts, s.Error.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount))) } agentCfg := config.Get().Agents["coder"] model := config.Get().GetModelByType(agentCfg.Model) percentage := (float64(h.session.CompletionTokens+h.session.PromptTokens) / float64(model.ContextWindow)) * 100 - formattedPercentage := t.S().Muted.Render(fmt.Sprintf("%d%%", int(percentage))) + formattedPercentage := s.Muted.Render(fmt.Sprintf("%d%%", int(percentage))) parts = append(parts, formattedPercentage) + const keystroke = "ctrl+d" if h.detailsOpen { - parts = append(parts, t.S().Muted.Render("ctrl+d")+t.S().Subtle.Render(" close")) + parts = append(parts, s.Muted.Render(keystroke)+s.Subtle.Render(" close")) } else { - parts = append(parts, t.S().Muted.Render("ctrl+d")+t.S().Subtle.Render(" open ")) + parts = append(parts, s.Muted.Render(keystroke)+s.Subtle.Render(" open ")) } - dot := t.S().Subtle.Render(" • ") - return strings.Join(parts, dot) + + dot := s.Subtle.Render(" • ") + metadata := strings.Join(parts, dot) + metadata = dot + metadata + + // Truncate cwd if necessary, and insert it at the beginning. + const dirTrimLimit = 4 + cwd := fsext.DirTrim(fsext.PrettyPath(config.Get().WorkingDir()), dirTrimLimit) + cwd = ansi.Truncate(cwd, max(0, availWidth-lipgloss.Width(metadata)), "…") + cwd = s.Muted.Render(cwd) + + return cwd + metadata } func (h *header) SetDetailsOpen(open bool) {