Skip to content

Commit 4ddb34f

Browse files
committed
Fixes
1 parent e43a880 commit 4ddb34f

File tree

4 files changed

+219
-0
lines changed

4 files changed

+219
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { CancellationToken } from 'vscode';
8+
import { IInterpreterSelector } from '../../../interpreter/configuration/types';
9+
import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types';
10+
import { KernelService } from './kernelService';
11+
import { IKernelSelectionListProvider, IKernelSpecQuickPickItem } from './types';
12+
13+
// Small classes, hence all put into one file.
14+
// tslint:disable: max-classes-per-file
15+
16+
/**
17+
* Given a kernel spec, this will return a quick pick item with appropriate display names and the like.
18+
*
19+
* @param {boolean} activeKernel Whether this is an active kernel in a jupyter session.
20+
* @param {IJupyterKernelSpec} kernelSpec
21+
* @returns {IKernelSpecQuickPickItem}
22+
*/
23+
function getQuickPickFromKernelSpec(activeKernel: boolean, kernelSpec: IJupyterKernelSpec): IKernelSpecQuickPickItem {
24+
return {
25+
label: kernelSpec.display_name,
26+
description: activeKernel ? '(active kernel)' : '(kernel)',
27+
selection: { kernelSpec: kernelSpec, interpreter: undefined }
28+
};
29+
}
30+
31+
/**
32+
* Provider for active kernel specs in a jupyter session.
33+
*
34+
* @export
35+
* @class ActiveJupyterSessionKernelSelectionListProvider
36+
* @implements {IKernelSelectionListProvider}
37+
*/
38+
export class ActiveJupyterSessionKernelSelectionListProvider implements IKernelSelectionListProvider {
39+
constructor(private readonly session: IJupyterSessionManager) {}
40+
public async getKernelSelections(_cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> {
41+
const items = await this.session.getActiveKernelSpecs();
42+
return items.filter(item => item.display_name || item.name).map(getQuickPickFromKernelSpec.bind(undefined, true));
43+
}
44+
}
45+
46+
/**
47+
* Provider for kernel specs in a jupyter process (`python -m jupyter kernelspec list`).
48+
*
49+
* @export
50+
* @class JupyterKernelSelectionListProvider
51+
* @implements {IKernelSelectionListProvider}
52+
*/
53+
export class JupyterKernelSelectionListProvider implements IKernelSelectionListProvider {
54+
constructor(private readonly kernelService: KernelService) {}
55+
public async getKernelSelections(cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> {
56+
const items = await this.kernelService.getLocalKernelSpecs(cancelToken);
57+
return items.filter(item => item.display_name || item.name).map(getQuickPickFromKernelSpec.bind(undefined, false));
58+
}
59+
}
60+
61+
/**
62+
* Provider for interpreters to be treated as kernel specs.
63+
*
64+
* @export
65+
* @class InterpreterKernelSelectionListProvider
66+
* @implements {IKernelSelectionListProvider}
67+
*/
68+
export class InterpreterKernelSelectionListProvider implements IKernelSelectionListProvider {
69+
constructor(private readonly interpreterSelector: IInterpreterSelector) {}
70+
public async getKernelSelections(_cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> {
71+
const items = await this.interpreterSelector.getSuggestions(undefined);
72+
return items.map(item => {
73+
return {
74+
...item,
75+
description: '(register and use interpreter as kernel)',
76+
selection: { interpreter: item.interpreter, kernelSpec: undefined }
77+
};
78+
});
79+
}
80+
}
81+
82+
/**
83+
* Factory class that provides a kernel spec list provider (local or remote).
84+
*
85+
* @export
86+
* @class KernelSelectionProviderFactory
87+
*/
88+
@injectable()
89+
export class KernelSelectionProvider {
90+
constructor(@inject(KernelService) private readonly kernelService: KernelService, @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector) {}
91+
/**
92+
* Gets a selection of kernel specs from a remote session.
93+
*
94+
* @param {IJupyterSessionManager} session
95+
* @param {CancellationToken} [cancelToken]
96+
* @returns {Promise<IKernelSpecQuickPickItem[]>}
97+
* @memberof KernelSelectionProvider
98+
*/
99+
public async getKernelSelectionsForRemoteSession(session: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]> {
100+
return new ActiveJupyterSessionKernelSelectionListProvider(session).getKernelSelections(cancelToken);
101+
}
102+
/**
103+
* Gets a selection of kernel specs for a local session.
104+
*
105+
* @param {IJupyterSessionManager} [session]
106+
* @param {CancellationToken} [cancelToken]
107+
* @returns {Promise<IKernelSelectionListProvider>}
108+
* @memberof KernelSelectionProvider
109+
*/
110+
public async getLocalKernelSelectionProvider(session?: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]> {
111+
const activeKernelsPromise = session ? new ActiveJupyterSessionKernelSelectionListProvider(session).getKernelSelections(cancelToken) : Promise.resolve([]);
112+
const jupyterKernelsPromise = new JupyterKernelSelectionListProvider(this.kernelService).getKernelSelections(cancelToken);
113+
const interpretersPromise = new InterpreterKernelSelectionListProvider(this.interpreterSelector).getKernelSelections(cancelToken);
114+
const [activeKernels, jupyterKernels, interprters] = await Promise.all([activeKernelsPromise, jupyterKernelsPromise, interpretersPromise]);
115+
return [...activeKernels!, ...jupyterKernels!, ...interprters];
116+
}
117+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { CancellationToken } from 'vscode-jsonrpc';
8+
import { IApplicationShell } from '../../../common/application/types';
9+
import { Cancellation } from '../../../common/cancellation';
10+
import { traceWarning } from '../../../common/logger';
11+
import { IInstaller, InstallerResponse, Product } from '../../../common/types';
12+
import { PythonInterpreter } from '../../../interpreter/contracts';
13+
import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types';
14+
import { KernelSelectionProvider } from './kernelSelections';
15+
import { KernelService } from './kernelService';
16+
17+
@injectable()
18+
export class KernelSelector {
19+
constructor(
20+
@inject(KernelSelectionProvider) private readonly selectionProvider: KernelSelectionProvider,
21+
@inject(IApplicationShell) private readonly applicationShell: IApplicationShell,
22+
@inject(KernelService) private readonly kernelService: KernelService,
23+
@inject(IInstaller) private readonly installer: IInstaller
24+
) {}
25+
public async selectRemoteKernel(session: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined> {
26+
const suggestions = this.selectionProvider.getKernelSelectionsForRemoteSession(session, cancelToken);
27+
const selection = await this.applicationShell.showQuickPick(suggestions, undefined, cancelToken);
28+
if (!selection) {
29+
return;
30+
}
31+
32+
// Nothing to validate if this is a remote connection.
33+
if (!selection.selection.kernelSpec) {
34+
return selection.selection.kernelSpec;
35+
}
36+
// This is not possible (remote kernels selector can only display remote kernels).
37+
throw new Error('Invalid Selection in kernel spec (somehow a local kernel/interpreter has been selected for a remote session!');
38+
}
39+
40+
public async selectLocalKernel(session?: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined> {
41+
const suggestions = this.selectionProvider.getLocalKernelSelectionProvider(session, cancelToken);
42+
const selection = await this.applicationShell.showQuickPick(suggestions, undefined, cancelToken);
43+
if (!selection) {
44+
return;
45+
}
46+
47+
// Check if ipykernel is installed in this kernel.
48+
if (selection.selection.interpreter) {
49+
const isValid = await this.isSelectionValid(selection.selection.interpreter, cancelToken);
50+
if (!isValid) {
51+
return;
52+
}
53+
54+
// Try an install this interpreter as a kernel.
55+
return this.kernelService.registerKernel(selection.selection.interpreter, cancelToken);
56+
} else {
57+
return selection.selection.kernelSpec;
58+
}
59+
}
60+
61+
private async isSelectionValid(interpreter: PythonInterpreter, cancelToken?: CancellationToken): Promise<boolean> {
62+
// Is ipykernel installed in this environment.
63+
if (!(await this.installer.isInstalled(Product.ipykernel, interpreter))) {
64+
if (Cancellation.isCanceled(cancelToken)) {
65+
return false;
66+
}
67+
const response = await this.installer.promptToInstall(Product.ipykernel, interpreter);
68+
if (response !== InstallerResponse.Installed) {
69+
traceWarning(`ipykernel not installed in the interpreter ${interpreter.path}`);
70+
return false;
71+
}
72+
}
73+
return true;
74+
}
75+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { CancellationToken, QuickPickItem } from 'vscode';
7+
import { PythonInterpreter } from '../../../interpreter/contracts';
8+
import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types';
9+
10+
export interface IKernelSpecQuickPickItem extends QuickPickItem {
11+
/**
12+
* Whether a kernel spec has been selected or interpter.
13+
* If interpreter is selected, then we need to install this as a kernel to get the kernel spec.
14+
*
15+
* @type {({ kernelSpec: IJupyterKernelSpec; interpreter: undefined } | { kernelSpec: undefined; interpreter: PythonInterpreter })}
16+
* @memberof IKernelSpecQuickPickItem
17+
*/
18+
selection: { kernelSpec: IJupyterKernelSpec; interpreter: undefined } | { kernelSpec: undefined; interpreter: PythonInterpreter };
19+
}
20+
21+
export interface IKernelSelectionListProvider {
22+
getKernelSelections(cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]>;
23+
}

src/client/datascience/serviceRegistry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { JupyterPasswordConnect } from './jupyter/jupyterPasswordConnect';
3838
import { JupyterServerFactory } from './jupyter/jupyterServerFactory';
3939
import { JupyterSessionManagerFactory } from './jupyter/jupyterSessionManagerFactory';
4040
import { JupyterVariables } from './jupyter/jupyterVariables';
41+
import { KernelSelector } from './jupyter/kernels/kernelSelector';
4142
import { PlotViewer } from './plotting/plotViewer';
4243
import { PlotViewerProvider } from './plotting/plotViewerProvider';
4344
import { StatusProvider } from './statusProvider';
@@ -76,6 +77,7 @@ import {
7677
IStatusProvider,
7778
IThemeFinder
7879
} from './types';
80+
import { KernelSelectionProvider } from './jupyter/kernels/kernelSelections';
7981

8082
export function registerTypes(serviceManager: IServiceManager) {
8183
serviceManager.addSingleton<IDataScienceCodeLensProvider>(IDataScienceCodeLensProvider, DataScienceCodeLensProvider);
@@ -121,4 +123,6 @@ export function registerTypes(serviceManager: IServiceManager) {
121123
serviceManager.addSingleton<IDebugLocationTracker>(IDebugLocationTracker, DebugLocationTrackerFactory);
122124
serviceManager.addSingleton<JupyterCommandFinder>(JupyterCommandFinder, JupyterCommandFinder);
123125
serviceManager.addSingleton<IExtensionSingleActivationService>(IExtensionSingleActivationService, Activation);
126+
serviceManager.addSingleton<KernelSelector>(KernelSelector, KernelSelector);
127+
serviceManager.addSingleton<KernelSelectionProvider>(KernelSelectionProvider, KernelSelectionProvider);
124128
}

0 commit comments

Comments
 (0)