lsp.go

  1package lsp
  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/config"
 10	"github.com/charmbracelet/crush/internal/csync"
 11	"github.com/charmbracelet/crush/internal/lsp"
 12	"github.com/charmbracelet/crush/internal/tui/components/core"
 13	"github.com/charmbracelet/crush/internal/tui/styles"
 14)
 15
 16// RenderOptions contains options for rendering LSP lists.
 17type RenderOptions struct {
 18	MaxWidth    int
 19	MaxItems    int
 20	ShowSection bool
 21	SectionName string
 22}
 23
 24// RenderLSPList renders a list of LSP status items with the given options.
 25func RenderLSPList(lspClients *csync.Map[string, *lsp.Client], opts RenderOptions) []string {
 26	t := styles.CurrentTheme()
 27	lspList := []string{}
 28
 29	if opts.ShowSection {
 30		sectionName := opts.SectionName
 31		if sectionName == "" {
 32			sectionName = "LSPs"
 33		}
 34		section := t.S().Subtle.Render(sectionName)
 35		lspList = append(lspList, section, "")
 36	}
 37
 38	lspConfigs := config.Get().LSP.Sorted()
 39	if len(lspConfigs) == 0 {
 40		lspList = append(lspList, t.S().Base.Foreground(t.Border).Render("None"))
 41		return lspList
 42	}
 43
 44	// Get LSP states
 45	lspStates := app.GetLSPStates()
 46
 47	// Determine how many items to show
 48	maxItems := len(lspConfigs)
 49	if opts.MaxItems > 0 {
 50		maxItems = min(opts.MaxItems, len(lspConfigs))
 51	}
 52
 53	for i, l := range lspConfigs {
 54		if i >= maxItems {
 55			break
 56		}
 57
 58		icon, description := iconAndDescription(l, t, lspStates)
 59
 60		// Calculate diagnostic counts if we have LSP clients
 61		var extraContent string
 62		if lspClients != nil {
 63			if client, ok := lspClients.Get(l.Name); ok {
 64				counts := client.GetDiagnosticCounts()
 65				errs := []string{}
 66				if counts.Error > 0 {
 67					errs = append(errs, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("%s %d", styles.ErrorIcon, counts.Error)))
 68				}
 69				if counts.Warning > 0 {
 70					errs = append(errs, t.S().Base.Foreground(t.Warning).Render(fmt.Sprintf("%s %d", styles.WarningIcon, counts.Warning)))
 71				}
 72				if counts.Hint > 0 {
 73					errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.HintIcon, counts.Hint)))
 74				}
 75				if counts.Information > 0 {
 76					errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.InfoIcon, counts.Information)))
 77				}
 78				extraContent = strings.Join(errs, " ")
 79			}
 80		}
 81
 82		lspList = append(lspList,
 83			core.Status(
 84				core.StatusOpts{
 85					Icon:         icon.String(),
 86					Title:        l.Name,
 87					Description:  description,
 88					ExtraContent: extraContent,
 89				},
 90				opts.MaxWidth,
 91			),
 92		)
 93	}
 94
 95	return lspList
 96}
 97
 98func iconAndDescription(l config.LSP, t *styles.Theme, states map[string]app.LSPClientInfo) (lipgloss.Style, string) {
 99	if l.LSP.Disabled {
100		return t.ItemOfflineIcon.Foreground(t.FgMuted), t.S().Subtle.Render("disabled")
101	}
102
103	info := states[l.Name]
104	switch info.State {
105	case lsp.StateStarting:
106		return t.ItemBusyIcon, t.S().Subtle.Render("starting...")
107	case lsp.StateReady:
108		return t.ItemOnlineIcon, ""
109	case lsp.StateError:
110		description := t.S().Subtle.Render("error")
111		if info.Error != nil {
112			description = t.S().Subtle.Render(fmt.Sprintf("error: %s", info.Error.Error()))
113		}
114		return t.ItemErrorIcon, description
115	case lsp.StateDisabled:
116		return t.ItemOfflineIcon.Foreground(t.FgMuted), t.S().Subtle.Render("inactive")
117	default:
118		return t.ItemOfflineIcon, ""
119	}
120}
121
122// RenderLSPBlock renders a complete LSP block with optional truncation indicator.
123func RenderLSPBlock(lspClients *csync.Map[string, *lsp.Client], opts RenderOptions, showTruncationIndicator bool) string {
124	t := styles.CurrentTheme()
125	lspList := RenderLSPList(lspClients, opts)
126
127	// Add truncation indicator if needed
128	if showTruncationIndicator && opts.MaxItems > 0 {
129		lspConfigs := config.Get().LSP.Sorted()
130		if len(lspConfigs) > opts.MaxItems {
131			remaining := len(lspConfigs) - opts.MaxItems
132			if remaining == 1 {
133				lspList = append(lspList, t.S().Base.Foreground(t.FgMuted).Render("…"))
134			} else {
135				lspList = append(lspList,
136					t.S().Base.Foreground(t.FgSubtle).Render(fmt.Sprintf("…and %d more", remaining)),
137				)
138			}
139		}
140	}
141
142	content := lipgloss.JoinVertical(lipgloss.Left, lspList...)
143	if opts.MaxWidth > 0 {
144		return lipgloss.NewStyle().Width(opts.MaxWidth).Render(content)
145	}
146	return content
147}