Skip to content

Commit a09ba40

Browse files
committed
refactor: Simplify environment selection logic in DeepnoteEnvironmentPicker and DeepnoteEnvironmentsView
- Removed redundant checks for existing environments and streamlined the user prompts for creating new environments. - Updated the createEnvironmentCommand to return the created environment for better handling in the environment selection process. - Enhanced logging for environment switching and user interactions. This refactor improves the clarity and efficiency of the environment management workflow. Signed-off-by: Tomas Kislan <tomas@kislan.sk>
1 parent 55dfca9 commit a09ba40

File tree

3 files changed

+82
-107
lines changed

3 files changed

+82
-107
lines changed

src/kernels/deepnote/environments/deepnoteEnvironmentPicker.ts

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,6 @@ export class DeepnoteEnvironmentPicker {
2626

2727
const environments = this.environmentManager.listEnvironments();
2828

29-
if (environments.length === 0) {
30-
// No environments exist - prompt user to create one
31-
const createLabel = l10n.t('Create Environment');
32-
const cancelLabel = l10n.t('Cancel');
33-
34-
const choice = await window.showInformationMessage(
35-
l10n.t('No environments found. Create one to use with {0}?', getDisplayPath(notebookUri)),
36-
createLabel,
37-
cancelLabel
38-
);
39-
40-
if (choice === createLabel) {
41-
// Trigger the create command
42-
logger.info('Triggering create environment command from picker');
43-
await commands.executeCommand('deepnote.environments.create');
44-
45-
// Check if an environment was created
46-
const newEnvironments = this.environmentManager.listEnvironments();
47-
if (newEnvironments.length > 0) {
48-
// Environment created, show picker again
49-
logger.info('Environment created, showing picker again');
50-
return this.pickEnvironment(notebookUri);
51-
}
52-
}
53-
54-
return undefined;
55-
}
56-
5729
// Build quick pick items
5830
const items: (QuickPickItem & { environment?: DeepnoteEnvironment })[] = environments.map((env) => {
5931
const envWithStatus = this.environmentManager.getEnvironmentWithStatus(env.id);

src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from '../types';
1212
import { DeepnoteEnvironmentTreeDataProvider } from './deepnoteEnvironmentTreeDataProvider.node';
1313
import { DeepnoteEnvironmentTreeItem } from './deepnoteEnvironmentTreeItem.node';
14-
import { CreateDeepnoteEnvironmentOptions, EnvironmentStatus } from './deepnoteEnvironment';
14+
import { CreateDeepnoteEnvironmentOptions, DeepnoteEnvironment, EnvironmentStatus } from './deepnoteEnvironment';
1515
import {
1616
getCachedEnvironment,
1717
resolvedPythonEnvToJupyterEnv,
@@ -58,7 +58,7 @@ export class DeepnoteEnvironmentsView implements Disposable {
5858
disposableRegistry.push(this);
5959
}
6060

61-
public async createEnvironmentCommand(): Promise<void> {
61+
public async createEnvironmentCommand(): Promise<DeepnoteEnvironment | undefined> {
6262
try {
6363
// Step 1: Select Python interpreter
6464
const api = await this.pythonApiProvider.getNewApi();
@@ -124,7 +124,7 @@ export class DeepnoteEnvironmentsView implements Disposable {
124124
// Step 3: Enter packages (optional)
125125
const packagesInput = await window.showInputBox({
126126
prompt: l10n.t('Enter additional packages to install (comma-separated, optional)'),
127-
placeHolder: l10n.t('e.g., pandas, numpy, matplotlib'),
127+
placeHolder: l10n.t('e.g., matplotlib, terraform'),
128128
validateInput: (value: string) => {
129129
if (!value || value.trim().length === 0) {
130130
return undefined; // Empty is OK
@@ -160,7 +160,7 @@ export class DeepnoteEnvironmentsView implements Disposable {
160160
});
161161

162162
// Create environment with progress
163-
await window.withProgress(
163+
return await window.withProgress(
164164
{
165165
location: ProgressLocation.Notification,
166166
title: l10n.t('Creating environment "{0}"...', name),
@@ -183,6 +183,8 @@ export class DeepnoteEnvironmentsView implements Disposable {
183183
void window.showInformationMessage(
184184
l10n.t('Environment "{0}" created successfully!', config.name)
185185
);
186+
187+
return config;
186188
} catch (error) {
187189
logger.error('Failed to create environment', error);
188190
throw error;
@@ -389,19 +391,6 @@ export class DeepnoteEnvironmentsView implements Disposable {
389391
// Get all environments
390392
const environments = this.environmentManager.listEnvironments();
391393

392-
if (environments.length === 0) {
393-
const choice = await window.showInformationMessage(
394-
l10n.t('No environments found. Create one first?'),
395-
l10n.t('Create Environment'),
396-
l10n.t('Cancel')
397-
);
398-
399-
if (choice === l10n.t('Create Environment')) {
400-
await commands.executeCommand('deepnote.environments.create');
401-
}
402-
return;
403-
}
404-
405394
// Build quick pick items
406395
const items: (QuickPickItem & { environmentId?: string })[] = environments.map((env) => {
407396
const envWithStatus = this.environmentManager.getEnvironmentWithStatus(env.id);
@@ -422,9 +411,11 @@ export class DeepnoteEnvironmentsView implements Disposable {
422411
};
423412
});
424413

414+
const createNewLabel = l10n.t('$(add) Create New Environment');
415+
425416
// Add "Create new" option at the end
426417
items.push({
427-
label: l10n.t('$(add) Create New Environment'),
418+
label: createNewLabel,
428419
description: l10n.t('Set up a new kernel environment'),
429420
alwaysShow: true
430421
});
@@ -439,16 +430,26 @@ export class DeepnoteEnvironmentsView implements Disposable {
439430
return; // User cancelled
440431
}
441432

442-
if (!selected.environmentId) {
443-
// User chose "Create new"
444-
await commands.executeCommand('deepnote.environments.create');
445-
return;
433+
let selectedEnvironmentId: string | undefined;
434+
435+
if (selected.label === createNewLabel) {
436+
const newEnvironment = await this.createEnvironmentCommand();
437+
if (newEnvironment == null) {
438+
return;
439+
}
440+
// return;
441+
selectedEnvironmentId = newEnvironment.id;
442+
} else {
443+
selectedEnvironmentId = selected.environmentId;
446444
}
447445

448446
// Check if user selected the same environment
449-
if (selected.environmentId === currentEnvironmentId) {
447+
if (selectedEnvironmentId === currentEnvironmentId) {
450448
logger.info(`User selected the same environment - no changes needed`);
451449
return;
450+
} else if (selectedEnvironmentId == null) {
451+
logger.info('User cancelled environment selection');
452+
return;
452453
}
453454

454455
// Check if any cells are currently executing using the kernel execution state
@@ -475,9 +476,7 @@ export class DeepnoteEnvironmentsView implements Disposable {
475476
}
476477

477478
// User selected a different environment - switch to it
478-
logger.info(
479-
`Switching notebook ${getDisplayPath(activeNotebook.uri)} to environment ${selected.environmentId}`
480-
);
479+
logger.info(`Switching notebook ${getDisplayPath(activeNotebook.uri)} to environment ${selectedEnvironmentId}`);
481480

482481
try {
483482
await window.withProgress(
@@ -488,16 +487,13 @@ export class DeepnoteEnvironmentsView implements Disposable {
488487
},
489488
async () => {
490489
// Update the notebook-to-environment mapping
491-
await this.notebookEnvironmentMapper.setEnvironmentForNotebook(
492-
baseFileUri,
493-
selected.environmentId!
494-
);
490+
await this.notebookEnvironmentMapper.setEnvironmentForNotebook(baseFileUri, selectedEnvironmentId);
495491

496492
// Force rebuild the controller with the new environment
497493
// This clears cached metadata and creates a fresh controller.
498494
await this.kernelAutoSelector.rebuildController(activeNotebook);
499495

500-
logger.info(`Successfully switched to environment ${selected.environmentId}`);
496+
logger.info(`Successfully switched to environment ${selectedEnvironmentId}`);
501497
}
502498
);
503499

src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import {
99
NotebookControllerAffinity,
1010
window,
1111
ProgressLocation,
12-
notebooks,
1312
NotebookController,
1413
CancellationTokenSource,
1514
Disposable,
1615
Uri,
1716
l10n,
18-
env
17+
env,
18+
commands
1919
} from 'vscode';
2020
import { IExtensionSyncActivationService } from '../../platform/activation/types';
2121
import { IDisposableRegistry } from '../../platform/common/types';
@@ -122,17 +122,6 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
122122

123123
logger.info(`Deepnote notebook opened: ${getDisplayPath(notebook.uri)}`);
124124

125-
// Check if we already have a controller ready for this notebook
126-
const baseFileUri = notebook.uri.with({ query: '', fragment: '' });
127-
const notebookKey = baseFileUri.fsPath;
128-
const hasExistingController = this.notebookControllers.has(notebookKey);
129-
130-
// If no existing controller, create a temporary "Loading" controller immediately
131-
// This prevents the kernel selector from appearing when user clicks Run All
132-
if (!hasExistingController) {
133-
this.createLoadingController(notebook, notebookKey);
134-
}
135-
136125
// Always try to ensure kernel is selected (this will reuse existing controllers)
137126
// Don't await - let it happen in background so notebook opens quickly
138127
void this.ensureKernelSelected(notebook).catch((error) => {
@@ -301,20 +290,22 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
301290
}
302291

303292
public async ensureKernelSelected(notebook: NotebookDocument, _token?: CancellationToken): Promise<void> {
304-
return window.withProgress(
293+
const baseFileUri = notebook.uri.with({ query: '', fragment: '' });
294+
const notebookKey = baseFileUri.fsPath;
295+
296+
const kernelSelected = await window.withProgress(
305297
{
306298
location: ProgressLocation.Notification,
307299
title: l10n.t('Loading Deepnote Kernel'),
308300
cancellable: true
309301
},
310-
async (progress, progressToken) => {
302+
async (progress, progressToken): Promise<boolean> => {
311303
try {
312304
logger.info(`Ensuring Deepnote kernel is selected for ${getDisplayPath(notebook.uri)}`);
313305

314306
// Extract the base file URI (without query parameters)
315307
// Notebooks from the same .deepnote file have different URIs with ?notebook=id query params
316-
const baseFileUri = notebook.uri.with({ query: '', fragment: '' });
317-
const notebookKey = baseFileUri.fsPath;
308+
318309
logger.info(`Base Deepnote file: ${getDisplayPath(baseFileUri)}`);
319310

320311
// Check if we already have a controller for this notebook file
@@ -345,7 +336,7 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
345336
const selectedController = this.controllerRegistration.getSelected(notebook);
346337
if (selectedController && selectedController.id === existingController.id) {
347338
logger.info(`Controller already selected for ${getDisplayPath(notebook.uri)}`);
348-
return;
339+
return true;
349340
}
350341

351342
// Auto-select the existing controller for this notebook
@@ -363,7 +354,7 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
363354
logger.info(`Disposed loading controller for ${notebookKey}`);
364355
}
365356

366-
return;
357+
return true;
367358
}
368359

369360
// No existing controller - check if user has selected a configuration for this notebook
@@ -387,10 +378,10 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
387378
selectedConfig = await this.configurationPicker.pickEnvironment(baseFileUri);
388379

389380
if (!selectedConfig) {
390-
logger.info(`User cancelled configuration selection - no kernel will be loaded`);
391-
throw new Error(
392-
'No environment selected. Please create an environment using the Deepnote Environments view.'
381+
logger.info(
382+
`User cancelled configuration selection or no environment found - no kernel will be loaded`
393383
);
384+
return false;
394385
}
395386

396387
// Save the selection
@@ -401,20 +392,61 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
401392
}
402393

403394
// Use the selected configuration
404-
return this.ensureKernelSelectedWithConfiguration(
395+
await this.ensureKernelSelectedWithConfiguration(
405396
notebook,
406397
selectedConfig,
407398
baseFileUri,
408399
notebookKey,
409400
progress,
410401
progressToken
411402
);
403+
404+
return true;
412405
} catch (ex) {
413406
logger.error('Failed to auto-select Deepnote kernel', ex);
414407
throw ex;
415408
}
416409
}
417410
);
411+
412+
if (!kernelSelected) {
413+
const createLabel = l10n.t('Create Environment');
414+
const cancelLabel = l10n.t('Cancel');
415+
416+
const choice = await window.showInformationMessage(
417+
l10n.t('No environments found. Create one to use with {0}?', getDisplayPath(baseFileUri)),
418+
createLabel,
419+
cancelLabel
420+
);
421+
422+
if (choice === createLabel) {
423+
// Trigger the create command
424+
logger.info('Triggering create environment command from picker');
425+
await commands.executeCommand('deepnote.environments.create');
426+
427+
const selectedConfig = await this.configurationPicker.pickEnvironment(baseFileUri);
428+
if (!selectedConfig) {
429+
return;
430+
}
431+
432+
const tmpCancellation = new CancellationTokenSource();
433+
const tmpCancellationToken = tmpCancellation.token;
434+
435+
// Use the selected configuration
436+
await this.ensureKernelSelectedWithConfiguration(
437+
notebook,
438+
selectedConfig,
439+
baseFileUri,
440+
notebookKey,
441+
{
442+
report: () => {
443+
logger.info('Progress report');
444+
}
445+
},
446+
tmpCancellationToken
447+
);
448+
}
449+
}
418450
}
419451

420452
private async ensureKernelSelectedWithConfiguration(
@@ -627,31 +659,6 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
627659
return kernelSpec;
628660
}
629661

630-
private createLoadingController(notebook: NotebookDocument, notebookKey: string): void {
631-
// Create a temporary controller that shows "Loading..." and prevents kernel selection prompt
632-
const loadingController = notebooks.createNotebookController(
633-
`deepnote-loading-${notebookKey}`,
634-
DEEPNOTE_NOTEBOOK_TYPE,
635-
l10n.t('Loading Deepnote Kernel...')
636-
);
637-
638-
// Set it as the preferred controller immediately
639-
loadingController.supportsExecutionOrder = false;
640-
loadingController.supportedLanguages = ['python'];
641-
642-
// Execution handler that does nothing - cells will just sit there until real kernel is ready
643-
loadingController.executeHandler = () => {
644-
// No-op: execution is blocked until the real controller takes over
645-
};
646-
647-
// Select this controller for the notebook
648-
loadingController.updateNotebookAffinity(notebook, NotebookControllerAffinity.Preferred);
649-
650-
// Store it so we can dispose it later
651-
this.loadingControllers.set(notebookKey, loadingController);
652-
logger.info(`Created loading controller for ${notebookKey}`);
653-
}
654-
655662
/**
656663
* Clear the controller selection for a notebook using a specific environment.
657664
* This is used when deleting an environment to unselect its controller from any open notebooks.

0 commit comments

Comments
 (0)