@@ -21,6 +21,14 @@ import (
"github.com/charmbracelet/x/powernap/pkg/transport"
)
+// DiagnosticCounts holds the count of diagnostics by severity.
+type DiagnosticCounts struct {
+ Error int
+ Warning int
+ Information int
+ Hint int
+}
+
type Client struct {
client *powernap.Client
name string
@@ -37,6 +45,10 @@ type Client struct {
// Diagnostic cache
diagnostics *csync.VersionedMap[protocol.DocumentURI, []protocol.Diagnostic]
+ // Cached diagnostic counts to avoid map copy on every UI render.
+ diagCountsCache DiagnosticCounts
+ diagCountsVersion uint64
+
// Files are currently opened by the LSP
openFiles *csync.Map[string, *OpenFileInfo]
@@ -353,6 +365,36 @@ func (c *Client) GetDiagnostics() map[protocol.DocumentURI][]protocol.Diagnostic
return c.diagnostics.Copy()
}
+// GetDiagnosticCounts returns cached diagnostic counts by severity.
+// Uses the VersionedMap version to avoid recomputing on every call.
+func (c *Client) GetDiagnosticCounts() DiagnosticCounts {
+ currentVersion := c.diagnostics.Version()
+ if currentVersion == c.diagCountsVersion {
+ return c.diagCountsCache
+ }
+
+ // Recompute counts.
+ counts := DiagnosticCounts{}
+ for _, diags := range c.diagnostics.Seq2() {
+ for _, diag := range diags {
+ switch diag.Severity {
+ case protocol.SeverityError:
+ counts.Error++
+ case protocol.SeverityWarning:
+ counts.Warning++
+ case protocol.SeverityInformation:
+ counts.Information++
+ case protocol.SeverityHint:
+ counts.Hint++
+ }
+ }
+ }
+
+ c.diagCountsCache = counts
+ c.diagCountsVersion = currentVersion
+ return counts
+}
+
// OpenFileOnDemand opens a file only if it's not already open.
func (c *Client) OpenFileOnDemand(ctx context.Context, filepath string) error {
// Check if the file is already open
@@ -15,7 +15,6 @@ import (
"github.com/charmbracelet/crush/internal/tui/styles"
"github.com/charmbracelet/crush/internal/tui/util"
"github.com/charmbracelet/x/ansi"
- "github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
)
type Header interface {
@@ -106,13 +105,7 @@ func (h *header) details(availWidth int) string {
errorCount := 0
for l := range h.lspClients.Seq() {
- for _, diagnostics := range l.GetDiagnostics() {
- for _, diagnostic := range diagnostics {
- if diagnostic.Severity == protocol.SeverityError {
- errorCount++
- }
- }
- }
+ errorCount += l.GetDiagnosticCounts().Error
}
if errorCount > 0 {
@@ -11,7 +11,6 @@ import (
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/tui/components/core"
"github.com/charmbracelet/crush/internal/tui/styles"
- "github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
)
// RenderOptions contains options for rendering LSP lists.
@@ -61,36 +60,23 @@ func RenderLSPList(lspClients *csync.Map[string, *lsp.Client], opts RenderOption
// Calculate diagnostic counts if we have LSP clients
var extraContent string
if lspClients != nil {
- lspErrs := map[protocol.DiagnosticSeverity]int{
- protocol.SeverityError: 0,
- protocol.SeverityWarning: 0,
- protocol.SeverityHint: 0,
- protocol.SeverityInformation: 0,
- }
if client, ok := lspClients.Get(l.Name); ok {
- for _, diagnostics := range client.GetDiagnostics() {
- for _, diagnostic := range diagnostics {
- if severity, ok := lspErrs[diagnostic.Severity]; ok {
- lspErrs[diagnostic.Severity] = severity + 1
- }
- }
+ counts := client.GetDiagnosticCounts()
+ errs := []string{}
+ if counts.Error > 0 {
+ errs = append(errs, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("%s %d", styles.ErrorIcon, counts.Error)))
}
+ if counts.Warning > 0 {
+ errs = append(errs, t.S().Base.Foreground(t.Warning).Render(fmt.Sprintf("%s %d", styles.WarningIcon, counts.Warning)))
+ }
+ if counts.Hint > 0 {
+ errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.HintIcon, counts.Hint)))
+ }
+ if counts.Information > 0 {
+ errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.InfoIcon, counts.Information)))
+ }
+ extraContent = strings.Join(errs, " ")
}
-
- errs := []string{}
- if lspErrs[protocol.SeverityError] > 0 {
- errs = append(errs, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("%s %d", styles.ErrorIcon, lspErrs[protocol.SeverityError])))
- }
- if lspErrs[protocol.SeverityWarning] > 0 {
- errs = append(errs, t.S().Base.Foreground(t.Warning).Render(fmt.Sprintf("%s %d", styles.WarningIcon, lspErrs[protocol.SeverityWarning])))
- }
- if lspErrs[protocol.SeverityHint] > 0 {
- errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.HintIcon, lspErrs[protocol.SeverityHint])))
- }
- if lspErrs[protocol.SeverityInformation] > 0 {
- errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.InfoIcon, lspErrs[protocol.SeverityInformation])))
- }
- extraContent = strings.Join(errs, " ")
}
lspList = append(lspList,
@@ -29,19 +29,12 @@ func (m *UI) lspInfo(width, maxItems int, isSection bool) string {
if !ok {
continue
}
+ counts := client.GetDiagnosticCounts()
lspErrs := map[protocol.DiagnosticSeverity]int{
- protocol.SeverityError: 0,
- protocol.SeverityWarning: 0,
- protocol.SeverityHint: 0,
- protocol.SeverityInformation: 0,
- }
-
- for _, diagnostics := range client.GetDiagnostics() {
- for _, diagnostic := range diagnostics {
- if severity, ok := lspErrs[diagnostic.Severity]; ok {
- lspErrs[diagnostic.Severity] = severity + 1
- }
- }
+ protocol.SeverityError: counts.Error,
+ protocol.SeverityWarning: counts.Warning,
+ protocol.SeverityHint: counts.Hint,
+ protocol.SeverityInformation: counts.Information,
}
lsps = append(lsps, LSPInfo{LSPClientInfo: state, Diagnostics: lspErrs})