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