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}