lsp.go

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