lsp.go

 1package app
 2
 3import (
 4	"context"
 5	"log/slog"
 6	"time"
 7
 8	"github.com/charmbracelet/crush/internal/config"
 9	"github.com/charmbracelet/crush/internal/lsp"
10)
11
12// initLSPClients initializes LSP clients.
13func (app *App) initLSPClients(ctx context.Context) {
14	for name, clientConfig := range app.config.LSP {
15		if clientConfig.Disabled {
16			slog.Info("Skipping disabled LSP client", "name", name)
17			continue
18		}
19		go app.createAndStartLSPClient(ctx, name, clientConfig)
20	}
21	slog.Info("LSP clients initialization started in background")
22}
23
24// createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
25func (app *App) createAndStartLSPClient(ctx context.Context, name string, lspCfg config.LSPConfig) {
26	slog.Info("Creating LSP client", "name", name, "command", lspCfg.Command, "fileTypes", lspCfg.FileTypes, "args", lspCfg.Args)
27
28	// Check if any root markers exist in the working directory (config now has defaults)
29	if !lsp.HasRootMarkers(app.config.WorkingDir(), lspCfg.RootMarkers) {
30		slog.Info("Skipping LSP client - no root markers found", "name", name, "rootMarkers", lspCfg.RootMarkers)
31		app.updateLSPState(name, lsp.StateDisabled, nil, 0)
32		return
33	}
34
35	// Update state to starting
36	app.updateLSPState(name, lsp.StateStarting, nil, 0)
37
38	// Create LSP client.
39	lspClient, err := lsp.New(ctx, app.config, name, lspCfg)
40	if err != nil {
41		slog.Error("Failed to create LSP client for", name, err)
42		app.updateLSPState(name, lsp.StateError, err, 0)
43		return
44	}
45
46	// Set diagnostics callback
47	lspClient.SetDiagnosticsCallback(app.updateLSPDiagnostics)
48
49	// Increase initialization timeout as some servers take more time to start.
50	initCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
51	defer cancel()
52
53	// Initialize LSP client.
54	_, err = lspClient.Initialize(initCtx, app.config.WorkingDir())
55	if err != nil {
56		slog.Error("Initialize failed", "name", name, "error", err)
57		app.updateLSPState(name, lsp.StateError, err, 0)
58		lspClient.Close(ctx)
59		return
60	}
61
62	// Wait for the server to be ready.
63	if err := lspClient.WaitForServerReady(initCtx); err != nil {
64		slog.Error("Server failed to become ready", "name", name, "error", err)
65		// Server never reached a ready state, but let's continue anyway, as
66		// some functionality might still work.
67		lspClient.SetServerState(lsp.StateError)
68		app.updateLSPState(name, lsp.StateError, err, 0)
69	} else {
70		// Server reached a ready state scuccessfully.
71		slog.Info("LSP server is ready", "name", name)
72		lspClient.SetServerState(lsp.StateReady)
73		app.updateLSPState(name, lsp.StateReady, nil, 0)
74	}
75
76	slog.Info("LSP client initialized", "name", name)
77
78	// Add to map with mutex protection before starting goroutine
79	app.LSPClients.Set(name, lspClient)
80}