diff --git a/internal/lsp/client.go b/internal/lsp/client.go index 3d7f07f977b1ef61bd20652d6bdc6a9b3643383a..18bc1ed954acbf4a0397dfa497bd2133513bb090 100644 --- a/internal/lsp/client.go +++ b/internal/lsp/client.go @@ -324,6 +324,9 @@ type OpenFileInfo struct { // HandlesFile checks if this LSP client handles the given file based on its // extension and whether it's within the working directory. func (c *Client) HandlesFile(path string) bool { + if c == nil { + return false + } if !fsext.HasPrefix(path, c.cwd) { slog.Debug("File outside workspace", "name", c.name, "file", path, "workDir", c.cwd) return false @@ -364,6 +367,9 @@ func (c *Client) OpenFile(ctx context.Context, filepath string) error { // NotifyChange notifies the server about a file change. func (c *Client) NotifyChange(ctx context.Context, filepath string) error { + if c == nil { + return nil + } uri := string(protocol.URIFromPath(filepath)) content, err := os.ReadFile(filepath) @@ -420,12 +426,18 @@ func (c *Client) GetFileDiagnostics(uri protocol.DocumentURI) []protocol.Diagnos // GetDiagnostics returns all diagnostics for all files. func (c *Client) GetDiagnostics() map[protocol.DocumentURI][]protocol.Diagnostic { + if c == nil { + return nil + } 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 { + if c == nil { + return DiagnosticCounts{} + } currentVersion := c.diagnostics.Version() c.diagCountsMu.Lock() @@ -459,6 +471,9 @@ func (c *Client) GetDiagnosticCounts() DiagnosticCounts { // OpenFileOnDemand opens a file only if it's not already open. func (c *Client) OpenFileOnDemand(ctx context.Context, filepath string) error { + if c == nil { + return nil + } // Check if the file is already open if c.IsFileOpen(filepath) { return nil @@ -501,6 +516,9 @@ func (c *Client) openKeyConfigFiles(ctx context.Context) { // WaitForDiagnostics waits until diagnostics change or the timeout is reached. func (c *Client) WaitForDiagnostics(ctx context.Context, d time.Duration) { + if c == nil { + return + } ticker := time.NewTicker(200 * time.Millisecond) defer ticker.Stop() timeout := time.After(d) diff --git a/internal/lsp/client_test.go b/internal/lsp/client_test.go index e444354800b6e41ad26a1d8f0d8ffb097a1f060a..62979144babd0abc77006a621301809c997420a4 100644 --- a/internal/lsp/client_test.go +++ b/internal/lsp/client_test.go @@ -3,9 +3,11 @@ package lsp import ( "context" "testing" + "time" "github.com/charmbracelet/crush/internal/config" "github.com/charmbracelet/crush/internal/env" + "github.com/stretchr/testify/require" ) func TestClient(t *testing.T) { @@ -55,3 +57,16 @@ func TestClient(t *testing.T) { t.Logf("Close failed as expected with dummy command: %v", err) } } + +func TestNilClient(t *testing.T) { + t.Parallel() + + var c *Client + + require.False(t, c.HandlesFile("/some/file.go")) + require.Equal(t, DiagnosticCounts{}, c.GetDiagnosticCounts()) + require.Nil(t, c.GetDiagnostics()) + require.Nil(t, c.OpenFileOnDemand(context.Background(), "/some/file.go")) + require.Nil(t, c.NotifyChange(context.Background(), "/some/file.go")) + c.WaitForDiagnostics(context.Background(), time.Second) +}