@@ -10,10 +10,9 @@ import (
10 "github.com/charmbracelet/crush/internal/lsp/watcher"
10 "github.com/charmbracelet/crush/internal/lsp/watcher"
13+// initLSPClients initializes LSP clients.
13func (app *App) initLSPClients(ctx context.Context) {
14func (app *App) initLSPClients(ctx context.Context) {
14− // Initialize LSP clients
15 for name, clientConfig := range app.config.LSP {
15 for name, clientConfig := range app.config.LSP {
16− // Start each client initialization in its own goroutine
17 go app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
16 go app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
19 slog.Info("LSP clients initialization started in background")
18 slog.Info("LSP clients initialization started in background")
@@ -21,68 +20,68 @@ func (app *App) initLSPClients(ctx context.Context) {
22// createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
21// createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
23func (app *App) createAndStartLSPClient(ctx context.Context, name string, command string, args ...string) {
22func (app *App) createAndStartLSPClient(ctx context.Context, name string, command string, args ...string) {
24− // Create a specific context for initialization with a timeout
25 slog.Info("Creating LSP client", "name", name, "command", command, "args", args)
23 slog.Info("Creating LSP client", "name", name, "command", command, "args", args)
27− // Create the LSP client
25+ // Create LSP client.
28 lspClient, err := lsp.NewClient(ctx, command, args...)
26 lspClient, err := lsp.NewClient(ctx, command, args...)
29 if err != nil {
27 if err != nil {
30 slog.Error("Failed to create LSP client for", name, err)
28 slog.Error("Failed to create LSP client for", name, err)
34− // Create a longer timeout for initialization (some servers take time to start)
32+ // Increase initialization timeout as some servers take more time to start.
35 initCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
33 initCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
36 defer cancel()
34 defer cancel()
38− // Initialize with the initialization context
36+ // Initialize LSP client.
39 _, err = lspClient.InitializeLSPClient(initCtx, app.config.WorkingDir())
37 _, err = lspClient.InitializeLSPClient(initCtx, app.config.WorkingDir())
40 if err != nil {
38 if err != nil {
41 slog.Error("Initialize failed", "name", name, "error", err)
39 slog.Error("Initialize failed", "name", name, "error", err)
42− // Clean up the client to prevent resource leaks
43 lspClient.Close()
40 lspClient.Close()
47− // Wait for the server to be ready
44+ // Wait for the server to be ready.
48 if err := lspClient.WaitForServerReady(initCtx); err != nil {
45 if err := lspClient.WaitForServerReady(initCtx); err != nil {
49 slog.Error("Server failed to become ready", "name", name, "error", err)
46 slog.Error("Server failed to become ready", "name", name, "error", err)
47+ // Server never reached a ready state, but let's continue anyway, as
50− // We'll continue anyway, as some functionality might still work
48+ // some functionality might still work.
51 lspClient.SetServerState(lsp.StateError)
49 lspClient.SetServerState(lsp.StateError)
51+ // Server reached a ready state scuccessfully.
53 slog.Info("LSP server is ready", "name", name)
52 slog.Info("LSP server is ready", "name", name)
54 lspClient.SetServerState(lsp.StateReady)
53 lspClient.SetServerState(lsp.StateReady)
57 slog.Info("LSP client initialized", "name", name)
56 slog.Info("LSP client initialized", "name", name)
59− // Create a child context that can be canceled when the app is shutting down
58+ // Create a child context that can be canceled when the app is shutting
60 watchCtx, cancelFunc := context.WithCancel(ctx)
60 watchCtx, cancelFunc := context.WithCancel(ctx)
62− // Create the workspace watcher
62+ // Create the workspace watcher.
63 workspaceWatcher := watcher.NewWorkspaceWatcher(name, lspClient)
63 workspaceWatcher := watcher.NewWorkspaceWatcher(name, lspClient)
65− // Store the cancel function to be called during cleanup
65+ // Store the cancel function to be called during cleanup.
66 app.cancelFuncsMutex.Lock()
66 app.cancelFuncsMutex.Lock()
67 app.watcherCancelFuncs = append(app.watcherCancelFuncs, cancelFunc)
67 app.watcherCancelFuncs = append(app.watcherCancelFuncs, cancelFunc)
68 app.cancelFuncsMutex.Unlock()
68 app.cancelFuncsMutex.Unlock()
70− // Add the watcher to a WaitGroup to track active goroutines
71− app.lspWatcherWG.Add(1)
73 // Add to map with mutex protection before starting goroutine
70 // Add to map with mutex protection before starting goroutine
74 app.clientsMutex.Lock()
71 app.clientsMutex.Lock()
75 app.LSPClients[name] = lspClient
72 app.LSPClients[name] = lspClient
76 app.clientsMutex.Unlock()
73 app.clientsMutex.Unlock()
75+ // Run workspace watcher.
76+ app.lspWatcherWG.Add(1)
78 go app.runWorkspaceWatcher(watchCtx, name, workspaceWatcher)
77 go app.runWorkspaceWatcher(watchCtx, name, workspaceWatcher)
81−// runWorkspaceWatcher executes the workspace watcher for an LSP client
80+// runWorkspaceWatcher executes the workspace watcher for an LSP client.
82func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
81func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
83 defer app.lspWatcherWG.Done()
82 defer app.lspWatcherWG.Done()
84 defer log.RecoverPanic("LSP-"+name, func() {
83 defer log.RecoverPanic("LSP-"+name, func() {
85− // Try to restart the client
84+ // Try to restart the client.
86 app.restartLSPClient(ctx, name)
85 app.restartLSPClient(ctx, name)
@@ -90,31 +89,32 @@ func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceW
90 slog.Info("Workspace watcher stopped", "client", name)
89 slog.Info("Workspace watcher stopped", "client", name)
93−// restartLSPClient attempts to restart a crashed or failed LSP client
92+// restartLSPClient attempts to restart a crashed or failed LSP client.
94func (app *App) restartLSPClient(ctx context.Context, name string) {
93func (app *App) restartLSPClient(ctx context.Context, name string) {
95− // Get the original configuration
94+ // Get the original configuration.
96 clientConfig, exists := app.config.LSP[name]
95 clientConfig, exists := app.config.LSP[name]
97 if !exists {
96 if !exists {
98 slog.Error("Cannot restart client, configuration not found", "client", name)
97 slog.Error("Cannot restart client, configuration not found", "client", name)
102− // Clean up the old client if it exists
101+ // Clean up the old client if it exists.
103 app.clientsMutex.Lock()
102 app.clientsMutex.Lock()
104 oldClient, exists := app.LSPClients[name]
103 oldClient, exists := app.LSPClients[name]
105 if exists {
104 if exists {
106− delete(app.LSPClients, name) // Remove from map before potentially slow shutdown
105+ // Remove from map before potentially slow shutdown.
106+ delete(app.LSPClients, name)
108 app.clientsMutex.Unlock()
108 app.clientsMutex.Unlock()
110 if exists && oldClient != nil {
110 if exists && oldClient != nil {
111− // Try to shut it down gracefully, but don't block on errors
111+ // Try to shut down client gracefully, but don't block on errors.
112 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
112 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
113 _ = oldClient.Shutdown(shutdownCtx)
113 _ = oldClient.Shutdown(shutdownCtx)
117− // Create a new client using the shared function
117+ // Create a new client using the shared function.
118 app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
118 app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
119 slog.Info("Successfully restarted LSP client", "client", name)
119 slog.Info("Successfully restarted LSP client", "client", name)