@@ -14,6 +14,7 @@ import (
14 "github.com/charmbracelet/crush/internal/tui/styles"
14 "github.com/charmbracelet/crush/internal/tui/styles"
15 "github.com/charmbracelet/crush/internal/tui/util"
15 "github.com/charmbracelet/crush/internal/tui/util"
16 "github.com/charmbracelet/lipgloss/v2"
16 "github.com/charmbracelet/lipgloss/v2"
17+ "github.com/charmbracelet/x/ansi"
19type Header interface {
20type Header interface {
@@ -42,56 +43,65 @@ func (h *header) Init() tea.Cmd {
42 return nil
43 return nil
45−func (p *header) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
46+func (h *header) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
46 switch msg := msg.(type) {
47 switch msg := msg.(type) {
47 case pubsub.Event[session.Session]:
48 case pubsub.Event[session.Session]:
48 if msg.Type == pubsub.UpdatedEvent {
49 if msg.Type == pubsub.UpdatedEvent {
49− if p.session.ID == msg.Payload.ID {
50+ if h.session.ID == msg.Payload.ID {
50− p.session = msg.Payload
51+ h.session = msg.Payload
54− return p, nil
55+ return h, nil
57−func (p *header) View() string {
58+func (h *header) View() string {
58− if p.session.ID == "" {
59+ if h.session.ID == "" {
62 t := styles.CurrentTheme()
71 t := styles.CurrentTheme()
63− details := p.details()
65− t.S().Base.Foreground(t.Secondary).Render("Charm™"),
67− styles.ApplyBoldForegroundGrad("CRUSH", t.Secondary, t.Primary),
73+ var b strings.Builder
75+ b.WriteString(t.S().Base.Foreground(t.Secondary).Render("Charm™"))
77+ b.WriteString(styles.ApplyBoldForegroundGrad("CRUSH", t.Secondary, t.Primary))
80+ availDetailWidth := h.width - leftPadding - rightPadding - lipgloss.Width(b.String()) - minDiags
81+ details := h.details(availDetailWidth)
83+ remainingWidth := h.width -
84+ lipgloss.Width(b.String()) -
71− remainingWidth := p.width - lipgloss.Width(strings.Join(parts, "")) - lipgloss.Width(details) - 2
85+ lipgloss.Width(details) -
72 if remainingWidth > 0 {
89 if remainingWidth > 0 {
74− lines := strings.Repeat(char, remainingWidth)
75− parts = append(parts, t.S().Base.Foreground(t.Primary).Render(lines), " ")
90+ b.WriteString(t.S().Base.Foreground(t.Primary).Render(
91+ strings.Repeat(diag, max(minDiags, remainingWidth)),
78− parts = append(parts, details)
96+ b.WriteString(details)
80− content := t.S().Base.Padding(0, 1).Render(
98+ return t.S().Base.Padding(0, rightPadding, 0, leftPadding).Render(b.String())
81− lipgloss.JoinHorizontal(
89−func (h *header) details() string {
101+func (h *header) details(availWidth int) string {
90− t := styles.CurrentTheme()
102+ s := styles.CurrentTheme().S()
91− cwd := fsext.DirTrim(fsext.PrettyPath(config.Get().WorkingDir()), 4)
103+
92− parts := []string{
104+ var parts []string
93− t.S().Muted.Render(cwd),
96 errorCount := 0
106 errorCount := 0
97 for _, l := range h.lspClients {
107 for _, l := range h.lspClients {
@@ -105,22 +115,33 @@ func (h *header) details() string {
107 if errorCount > 0 {
117 if errorCount > 0 {
108− parts = append(parts, t.S().Error.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount)))
118+ parts = append(parts, s.Error.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount)))
111 agentCfg := config.Get().Agents["coder"]
121 agentCfg := config.Get().Agents["coder"]
112 model := config.Get().GetModelByType(agentCfg.Model)
122 model := config.Get().GetModelByType(agentCfg.Model)
113 percentage := (float64(h.session.CompletionTokens+h.session.PromptTokens) / float64(model.ContextWindow)) * 100
123 percentage := (float64(h.session.CompletionTokens+h.session.PromptTokens) / float64(model.ContextWindow)) * 100
114− formattedPercentage := t.S().Muted.Render(fmt.Sprintf("%d%%", int(percentage)))
124+ formattedPercentage := s.Muted.Render(fmt.Sprintf("%d%%", int(percentage)))
115 parts = append(parts, formattedPercentage)
125 parts = append(parts, formattedPercentage)
127+ const keystroke = "ctrl+d"
117 if h.detailsOpen {
128 if h.detailsOpen {
118− parts = append(parts, t.S().Muted.Render("ctrl+d")+t.S().Subtle.Render(" close"))
129+ parts = append(parts, s.Muted.Render(keystroke)+s.Subtle.Render(" close"))
120− parts = append(parts, t.S().Muted.Render("ctrl+d")+t.S().Subtle.Render(" open "))
131+ parts = append(parts, s.Muted.Render(keystroke)+s.Subtle.Render(" open "))
122− dot := t.S().Subtle.Render(" • ")
134+ dot := s.Subtle.Render(" • ")
123− return strings.Join(parts, dot)
135+ metadata := strings.Join(parts, dot)
136+ metadata = dot + metadata
138+ // Truncate cwd if necessary, and insert it at the beginning.
139+ const dirTrimLimit = 4
140+ cwd := fsext.DirTrim(fsext.PrettyPath(config.Get().WorkingDir()), dirTrimLimit)
141+ cwd = ansi.Truncate(cwd, max(0, availWidth-lipgloss.Width(metadata)), "…")
142+ cwd = s.Muted.Render(cwd)
144+ return cwd + metadata
126func (h *header) SetDetailsOpen(open bool) {
147func (h *header) SetDetailsOpen(open bool) {