@@ -21,14 +21,16 @@ import (
21 "github.com/sourcegraph/jsonrpc2"
21 "github.com/sourcegraph/jsonrpc2"
24−var unavailable = csync.NewMap[string, struct{}]()
24+const unavailableRetryDelay = 30 * time.Second
26// Manager handles lazy initialization of LSP clients based on file types.
26// Manager handles lazy initialization of LSP clients based on file types.
27type Manager struct {
27type Manager struct {
28− clients *csync.Map[string, *Client]
28+ clients *csync.Map[string, *Client]
29+ unavailable *csync.Map[string, time.Time]
29− cfg *config.ConfigStore
30+ cfg *config.ConfigStore
30− manager *powernapconfig.Manager
31+ manager *powernapconfig.Manager
31− callback func(name string, client *Client)
32+ callback func(name string, client *Client)
34// NewManager creates a new LSP manager service.
36// NewManager creates a new LSP manager service.
@@ -59,10 +61,12 @@ func NewManager(cfg *config.ConfigStore) *Manager {
61 return &Manager{
63 return &Manager{
62− clients: csync.NewMap[string, *Client](),
64+ clients: csync.NewMap[string, *Client](),
65+ unavailable: csync.NewMap[string, time.Time](),
63− cfg: cfg,
66+ cfg: cfg,
64− manager: manager,
67+ manager: manager,
65− callback: func(string, *Client) {}, // default no-op callback
68+ callback: func(string, *Client) {}, // default no-op callback
@@ -155,10 +159,6 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
158− if _, exists := unavailable.Get(name); exists {
162 if client, ok := s.clients.Get(name); ok {
162 if client, ok := s.clients.Get(name); ok {
163 switch client.GetServerState() {
163 switch client.GetServerState() {
164 case StateReady, StateStarting, StateDisabled:
164 case StateReady, StateStarting, StateDisabled:
@@ -169,11 +169,15 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
171 if !isUserConfigured {
171 if !isUserConfigured {
172+ if s.recentlyUnavailable(name) {
172 if _, err := exec.LookPath(server.Command); err != nil {
175 if _, err := exec.LookPath(server.Command); err != nil {
173 slog.Debug("LSP server not installed, skipping", "name", name, "command", server.Command)
176 slog.Debug("LSP server not installed, skipping", "name", name, "command", server.Command)
174− unavailable.Set(name, struct{}{})
177+ s.markUnavailable(name)
180+ s.clearUnavailable(name)
177 if skipAutoStartCommands[server.Command] {
181 if skipAutoStartCommands[server.Command] {
178 slog.Debug("LSP command too generic for auto-start, skipping", "name", name, "command", server.Command)
182 slog.Debug("LSP command too generic for auto-start, skipping", "name", name, "command", server.Command)
@@ -255,6 +259,26 @@ func (s *Manager) isUserConfigured(name string) bool {
255 return ok && !cfg.Disabled
259 return ok && !cfg.Disabled
262+func (s *Manager) recentlyUnavailable(name string) bool {
263+ lastUnavailableAt, exists := s.unavailable.Get(name)
267+ if s.now().Sub(lastUnavailableAt) < unavailableRetryDelay {
270+ s.unavailable.Del(name)
274+func (s *Manager) markUnavailable(name string) {
275+ s.unavailable.Set(name, s.now())
278+func (s *Manager) clearUnavailable(name string) {
279+ s.unavailable.Del(name)
258func (s *Manager) buildConfig(name string, server *powernapconfig.ServerConfig) config.LSPConfig {
282func (s *Manager) buildConfig(name string, server *powernapconfig.ServerConfig) config.LSPConfig {
259 cfg := config.LSPConfig{
283 cfg := config.LSPConfig{
260 Command: server.Command,
284 Command: server.Command,