1package lsp
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"github.com/charmbracelet/crush/internal/app"
  8	"github.com/charmbracelet/crush/internal/config"
  9	"github.com/charmbracelet/crush/internal/lsp"
 10	"github.com/charmbracelet/crush/internal/tui/components/core"
 11	"github.com/charmbracelet/crush/internal/tui/styles"
 12	"github.com/charmbracelet/lipgloss/v2"
 13	"github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
 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 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		// Determine icon color and description based on state
 59		icon := t.ItemOfflineIcon
 60		description := l.LSP.Command
 61
 62		if l.LSP.Disabled {
 63			description = t.S().Subtle.Render("disabled")
 64		} else if state, exists := lspStates[l.Name]; exists {
 65			switch state.State {
 66			case lsp.StateStarting:
 67				icon = t.ItemBusyIcon
 68				description = t.S().Subtle.Render("starting...")
 69			case lsp.StateReady:
 70				icon = t.ItemOnlineIcon
 71				description = l.LSP.Command
 72			case lsp.StateError:
 73				icon = t.ItemErrorIcon
 74				if state.Error != nil {
 75					description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
 76				} else {
 77					description = t.S().Subtle.Render("error")
 78				}
 79			case lsp.StateDisabled:
 80				icon = t.ItemOfflineIcon.Foreground(t.FgMuted)
 81				description = t.S().Base.Foreground(t.FgMuted).Render("no root markers found")
 82			}
 83		}
 84
 85		// Calculate diagnostic counts if we have LSP clients
 86		var extraContent string
 87		if lspClients != nil {
 88			lspErrs := map[protocol.DiagnosticSeverity]int{
 89				protocol.SeverityError:       0,
 90				protocol.SeverityWarning:     0,
 91				protocol.SeverityHint:        0,
 92				protocol.SeverityInformation: 0,
 93			}
 94			if client, ok := lspClients[l.Name]; ok {
 95				for _, diagnostics := range client.GetDiagnostics() {
 96					for _, diagnostic := range diagnostics {
 97						if severity, ok := lspErrs[diagnostic.Severity]; ok {
 98							lspErrs[diagnostic.Severity] = severity + 1
 99						}
100					}
101				}
102			}
103
104			errs := []string{}
105			if lspErrs[protocol.SeverityError] > 0 {
106				errs = append(errs, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("%s %d", styles.ErrorIcon, lspErrs[protocol.SeverityError])))
107			}
108			if lspErrs[protocol.SeverityWarning] > 0 {
109				errs = append(errs, t.S().Base.Foreground(t.Warning).Render(fmt.Sprintf("%s %d", styles.WarningIcon, lspErrs[protocol.SeverityWarning])))
110			}
111			if lspErrs[protocol.SeverityHint] > 0 {
112				errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.HintIcon, lspErrs[protocol.SeverityHint])))
113			}
114			if lspErrs[protocol.SeverityInformation] > 0 {
115				errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.InfoIcon, lspErrs[protocol.SeverityInformation])))
116			}
117			extraContent = strings.Join(errs, " ")
118		}
119
120		lspList = append(lspList,
121			core.Status(
122				core.StatusOpts{
123					Icon:         icon.String(),
124					Title:        l.Name,
125					Description:  description,
126					ExtraContent: extraContent,
127				},
128				opts.MaxWidth,
129			),
130		)
131	}
132
133	return lspList
134}
135
136// RenderLSPBlock renders a complete LSP block with optional truncation indicator.
137func RenderLSPBlock(lspClients map[string]*lsp.Client, opts RenderOptions, showTruncationIndicator bool) string {
138	t := styles.CurrentTheme()
139	lspList := RenderLSPList(lspClients, opts)
140
141	// Add truncation indicator if needed
142	if showTruncationIndicator && opts.MaxItems > 0 {
143		lspConfigs := config.Get().LSP.Sorted()
144		if len(lspConfigs) > opts.MaxItems {
145			remaining := len(lspConfigs) - opts.MaxItems
146			if remaining == 1 {
147				lspList = append(lspList, t.S().Base.Foreground(t.FgMuted).Render("β¦"))
148			} else {
149				lspList = append(lspList,
150					t.S().Base.Foreground(t.FgSubtle).Render(fmt.Sprintf("β¦and %d more", remaining)),
151				)
152			}
153		}
154	}
155
156	content := lipgloss.JoinVertical(lipgloss.Left, lspList...)
157	if opts.MaxWidth > 0 {
158		return lipgloss.NewStyle().Width(opts.MaxWidth).Render(content)
159	}
160	return content
161}