1- // Copyright (c) Microsoft Corporation.
2- // Licensed under the MIT License.
3-
41import * as vscode from 'vscode' ;
52import { inject , injectable } from 'inversify' ;
63import { LanguageClient , LanguageClientOptions , Executable } from 'vscode-languageclient/node' ;
4+
75import { IDisposable , IDisposableRegistry } from '../../platform/common/types' ;
86import { IExtensionSyncActivationService } from '../../platform/activation/types' ;
97import { DeepnoteServerInfo , IDeepnoteLspClientManager } from './types' ;
@@ -24,43 +22,67 @@ interface LspClientInfo {
2422export class DeepnoteLspClientManager
2523 implements IDeepnoteLspClientManager , IExtensionSyncActivationService , IDisposable
2624{
27- // Map notebook URIs to their LSP clients
2825 private readonly clients = new Map < string , LspClientInfo > ( ) ;
26+ private readonly pendingStarts = new Map < string , boolean > ( ) ;
27+
2928 private disposed = false ;
3029
3130 constructor ( @inject ( IDisposableRegistry ) private readonly disposables : IDisposableRegistry ) {
3231 this . disposables . push ( this ) ;
3332 }
3433
3534 public activate ( ) : void {
36- // This service is activated synchronously and doesn't need async initialization
3735 logger . info ( 'DeepnoteLspClientManager activated' ) ;
3836 }
3937
4038 public async startLspClients (
4139 _serverInfo : DeepnoteServerInfo ,
4240 notebookUri : vscode . Uri ,
43- interpreter : PythonEnvironment
41+ interpreter : PythonEnvironment ,
42+ token ?: vscode . CancellationToken
4443 ) : Promise < void > {
4544 if ( this . disposed ) {
4645 return ;
4746 }
4847
48+ // Check for cancellation before starting
49+ if ( token ?. isCancellationRequested ) {
50+ return ;
51+ }
52+
4953 const notebookKey = notebookUri . toString ( ) ;
5054
51- // Check if clients already exist for this notebook
55+ const pendingStart = this . pendingStarts . get ( notebookKey ) ;
56+
57+ if ( pendingStart ) {
58+ logger . trace ( `LSP client is already starting up for ${ notebookKey } .` ) ;
59+
60+ return ;
61+ }
62+
5263 if ( this . clients . has ( notebookKey ) ) {
53- logger . trace ( `LSP clients already started for ${ notebookKey } ` ) ;
64+ logger . trace ( `LSP clients already started for ${ notebookKey } .` ) ;
65+
5466 return ;
5567 }
5668
57- logger . info ( `Starting LSP clients for ${ notebookKey } using interpreter ${ interpreter . uri . fsPath } ` ) ;
69+ logger . info ( `Starting LSP clients for ${ notebookKey } using interpreter ${ interpreter . uri . fsPath } .` ) ;
70+
71+ this . pendingStarts . set ( notebookKey , true ) ;
5872
5973 try {
60- // Start Python LSP client
61- const pythonClient = await this . createPythonLspClient ( notebookUri , interpreter ) ;
74+ // Check cancellation before expensive operation
75+ if ( token ?. isCancellationRequested ) {
76+ return ;
77+ }
78+
79+ const pythonClient = await this . createPythonLspClient ( notebookUri , interpreter , token ) ;
80+
81+ // Check cancellation after client creation
82+ if ( token ?. isCancellationRequested ) {
83+ return ;
84+ }
6285
63- // Store the client info
6486 const clientInfo : LspClientInfo = {
6587 pythonClient
6688 // TODO: Add SQL client when endpoint is determined
@@ -71,48 +93,76 @@ export class DeepnoteLspClientManager
7193 logger . info ( `LSP clients started successfully for ${ notebookKey } ` ) ;
7294 } catch ( error ) {
7395 logger . error ( `Failed to start LSP clients for ${ notebookKey } :` , error ) ;
96+
7497 throw error ;
98+ } finally {
99+ this . pendingStarts . delete ( notebookKey ) ;
75100 }
76101 }
77102
78- public async stopLspClients ( notebookUri : vscode . Uri ) : Promise < void > {
103+ public async stopLspClients ( notebookUri : vscode . Uri , token ?: vscode . CancellationToken ) : Promise < void > {
79104 const notebookKey = notebookUri . toString ( ) ;
80105 const clientInfo = this . clients . get ( notebookKey ) ;
81106
82107 if ( ! clientInfo ) {
83108 return ;
84109 }
85110
111+ // Check cancellation before stopping
112+ if ( token ?. isCancellationRequested ) {
113+ return ;
114+ }
115+
86116 logger . info ( `Stopping LSP clients for ${ notebookKey } ` ) ;
87117
88118 try {
89- // Stop Python client
90119 if ( clientInfo . pythonClient ) {
120+ if ( token ?. isCancellationRequested ) {
121+ return ;
122+ }
91123 await clientInfo . pythonClient . stop ( ) ;
124+ await clientInfo . pythonClient . dispose ( ) ;
92125 }
93126
94- // Stop SQL client
95127 if ( clientInfo . sqlClient ) {
128+ if ( token ?. isCancellationRequested ) {
129+ return ;
130+ }
96131 await clientInfo . sqlClient . stop ( ) ;
132+ await clientInfo . sqlClient . dispose ( ) ;
97133 }
98134
99135 this . clients . delete ( notebookKey ) ;
136+
100137 logger . info ( `LSP clients stopped for ${ notebookKey } ` ) ;
101138 } catch ( error ) {
102139 logger . error ( `Error stopping LSP clients for ${ notebookKey } :` , error ) ;
103140 }
104141 }
105142
106- public async stopAllClients ( ) : Promise < void > {
143+ public async stopAllClients ( token ?: vscode . CancellationToken ) : Promise < void > {
144+ // Check cancellation before stopping
145+ if ( token ?. isCancellationRequested ) {
146+ return ;
147+ }
148+
107149 logger . info ( 'Stopping all LSP clients' ) ;
108150
109151 const stopPromises : Promise < void > [ ] = [ ] ;
110152 for ( const [ , clientInfo ] of this . clients . entries ( ) ) {
153+ // Check cancellation during iteration
154+ if ( token ?. isCancellationRequested ) {
155+ break ;
156+ }
157+
111158 if ( clientInfo . pythonClient ) {
112159 stopPromises . push ( clientInfo . pythonClient . stop ( ) . catch ( noop ) ) ;
160+ stopPromises . push ( clientInfo . pythonClient . dispose ( ) . catch ( noop ) ) ;
113161 }
162+
114163 if ( clientInfo . sqlClient ) {
115164 stopPromises . push ( clientInfo . sqlClient . stop ( ) . catch ( noop ) ) ;
165+ stopPromises . push ( clientInfo . sqlClient . dispose ( ) . catch ( noop ) ) ;
116166 }
117167 }
118168
@@ -122,20 +172,24 @@ export class DeepnoteLspClientManager
122172
123173 public dispose ( ) : void {
124174 this . disposed = true ;
125- // Stop all clients asynchronously but don't wait
175+
126176 void this . stopAllClients ( ) ;
127177 }
128178
129179 private async createPythonLspClient (
130180 notebookUri : vscode . Uri ,
131- interpreter : PythonEnvironment
181+ interpreter : PythonEnvironment ,
182+ token ?: vscode . CancellationToken
132183 ) : Promise < LanguageClient > {
133- // Start python-lsp-server as a child process using stdio
184+ // Check cancellation before creating client
185+ if ( token ?. isCancellationRequested ) {
186+ throw new Error ( 'Operation cancelled' ) ;
187+ }
188+
134189 const pythonPath = interpreter . uri . fsPath ;
135190
136191 logger . trace ( `Creating Python LSP client using interpreter: ${ pythonPath } ` ) ;
137192
138- // Define the server executable
139193 const serverOptions : Executable = {
140194 command : pythonPath ,
141195 args : [ '-m' , 'pylsp' ] , // Start python-lsp-server
@@ -145,7 +199,6 @@ export class DeepnoteLspClientManager
145199 } ;
146200
147201 const clientOptions : LanguageClientOptions = {
148- // Document selector for Python cells in Deepnote notebooks
149202 documentSelector : [
150203 {
151204 scheme : 'vscode-notebook-cell' ,
@@ -158,25 +211,26 @@ export class DeepnoteLspClientManager
158211 pattern : '**/*.deepnote'
159212 }
160213 ] ,
161- // Synchronization settings
162214 synchronize : {
163- // Notify the server about file changes to '.py' files in the workspace
164- fileEvents : vscode . workspace . createFileSystemWatcher ( '**/*.py' )
215+ fileEvents : vscode . workspace . createFileSystemWatcher ( '**/*.{py,deepnote}' )
165216 } ,
166- // Output channel for diagnostics
167217 outputChannelName : 'Deepnote Python LSP'
168218 } ;
169219
170- // Create the language client with stdio connection
171220 const client = new LanguageClient (
172221 'deepnote-python-lsp' ,
173222 'Deepnote Python Language Server' ,
174223 serverOptions ,
175224 clientOptions
176225 ) ;
177226
178- // Start the client
227+ // Check cancellation before starting client
228+ if ( token ?. isCancellationRequested ) {
229+ throw new Error ( 'Operation cancelled' ) ;
230+ }
231+
179232 await client . start ( ) ;
233+
180234 logger . info ( `Python LSP client started for ${ notebookUri . toString ( ) } ` ) ;
181235
182236 return client ;
0 commit comments