lsp.go 3.5 KB

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