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		lspErrs := map[protocol.DiagnosticSeverity]int{
 33			protocol.SeverityError:       0,
 34			protocol.SeverityWarning:     0,
 35			protocol.SeverityHint:        0,
 36			protocol.SeverityInformation: 0,
 37		}
 38
 39		for _, diagnostics := range client.GetDiagnostics() {
 40			for _, diagnostic := range diagnostics {
 41				if severity, ok := lspErrs[diagnostic.Severity]; ok {
 42					lspErrs[diagnostic.Severity] = severity + 1
 43				}
 44			}
 45		}
 46
 47		lsps = append(lsps, LSPInfo{LSPClientInfo: state, Diagnostics: lspErrs})
 48	}
 49	title := t.Subtle.Render("LSPs")
 50	if isSection {
 51		title = common.Section(t, title, width)
 52	}
 53	list := t.Subtle.Render("None")
 54	if len(lsps) > 0 {
 55		list = lspList(t, lsps, width, maxItems)
 56	}
 57
 58	return lipgloss.NewStyle().Width(width).Render(fmt.Sprintf("%s\n\n%s", title, list))
 59}
 60
 61// lspDiagnostics formats diagnostic counts with appropriate icons and colors.
 62func lspDiagnostics(t *styles.Styles, diagnostics map[protocol.DiagnosticSeverity]int) string {
 63	errs := []string{}
 64	if diagnostics[protocol.SeverityError] > 0 {
 65		errs = append(errs, t.LSP.ErrorDiagnostic.Render(fmt.Sprintf("%s %d", styles.ErrorIcon, diagnostics[protocol.SeverityError])))
 66	}
 67	if diagnostics[protocol.SeverityWarning] > 0 {
 68		errs = append(errs, t.LSP.WarningDiagnostic.Render(fmt.Sprintf("%s %d", styles.WarningIcon, diagnostics[protocol.SeverityWarning])))
 69	}
 70	if diagnostics[protocol.SeverityHint] > 0 {
 71		errs = append(errs, t.LSP.HintDiagnostic.Render(fmt.Sprintf("%s %d", styles.HintIcon, diagnostics[protocol.SeverityHint])))
 72	}
 73	if diagnostics[protocol.SeverityInformation] > 0 {
 74		errs = append(errs, t.LSP.InfoDiagnostic.Render(fmt.Sprintf("%s %d", styles.InfoIcon, diagnostics[protocol.SeverityInformation])))
 75	}
 76	return strings.Join(errs, " ")
 77}
 78
 79// lspList renders a list of LSP clients with their status and diagnostics,
 80// truncating to maxItems if needed.
 81func lspList(t *styles.Styles, lsps []LSPInfo, width, maxItems int) string {
 82	var renderedLsps []string
 83	for _, l := range lsps {
 84		var icon string
 85		title := l.Name
 86		var description string
 87		var diagnostics string
 88		switch l.State {
 89		case lsp.StateStarting:
 90			icon = t.ItemBusyIcon.String()
 91			description = t.Subtle.Render("starting...")
 92		case lsp.StateReady:
 93			icon = t.ItemOnlineIcon.String()
 94			diagnostics = lspDiagnostics(t, l.Diagnostics)
 95		case lsp.StateError:
 96			icon = t.ItemErrorIcon.String()
 97			description = t.Subtle.Render("error")
 98			if l.Error != nil {
 99				description = t.Subtle.Render(fmt.Sprintf("error: %s", l.Error.Error()))
100			}
101		case lsp.StateDisabled:
102			icon = t.ItemOfflineIcon.Foreground(t.Muted.GetBackground()).String()
103			description = t.Subtle.Render("inactive")
104		default:
105			icon = t.ItemOfflineIcon.String()
106		}
107		renderedLsps = append(renderedLsps, common.Status(t, common.StatusOpts{
108			Icon:         icon,
109			Title:        title,
110			Description:  description,
111			ExtraContent: diagnostics,
112		}, width))
113	}
114
115	if len(renderedLsps) > maxItems {
116		visibleItems := renderedLsps[:maxItems-1]
117		remaining := len(renderedLsps) - maxItems
118		visibleItems = append(visibleItems, t.Subtle.Render(fmt.Sprintf("…and %d more", remaining)))
119		return lipgloss.JoinVertical(lipgloss.Left, visibleItems...)
120	}
121	return lipgloss.JoinVertical(lipgloss.Left, renderedLsps...)
122}