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 if client, ok := m.com.App.LSPManager.Clients().Get(state.Name); ok {
36 counts := client.GetDiagnosticCounts()
37 lspErrs[protocol.SeverityError] = counts.Error
38 lspErrs[protocol.SeverityWarning] = counts.Warning
39 lspErrs[protocol.SeverityHint] = counts.Hint
40 lspErrs[protocol.SeverityInformation] = counts.Information
41 }
42
43 lsps = append(lsps, LSPInfo{LSPClientInfo: state, Diagnostics: lspErrs})
44 }
45
46 title := t.ResourceGroupTitle.Render("LSPs")
47 if isSection {
48 title = common.Section(t, title, width)
49 }
50 list := t.ResourceAdditionalText.Render("None")
51 if len(lsps) > 0 {
52 list = lspList(t, lsps, width, maxItems)
53 }
54
55 return lipgloss.NewStyle().Width(width).Render(fmt.Sprintf("%s\n\n%s", title, list))
56}
57
58// lspDiagnostics formats diagnostic counts with appropriate icons and colors.
59func lspDiagnostics(t *styles.Styles, diagnostics map[protocol.DiagnosticSeverity]int) string {
60 var errs []string
61 if diagnostics[protocol.SeverityError] > 0 {
62 errs = append(errs, t.LSP.ErrorDiagnostic.Render(fmt.Sprintf("%s%d", styles.LSPErrorIcon, diagnostics[protocol.SeverityError])))
63 }
64 if diagnostics[protocol.SeverityWarning] > 0 {
65 errs = append(errs, t.LSP.WarningDiagnostic.Render(fmt.Sprintf("%s%d", styles.LSPWarningIcon, diagnostics[protocol.SeverityWarning])))
66 }
67 if diagnostics[protocol.SeverityHint] > 0 {
68 errs = append(errs, t.LSP.HintDiagnostic.Render(fmt.Sprintf("%s%d", styles.LSPHintIcon, diagnostics[protocol.SeverityHint])))
69 }
70 if diagnostics[protocol.SeverityInformation] > 0 {
71 errs = append(errs, t.LSP.InfoDiagnostic.Render(fmt.Sprintf("%s%d", styles.LSPInfoIcon, diagnostics[protocol.SeverityInformation])))
72 }
73 return strings.Join(errs, " ")
74}
75
76// lspList renders a list of LSP clients with their status and diagnostics,
77// truncating to maxItems if needed.
78func lspList(t *styles.Styles, lsps []LSPInfo, width, maxItems int) string {
79 if maxItems <= 0 {
80 return ""
81 }
82 var renderedLsps []string
83 for _, l := range lsps {
84 var icon string
85 title := t.ResourceName.Render(l.Name)
86 var description string
87 var diagnostics string
88 switch l.State {
89 case lsp.StateUnstarted:
90 icon = t.ResourceOfflineIcon.String()
91 description = t.ResourceStatus.Render("unstarted")
92 case lsp.StateStopped:
93 icon = t.ResourceOfflineIcon.String()
94 description = t.ResourceStatus.Render("stopped")
95 case lsp.StateStarting:
96 icon = t.ResourceBusyIcon.String()
97 description = t.ResourceStatus.Render("starting...")
98 case lsp.StateReady:
99 icon = t.ResourceOnlineIcon.String()
100 diagnostics = lspDiagnostics(t, l.Diagnostics)
101 case lsp.StateError:
102 icon = t.ResourceErrorIcon.String()
103 description = t.ResourceStatus.Render("error")
104 if l.Error != nil {
105 description = t.ResourceStatus.Render(fmt.Sprintf("error: %s", l.Error.Error()))
106 }
107 case lsp.StateDisabled:
108 icon = t.ResourceOfflineIcon.Foreground(t.Muted.GetBackground()).String()
109 description = t.ResourceStatus.Render("disabled")
110 default:
111 continue
112 }
113 renderedLsps = append(renderedLsps, common.Status(t, common.StatusOpts{
114 Icon: icon,
115 Title: title,
116 Description: description,
117 ExtraContent: diagnostics,
118 }, width))
119 }
120
121 if len(renderedLsps) > maxItems {
122 visibleItems := renderedLsps[:maxItems-1]
123 remaining := len(renderedLsps) - maxItems
124 visibleItems = append(visibleItems, t.ResourceAdditionalText.Render(fmt.Sprintf("β¦and %d more", remaining)))
125 return lipgloss.JoinVertical(lipgloss.Left, visibleItems...)
126 }
127 return lipgloss.JoinVertical(lipgloss.Left, renderedLsps...)
128}