@@ -40,6 +40,10 @@ type Client struct {
4040 // Configuration for this LSP client
4141 config config.LSPConfig
4242
43+ // Original context and resolver for recreating the client
44+ ctx context.Context
45+ resolver config.VariableResolver
46+
4347 // Diagnostic change callback
4448 onDiagnosticsChanged func(name string, count int)
4549
@@ -59,58 +63,22 @@ type Client struct {
5963}
6064
6165// New creates a new LSP client using the powernap implementation.
62−func New(ctx context.Context, name string, config config.LSPConfig, resolver config.VariableResolver) (*Client, error) {
63− // Convert working directory to file URI
64− workDir, err := os.Getwd()
65− if err != nil {
66− return nil, fmt.Errorf("failed to get working directory: %w", err)
67− }
68−
69− rootURI := string(protocol.URIFromPath(workDir))
70−
71− command, err := resolver.ResolveValue(config.Command)
72− if err != nil {
73− return nil, fmt.Errorf("invalid lsp command: %w", err)
74− }
75−
76− // Create powernap client config
77− clientConfig := powernap.ClientConfig{
78− Command: home.Long(command),
79− Args: config.Args,
80− RootURI: rootURI,
81− Environment: func() map[string]string {
82− env := make(map[string]string)
83− maps.Copy(env, config.Env)
84− return env
85− }(),
86− Settings: config.Options,
87− InitOptions: config.InitOptions,
88− WorkspaceFolders: []protocol.WorkspaceFolder{
89− {
90− URI: rootURI,
91− Name: filepath.Base(workDir),
92− },
93− },
94− }
95−
96− // Create the powernap client
97− powernapClient, err := powernap.NewClient(clientConfig)
98− if err != nil {
99− return nil, fmt.Errorf("failed to create lsp client: %w", err)
100− }
101−
66+func New(ctx context.Context, name string, cfg config.LSPConfig, resolver config.VariableResolver) (*Client, error) {
10267 client := &Client{
103− client: powernapClient,
10468 name: name,
105− fileTypes: config.FileTypes,
69+ fileTypes: cfg.FileTypes,
10670 diagnostics: csync.NewVersionedMap[protocol.DocumentURI, []protocol.Diagnostic](),
10771 openFiles: csync.NewMap[string, *OpenFileInfo](),
108− config: config,
72+ config: cfg,
73+ ctx: ctx,
74+ resolver: resolver,
10975 }
110−
111− // Initialize server state
11276 client.serverState.Store(StateStarting)
11377
78+ if err := client.createPowernapClient(); err != nil {
79+ return nil, err
80+ }
81+
11482 return client, nil
11583}
11684
@@ -140,13 +108,7 @@ func (c *Client) Initialize(ctx context.Context, workspaceDir string) (*protocol
140108 Capabilities: protocolCaps,
141109 }
142110
143− c.RegisterServerRequestHandler("workspace/applyEdit", HandleApplyEdit)
144− c.RegisterServerRequestHandler("workspace/configuration", HandleWorkspaceConfiguration)
145− c.RegisterServerRequestHandler("client/registerCapability", HandleRegisterCapability)
146− c.RegisterNotificationHandler("window/showMessage", HandleServerMessage)
147− c.RegisterNotificationHandler("textDocument/publishDiagnostics", func(_ context.Context, _ string, params json.RawMessage) {
148− HandleDiagnostics(c, params)
149− })
111+ c.registerHandlers()
150112
151113 return result, nil
152114}
@@ -163,6 +125,102 @@ func (c *Client) Close(ctx context.Context) error {
163125 return c.client.Exit()
164126}
165127
128+// createPowernapClient creates a new powernap client with the current configuration.
129+func (c *Client) createPowernapClient() error {
130+ workDir, err := os.Getwd()
131+ if err != nil {
132+ return fmt.Errorf("failed to get working directory: %w", err)
133+ }
134+
135+ rootURI := string(protocol.URIFromPath(workDir))
136+
137+ command, err := c.resolver.ResolveValue(c.config.Command)
138+ if err != nil {
139+ return fmt.Errorf("invalid lsp command: %w", err)
140+ }
141+
142+ clientConfig := powernap.ClientConfig{
143+ Command: home.Long(command),
144+ Args: c.config.Args,
145+ RootURI: rootURI,
146+ Environment: maps.Clone(c.config.Env),
147+ Settings: c.config.Options,
148+ InitOptions: c.config.InitOptions,
149+ WorkspaceFolders: []protocol.WorkspaceFolder{
150+ {
151+ URI: rootURI,
152+ Name: filepath.Base(workDir),
153+ },
154+ },
155+ }
156+
157+ powernapClient, err := powernap.NewClient(clientConfig)
158+ if err != nil {
159+ return fmt.Errorf("failed to create lsp client: %w", err)
160+ }
161+
162+ c.client = powernapClient
163+ return nil
164+}
165+
166+// registerHandlers registers the standard LSP notification and request handlers.
167+func (c *Client) registerHandlers() {
168+ c.RegisterServerRequestHandler("workspace/applyEdit", HandleApplyEdit)
169+ c.RegisterServerRequestHandler("workspace/configuration", HandleWorkspaceConfiguration)
170+ c.RegisterServerRequestHandler("client/registerCapability", HandleRegisterCapability)
171+ c.RegisterNotificationHandler("window/showMessage", HandleServerMessage)
172+ c.RegisterNotificationHandler("textDocument/publishDiagnostics", func(_ context.Context, _ string, params json.RawMessage) {
173+ HandleDiagnostics(c, params)
174+ })
175+}
176+
177+// Restart closes the current LSP client and creates a new one with the same configuration.
178+func (c *Client) Restart() error {
179+ var openFiles []string
180+ for uri := range c.openFiles.Seq2() {
181+ openFiles = append(openFiles, string(uri))
182+ }
183+
184+ closeCtx, cancel := context.WithTimeout(c.ctx, 10*time.Second)
185+ defer cancel()
186+
187+ if err := c.Close(closeCtx); err != nil {
188+ slog.Warn("Error closing client during restart", "name", c.name, "error", err)
189+ }
190+
191+ c.diagCountsCache = DiagnosticCounts{}
192+ c.diagCountsVersion = 0
193+
194+ if err := c.createPowernapClient(); err != nil {
195+ return err
196+ }
197+
198+ initCtx, cancel := context.WithTimeout(c.ctx, 30*time.Second)
199+ defer cancel()
200+
201+ c.SetServerState(StateStarting)
202+
203+ if err := c.client.Initialize(initCtx, false); err != nil {
204+ c.SetServerState(StateError)
205+ return fmt.Errorf("failed to initialize lsp client: %w", err)
206+ }
207+
208+ c.registerHandlers()
209+
210+ if err := c.WaitForServerReady(initCtx); err != nil {
211+ slog.Error("Server failed to become ready after restart", "name", c.name, "error", err)
212+ c.SetServerState(StateError)
213+ return err
214+ }
215+
216+ for _, uri := range openFiles {
217+ if err := c.OpenFile(initCtx, uri); err != nil {
218+ slog.Warn("Failed to reopen file after restart", "file", uri, "error", err)
219+ }
220+ }
221+ return nil
222+}
223+
166224// ServerState represents the state of an LSP server
167225type ServerState int
168226