@@ -1,11 +1,16 @@
11package sidebar
22
33import (
4+ "fmt"
45 "os"
56 "strings"
67
78 tea "github.com/charmbracelet/bubbletea/v2"
89 "github.com/charmbracelet/crush/internal/config"
10+ "github.com/charmbracelet/crush/internal/llm/models"
11+ "github.com/charmbracelet/crush/internal/logging"
12+ "github.com/charmbracelet/crush/internal/lsp"
13+ "github.com/charmbracelet/crush/internal/lsp/protocol"
914 "github.com/charmbracelet/crush/internal/pubsub"
1015 "github.com/charmbracelet/crush/internal/session"
1116 "github.com/charmbracelet/crush/internal/tui/components/chat"
@@ -32,10 +37,13 @@ type sidebarCmp struct {
3237 session session.Session
3338 logo string
3439 cwd string
40+ lspClients map[string]*lsp.Client
3541}
3642
37−func NewSidebarCmp() Sidebar {
38− return &sidebarCmp{}
43+func NewSidebarCmp(lspClients map[string]*lsp.Client) Sidebar {
44+ return &sidebarCmp{
45+ lspClients: lspClients,
46+ }
3947}
4048
4149func (m *sidebarCmp) Init() tea.Cmd {
@@ -75,6 +83,8 @@ func (m *sidebarCmp) View() tea.View {
7583 parts = append(parts,
7684 m.cwd,
7785 "",
86+ m.currentModelBlock(),
87+ "",
7888 m.lspBlock(),
7989 "",
8090 m.mcpBlock(),
@@ -137,12 +147,44 @@ func (m *sidebarCmp) lspBlock() string {
137147 if l.Disabled {
138148 iconColor = t.FgMuted
139149 }
150+ lspErrs := map[protocol.DiagnosticSeverity]int{
151+ protocol.SeverityError: 0,
152+ protocol.SeverityWarning: 0,
153+ protocol.SeverityHint: 0,
154+ protocol.SeverityInformation: 0,
155+ }
156+ if client, ok := m.lspClients[n]; ok {
157+ for _, diagnostics := range client.GetDiagnostics() {
158+ for _, diagnostic := range diagnostics {
159+ if severity, ok := lspErrs[diagnostic.Severity]; ok {
160+ lspErrs[diagnostic.Severity] = severity + 1
161+ }
162+ }
163+ }
164+ }
165+
166+ errs := []string{}
167+ if lspErrs[protocol.SeverityError] > 0 {
168+ errs = append(errs, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("%s%d", styles.ErrorIcon, lspErrs[protocol.SeverityError])))
169+ }
170+ if lspErrs[protocol.SeverityWarning] > 0 {
171+ errs = append(errs, t.S().Base.Foreground(t.Warning).Render(fmt.Sprintf("%s%d", styles.WarningIcon, lspErrs[protocol.SeverityWarning])))
172+ }
173+ if lspErrs[protocol.SeverityHint] > 0 {
174+ errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s%d", styles.HintIcon, lspErrs[protocol.SeverityHint])))
175+ }
176+ if lspErrs[protocol.SeverityInformation] > 0 {
177+ errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s%d", styles.InfoIcon, lspErrs[protocol.SeverityInformation])))
178+ }
179+
180+ logging.Info("LSP Errors", "errors", errs)
140181 lspList = append(lspList,
141182 core.Status(
142183 core.StatusOpts{
143− IconColor: iconColor,
144− Title: n,
145− Description: l.Command,
184+ IconColor: iconColor,
185+ Title: n,
186+ Description: l.Command,
187+ ExtraContent: strings.Join(errs, " "),
146188 },
147189 m.width,
148190 ),
@@ -195,6 +237,76 @@ func (m *sidebarCmp) mcpBlock() string {
195237 )
196238}
197239
240+func formatTokensAndCost(tokens, contextWindow int64, cost float64) string {
241+ t := styles.CurrentTheme()
242+ // Format tokens in human-readable format (e.g., 110K, 1.2M)
243+ var formattedTokens string
244+ switch {
245+ case tokens >= 1_000_000:
246+ formattedTokens = fmt.Sprintf("%.1fM", float64(tokens)/1_000_000)
247+ case tokens >= 1_000:
248+ formattedTokens = fmt.Sprintf("%.1fK", float64(tokens)/1_000)
249+ default:
250+ formattedTokens = fmt.Sprintf("%d", tokens)
251+ }
252+
253+ // Remove .0 suffix if present
254+ if strings.HasSuffix(formattedTokens, ".0K") {
255+ formattedTokens = strings.Replace(formattedTokens, ".0K", "K", 1)
256+ }
257+ if strings.HasSuffix(formattedTokens, ".0M") {
258+ formattedTokens = strings.Replace(formattedTokens, ".0M", "M", 1)
259+ }
260+
261+ percentage := (float64(tokens) / float64(contextWindow)) * 100
262+
263+ baseStyle := t.S().Base
264+
265+ formattedCost := baseStyle.Foreground(t.FgMuted).Render(fmt.Sprintf("$%.2f", cost))
266+
267+ formattedTokens = baseStyle.Foreground(t.FgMuted).Render(fmt.Sprintf("(%s)", formattedTokens))
268+ formattedPercentage := baseStyle.Foreground(t.FgSubtle).Render(fmt.Sprintf("%d%%", int(percentage)))
269+ formattedTokens = fmt.Sprintf("%s %s", formattedPercentage, formattedTokens)
270+ if percentage > 80 {
271+ // add the warning icon
272+ formattedTokens = fmt.Sprintf("%s %s", styles.WarningIcon, formattedTokens)
273+ }
274+
275+ return fmt.Sprintf("%s %s", formattedTokens, formattedCost)
276+}
277+
278+func (s *sidebarCmp) currentModelBlock() string {
279+ cfg := config.Get()
280+ agentCfg := cfg.Agents[config.AgentCoder]
281+ selectedModelID := agentCfg.Model
282+ model := models.SupportedModels[selectedModelID]
283+
284+ t := styles.CurrentTheme()
285+
286+ modelIcon := t.S().Base.Foreground(t.FgSubtle).Render(styles.ModelIcon)
287+ modelName := t.S().Text.Render(model.Name)
288+ modelInfo := fmt.Sprintf("%s %s", modelIcon, modelName)
289+ parts := []string{
290+ // section,
291+ // "",
292+ modelInfo,
293+ }
294+ if s.session.ID != "" {
295+ parts = append(
296+ parts,
297+ " "+formatTokensAndCost(
298+ s.session.CompletionTokens+s.session.PromptTokens,
299+ model.ContextWindow,
300+ s.session.Cost,
301+ ),
302+ )
303+ }
304+ return lipgloss.JoinVertical(
305+ lipgloss.Left,
306+ parts...,
307+ )
308+}
309+
198310func cwd() string {
199311 cwd := config.WorkingDirectory()
200312 t := styles.CurrentTheme()