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
3296 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
let suggestedKernelLanguage: string | undefined = (metaData as any)?.metadata?.language_info?.name;
425
// TODO how do we suggest multi language notebooks?
426
if (!suggestedKernelLanguage) {
427
const cellLanguages = notebookTextModel.cells.map(cell => cell.language).filter(language => language !== 'markdown');
428
// Check if cell languages is all the same
429
if (cellLanguages.length > 1) {
430
const firstLanguage = cellLanguages[0];
431
if (cellLanguages.every(language => language === firstLanguage)) {
432
suggestedKernelLanguage = firstLanguage;
433
}
434
}
435
}
436
return suggestedKernelLanguage;
437
}
438
439
/**
440
* Given a language and notebook view type suggest a kernel for installation
441
* @param language The language to find a suggested kernel extension for
442
* @returns A recommednation object for the recommended extension, else undefined
443
*/
444
private getSuggestedKernelFromLanguage(viewType: string, language: string): INotebookExtensionRecommendation | undefined {
445
const recommendation = KERNEL_RECOMMENDATIONS.get(viewType)?.get(language);
446
return recommendation;
447
}
448
}
449
450
export class KernelPickerMRUStrategy extends KernelPickerStrategyBase {
451
constructor(
452
@INotebookKernelService _notebookKernelService: INotebookKernelService,
453
@IProductService _productService: IProductService,
454
@IQuickInputService _quickInputService: IQuickInputService,
455
@ILabelService _labelService: ILabelService,
456
@ILogService _logService: ILogService,
457
@IExtensionsWorkbenchService _extensionWorkbenchService: IExtensionsWorkbenchService,
458
@IExtensionService _extensionService: IExtensionService,
459
@IExtensionManagementServerService _extensionManagementServerService: IExtensionManagementServerService,
460
@ICommandService _commandService: ICommandService,
461
@INotebookKernelHistoryService private readonly _notebookKernelHistoryService: INotebookKernelHistoryService,
462
@IOpenerService private readonly _openerService: IOpenerService
463
464
) {
465
super(
466
_notebookKernelService,
467
_productService,
468
_quickInputService,
469
_labelService,
470
_logService,
471
_extensionWorkbenchService,
472
_extensionService,
473
_commandService,
474
_extensionManagementServerService,
475
);
476
}
477
478
protected _getKernelPickerQuickPickItems(notebookTextModel: NotebookTextModel, matchResult: INotebookKernelMatchResult, notebookKernelService: INotebookKernelService, scopedContextKeyService: IContextKeyService): QuickPickInput<KernelQuickPickItem>[] {
479
const quickPickItems: QuickPickInput<KernelQuickPickItem>[] = [];
480
481
if (matchResult.selected) {
482
const kernelItem = toKernelQuickPick(matchResult.selected, matchResult.selected);
483
quickPickItems.push(kernelItem);
484
}
485
486
matchResult.suggestions.filter(kernel => kernel.id !== matchResult.selected?.id).map(kernel => toKernelQuickPick(kernel, matchResult.selected))
487
.forEach(kernel => {
488
quickPickItems.push(kernel);
489
});
490
491
const shouldAutoRun = quickPickItems.length === 0;
492
493
if (quickPickItems.length > 0) {
494
quickPickItems.push({
495
type: 'separator'
496
});
497
}
498
499
// select another kernel quick pick
500
quickPickItems.push({
501
id: 'selectAnother',
502
label: localize('selectAnotherKernel.more', "Select Another Kernel..."),
503
autoRun: shouldAutoRun
504
});
505
506
return quickPickItems;
507
}
508
509
protected override _selecteKernel(notebook: NotebookTextModel, kernel: INotebookKernel): void {
510
const currentInfo = this._notebookKernelService.getMatchingKernel(notebook);
511
if (currentInfo.selected) {
512
// there is already a selected kernel
513
this._notebookKernelHistoryService.addMostRecentKernel(currentInfo.selected);
514
}
515
super._selecteKernel(notebook, kernel);
516
this._notebookKernelHistoryService.addMostRecentKernel(kernel);
517
}
518
519
protected override _getMatchingResult(notebook: NotebookTextModel): INotebookKernelMatchResult {
520
const { selected, all } = this._notebookKernelHistoryService.getKernels(notebook);
521
const matchingResult = this._notebookKernelService.getMatchingKernel(notebook);
522
return {
523
selected: selected,
524
all: matchingResult.all,
525
suggestions: all,
526
hidden: []
527
};
528
}
529
530
protected override async _handleQuickPick(editor: IActiveNotebookEditor, pick: KernelQuickPickItem, items: KernelQuickPickItem[]): Promise<boolean> {
531
if (pick.id === 'selectAnother') {
532
return this.displaySelectAnotherQuickPick(editor, items.length === 1 && items[0] === pick);
533
}
534
535
return super._handleQuickPick(editor, pick, items);
536
}
537
538
private async displaySelectAnotherQuickPick(editor: IActiveNotebookEditor, kernelListEmpty: boolean): Promise<boolean> {
539
const notebook: NotebookTextModel = editor.textModel;
540
const disposables = new DisposableStore();
541
const quickPick = disposables.add(this._quickInputService.createQuickPick<KernelQuickPickItem>({ useSeparators: true }));
542
const quickPickItem = await new Promise<KernelQuickPickItem | IQuickInputButton | undefined>(resolve => {
543
// select from kernel sources
544
quickPick.title = kernelListEmpty ? localize('select', "Select Kernel") : localize('selectAnotherKernel', "Select Another Kernel");
545
quickPick.placeholder = localize('selectKernel.placeholder', "Type to choose a kernel source");
546
quickPick.busy = true;
547
quickPick.buttons = [this._quickInputService.backButton];
548
quickPick.show();
549
550
disposables.add(quickPick.onDidTriggerButton(button => {
551
if (button === this._quickInputService.backButton) {
552
resolve(button);
553
}
554
}));
555
disposables.add(quickPick.onDidTriggerItemButton(async (e) => {
556
if (isKernelSourceQuickPickItem(e.item) && e.item.documentation !== undefined) {
557
const uri = URI.isUri(e.item.documentation) ? URI.parse(e.item.documentation) : await this._commandService.executeCommand(e.item.documentation);
558
void this._openerService.open(uri, { openExternal: true });
559
}
560
}));
561
disposables.add(quickPick.onDidAccept(async () => {
562
resolve(quickPick.selectedItems[0]);
563
}));
564
disposables.add(quickPick.onDidHide(() => {
565
resolve(undefined);
566
}));
567
568
this._calculdateKernelSources(editor).then(quickPickItems => {
569
quickPick.items = quickPickItems;
570
if (quickPick.items.length > 0) {
571
quickPick.busy = false;
572
}
573
});
574
575
disposables.add(Event.debounce<void, void>(
576
Event.any(
577
this._notebookKernelService.onDidChangeSourceActions,
578
this._notebookKernelService.onDidAddKernel,
579
this._notebookKernelService.onDidRemoveKernel
580
),
581
(last, _current) => last,
582
KERNEL_PICKER_UPDATE_DEBOUNCE
583
)(async () => {
584
quickPick.busy = true;
585
const quickPickItems = await this._calculdateKernelSources(editor);
586
quickPick.items = quickPickItems;
587
quickPick.busy = false;
588
}));
589
});
590
591
quickPick.hide();
592
disposables.dispose();
593
594
if (quickPickItem === this._quickInputService.backButton) {
595
return this.showQuickPick(editor, undefined, true);
596
}
597
598
if (quickPickItem) {
599
const selectedKernelPickItem = quickPickItem as KernelQuickPickItem;
600
if (isKernelSourceQuickPickItem(selectedKernelPickItem)) {
601
try {
602
const selectedKernelId = await this._executeCommand<string>(notebook, selectedKernelPickItem.command);
603
if (selectedKernelId) {
604
const { all } = await this._getMatchingResult(notebook);
605
const kernel = all.find(kernel => kernel.id === `ms-toolsai.jupyter/${selectedKernelId}`);
606
if (kernel) {
607
await this._selecteKernel(notebook, kernel);
608
return true;
609
}
610
return true;
611
} else {
612
return this.displaySelectAnotherQuickPick(editor, false);
613
}
614
} catch (ex) {
615
return false;
616
}
617
} else if (isKernelPick(selectedKernelPickItem)) {
618
await this._selecteKernel(notebook, selectedKernelPickItem.kernel);
619
return true;
620
} else if (isGroupedKernelsPick(selectedKernelPickItem)) {
621
await this._selectOneKernel(notebook, selectedKernelPickItem.label, selectedKernelPickItem.kernels);
622
return true;
623
} else if (isSourcePick(selectedKernelPickItem)) {
624
// selected explicilty, it should trigger the execution?
625
try {
626
await selectedKernelPickItem.action.runAction();
627
return true;
628
} catch (ex) {
629
return false;
630
}
631
} else if (isSearchMarketplacePick(selectedKernelPickItem)) {
632
await this._showKernelExtension(
633
this._extensionWorkbenchService,
634
this._extensionService,
635
this._extensionManagementServerService,
636
editor.textModel.viewType,
637
[]
638
);
639
return true;
640
} else if (isInstallExtensionPick(selectedKernelPickItem)) {
641
await this._showKernelExtension(
642
this._extensionWorkbenchService,
643
this._extensionService,
644
this._extensionManagementServerService,
645
editor.textModel.viewType,
646
selectedKernelPickItem.extensionIds,
647
this._productService.quality !== 'stable'
648
);
649
return this.displaySelectAnotherQuickPick(editor, false);
650
}
651
}
652
653
return false;
654
}
655
656
private async _calculdateKernelSources(editor: IActiveNotebookEditor) {
657
const notebook: NotebookTextModel = editor.textModel;
658
659
const sourceActionCommands = this._notebookKernelService.getSourceActions(notebook, editor.scopedContextKeyService);
660
const actions = await this._notebookKernelService.getKernelSourceActions2(notebook);
661
const matchResult = this._getMatchingResult(notebook);
662
663
if (sourceActionCommands.length === 0 && matchResult.all.length === 0 && actions.length === 0) {
664
return await this._getKernelRecommendationsQuickPickItems(notebook, this._extensionWorkbenchService) ?? [];
665
}
666
667
const others = matchResult.all.filter(item => item.extension.value !== JUPYTER_EXTENSION_ID);
668
const quickPickItems: QuickPickInput<KernelQuickPickItem>[] = [];
669
670
// group controllers by extension
671
for (const group of groupBy(others, (a, b) => a.extension.value === b.extension.value ? 0 : 1)) {
672
const extension = this._extensionService.extensions.find(extension => extension.identifier.value === group[0].extension.value);
673
const source = extension?.displayName ?? extension?.description ?? group[0].extension.value;
674
if (group.length > 1) {
675
quickPickItems.push({
676
label: source,
677
kernels: group
678
});
679
} else {
680
quickPickItems.push({
681
label: group[0].label,
682
kernel: group[0]
683
});
684
}
685
}
686
687
const validActions = actions.filter(action => action.command);
688
689
quickPickItems.push(...validActions.map(action => {
690
const buttons = action.documentation ? [{
691
iconClass: ThemeIcon.asClassName(Codicon.info),
692
tooltip: localize('learnMoreTooltip', 'Learn More'),
693
}] : [];
694
return {
695
id: typeof action.command! === 'string' ? action.command : action.command!.id,
696
label: action.label,
697
description: action.description,
698
command: action.command,
699
documentation: action.documentation,
700
buttons
701
};
702
}));
703
704
for (const sourceAction of sourceActionCommands) {
705
const res: SourcePick = {
706
action: sourceAction,
707
picked: false,
708
label: sourceAction.action.label,
709
tooltip: sourceAction.action.tooltip
710
};
711
712
quickPickItems.push(res);
713
}
714
715
return quickPickItems;
716
}
717
718
private async _selectOneKernel(notebook: NotebookTextModel, source: string, kernels: INotebookKernel[]) {
719
const quickPickItems: QuickPickInput<KernelPick>[] = kernels.map(kernel => toKernelQuickPick(kernel, undefined));
720
const localDisposableStore = new DisposableStore();
721
const quickPick = localDisposableStore.add(this._quickInputService.createQuickPick<KernelQuickPickItem>({ useSeparators: true }));
722
quickPick.items = quickPickItems;
723
quickPick.canSelectMany = false;
724
725
quickPick.title = localize('selectKernelFromExtension', "Select Kernel from {0}", source);
726
727
localDisposableStore.add(quickPick.onDidAccept(async () => {
728
if (quickPick.selectedItems && quickPick.selectedItems.length > 0 && isKernelPick(quickPick.selectedItems[0])) {
729
await this._selecteKernel(notebook, quickPick.selectedItems[0].kernel);
730
}
731
732
quickPick.hide();
733
quickPick.dispose();
734
}));
735
736
localDisposableStore.add(quickPick.onDidHide(() => {
737
localDisposableStore.dispose();
738
}));
739
740
quickPick.show();
741
}
742
743
private async _executeCommand<T>(notebook: NotebookTextModel, command: string | Command): Promise<T | undefined | void> {
744
const id = typeof command === 'string' ? command : command.id;
745
const args = typeof command === 'string' ? [] : command.arguments ?? [];
746
747
if (typeof command === 'string' || !command.arguments || !Array.isArray(command.arguments) || command.arguments.length === 0) {
748
args.unshift({
749
uri: notebook.uri,
750
$mid: MarshalledId.NotebookActionContext
751
});
752
}
753
754
if (typeof command === 'string') {
755
return this._commandService.executeCommand(id);
756
} else {
757
return this._commandService.executeCommand(id, ...args);
758
}
759
}
760
761
static updateKernelStatusAction(notebook: NotebookTextModel, action: IAction, notebookKernelService: INotebookKernelService, notebookKernelHistoryService: INotebookKernelHistoryService) {
762
const detectionTasks = notebookKernelService.getKernelDetectionTasks(notebook);
763
if (detectionTasks.length) {
764
const info = notebookKernelService.getMatchingKernel(notebook);
765
action.enabled = true;
766
action.class = ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin'));
767
768
if (info.selected) {
769
action.label = info.selected.label;
770
const kernelInfo = info.selected.description ?? info.selected.detail;
771
action.tooltip = kernelInfo
772
? localize('kernels.selectedKernelAndKernelDetectionRunning', "Selected Kernel: {0} (Kernel Detection Tasks Running)", kernelInfo)
773
: localize('kernels.detecting', "Detecting Kernels");
774
} else {
775
action.label = localize('kernels.detecting', "Detecting Kernels");
776
}
777
return;
778
}
779
780
const runningActions = notebookKernelService.getRunningSourceActions(notebook);
781
782
const updateActionFromSourceAction = (sourceAction: ISourceAction, running: boolean) => {
783
const sAction = sourceAction.action;
784
action.class = running ? ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')) : ThemeIcon.asClassName(selectKernelIcon);
785
action.label = sAction.label;
786
action.enabled = true;
787
};
788
789
if (runningActions.length) {
790
return updateActionFromSourceAction(runningActions[0] /** TODO handle multiple actions state */, true);
791
}
792
793
const { selected } = notebookKernelHistoryService.getKernels(notebook);
794
795
if (selected) {
796
action.label = selected.label;
797
action.class = ThemeIcon.asClassName(selectKernelIcon);
798
action.tooltip = selected.description ?? selected.detail ?? '';
799
} else {
800
action.label = localize('select', "Select Kernel");
801
action.class = ThemeIcon.asClassName(selectKernelIcon);
802
action.tooltip = '';
803
}
804
}
805
806
static async resolveKernel(notebook: INotebookTextModel, notebookKernelService: INotebookKernelService, notebookKernelHistoryService: INotebookKernelHistoryService, commandService: ICommandService): Promise<INotebookKernel | undefined> {
807
const alreadySelected = notebookKernelHistoryService.getKernels(notebook);
808
809
if (alreadySelected.selected) {
810
return alreadySelected.selected;
811
}
812
813
await commandService.executeCommand(SELECT_KERNEL_ID);
814
const { selected } = notebookKernelHistoryService.getKernels(notebook);
815
return selected;
816
}
817
}
818
819