@@ -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=
@@ -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) {