lsp.go

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