lsp.go

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