@@ -21,14 +21,16 @@ import (
2121 "github.com/sourcegraph/jsonrpc2"
2222)
2323
24−var unavailable = csync.NewMap[string, struct{}]()
24+const unavailableRetryDelay = 30 * time.Second
2525
2626// Manager handles lazy initialization of LSP clients based on file types.
2727type Manager struct {
28− clients *csync.Map[string, *Client]
29− cfg *config.ConfigStore
30− manager *powernapconfig.Manager
31− callback func(name string, client *Client)
28+ clients *csync.Map[string, *Client]
29+ unavailable *csync.Map[string, time.Time]
30+ cfg *config.ConfigStore
31+ manager *powernapconfig.Manager
32+ callback func(name string, client *Client)
33+ now func() time.Time
3234}
3335
3436// NewManager creates a new LSP manager service.
@@ -59,10 +61,12 @@ func NewManager(cfg *config.ConfigStore) *Manager {
5961 }
6062
6163 return &Manager{
62− clients: csync.NewMap[string, *Client](),
63− cfg: cfg,
64− manager: manager,
65− callback: func(string, *Client) {}, // default no-op callback
64+ clients: csync.NewMap[string, *Client](),
65+ unavailable: csync.NewMap[string, time.Time](),
66+ cfg: cfg,
67+ manager: manager,
68+ callback: func(string, *Client) {}, // default no-op callback
69+ now: time.Now,
6670 }
6771}
6872
@@ -155,10 +159,6 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
155159 return
156160 }
157161
158− if _, exists := unavailable.Get(name); exists {
159− return
160− }
161−
162162 if client, ok := s.clients.Get(name); ok {
163163 switch client.GetServerState() {
164164 case StateReady, StateStarting, StateDisabled:
@@ -169,11 +169,15 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
169169 }
170170
171171 if !isUserConfigured {
172+ if s.recentlyUnavailable(name) {
173+ return
174+ }
172175 if _, err := exec.LookPath(server.Command); err != nil {
173176 slog.Debug("LSP server not installed, skipping", "name", name, "command", server.Command)
174− unavailable.Set(name, struct{}{})
177+ s.markUnavailable(name)
175178 return
176179 }
180+ s.clearUnavailable(name)
177181 if skipAutoStartCommands[server.Command] {
178182 slog.Debug("LSP command too generic for auto-start, skipping", "name", name, "command", server.Command)
179183 return
@@ -255,6 +259,26 @@ func (s *Manager) isUserConfigured(name string) bool {
255259 return ok && !cfg.Disabled
256260}
257261
262+func (s *Manager) recentlyUnavailable(name string) bool {
263+ lastUnavailableAt, exists := s.unavailable.Get(name)
264+ if !exists {
265+ return false
266+ }
267+ if s.now().Sub(lastUnavailableAt) < unavailableRetryDelay {
268+ return true
269+ }
270+ s.unavailable.Del(name)
271+ return false
272+}
273+
274+func (s *Manager) markUnavailable(name string) {
275+ s.unavailable.Set(name, s.now())
276+}
277+
278+func (s *Manager) clearUnavailable(name string) {
279+ s.unavailable.Del(name)
280+}
281+
258282func (s *Manager) buildConfig(name string, server *powernapconfig.ServerConfig) config.LSPConfig {
259283 cfg := config.LSPConfig{
260284 Command: server.Command,