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