1package app
2
3import (
4 "context"
5 "time"
6
7 "github.com/kujtimiihoxha/termai/internal/config"
8 "github.com/kujtimiihoxha/termai/internal/logging"
9 "github.com/kujtimiihoxha/termai/internal/lsp"
10 "github.com/kujtimiihoxha/termai/internal/lsp/watcher"
11)
12
13func (app *App) initLSPClients(ctx context.Context) {
14 cfg := config.Get()
15
16 // Initialize LSP clients
17 for name, clientConfig := range cfg.LSP {
18 app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
19 }
20}
21
22// 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) {
24 // Create a specific context for initialization with a timeout
25
26 // Create the LSP client
27 lspClient, err := lsp.NewClient(ctx, command, args...)
28 if err != nil {
29 logging.Error("Failed to create LSP client for", name, err)
30 return
31
32 }
33
34 initCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
35 defer cancel()
36 // Initialize with the initialization context
37 _, err = lspClient.InitializeLSPClient(initCtx, config.WorkingDirectory())
38 if err != nil {
39 logging.Error("Initialize failed", "name", name, "error", err)
40 // Clean up the client to prevent resource leaks
41 lspClient.Close()
42 return
43 }
44
45 // Create a child context that can be canceled when the app is shutting down
46 watchCtx, cancelFunc := context.WithCancel(ctx)
47 workspaceWatcher := watcher.NewWorkspaceWatcher(lspClient)
48
49 // Store the cancel function to be called during cleanup
50 app.cancelFuncsMutex.Lock()
51 app.watcherCancelFuncs = append(app.watcherCancelFuncs, cancelFunc)
52 app.cancelFuncsMutex.Unlock()
53
54 // Add the watcher to a WaitGroup to track active goroutines
55 app.watcherWG.Add(1)
56
57 // Add to map with mutex protection before starting goroutine
58 app.clientsMutex.Lock()
59 app.LSPClients[name] = lspClient
60 app.clientsMutex.Unlock()
61
62 go app.runWorkspaceWatcher(watchCtx, name, workspaceWatcher)
63}
64
65// runWorkspaceWatcher executes the workspace watcher for an LSP client
66func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
67 defer app.watcherWG.Done()
68 defer logging.RecoverPanic("LSP-"+name, func() {
69 // Try to restart the client
70 app.restartLSPClient(ctx, name)
71 })
72
73 workspaceWatcher.WatchWorkspace(ctx, config.WorkingDirectory())
74 logging.Info("Workspace watcher stopped", "client", name)
75}
76
77// restartLSPClient attempts to restart a crashed or failed LSP client
78func (app *App) restartLSPClient(ctx context.Context, name string) {
79 // Get the original configuration
80 cfg := config.Get()
81 clientConfig, exists := cfg.LSP[name]
82 if !exists {
83 logging.Error("Cannot restart client, configuration not found", "client", name)
84 return
85 }
86
87 // Clean up the old client if it exists
88 app.clientsMutex.Lock()
89 oldClient, exists := app.LSPClients[name]
90 if exists {
91 delete(app.LSPClients, name) // Remove from map before potentially slow shutdown
92 }
93 app.clientsMutex.Unlock()
94
95 if exists && oldClient != nil {
96 // Try to shut it down gracefully, but don't block on errors
97 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
98 _ = oldClient.Shutdown(shutdownCtx)
99 cancel()
100 }
101
102 // Create a new client using the shared function
103 app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
104 logging.Info("Successfully restarted LSP client", "client", name)
105}