7 tea "charm.land/bubbletea/v2"
8 "charm.land/lipgloss/v2"
9 "github.com/charmbracelet/crush/internal/config"
10 "github.com/charmbracelet/crush/internal/csync"
11 "github.com/charmbracelet/crush/internal/fsext"
12 "github.com/charmbracelet/crush/internal/lsp"
13 "github.com/charmbracelet/crush/internal/pubsub"
14 "github.com/charmbracelet/crush/internal/session"
15 "github.com/charmbracelet/crush/internal/tui/styles"
16 "github.com/charmbracelet/crush/internal/tui/util"
17 "github.com/charmbracelet/x/ansi"
18 "github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
21type Header interface {
23 SetSession(session session.Session) tea.Cmd
24 SetWidth(width int) tea.Cmd
25 SetDetailsOpen(open bool)
31 session session.Session
32 lspClients *csync.Map[string, *lsp.Client]
36func New(lspClients *csync.Map[string, *lsp.Client]) Header {
38 lspClients: lspClients,
43func (h *header) Init() tea.Cmd {
47func (h *header) Update(msg tea.Msg) (util.Model, tea.Cmd) {
48 switch msg := msg.(type) {
49 case pubsub.Event[session.Session]:
50 if msg.Type == pubsub.UpdatedEvent {
51 if h.session.ID == msg.Payload.ID {
52 h.session = msg.Payload
59func (h *header) View() string {
60 if h.session.ID == "" {
72 t := styles.CurrentTheme()
76 b.WriteString(t.S().Base.Foreground(t.Secondary).Render("Charm™"))
78 b.WriteString(styles.ApplyBoldForegroundGrad("CRUSH", t.Secondary, t.Primary))
81 availDetailWidth := h.width - leftPadding - rightPadding - lipgloss.Width(b.String()) - minDiags
82 details := h.details(availDetailWidth)
84 remainingWidth := h.width -
85 lipgloss.Width(b.String()) -
86 lipgloss.Width(details) -
90 if remainingWidth > 0 {
91 b.WriteString(t.S().Base.Foreground(t.Primary).Render(
92 strings.Repeat(diag, max(minDiags, remainingWidth)),
97 b.WriteString(details)
99 return t.S().Base.Padding(0, rightPadding, 0, leftPadding).Render(b.String())
102func (h *header) details(availWidth int) string {
103 s := styles.CurrentTheme().S()
108 for l := range h.lspClients.Seq() {
109 for _, diagnostics := range l.GetDiagnostics() {
110 for _, diagnostic := range diagnostics {
111 if diagnostic.Severity == protocol.SeverityError {
119 parts = append(parts, s.Error.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount)))
122 agentCfg := config.Get().Agents[config.AgentCoder]
123 model := config.Get().GetModelByType(agentCfg.Model)
124 percentage := (float64(h.session.CompletionTokens+h.session.PromptTokens) / float64(model.ContextWindow)) * 100
125 formattedPercentage := s.Muted.Render(fmt.Sprintf("%d%%", int(percentage)))
126 parts = append(parts, formattedPercentage)
128 const keystroke = "ctrl+d"
130 parts = append(parts, s.Muted.Render(keystroke)+s.Subtle.Render(" close"))
132 parts = append(parts, s.Muted.Render(keystroke)+s.Subtle.Render(" open "))
135 dot := s.Subtle.Render(" • ")
136 metadata := strings.Join(parts, dot)
137 metadata = dot + metadata
139 // Truncate cwd if necessary, and insert it at the beginning.
140 const dirTrimLimit = 4
141 cwd := fsext.DirTrim(fsext.PrettyPath(config.Get().WorkingDir()), dirTrimLimit)
142 cwd = ansi.Truncate(cwd, max(0, availWidth-lipgloss.Width(metadata)), "…")
143 cwd = s.Muted.Render(cwd)
145 return cwd + metadata
148func (h *header) SetDetailsOpen(open bool) {
152// SetSession implements Header.
153func (h *header) SetSession(session session.Session) tea.Cmd {
158// SetWidth implements Header.
159func (h *header) SetWidth(width int) tea.Cmd {
164// ShowingDetails implements Header.
165func (h *header) ShowingDetails() bool {
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list