1package model
2
3import (
4 "fmt"
5 "slices"
6 "strings"
7
8 "charm.land/lipgloss/v2"
9 "github.com/charmbracelet/crush/internal/app"
10 "github.com/charmbracelet/crush/internal/lsp"
11 "github.com/charmbracelet/crush/internal/ui/common"
12 "github.com/charmbracelet/crush/internal/ui/styles"
13 "github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
14)
15
16// LSPInfo wraps LSP client information with diagnostic counts by severity.
17type LSPInfo struct {
18 app.LSPClientInfo
19 Diagnostics map[protocol.DiagnosticSeverity]int
20}
21
22// lspInfo renders the LSP status section showing active LSP clients and their
23// diagnostic counts.
24func (m *UI) lspInfo(width, maxItems int, isSection bool) string {
25 var lsps []LSPInfo
26 t := m.com.Styles
27
28 for _, state := range m.lspStates {
29 client, ok := m.com.App.LSPClients.Get(state.Name)
30 if !ok {
31 continue
32 }
33 counts := client.GetDiagnosticCounts()
34 lspErrs := map[protocol.DiagnosticSeverity]int{
35 protocol.SeverityError: counts.Error,
36 protocol.SeverityWarning: counts.Warning,
37 protocol.SeverityHint: counts.Hint,
38 protocol.SeverityInformation: counts.Information,
39 }
40
41 lsps = append(lsps, LSPInfo{LSPClientInfo: state, Diagnostics: lspErrs})
42 }
43
44 slices.SortFunc(lsps, func(a, b LSPInfo) int {
45 return strings.Compare(a.Name, b.Name)
46 })
47
48 title := t.Subtle.Render("LSPs")
49 if isSection {
50 title = common.Section(t, title, width)
51 }
52 list := t.Subtle.Render("None")
53 if len(lsps) > 0 {
54 list = lspList(t, lsps, width, maxItems)
55 }
56
57 return lipgloss.NewStyle().Width(width).Render(fmt.Sprintf("%s\n\n%s", title, list))
58}
59
60// lspDiagnostics formats diagnostic counts with appropriate icons and colors.
61func lspDiagnostics(t *styles.Styles, diagnostics map[protocol.DiagnosticSeverity]int) string {
62 errs := []string{}
63 if diagnostics[protocol.SeverityError] > 0 {
64 errs = append(errs, t.LSP.ErrorDiagnostic.Render(fmt.Sprintf("%s %d", styles.ErrorIcon, diagnostics[protocol.SeverityError])))
65 }
66 if diagnostics[protocol.SeverityWarning] > 0 {
67 errs = append(errs, t.LSP.WarningDiagnostic.Render(fmt.Sprintf("%s %d", styles.WarningIcon, diagnostics[protocol.SeverityWarning])))
68 }
69 if diagnostics[protocol.SeverityHint] > 0 {
70 errs = append(errs, t.LSP.HintDiagnostic.Render(fmt.Sprintf("%s %d", styles.HintIcon, diagnostics[protocol.SeverityHint])))
71 }
72 if diagnostics[protocol.SeverityInformation] > 0 {
73 errs = append(errs, t.LSP.InfoDiagnostic.Render(fmt.Sprintf("%s %d", styles.InfoIcon, diagnostics[protocol.SeverityInformation])))
74 }
75 return strings.Join(errs, " ")
76}
77
78// lspList renders a list of LSP clients with their status and diagnostics,
79// truncating to maxItems if needed.
80func lspList(t *styles.Styles, lsps []LSPInfo, width, maxItems int) string {
81 if maxItems <= 0 {
82 return ""
83 }
84 var renderedLsps []string
85 for _, l := range lsps {
86 var icon string
87 title := l.Name
88 var description string
89 var diagnostics string
90 switch l.State {
91 case lsp.StateStarting:
92 icon = t.ItemBusyIcon.String()
93 description = t.Subtle.Render("starting...")
94 case lsp.StateReady:
95 icon = t.ItemOnlineIcon.String()
96 diagnostics = lspDiagnostics(t, l.Diagnostics)
97 case lsp.StateError:
98 icon = t.ItemErrorIcon.String()
99 description = t.Subtle.Render("error")
100 if l.Error != nil {
101 description = t.Subtle.Render(fmt.Sprintf("error: %s", l.Error.Error()))
102 }
103 case lsp.StateDisabled:
104 icon = t.ItemOfflineIcon.Foreground(t.Muted.GetBackground()).String()
105 description = t.Subtle.Render("inactive")
106 default:
107 icon = t.ItemOfflineIcon.String()
108 }
109 renderedLsps = append(renderedLsps, common.Status(t, common.StatusOpts{
110 Icon: icon,
111 Title: title,
112 Description: description,
113 ExtraContent: diagnostics,
114 }, width))
115 }
116
117 if len(renderedLsps) > maxItems {
118 visibleItems := renderedLsps[:maxItems-1]
119 remaining := len(renderedLsps) - maxItems
120 visibleItems = append(visibleItems, t.Subtle.Render(fmt.Sprintf("β¦and %d more", remaining)))
121 return lipgloss.JoinVertical(lipgloss.Left, visibleItems...)
122 }
123 return lipgloss.JoinVertical(lipgloss.Left, renderedLsps...)
124}