Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts
5272 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IAction } from '../../../../../base/common/actions.js';
7
import { groupBy } from '../../../../../base/common/arrays.js';
8
import { createCancelablePromise } from '../../../../../base/common/async.js';
9
import { CancellationToken } from '../../../../../base/common/cancellation.js';
10
import { Codicon } from '../../../../../base/common/codicons.js';
11
import { Event } from '../../../../../base/common/event.js';
12
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
13
import { MarshalledId } from '../../../../../base/common/marshallingIds.js';
14
import { uppercaseFirstLetter } from '../../../../../base/common/strings.js';
15
import { Command } from '../../../../../editor/common/languages.js';
16
import { localize } from '../../../../../nls.js';
17
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
18
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
19
import { ILabelService } from '../../../../../platform/label/common/label.js';
20
import { ILogService } from '../../../../../platform/log/common/log.js';
21
import { IProductService } from '../../../../../platform/product/common/productService.js';
22
import { ProgressLocation } from '../../../../../platform/progress/common/progress.js';
23
import { IQuickInputButton, IQuickInputService, IQuickPick, IQuickPickItem, QuickPickInput } from '../../../../../platform/quickinput/common/quickInput.js';
24
import { ThemeIcon } from '../../../../../base/common/themables.js';
25
import { IExtension, IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js';
26
import { IActiveNotebookEditor, INotebookExtensionRecommendation, JUPYTER_EXTENSION_ID, KERNEL_RECOMMENDATIONS } from '../notebookBrowser.js';
27
import { NotebookEditorWidget } from '../notebookEditorWidget.js';
28
import { executingStateIcon, selectKernelIcon } from '../notebookIcons.js';
29
import { NotebookTextModel } from '../../common/model/notebookTextModel.js';
30
import { INotebookKernel, INotebookKernelHistoryService, INotebookKernelMatchResult, INotebookKernelService, ISourceAction } from '../../common/notebookKernelService.js';
31
import { IExtensionService } from '../../../../services/extensions/common/extensions.js';
32
import { URI } from '../../../../../base/common/uri.js';
33
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
34
import { INotebookTextModel } from '../../common/notebookCommon.js';
35
import { SELECT_KERNEL_ID } from '../controller/coreActions.js';
36
import { EnablementState, IExtensionManagementServerService } from '../../../../services/extensionManagement/common/extensionManagement.js';
37
import { areSameExtensions } from '../../../../../platform/extensionManagement/common/extensionManagementUtil.js';
38
39
type KernelPick = IQuickPickItem & { kernel: INotebookKernel };
40
function isKernelPick(item: QuickPickInput<IQuickPickItem>): item is KernelPick {
41
return 'kernel' in item;
42
}
43
type GroupedKernelsPick = IQuickPickItem & { kernels: INotebookKernel[]; source: string };
44
function isGroupedKernelsPick(item: QuickPickInput<IQuickPickItem>): item is GroupedKernelsPick {
45
return 'kernels' in item;
46
}
47
type SourcePick = IQuickPickItem & { action: ISourceAction };
48
function isSourcePick(item: QuickPickInput<IQuickPickItem>): item is SourcePick {
49
return 'action' in item;
50
}
51
type InstallExtensionPick = IQuickPickItem & { extensionIds: string[] };
52
function isInstallExtensionPick(item: QuickPickInput<IQuickPickItem>): item is InstallExtensionPick {
53
return item.id === 'installSuggested' && 'extensionIds' in item;
54
}
55
type SearchMarketplacePick = IQuickPickItem & { id: 'install' };
56
function isSearchMarketplacePick(item: QuickPickInput<IQuickPickItem>): item is SearchMarketplacePick {
57
return item.id === 'install';
58
}
59
60
type KernelSourceQuickPickItem = IQuickPickItem & { command: Command; documentation?: string };
61
function isKernelSourceQuickPickItem(item: IQuickPickItem): item is KernelSourceQuickPickItem {
62
return 'command' in item;
63
}
64
65
function supportAutoRun(item: QuickPickInput<IQuickPickItem>): item is IQuickPickItem {
66
return 'autoRun' in item && !!item.autoRun;
67
}
68
type KernelQuickPickItem = (IQuickPickItem & { autoRun?: boolean }) | SearchMarketplacePick | InstallExtensionPick | KernelPick | GroupedKernelsPick | SourcePick | KernelSourceQuickPickItem;
69
const KERNEL_PICKER_UPDATE_DEBOUNCE = 200;
70
71
export type KernelQuickPickContext =
72
{ id: string; extension: string } |
73
{ notebookEditorId: string } |
74
{ id: string; extension: string; notebookEditorId: string } |
75
{ ui?: boolean; notebookEditor?: NotebookEditorWidget; skipIfAlreadySelected?: boolean };
76
77
export interface IKernelPickerStrategy {
78
showQuickPick(editor: IActiveNotebookEditor, wantedKernelId?: string): Promise<boolean>;
79
}
80
81
function toKernelQuickPick(kernel: INotebookKernel, selected: INotebookKernel | undefined) {
82
const res: KernelPick = {
83
kernel,
84
picked: kernel.id === selected?.id,
85
label: kernel.label,
86
description: kernel.description,
87
detail: kernel.detail
88
};
89
if (kernel.id === selected?.id) {
90
if (!res.description) {
91
res.description = localize('current1', "Currently Selected");
92
} else {
93
res.description = localize('current2', "{0} - Currently Selected", res.description);
94
}
95
}
96
return res;
97
}
98
99
100
abstract class KernelPickerStrategyBase implements IKernelPickerStrategy {
101
constructor(
102
protected readonly _notebookKernelService: INotebookKernelService,
103
protected readonly _productService: IProductService,
104
protected readonly _quickInputService: IQuickInputService,
105
protected readonly _labelService: ILabelService,
106
protected readonly _logService: ILogService,
107
protected readonly _extensionWorkbenchService: IExtensionsWorkbenchService,
108
protected readonly _extensionService: IExtensionService,
109
protected readonly _commandService: ICommandService,
110
protected readonly _extensionManagementServerService: IExtensionManagementServerService
111
) { }
112
113
async showQuickPick(editor: IActiveNotebookEditor, wantedId?: string, skipAutoRun?: boolean): Promise<boolean> {
114
const notebook = editor.textModel;
115
const scopedContextKeyService = editor.scopedContextKeyService;
116
const matchResult = this._getMatchingResult(notebook);
117
const { selected, all } = matchResult;
118
119
let newKernel: INotebookKernel | undefined;
120
if (wantedId) {
121
for (const candidate of all) {
122
if (candidate.id === wantedId) {
123
newKernel = candidate;
124
break;
125
}
126
}
127
if (!newKernel) {
128
this._logService.warn(`wanted kernel DOES NOT EXIST, wanted: ${wantedId}, all: ${all.map(k => k.id)}`);
129
return false;
130
}
131
}
132
133
if (newKernel) {
134
this._selecteKernel(notebook, newKernel);
135
return true;
136
}
137
138
139
const localDisposableStore = new DisposableStore();
140
const quickPick = localDisposableStore.add(this._quickInputService.createQuickPick<KernelQuickPickItem>({ useSeparators: true }));
141
const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService);
142
143
if (quickPickItems.length === 1 && supportAutoRun(quickPickItems[0]) && !skipAutoRun) {
144
const picked = await this._handleQuickPick(editor, quickPickItems[0], quickPickItems as KernelQuickPickItem[]);
145
localDisposableStore.dispose();
146
return picked;
147
}
148
149
quickPick.items = quickPickItems;
150
quickPick.canSelectMany = false;
151
quickPick.placeholder = selected
152
? localize('prompt.placeholder.change', "Change kernel for '{0}'", this._labelService.getUriLabel(notebook.uri, { relative: true }))
153
: localize('prompt.placeholder.select', "Select kernel for '{0}'", this._labelService.getUriLabel(notebook.uri, { relative: true }));
154
155
quickPick.busy = this._notebookKernelService.getKernelDetectionTasks(notebook).length > 0;
156
157
const kernelDetectionTaskListener = this._notebookKernelService.onDidChangeKernelDetectionTasks(() => {
158
quickPick.busy = this._notebookKernelService.getKernelDetectionTasks(notebook).length > 0;
159
});
160
161
// run extension recommendataion task if quickPickItems is empty
162
const extensionRecommendataionPromise = quickPickItems.length === 0
163
? createCancelablePromise(token => this._showInstallKernelExtensionRecommendation(notebook, quickPick, this._extensionWorkbenchService, token))
164
: undefined;
165
166
const kernelChangeEventListener = Event.debounce<void, void>(
167
Event.any(
168
this._notebookKernelService.onDidChangeSourceActions,
169
this._notebookKernelService.onDidAddKernel,
170
this._notebookKernelService.onDidRemoveKernel,
171
this._notebookKernelService.onDidChangeNotebookAffinity
172
),
173
(last, _current) => last,
174
KERNEL_PICKER_UPDATE_DEBOUNCE
175
)(async () => {
176
// reset quick pick progress
177
quickPick.busy = false;
178
extensionRecommendataionPromise?.cancel();
179
180
const currentActiveItems = quickPick.activeItems;
181
const matchResult = this._getMatchingResult(notebook);
182
const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService);
183
quickPick.keepScrollPosition = true;
184
185
// recalcuate active items
186
const activeItems: KernelQuickPickItem[] = [];
187
for (const item of currentActiveItems) {
188
if (isKernelPick(item)) {
189
const kernelId = item.kernel.id;
190
const sameItem = quickPickItems.find(pi => isKernelPick(pi) && pi.kernel.id === kernelId) as KernelPick | undefined;
191
if (sameItem) {
192
activeItems.push(sameItem);
193
}
194
} else if (isSourcePick(item)) {
195
const sameItem = quickPickItems.find(pi => isSourcePick(pi) && pi.action.action.id === item.action.action.id) as SourcePick | undefined;
196
if (sameItem) {
197
activeItems.push(sameItem);
198
}
199
}
200
}
201
202
quickPick.items = quickPickItems;
203
quickPick.activeItems = activeItems;
204
}, this);
205
206
const pick = await new Promise<{ selected: KernelQuickPickItem | undefined; items: KernelQuickPickItem[] }>((resolve, reject) => {
207
localDisposableStore.add(quickPick.onDidAccept(() => {
208
const item = quickPick.selectedItems[0];
209
if (item) {
210
resolve({ selected: item, items: quickPick.items as KernelQuickPickItem[] });
211
} else {
212
resolve({ selected: undefined, items: quickPick.items as KernelQuickPickItem[] });
213
}
214
215
quickPick.hide();
216
}));
217
218
localDisposableStore.add(quickPick.onDidHide(() => {
219
kernelDetectionTaskListener.dispose();
220
kernelChangeEventListener.dispose();
221
quickPick.dispose();
222
resolve({ selected: undefined, items: quickPick.items as KernelQuickPickItem[] });
223
}));
224
quickPick.show();
225
});
226
227
localDisposableStore.dispose();
228
229
if (pick.selected) {
230
return await this._handleQuickPick(editor, pick.selected, pick.items);
231
}
232
233
return false;
234
}
235
236
protected _getMatchingResult(notebook: NotebookTextModel) {
237
return this._notebookKernelService.getMatchingKernel(notebook);
238
}
239
240
protected abstract _getKernelPickerQuickPickItems(
241
notebookTextModel: NotebookTextModel,
242
matchResult: INotebookKernelMatchResult,
243
notebookKernelService: INotebookKernelService,
244
scopedContextKeyService: IContextKeyService
245
): QuickPickInput<KernelQuickPickItem>[];
246
247
protected async _handleQuickPick(editor: IActiveNotebookEditor, pick: KernelQuickPickItem, quickPickItems: KernelQuickPickItem[]): Promise<boolean> {
248
if (isKernelPick(pick)) {
249
const newKernel = pick.kernel;
250
this._selecteKernel(editor.textModel, newKernel);
251
return true;
252
}
253
254
// actions
255
if (isSearchMarketplacePick(pick)) {
256
await this._showKernelExtension(
257
this._extensionWorkbenchService,
258
this._extensionService,
259
this._extensionManagementServerService,
260
editor.textModel.viewType,
261
[]
262
);
263
// suggestedExtension must be defined for this option to be shown, but still check to make TS happy
264
} else if (isInstallExtensionPick(pick)) {
265
await this._showKernelExtension(
266
this._extensionWorkbenchService,
267
this._extensionService,
268
this._extensionManagementServerService,
269
editor.textModel.viewType,
270
pick.extensionIds,
271
this._productService.quality !== 'stable'
272
);
273
} else if (isSourcePick(pick)) {
274
// selected explicilty, it should trigger the execution?
275
pick.action.runAction();
276
}
277
278
return true;
279
}
280
281
protected _selecteKernel(notebook: NotebookTextModel, kernel: INotebookKernel) {
282
this._notebookKernelService.selectKernelForNotebook(kernel, notebook);
283
}
284
285
protected async _showKernelExtension(
286
extensionWorkbenchService: IExtensionsWorkbenchService,
287
extensionService: IExtensionService,
288
extensionManagementServerService: IExtensionManagementServerService,
289
viewType: string,
290
extIds: string[],
291
isInsiders?: boolean
292
) {
293
// If extension id is provided attempt to install the extension as the user has requested the suggested ones be installed
294
const extensionsToInstall: IExtension[] = [];
295
const extensionsToInstallOnRemote: IExtension[] = [];
296
const extensionsToEnable: IExtension[] = [];
297
298
for (const extId of extIds) {
299
const extension = (await extensionWorkbenchService.getExtensions([{ id: extId }], CancellationToken.None))[0];
300
if (extension.enablementState === EnablementState.DisabledGlobally || extension.enablementState === EnablementState.DisabledWorkspace || extension.enablementState === EnablementState.DisabledByEnvironment) {
301
extensionsToEnable.push(extension);
302
} else if (!extensionWorkbenchService.installed.some(e => areSameExtensions(e.identifier, extension.identifier))) {
303
// Install this extension only if it hasn't already been installed.
304
const canInstall = await extensionWorkbenchService.canInstall(extension);
305
if (canInstall === true) {
306
extensionsToInstall.push(extension);
307
}
308
} else if (extensionManagementServerService.remoteExtensionManagementServer) {
309
// already installed, check if it should be installed on remote since we are not getting any kernels or kernel providers.
310
if (extensionWorkbenchService.installed.some(e => areSameExtensions(e.identifier, extension.identifier) && e.server === extensionManagementServerService.remoteExtensionManagementServer)) {
311
// extension exists on remote server. should not happen
312
continue;
313
} else {
314
// extension doesn't exist on remote server
315
const canInstall = await extensionWorkbenchService.canInstall(extension);
316
if (canInstall) {
317
extensionsToInstallOnRemote.push(extension);
318
}
319
}
320
}
321
}
322
323
if (extensionsToInstall.length || extensionsToEnable.length || extensionsToInstallOnRemote.length) {
324
await Promise.all([...extensionsToInstall.map(async extension => {
325
await extensionWorkbenchService.install(
326
extension,
327
{
328
installPreReleaseVersion: isInsiders ?? false,
329
context: { skipWalkthrough: true },
330
},
331
ProgressLocation.Notification
332
);
333
}), ...extensionsToEnable.map(async extension => {
334
switch (extension.enablementState) {
335
case EnablementState.DisabledWorkspace:
336
await extensionWorkbenchService.setEnablement([extension], EnablementState.EnabledWorkspace);
337
return;
338
case EnablementState.DisabledGlobally:
339
await extensionWorkbenchService.setEnablement([extension], EnablementState.EnabledGlobally);
340
return;
341
case EnablementState.DisabledByEnvironment:
342
await extensionWorkbenchService.setEnablement([extension], EnablementState.EnabledByEnvironment);
343
return;
344
default:
345
break;
346
}
347
}), ...extensionsToInstallOnRemote.map(async extension => {
348
await extensionWorkbenchService.installInServer(extension, this._extensionManagementServerService.remoteExtensionManagementServer!);
349
})]);
350
351
await extensionService.activateByEvent(`onNotebook:${viewType}`);
352
return;
353
}
354
355
const pascalCased = viewType.split(/[^a-z0-9]/ig).map(uppercaseFirstLetter).join('');
356
await extensionWorkbenchService.openSearch(`@tag:notebookKernel${pascalCased}`);
357
}
358
359
private async _showInstallKernelExtensionRecommendation(
360
notebookTextModel: NotebookTextModel,
361
quickPick: IQuickPick<KernelQuickPickItem, { useSeparators: true }>,
362
extensionWorkbenchService: IExtensionsWorkbenchService,
363
token: CancellationToken
364
) {
365
quickPick.busy = true;
366
367
const newQuickPickItems = await this._getKernelRecommendationsQuickPickItems(notebookTextModel, extensionWorkbenchService);
368
quickPick.busy = false;
369
370
if (token.isCancellationRequested) {
371
return;
372
}
373
374
if (newQuickPickItems && quickPick.items.length === 0) {
375
quickPick.items = newQuickPickItems;
376
}
377
}
378
379
protected async _getKernelRecommendationsQuickPickItems(
380
notebookTextModel: NotebookTextModel,
381
extensionWorkbenchService: IExtensionsWorkbenchService,
382
): Promise<QuickPickInput<SearchMarketplacePick | InstallExtensionPick>[] | undefined> {
383
const quickPickItems: QuickPickInput<SearchMarketplacePick | InstallExtensionPick>[] = [];
384
385
const language = this.getSuggestedLanguage(notebookTextModel);
386
const suggestedExtension: INotebookExtensionRecommendation | undefined = language ? this.getSuggestedKernelFromLanguage(notebookTextModel.viewType, language) : undefined;
387
if (suggestedExtension) {
388
await extensionWorkbenchService.queryLocal();
389
390
const extensions = extensionWorkbenchService.installed.filter(e =>
391
(e.enablementState === EnablementState.EnabledByEnvironment || e.enablementState === EnablementState.EnabledGlobally || e.enablementState === EnablementState.EnabledWorkspace)
392
&& suggestedExtension.extensionIds.includes(e.identifier.id)
393
);
394
395
if (extensions.length === suggestedExtension.extensionIds.length) {
396
// it's installed but might be detecting kernels
397
return undefined;
398
}
399
400
// We have a suggested kernel, show an option to install it
401
quickPickItems.push({
402
id: 'installSuggested',
403
description: suggestedExtension.displayName ?? suggestedExtension.extensionIds.join(', '),
404
label: `$(${Codicon.lightbulb.id}) ` + localize('installSuggestedKernel', 'Install/Enable suggested extensions'),
405
extensionIds: suggestedExtension.extensionIds
406
} satisfies InstallExtensionPick);
407
}
408
// there is no kernel, show the install from marketplace
409
quickPickItems.push({
410
id: 'install',
411
label: localize('searchForKernels', "Browse marketplace for kernel extensions"),
412
} satisfies SearchMarketplacePick);
413
414
return quickPickItems;
415
}
416
417
/**
418
* Examine the most common language in the notebook
419
* @param notebookTextModel The notebook text model
420
* @returns What the suggested language is for the notebook. Used for kernal installing
421
*/
422
private getSuggestedLanguage(notebookTextModel: NotebookTextModel): string | undefined {
423
const metaData = notebookTextModel.metadata;
424
const language_info = (metaData?.metadata as Record<string, unknown>)?.language_info as Record<string, string> | undefined;
425
let suggestedKernelLanguage: string | undefined = language_info?.name;
426
// TODO how do we suggest multi language notebooks?
427
if (!suggestedKernelLanguage) {
428
const cellLanguages = notebookTextModel.cells.map(cell => cell.language).filter(language => language !== 'markdown');
429
// Check if cell languages is all the same
430
if (cellLanguages.length > 1) {
431
const firstLanguage = cellLanguages[0];
432
if (cellLanguages.every(language => language === firstLanguage)) {
433
suggestedKernelLanguage = firstLanguage;
434
}
435
}
436
}
437
return suggestedKernelLanguage;
438
}
439
440
/**
441
* Given a language and notebook view type suggest a kernel for installation
442
* @param language The language to find a suggested kernel extension for
443
* @returns A recommednation object for the recommended extension, else undefined
444
*/
445
private getSuggestedKernelFromLanguage(viewType: string, language: string): INotebookExtensionRecommendation | undefined {
446
const recommendation = KERNEL_RECOMMENDATIONS.get(viewType)?.get(language);
447
return recommendation;
448
}
449
}
450
451
export class KernelPickerMRUStrategy extends KernelPickerStrategyBase {
452
constructor(
453
@INotebookKernelService _notebookKernelService: INotebookKernelService,
454
@IProductService _productService: IProductService,
455
@IQuickInputService _quickInputService: IQuickInputService,
456
@ILabelService _labelService: ILabelService,
457
@ILogService _logService: ILogService,
458
@IExtensionsWorkbenchService _extensionWorkbenchService: IExtensionsWorkbenchService,
459
@IExtensionService _extensionService: IExtensionService,
460
@IExtensionManagementServerService _extensionManagementServerService: IExtensionManagementServerService,
461
@ICommandService _commandService: ICommandService,
462
@INotebookKernelHistoryService private readonly _notebookKernelHistoryService: INotebookKernelHistoryService,
463
@IOpenerService private readonly _openerService: IOpenerService
464
465
) {
466
super(
467
_notebookKernelService,
468
_productService,
469
_quickInputService,
470
_labelService,
471
_logService,
472
_extensionWorkbenchService,
473
_extensionService,
474
_commandService,
475
_extensionManagementServerService,
476
);
477
}
478
479
protected _getKernelPickerQuickPickItems(notebookTextModel: NotebookTextModel, matchResult: INotebookKernelMatchResult, notebookKernelService: INotebookKernelService, scopedContextKeyService: IContextKeyService): QuickPickInput<KernelQuickPickItem>[] {
480
const quickPickItems: QuickPickInput<KernelQuickPickItem>[] = [];
481
482
if (matchResult.selected) {
483
const kernelItem = toKernelQuickPick(matchResult.selected, matchResult.selected);
484
quickPickItems.push(kernelItem);
485
}
486
487
matchResult.suggestions.filter(kernel => kernel.id !== matchResult.selected?.id).map(kernel => toKernelQuickPick(kernel, matchResult.selected))
488
.forEach(kernel => {
489
quickPickItems.push(kernel);
490
});
491
492
const shouldAutoRun = quickPickItems.length === 0;
493
494
if (quickPickItems.length > 0) {
495
quickPickItems.push({
496
type: 'separator'
497
});
498
}
499
500
// select another kernel quick pick
501
quickPickItems.push({
502
id: 'selectAnother',
503
label: localize('selectAnotherKernel.more', "Select Another Kernel..."),
504
autoRun: shouldAutoRun
505
});
506
507
return quickPickItems;
508
}
509
510
protected override _selecteKernel(notebook: NotebookTextModel, kernel: INotebookKernel): void {
511
const currentInfo = this._notebookKernelService.getMatchingKernel(notebook);
512
if (currentInfo.selected) {
513
// there is already a selected kernel
514
this._notebookKernelHistoryService.addMostRecentKernel(currentInfo.selected);
515
}
516
super._selecteKernel(notebook, kernel);
517
this._notebookKernelHistoryService.addMostRecentKernel(kernel);
518
}
519
520
protected override _getMatchingResult(notebook: NotebookTextModel): INotebookKernelMatchResult {
521
const { selected, all } = this._notebookKernelHistoryService.getKernels(notebook);
522
const matchingResult = this._notebookKernelService.getMatchingKernel(notebook);
523
return {
524
selected: selected,
525
all: matchingResult.all,
526
suggestions: all,
527
hidden: []
528
};
529
}
530
531
protected override async _handleQuickPick(editor: IActiveNotebookEditor, pick: KernelQuickPickItem, items: KernelQuickPickItem[]): Promise<boolean> {
532
if (pick.id === 'selectAnother') {
533
return this.displaySelectAnotherQuickPick(editor, items.length === 1 && items[0] === pick);
534
}
535
536
return super._handleQuickPick(editor, pick, items);
537
}
538
539
private async displaySelectAnotherQuickPick(editor: IActiveNotebookEditor, kernelListEmpty: boolean): Promise<boolean> {
540
const notebook: NotebookTextModel = editor.textModel;
541
const disposables = new DisposableStore();
542
const quickPick = disposables.add(this._quickInputService.createQuickPick<KernelQuickPickItem>({ useSeparators: true }));
543
const quickPickItem = await new Promise<KernelQuickPickItem | IQuickInputButton | undefined>(resolve => {
544
// select from kernel sources
545
quickPick.title = kernelListEmpty ? localize('select', "Select Kernel") : localize('selectAnotherKernel', "Select Another Kernel");
546
quickPick.placeholder = localize('selectKernel.placeholder', "Type to choose a kernel source");
547
quickPick.busy = true;
548
quickPick.buttons = [this._quickInputService.backButton];
549
quickPick.show();
550
551
disposables.add(quickPick.onDidTriggerButton(button => {
552
if (button === this._quickInputService.backButton) {
553
resolve(button);
554
}
555
}));
556
disposables.add(quickPick.onDidTriggerItemButton(async (e) => {
557
if (isKernelSourceQuickPickItem(e.item) && e.item.documentation !== undefined) {
558
const uri = URI.isUri(e.item.documentation) ? URI.parse(e.item.documentation) : await this._commandService.executeCommand<URI>(e.item.documentation);
559
if (uri) {
560
void this._openerService.open(uri, { openExternal: true });
561
}
562
}
563
}));
564
disposables.add(quickPick.onDidAccept(async () => {
565
resolve(quickPick.selectedItems[0]);
566
}));
567
disposables.add(quickPick.onDidHide(() => {
568
resolve(undefined);
569
}));
570
571
this._calculdateKernelSources(editor).then(quickPickItems => {
572
quickPick.items = quickPickItems;
573
if (quickPick.items.length > 0) {
574
quickPick.busy = false;
575
}
576
});
577
578
disposables.add(Event.debounce<void, void>(
579
Event.any(
580
this._notebookKernelService.onDidChangeSourceActions,
581
this._notebookKernelService.onDidAddKernel,
582
this._notebookKernelService.onDidRemoveKernel
583
),
584
(last, _current) => last,
585
KERNEL_PICKER_UPDATE_DEBOUNCE
586
)(async () => {
587
quickPick.busy = true;
588
const quickPickItems = await this._calculdateKernelSources(editor);
589
quickPick.items = quickPickItems;
590
quickPick.busy = false;
591
}));
592
});
593
594
quickPick.hide();
595
disposables.dispose();
596
597
if (quickPickItem === this._quickInputService.backButton) {
598
return this.showQuickPick(editor, undefined, true);
599
}
600
601
if (quickPickItem) {
602
const selectedKernelPickItem = quickPickItem as KernelQuickPickItem;
603
if (isKernelSourceQuickPickItem(selectedKernelPickItem)) {
604
try {
605
const selectedKernelId = await this._executeCommand<string>(notebook, selectedKernelPickItem.command);
606
if (selectedKernelId) {
607
const { all } = await this._getMatchingResult(notebook);
608
const kernel = all.find(kernel => kernel.id === `ms-toolsai.jupyter/${selectedKernelId}`);
609
if (kernel) {
610
await this._selecteKernel(notebook, kernel);
611
return true;
612
}
613
return true;
614
} else {
615
return this.displaySelectAnotherQuickPick(editor, false);
616
}
617
} catch (ex) {
618
return false;
619
}
620
} else if (isKernelPick(selectedKernelPickItem)) {
621
await this._selecteKernel(notebook, selectedKernelPickItem.kernel);
622
return true;
623
} else if (isGroupedKernelsPick(selectedKernelPickItem)) {
624
await this._selectOneKernel(notebook, selectedKernelPickItem.label, selectedKernelPickItem.kernels);
625
return true;
626
} else if (isSourcePick(selectedKernelPickItem)) {
627
// selected explicilty, it should trigger the execution?
628
try {
629
await selectedKernelPickItem.action.runAction();
630
return true;
631
} catch (ex) {
632
return false;
633
}
634
} else if (isSearchMarketplacePick(selectedKernelPickItem)) {
635
await this._showKernelExtension(
636
this._extensionWorkbenchService,
637
this._extensionService,
638
this._extensionManagementServerService,
639
editor.textModel.viewType,
640
[]
641
);
642
return true;
643
} else if (isInstallExtensionPick(selectedKernelPickItem)) {
644
await this._showKernelExtension(
645
this._extensionWorkbenchService,
646
this._extensionService,
647
this._extensionManagementServerService,
648
editor.textModel.viewType,
649
selectedKernelPickItem.extensionIds,
650
this._productService.quality !== 'stable'
651
);
652
return this.displaySelectAnotherQuickPick(editor, false);
653
}
654
}
655
656
return false;
657
}
658
659
private async _calculdateKernelSources(editor: IActiveNotebookEditor) {
660
const notebook: NotebookTextModel = editor.textModel;
661
662
const sourceActionCommands = this._notebookKernelService.getSourceActions(notebook, editor.scopedContextKeyService);
663
const actions = await this._notebookKernelService.getKernelSourceActions2(notebook);
664
const matchResult = this._getMatchingResult(notebook);
665
666
if (sourceActionCommands.length === 0 && matchResult.all.length === 0 && actions.length === 0) {
667
return await this._getKernelRecommendationsQuickPickItems(notebook, this._extensionWorkbenchService) ?? [];
668
}
669
670
const others = matchResult.all.filter(item => item.extension.value !== JUPYTER_EXTENSION_ID);
671
const quickPickItems: QuickPickInput<KernelQuickPickItem>[] = [];
672
673
// group controllers by extension
674
for (const group of groupBy(others, (a, b) => a.extension.value === b.extension.value ? 0 : 1)) {
675
const extension = this._extensionService.extensions.find(extension => extension.identifier.value === group[0].extension.value);
676
const source = extension?.displayName ?? extension?.description ?? group[0].extension.value;
677
if (group.length > 1) {
678
quickPickItems.push({
679
label: source,
680
kernels: group
681
});
682
} else {
683
quickPickItems.push({
684
label: group[0].label,
685
kernel: group[0]
686
});
687
}
688
}
689
690
const validActions = actions.filter(action => action.command);
691
692
quickPickItems.push(...validActions.map(action => {
693
const buttons = action.documentation ? [{
694
iconClass: ThemeIcon.asClassName(Codicon.info),
695
tooltip: localize('learnMoreTooltip', 'Learn More'),
696
}] : [];
697
return {
698
id: typeof action.command! === 'string' ? action.command : action.command!.id,
699
label: action.label,
700
description: action.description,
701
command: action.command,
702
documentation: action.documentation,
703
buttons
704
};
705
}));
706
707
for (const sourceAction of sourceActionCommands) {
708
const res: SourcePick = {
709
action: sourceAction,
710
picked: false,
711
label: sourceAction.action.label,
712
tooltip: sourceAction.action.tooltip
713
};
714
715
quickPickItems.push(res);
716
}
717
718
return quickPickItems;
719
}
720
721
private async _selectOneKernel(notebook: NotebookTextModel, source: string, kernels: INotebookKernel[]) {
722
const quickPickItems: QuickPickInput<KernelPick>[] = kernels.map(kernel => toKernelQuickPick(kernel, undefined));
723
const localDisposableStore = new DisposableStore();
724
const quickPick = localDisposableStore.add(this._quickInputService.createQuickPick<KernelQuickPickItem>({ useSeparators: true }));
725
quickPick.items = quickPickItems;
726
quickPick.canSelectMany = false;
727
728
quickPick.title = localize('selectKernelFromExtension', "Select Kernel from {0}", source);
729
730
localDisposableStore.add(quickPick.onDidAccept(async () => {
731
if (quickPick.selectedItems && quickPick.selectedItems.length > 0 && isKernelPick(quickPick.selectedItems[0])) {
732
await this._selecteKernel(notebook, quickPick.selectedItems[0].kernel);
733
}
734
735
quickPick.hide();
736
quickPick.dispose();
737
}));
738
739
localDisposableStore.add(quickPick.onDidHide(() => {
740
localDisposableStore.dispose();
741
}));
742
743
quickPick.show();
744
}
745
746
private async _executeCommand<T>(notebook: NotebookTextModel, command: string | Command): Promise<T | undefined | void> {
747
const id = typeof command === 'string' ? command : command.id;
748
const args = typeof command === 'string' ? [] : command.arguments ?? [];
749
750
if (typeof command === 'string' || !command.arguments || !Array.isArray(command.arguments) || command.arguments.length === 0) {
751
args.unshift({
752
uri: notebook.uri,
753
$mid: MarshalledId.NotebookActionContext
754
});
755
}
756
757
if (typeof command === 'string') {
758
return this._commandService.executeCommand(id);
759
} else {
760
return this._commandService.executeCommand(id, ...args);
761
}
762
}
763
764
static updateKernelStatusAction(notebook: NotebookTextModel, action: IAction, notebookKernelService: INotebookKernelService, notebookKernelHistoryService: INotebookKernelHistoryService) {
765
const detectionTasks = notebookKernelService.getKernelDetectionTasks(notebook);
766
if (detectionTasks.length) {
767
const info = notebookKernelService.getMatchingKernel(notebook);
768
action.enabled = true;
769
action.class = ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin'));
770
771
if (info.selected) {
772
action.label = info.selected.label;
773
const kernelInfo = info.selected.description ?? info.selected.detail;
774
action.tooltip = kernelInfo
775
? localize('kernels.selectedKernelAndKernelDetectionRunning', "Selected Kernel: {0} (Kernel Detection Tasks Running)", kernelInfo)
776
: localize('kernels.detecting', "Detecting Kernels");
777
} else {
778
action.label = localize('kernels.detecting', "Detecting Kernels");
779
}
780
return;
781
}
782
783
const runningActions = notebookKernelService.getRunningSourceActions(notebook);
784
785
const updateActionFromSourceAction = (sourceAction: ISourceAction, running: boolean) => {
786
const sAction = sourceAction.action;
787
action.class = running ? ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')) : ThemeIcon.asClassName(selectKernelIcon);
788
action.label = sAction.label;
789
action.enabled = true;
790
};
791
792
if (runningActions.length) {
793
return updateActionFromSourceAction(runningActions[0] /** TODO handle multiple actions state */, true);
794
}
795
796
const { selected } = notebookKernelHistoryService.getKernels(notebook);
797
798
if (selected) {
799
action.label = selected.label;
800
action.class = ThemeIcon.asClassName(selectKernelIcon);
801
action.tooltip = selected.description ?? selected.detail ?? '';
802
} else {
803
action.label = localize('select', "Select Kernel");
804
action.class = ThemeIcon.asClassName(selectKernelIcon);
805
action.tooltip = '';
806
}
807
}
808
809
static async resolveKernel(notebook: INotebookTextModel, notebookKernelService: INotebookKernelService, notebookKernelHistoryService: INotebookKernelHistoryService, commandService: ICommandService): Promise<INotebookKernel | undefined> {
810
const alreadySelected = notebookKernelHistoryService.getKernels(notebook);
811
812
if (alreadySelected.selected) {
813
return alreadySelected.selected;
814
}
815
816
await commandService.executeCommand(SELECT_KERNEL_ID);
817
const { selected } = notebookKernelHistoryService.getKernels(notebook);
818
return selected;
819
}
820
}
821
822