Skip to content

Commit 4b93ab2

Browse files
committed
clean up the code.
1 parent 5f5d969 commit 4b93ab2

File tree

5 files changed

+107
-49
lines changed

5 files changed

+107
-49
lines changed

src/kernels/deepnote/deepnoteLspClientManager.node.ts

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
// Copyright (c) Microsoft Corporation.
2-
// Licensed under the MIT License.
3-
41
import * as vscode from 'vscode';
52
import { inject, injectable } from 'inversify';
63
import { LanguageClient, LanguageClientOptions, Executable } from 'vscode-languageclient/node';
4+
75
import { IDisposable, IDisposableRegistry } from '../../platform/common/types';
86
import { IExtensionSyncActivationService } from '../../platform/activation/types';
97
import { DeepnoteServerInfo, IDeepnoteLspClientManager } from './types';
@@ -24,43 +22,67 @@ interface LspClientInfo {
2422
export 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;

src/kernels/deepnote/deepnoteLspClientManager.node.vscode.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ suite('DeepnoteLspClientManager Integration Tests', () => {
4444
test('Should handle starting LSP clients with mock interpreter', async function () {
4545
this.timeout(10000);
4646

47-
// Create a mock interpreter (pointing to system Python for test)
47+
// Use invalid path to deliberately trigger failure and test error handling
48+
// This ensures consistent test behavior across platforms (simulates missing pylsp)
4849
const mockInterpreter: PythonEnvironment = {
49-
id: '/usr/bin/python3',
50-
uri: Uri.file('/usr/bin/python3')
50+
id: '/nonexistent/path/to/python',
51+
uri: Uri.file('/nonexistent/path/to/python')
5152
} as PythonEnvironment;
5253

5354
const mockServerInfo = {
@@ -94,9 +95,11 @@ suite('DeepnoteLspClientManager Integration Tests', () => {
9495
test('Should not start duplicate clients for same notebook', async function () {
9596
this.timeout(10000);
9697

98+
// Use invalid path to deliberately trigger failure and test error handling
99+
// This ensures consistent test behavior across platforms (simulates missing pylsp)
97100
const mockInterpreter: PythonEnvironment = {
98-
id: '/usr/bin/python3',
99-
uri: Uri.file('/usr/bin/python3')
101+
id: '/nonexistent/path/to/python',
102+
uri: Uri.file('/nonexistent/path/to/python')
100103
} as PythonEnvironment;
101104

102105
const mockServerInfo = {

src/kernels/deepnote/types.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,23 +324,27 @@ export interface IDeepnoteLspClientManager {
324324
* @param serverInfo Server information
325325
* @param notebookUri The notebook URI for which to start LSP clients
326326
* @param interpreter The Python interpreter from the venv
327+
* @param token Optional cancellation token to cancel the operation
327328
*/
328329
startLspClients(
329330
serverInfo: DeepnoteServerInfo,
330331
notebookUri: vscode.Uri,
331-
interpreter: PythonEnvironment
332+
interpreter: PythonEnvironment,
333+
token?: vscode.CancellationToken
332334
): Promise<void>;
333335

334336
/**
335337
* Stop LSP clients for a notebook
336338
* @param notebookUri The notebook URI
339+
* @param token Optional cancellation token to cancel the operation
337340
*/
338-
stopLspClients(notebookUri: vscode.Uri): Promise<void>;
341+
stopLspClients(notebookUri: vscode.Uri, token?: vscode.CancellationToken): Promise<void>;
339342

340343
/**
341344
* Stop all LSP clients
345+
* @param token Optional cancellation token to cancel the operation
342346
*/
343-
stopAllClients(): Promise<void>;
347+
stopAllClients(token?: vscode.CancellationToken): Promise<void>;
344348
}
345349

346350
export const DEEPNOTE_TOOLKIT_VERSION = '1.1.0';

src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -568,28 +568,23 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
568568
this.serverProvider.registerServer(serverProviderHandle.handle, serverInfo);
569569
this.projectServerHandles.set(projectKey, serverProviderHandle.handle);
570570

571-
// Get the venv interpreter for LSP
572-
const lspInterpreterUri =
573-
process.platform === 'win32'
574-
? Uri.joinPath(configuration.venvPath, 'Scripts', 'python.exe')
575-
: Uri.joinPath(configuration.venvPath, 'bin', 'python');
571+
const lspInterpreterUri = this.getVenvInterpreterUri(configuration.venvPath);
576572

577573
const lspInterpreter: PythonEnvironment = {
578574
uri: lspInterpreterUri,
579575
id: lspInterpreterUri.fsPath
580576
} as PythonEnvironment;
581577

582-
// Start LSP clients for code intelligence
583578
try {
584579
await this.lspClientManager.startLspClients(serverInfo, notebook.uri, lspInterpreter);
580+
585581
logger.info(`✓ LSP clients started for ${notebookKey}`);
586582
} catch (error) {
587583
logger.error(`Failed to start LSP clients for ${notebookKey}:`, error);
588-
// Don't fail the kernel selection if LSP fails - it's supplementary
589584
}
590585

591-
// Connect to the server and get available kernel specs
592586
progress.report({ message: 'Connecting to kernel...' });
587+
593588
const connectionInfo = createJupyterConnectionInfo(
594589
serverProviderHandle,
595590
{
@@ -620,11 +615,7 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
620615

621616
progress.report({ message: 'Finalizing kernel setup...' });
622617

623-
// Get the venv Python interpreter (not the base interpreter)
624-
const venvInterpreter =
625-
process.platform === 'win32'
626-
? Uri.joinPath(configuration.venvPath, 'Scripts', 'python.exe')
627-
: Uri.joinPath(configuration.venvPath, 'bin', 'python');
618+
const venvInterpreter = this.getVenvInterpreterUri(configuration.venvPath);
628619

629620
logger.info(`Using venv path: ${configuration.venvPath.fsPath}`);
630621
logger.info(`Venv interpreter path: ${venvInterpreter.fsPath}`);
@@ -803,6 +794,12 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
803794
}
804795
}
805796

797+
private getVenvInterpreterUri(venvPath: Uri): Uri {
798+
return process.platform === 'win32'
799+
? Uri.joinPath(venvPath, 'Scripts', 'python.exe')
800+
: Uri.joinPath(venvPath, 'bin', 'python');
801+
}
802+
806803
/**
807804
* Handle kernel selection errors with user-friendly messages and actions
808805
*/

src/test/datascience/.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"python.experiments.enabled": true,
3131
// See https://github.com/microsoft/vscode-jupyter/issues/10258
3232
"deepnote.forceIPyKernelDebugger": false,
33-
"python.defaultInterpreterPath": "/Users/artmann/deepnote/vscode-deepnote/.venv/bin/python",
33+
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
3434
"task.problemMatchers.neverPrompt": {
3535
"shell": true,
3636
"npm": true

0 commit comments

Comments
 (0)