lsp.go

  1package model
  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/lsp"
 10	"github.com/charmbracelet/crush/internal/ui/common"
 11	"github.com/charmbracelet/crush/internal/ui/styles"
 12	"github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
 13)
 14
 15// LSPInfo wraps LSP client information with diagnostic counts by severity.
 16type LSPInfo struct {
 17	app.LSPClientInfo
 18	Diagnostics map[protocol.DiagnosticSeverity]int
 19}
 20
 21// lspInfo renders the LSP status section showing active LSP clients and their
 22// diagnostic counts.
 23func (m *UI) lspInfo(width, maxItems int, isSection bool) string {
 24	var lsps []LSPInfo
 25	t := m.com.Styles
 26
 27	for _, state := range m.lspStates {
 28		client, ok := m.com.App.LSPClients.Get(state.Name)
 29		if !ok {
 30			continue
 31		}
 32		counts := client.GetDiagnosticCounts()
 33		lspErrs := map[protocol.DiagnosticSeverity]int{
 34			protocol.SeverityError:       counts.Error,
 35			protocol.SeverityWarning:     counts.Warning,
 36			protocol.SeverityHint:        counts.Hint,
 37			protocol.SeverityInformation: counts.Information,
 38		}
 39
 40		lsps = append(lsps, LSPInfo{LSPClientInfo: state, Diagnostics: lspErrs})
 41	}
 42	title := t.Subtle.Render("LSPs")
 43	if isSection {
 44		title = common.Section(t, title, width)
 45	}
 46	list := t.Subtle.Render("None")
 47	if len(lsps) > 0 {
 48		list = lspList(t, lsps, width, maxItems)
 49	}
 50
 51	return lipgloss.NewStyle().Width(width).Render(fmt.Sprintf("%s\n\n%s", title, list))
 52}
 53
 54// lspDiagnostics formats diagnostic counts with appropriate icons and colors.
 55func lspDiagnostics(t *styles.Styles, diagnostics map[protocol.DiagnosticSeverity]int) string {
 56	errs := []string{}
 57	if diagnostics[protocol.SeverityError] > 0 {
 58		errs = append(errs, t.LSP.ErrorDiagnostic.Render(fmt.Sprintf("%s %d", styles.ErrorIcon, diagnostics[protocol.SeverityError])))
 59	}
 60	if diagnostics[protocol.SeverityWarning] > 0 {
 61		errs = append(errs, t.LSP.WarningDiagnostic.Render(fmt.Sprintf("%s %d", styles.WarningIcon, diagnostics[protocol.SeverityWarning])))
 62	}
 63	if diagnostics[protocol.SeverityHint] > 0 {
 64		errs = append(errs, t.LSP.HintDiagnostic.Render(fmt.Sprintf("%s %d", styles.HintIcon, diagnostics[protocol.SeverityHint])))
 65	}
 66	if diagnostics[protocol.SeverityInformation] > 0 {
 67		errs = append(errs, t.LSP.InfoDiagnostic.Render(fmt.Sprintf("%s %d", styles.InfoIcon, diagnostics[protocol.SeverityInformation])))
 68	}
 69	return strings.Join(errs, " ")
 70}
 71
 72// lspList renders a list of LSP clients with their status and diagnostics,
 73// truncating to maxItems if needed.
 74func lspList(t *styles.Styles, lsps []LSPInfo, width, maxItems int) string {
 75	if maxItems <= 0 {
 76		return ""
 77	}
 78	var renderedLsps []string
 79	for _, l := range lsps {
 80		var icon string
 81		title := l.Name
 82		var description string
 83		var diagnostics string
 84		switch l.State {
 85		case lsp.StateStarting:
 86			icon = t.ItemBusyIcon.String()
 87			description = t.Subtle.Render("starting...")
 88		case lsp.StateReady:
 89			icon = t.ItemOnlineIcon.String()
 90			diagnostics = lspDiagnostics(t, l.Diagnostics)
 91		case lsp.StateError:
 92			icon = t.ItemErrorIcon.String()
 93			description = t.Subtle.Render("error")
 94			if l.Error != nil {
 95				description = t.Subtle.Render(fmt.Sprintf("error: %s", l.Error.Error()))
 96			}
 97		case lsp.StateDisabled:
 98			icon = t.ItemOfflineIcon.Foreground(t.Muted.GetBackground()).String()
 99			description = t.Subtle.Render("inactive")
100		default:
101			icon = t.ItemOfflineIcon.String()
102		}
103		renderedLsps = append(renderedLsps, common.Status(t, common.StatusOpts{
104			Icon:         icon,
105			Title:        title,
106			Description:  description,
107			ExtraContent: diagnostics,
108		}, width))
109	}
110
111	if len(renderedLsps) > maxItems {
112		visibleItems := renderedLsps[:maxItems-1]
113		remaining := len(renderedLsps) - maxItems
114		visibleItems = append(visibleItems, t.Subtle.Render(fmt.Sprintf("…and %d more", remaining)))
115		return lipgloss.JoinVertical(lipgloss.Left, visibleItems...)
116	}
117	return lipgloss.JoinVertical(lipgloss.Left, renderedLsps...)
118}