1package lsp
2
3import (
4 "fmt"
5 "strings"
6
7 "charm.land/lipgloss/v2"
8 "github.com/charmbracelet/crush/internal/app"
9 "github.com/charmbracelet/crush/internal/config"
10 "github.com/charmbracelet/crush/internal/csync"
11 "github.com/charmbracelet/crush/internal/lsp"
12 "github.com/charmbracelet/crush/internal/tui/components/core"
13 "github.com/charmbracelet/crush/internal/tui/styles"
14)
15
16// RenderOptions contains options for rendering LSP lists.
17type RenderOptions struct {
18 MaxWidth int
19 MaxItems int
20 ShowSection bool
21 SectionName string
22}
23
24// RenderLSPList renders a list of LSP status items with the given options.
25func RenderLSPList(lspClients *csync.Map[string, *lsp.Client], opts RenderOptions) []string {
26 t := styles.CurrentTheme()
27 lspList := []string{}
28
29 if opts.ShowSection {
30 sectionName := opts.SectionName
31 if sectionName == "" {
32 sectionName = "LSPs"
33 }
34 section := t.S().Subtle.Render(sectionName)
35 lspList = append(lspList, section, "")
36 }
37
38 lspConfigs := config.Get().LSP.Sorted()
39 if len(lspConfigs) == 0 {
40 lspList = append(lspList, t.S().Base.Foreground(t.Border).Render("None"))
41 return lspList
42 }
43
44 // Get LSP states
45 lspStates := app.GetLSPStates()
46
47 // Determine how many items to show
48 maxItems := len(lspConfigs)
49 if opts.MaxItems > 0 {
50 maxItems = min(opts.MaxItems, len(lspConfigs))
51 }
52
53 for i, l := range lspConfigs {
54 if i >= maxItems {
55 break
56 }
57
58 icon, description := iconAndDescription(l, t, lspStates)
59
60 // Calculate diagnostic counts if we have LSP clients
61 var extraContent string
62 if lspClients != nil {
63 if client, ok := lspClients.Get(l.Name); ok {
64 counts := client.GetDiagnosticCounts()
65 errs := []string{}
66 if counts.Error > 0 {
67 errs = append(errs, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("%s %d", styles.ErrorIcon, counts.Error)))
68 }
69 if counts.Warning > 0 {
70 errs = append(errs, t.S().Base.Foreground(t.Warning).Render(fmt.Sprintf("%s %d", styles.WarningIcon, counts.Warning)))
71 }
72 if counts.Hint > 0 {
73 errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.HintIcon, counts.Hint)))
74 }
75 if counts.Information > 0 {
76 errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.InfoIcon, counts.Information)))
77 }
78 extraContent = strings.Join(errs, " ")
79 }
80 }
81
82 lspList = append(lspList,
83 core.Status(
84 core.StatusOpts{
85 Icon: icon.String(),
86 Title: l.Name,
87 Description: description,
88 ExtraContent: extraContent,
89 },
90 opts.MaxWidth,
91 ),
92 )
93 }
94
95 return lspList
96}
97
98func iconAndDescription(l config.LSP, t *styles.Theme, states map[string]app.LSPClientInfo) (lipgloss.Style, string) {
99 if l.LSP.Disabled {
100 return t.ItemOfflineIcon.Foreground(t.FgMuted), t.S().Subtle.Render("disabled")
101 }
102
103 info := states[l.Name]
104 switch info.State {
105 case lsp.StateStarting:
106 return t.ItemBusyIcon, t.S().Subtle.Render("starting...")
107 case lsp.StateReady:
108 return t.ItemOnlineIcon, ""
109 case lsp.StateError:
110 description := t.S().Subtle.Render("error")
111 if info.Error != nil {
112 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", info.Error.Error()))
113 }
114 return t.ItemErrorIcon, description
115 case lsp.StateDisabled:
116 return t.ItemOfflineIcon.Foreground(t.FgMuted), t.S().Subtle.Render("inactive")
117 default:
118 return t.ItemOfflineIcon, ""
119 }
120}
121
122// RenderLSPBlock renders a complete LSP block with optional truncation indicator.
123func RenderLSPBlock(lspClients *csync.Map[string, *lsp.Client], opts RenderOptions, showTruncationIndicator bool) string {
124 t := styles.CurrentTheme()
125 lspList := RenderLSPList(lspClients, opts)
126
127 // Add truncation indicator if needed
128 if showTruncationIndicator && opts.MaxItems > 0 {
129 lspConfigs := config.Get().LSP.Sorted()
130 if len(lspConfigs) > opts.MaxItems {
131 remaining := len(lspConfigs) - opts.MaxItems
132 if remaining == 1 {
133 lspList = append(lspList, t.S().Base.Foreground(t.FgMuted).Render("β¦"))
134 } else {
135 lspList = append(lspList,
136 t.S().Base.Foreground(t.FgSubtle).Render(fmt.Sprintf("β¦and %d more", remaining)),
137 )
138 }
139 }
140 }
141
142 content := lipgloss.JoinVertical(lipgloss.Left, lspList...)
143 if opts.MaxWidth > 0 {
144 return lipgloss.NewStyle().Width(opts.MaxWidth).Render(content)
145 }
146 return content
147}