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}