lsp.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. package app
  2. import (
  3. "context"
  4. "time"
  5. "github.com/kujtimiihoxha/opencode/internal/config"
  6. "github.com/kujtimiihoxha/opencode/internal/logging"
  7. "github.com/kujtimiihoxha/opencode/internal/lsp"
  8. "github.com/kujtimiihoxha/opencode/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. // Create the LSP client
  21. lspClient, err := lsp.NewClient(ctx, command, args...)
  22. if err != nil {
  23. logging.Error("Failed to create LSP client for", name, err)
  24. return
  25. }
  26. initCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
  27. defer cancel()
  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 logging.RecoverPanic("LSP-"+name, func() {
  55. // Try to restart the client
  56. app.restartLSPClient(ctx, name)
  57. })
  58. workspaceWatcher.WatchWorkspace(ctx, config.WorkingDirectory())
  59. logging.Info("Workspace watcher stopped", "client", name)
  60. }
  61. // restartLSPClient attempts to restart a crashed or failed LSP client
  62. func (app *App) restartLSPClient(ctx context.Context, name string) {
  63. // Get the original configuration
  64. cfg := config.Get()
  65. clientConfig, exists := cfg.LSP[name]
  66. if !exists {
  67. logging.Error("Cannot restart client, configuration not found", "client", name)
  68. return
  69. }
  70. // Clean up the old client if it exists
  71. app.clientsMutex.Lock()
  72. oldClient, exists := app.LSPClients[name]
  73. if exists {
  74. delete(app.LSPClients, name) // Remove from map before potentially slow shutdown
  75. }
  76. app.clientsMutex.Unlock()
  77. if exists && oldClient != nil {
  78. // Try to shut it down gracefully, but don't block on errors
  79. shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  80. _ = oldClient.Shutdown(shutdownCtx)
  81. cancel()
  82. }
  83. // Create a new client using the shared function
  84. app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
  85. logging.Info("Successfully restarted LSP client", "client", name)
  86. }