Repository: microsoft/vscode (TypeScript)
Date: 2023-01-18 02:36:14+00:00
Task type: Bug Fix

Issue:
Notebook controller always defaulting to Pyolite with the new MRU kernel picker
<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
<!-- 🔧 Launch with `code --disable-extensions` to check. -->
Does this issue occur when all extensions are disabled?: Yes/No

<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
<!-- 📣 Issues caused by an extension need to be reported directly to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->
- VS Code Version:  Latest Insiders 1.75
- OS Version:  Mac

Steps to Reproduce:

1.  Install VSCode insiders
2.  Install Julia , pyolite, Jupyter pre-release & python pre-release extension
3. Ensure MRU is used as the kernel picker
4. Create a new workspace folder and a new ipynb file with a name such as `helo_world1234.ipynb`
5. The kernel that is selected is `pyolite`
6. Create a new cell and run the cell and there is no prompt to select a kernel (proving the fact that pyolite is somehow selected by VS Code as the default kernel)

Expected experience - as a user I would be required to select a kernel



Issue discussion:

Solution patch:
diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionServiceImpl.ts
--- a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionServiceImpl.ts
@@ -7,15 +7,16 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
 import { IDisposable } from 'vs/base/common/lifecycle';
 import * as nls from 'vs/nls';
 import { ICommandService } from 'vs/platform/commands/common/commands';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
 import { ILogService } from 'vs/platform/log/common/log';
 import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
-import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
+import { KernelPickerFlatStrategy, KernelPickerMRUStrategy } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy';
 import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
 import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
 import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
 import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
-import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernel, INotebookKernelHistoryService, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
 
 export class NotebookExecutionService implements INotebookExecutionService, IDisposable {
 	declare _serviceBrand: undefined;
@@ -24,9 +25,11 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis
 	constructor(
 		@ICommandService private readonly _commandService: ICommandService,
 		@INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
+		@INotebookKernelHistoryService private readonly _notebookKernelHistoryService: INotebookKernelHistoryService,
 		@IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService,
 		@ILogService private readonly _logService: ILogService,
-		@INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService
+		@INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService,
+		@IConfigurationService private readonly _configurationService: IConfigurationService,
 	) {
 	}
 
@@ -54,15 +57,13 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis
 			cellExecutions.push([cell, this._notebookExecutionStateService.createCellExecution(notebook.uri, cell.handle)]);
 		}
 
-		let kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook);
-		const noPendingKernelDetections = this._notebookKernelService.getKernelDetectionTasks(notebook).length === 0;
-		// do not auto run source action when there is pending kernel detections
-		if (!kernel && noPendingKernelDetections) {
-			kernel = await this.resolveSourceActions(notebook, contextKeyService);
-		}
+		let kernel: INotebookKernel | undefined;
+		const kernelPickerType = this._configurationService.getValue<'all' | 'mru'>('notebook.kernelPicker.type');
 
-		if (!kernel) {
-			kernel = await this.resolveKernelFromKernelPicker(notebook);
+		if (kernelPickerType === 'mru') {
+			kernel = await KernelPickerMRUStrategy.resolveKernel(notebook, this._notebookKernelService, this._notebookKernelHistoryService, this._commandService);
+		} else {
+			kernel = await KernelPickerFlatStrategy.resolveKernel(notebook, this._notebookKernelService, this._commandService, contextKeyService);
 		}
 
 		if (!kernel) {
@@ -94,50 +95,6 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis
 		}
 	}
 
-	private async resolveKernelFromKernelPicker(notebook: INotebookTextModel, attempt: number = 1): Promise<INotebookKernel | undefined> {
-		if (attempt > 3) {
-			// we couldnt resolve kernels through kernel picker multiple times, skip
-			return;
-		}
-
-		await this._commandService.executeCommand(SELECT_KERNEL_ID);
-		const runningSourceActions = this._notebookKernelService.getRunningSourceActions(notebook);
-
-		if (runningSourceActions.length) {
-			await Promise.all(runningSourceActions.map(action => action.runAction()));
-
-			const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook);
-			if (kernel) {
-				return kernel;
-			}
-
-			attempt += 1;
-			return this.resolveKernelFromKernelPicker(notebook, attempt);
-		} else {
-			return this._notebookKernelService.getSelectedOrSuggestedKernel(notebook);
-		}
-	}
-
-	private async resolveSourceActions(notebook: INotebookTextModel, contextKeyService: IContextKeyService) {
-		let kernel: INotebookKernel | undefined;
-		const info = this._notebookKernelService.getMatchingKernel(notebook);
-		if (info.all.length === 0) {
-			// no kernel at all
-			const sourceActions = this._notebookKernelService.getSourceActions(notebook, contextKeyService);
-			const primaryActions = sourceActions.filter(action => action.isPrimary);
-			const action = sourceActions.length === 1
-				? sourceActions[0]
-				: (primaryActions.length === 1 ? primaryActions[0] : undefined);
-
-			if (action) {
-				await action.runAction();
-				kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook);
-			}
-		}
-
-		return kernel;
-	}
-
 	async cancelNotebookCellHandles(notebook: INotebookTextModel, cells: Iterable<number>): Promise<void> {
 		const cellsArr = Array.from(cells);
 		this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`);
diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts
--- a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts
@@ -40,8 +40,8 @@ export class NotebookKernelHistoryService extends Disposable implements INoteboo
 		const allAvailableKernels = this._notebookKernelService.getMatchingKernel(notebook);
 		const allKernels = allAvailableKernels.all;
 		const selectedKernel = allAvailableKernels.selected;
-		const suggested = (allAvailableKernels.suggestions.length === 1 ? allAvailableKernels.suggestions[0] : undefined)
-			?? (allAvailableKernels.all.length === 1) ? allAvailableKernels.all[0] : undefined;
+		// We will suggest the only kernel
+		const suggested = allAvailableKernels.all.length === 1 ? allAvailableKernels.all[0] : undefined;
 
 		const mostRecentKernelIds = this._mostRecentKernelsMap[notebook.viewType] ? [...this._mostRecentKernelsMap[notebook.viewType].values()] : [];
 
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts
@@ -34,6 +34,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
 import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
 import { URI } from 'vs/base/common/uri';
 import { IOpenerService } from 'vs/platform/opener/common/opener';
+import { INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
 
 type KernelPick = IQuickPickItem & { kernel: INotebookKernel };
 function isKernelPick(item: QuickPickInput<IQuickPickItem>): item is KernelPick {
@@ -568,6 +570,65 @@ export class KernelPickerFlatStrategy extends KernelPickerStrategyBase {
 			action.tooltip = '';
 		}
 	}
+
+	static async resolveKernel(notebook: INotebookTextModel, notebookKernelService: INotebookKernelService, commandService: ICommandService, contextKeyService: IContextKeyService): Promise<INotebookKernel | undefined> {
+		let kernel = notebookKernelService.getSelectedOrSuggestedKernel(notebook);
+		const noPendingKernelDetections = notebookKernelService.getKernelDetectionTasks(notebook).length === 0;
+		// do not auto run source action when there is pending kernel detections
+		if (!kernel && noPendingKernelDetections) {
+			kernel = await KernelPickerFlatStrategy.resolveSourceActions(notebook, notebookKernelService, contextKeyService);
+		}
+
+		if (!kernel) {
+			kernel = await this.resolveKernelFromKernelPicker(notebook, commandService, notebookKernelService);
+		}
+
+		return kernel;
+	}
+
+	private static async resolveSourceActions(notebook: INotebookTextModel, notebookKernelService: INotebookKernelService, contextKeyService: IContextKeyService): Promise<INotebookKernel | undefined> {
+		let kernel: INotebookKernel | undefined;
+		const info = notebookKernelService.getMatchingKernel(notebook);
+		if (info.all.length === 0) {
+			// no kernel at all
+			const sourceActions = notebookKernelService.getSourceActions(notebook, contextKeyService);
+			const primaryActions = sourceActions.filter(action => action.isPrimary);
+			const action = sourceActions.length === 1
+				? sourceActions[0]
+				: (primaryActions.length === 1 ? primaryActions[0] : undefined);
+
+			if (action) {
+				await action.runAction();
+				kernel = notebookKernelService.getSelectedOrSuggestedKernel(notebook);
+			}
+		}
+
+		return kernel;
+	}
+
+	private static async resolveKernelFromKernelPicker(notebook: INotebookTextModel, commandService: ICommandService, notebookKernelService: INotebookKernelService, attempt: number = 1): Promise<INotebookKernel | undefined> {
+		if (attempt > 3) {
+			// we couldnt resolve kernels through kernel picker multiple times, skip
+			return;
+		}
+
+		await commandService.executeCommand(SELECT_KERNEL_ID);
+		const runningSourceActions = notebookKernelService.getRunningSourceActions(notebook);
+
+		if (runningSourceActions.length) {
+			await Promise.all(runningSourceActions.map(action => action.runAction()));
+
+			const kernel = notebookKernelService.getSelectedOrSuggestedKernel(notebook);
+			if (kernel) {
+				return kernel;
+			}
+
+			attempt += 1;
+			return KernelPickerFlatStrategy.resolveKernelFromKernelPicker(notebook, commandService, notebookKernelService, attempt);
+		} else {
+			return notebookKernelService.getSelectedOrSuggestedKernel(notebook);
+		}
+	}
 }
 
 export class KernelPickerMRUStrategy extends KernelPickerStrategyBase {
@@ -877,7 +938,7 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase {
 		}
 	}
 
-	static updateKernelStatusAction(notebook: NotebookTextModel, action: IAction, notebookKernelService: INotebookKernelService) {
+	static updateKernelStatusAction(notebook: NotebookTextModel, action: IAction, notebookKernelService: INotebookKernelService, notebookKernelHistoryService: INotebookKernelHistoryService) {
 		const detectionTasks = notebookKernelService.getKernelDetectionTasks(notebook);
 		if (detectionTasks.length) {
 			const info = notebookKernelService.getMatchingKernel(notebook);
@@ -909,20 +970,28 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase {
 			return updateActionFromSourceAction(runningActions[0] /** TODO handle multiple actions state */, true);
 		}
 
-		const info = notebookKernelService.getMatchingKernel(notebook);
-		const suggested = (info.suggestions.length === 1 ? info.suggestions[0] : undefined)
-			?? (info.all.length === 1) ? info.all[0] : undefined;
-
-		const selectedOrSuggested = info.selected ?? suggested;
+		const { selected } = notebookKernelHistoryService.getKernels(notebook);
 
-		if (selectedOrSuggested) {
-			action.label = selectedOrSuggested.label;
+		if (selected) {
+			action.label = selected.label;
 			action.class = ThemeIcon.asClassName(selectKernelIcon);
-			action.tooltip = selectedOrSuggested.description ?? selectedOrSuggested.detail ?? '';
+			action.tooltip = selected.description ?? selected.detail ?? '';
 		} else {
 			action.label = localize('select', "Select Kernel");
 			action.class = ThemeIcon.asClassName(selectKernelIcon);
 			action.tooltip = '';
 		}
 	}
+
+	static async resolveKernel(notebook: INotebookTextModel, notebookKernelService: INotebookKernelService, notebookKernelHistoryService: INotebookKernelHistoryService, commandService: ICommandService): Promise<INotebookKernel | undefined> {
+		const { selected } = notebookKernelHistoryService.getKernels(notebook);
+
+		if (selected) {
+			return selected;
+		}
+
+		await commandService.executeCommand(SELECT_KERNEL_ID);
+		const kernel = notebookKernelService.getSelectedOrSuggestedKernel(notebook);
+		return kernel;
+	}
 }
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts
@@ -20,7 +20,7 @@ import { selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebook
 import { KernelPickerFlatStrategy, KernelPickerMRUStrategy, KernelQuickPickContext } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy';
 import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
 import { NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
-import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernelHistoryService, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
 import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
 
 function getEditorFromContext(editorService: IEditorService, context?: KernelQuickPickContext): INotebookEditor | undefined {
@@ -146,6 +146,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem {
 		actualAction: IAction,
 		private readonly _editor: { onDidChangeModel: Event<void>; textModel: NotebookTextModel | undefined; scopedContextKeyService?: IContextKeyService } | INotebookEditor,
 		@INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
+		@INotebookKernelHistoryService private readonly _notebookKernelHistoryService: INotebookKernelHistoryService,
 		@IConfigurationService private readonly _configurationService: IConfigurationService,
 	) {
 		super(
@@ -187,7 +188,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem {
 
 		const kernelPickerType = this._configurationService.getValue<'all' | 'mru'>('notebook.kernelPicker.type');
 		if (kernelPickerType === 'mru') {
-			KernelPickerMRUStrategy.updateKernelStatusAction(notebook, this._action, this._notebookKernelService);
+			KernelPickerMRUStrategy.updateKernelStatusAction(notebook, this._action, this._notebookKernelService, this._notebookKernelHistoryService);
 		} else {
 			KernelPickerFlatStrategy.updateKernelStatusAction(notebook, this._action, this._notebookKernelService, this._editor.scopedContextKeyService);
 		}


Test patch:
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts
@@ -95,8 +95,8 @@ suite('NotebookKernelHistoryService', () => {
 
 		info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' });
 		assert.equal(info.all.length, 0);
-		// suggested kernel should be visible
-		assert.deepStrictEqual(info.selected, k2);
+		// MRU only auto selects kernel if there is only one
+		assert.deepStrictEqual(info.selected, undefined);
 	});
 
 	test('notebook kernel history restore', function () {