lsp.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package app
  2. import (
  3. "context"
  4. "log/slog"
  5. "time"
  6. "github.com/charmbracelet/crush/internal/log"
  7. "github.com/charmbracelet/crush/internal/lsp"
  8. "github.com/charmbracelet/crush/internal/lsp/watcher"
  9. )
  10. // initLSPClients initializes LSP clients.
  11. func (app *App) initLSPClients(ctx context.Context) {
  12. for name, clientConfig := range app.config.LSP {
  13. go app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
  14. }
  15. slog.Info("LSP clients initialization started in background")
  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. slog.Info("Creating LSP client", "name", name, "command", command, "args", args)
  20. // Create LSP client.
  21. lspClient, err := lsp.NewClient(ctx, command, args...)
  22. if err != nil {
  23. slog.Error("Failed to create LSP client for", name, err)
  24. return
  25. }
  26. // Increase initialization timeout as some servers take more time to start.
  27. initCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
  28. defer cancel()
  29. // Initialize LSP client.
  30. _, err = lspClient.InitializeLSPClient(initCtx, app.config.WorkingDir())
  31. if err != nil {
  32. slog.Error("Initialize failed", "name", name, "error", err)
  33. lspClient.Close()
  34. return
  35. }
  36. // Wait for the server to be ready.
  37. if err := lspClient.WaitForServerReady(initCtx); err != nil {
  38. slog.Error("Server failed to become ready", "name", name, "error", err)
  39. // Server never reached a ready state, but let's continue anyway, as
  40. // some functionality might still work.
  41. lspClient.SetServerState(lsp.StateError)
  42. } else {
  43. // Server reached a ready state scuccessfully.
  44. slog.Info("LSP server is ready", "name", name)
  45. lspClient.SetServerState(lsp.StateReady)
  46. }
  47. slog.Info("LSP client initialized", "name", name)
  48. // Create a child context that can be canceled when the app is shutting
  49. // down.
  50. watchCtx, cancelFunc := context.WithCancel(ctx)
  51. // Create the workspace watcher.
  52. workspaceWatcher := watcher.NewWorkspaceWatcher(name, lspClient)
  53. // Store the cancel function to be called during cleanup.
  54. app.watcherCancelFuncs.Append(cancelFunc)
  55. // Add to map with mutex protection before starting goroutine
  56. app.clientsMutex.Lock()
  57. app.LSPClients[name] = lspClient
  58. app.clientsMutex.Unlock()
  59. // Run workspace watcher.
  60. app.lspWatcherWG.Add(1)
  61. go app.runWorkspaceWatcher(watchCtx, name, workspaceWatcher)
  62. }
  63. // runWorkspaceWatcher executes the workspace watcher for an LSP client.
  64. func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
  65. defer app.lspWatcherWG.Done()
  66. defer log.RecoverPanic("LSP-"+name, func() {
  67. // Try to restart the client.
  68. app.restartLSPClient(ctx, name)
  69. })
  70. workspaceWatcher.WatchWorkspace(ctx, app.config.WorkingDir())
  71. slog.Info("Workspace watcher stopped", "client", name)
  72. }
  73. // restartLSPClient attempts to restart a crashed or failed LSP client.
  74. func (app *App) restartLSPClient(ctx context.Context, name string) {
  75. // Get the original configuration.
  76. clientConfig, exists := app.config.LSP[name]
  77. if !exists {
  78. slog.Error("Cannot restart client, configuration not found", "client", name)
  79. return
  80. }
  81. // Clean up the old client if it exists.
  82. app.clientsMutex.Lock()
  83. oldClient, exists := app.LSPClients[name]
  84. if exists {
  85. // Remove from map before potentially slow shutdown.
  86. delete(app.LSPClients, name)
  87. }
  88. app.clientsMutex.Unlock()
  89. if exists && oldClient != nil {
  90. // Try to shut down client gracefully, but don't block on errors.
  91. shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  92. _ = oldClient.Shutdown(shutdownCtx)
  93. cancel()
  94. }
  95. // Create a new client using the shared function.
  96. app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
  97. slog.Info("Successfully restarted LSP client", "client", name)
  98. }