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