Skip to content

Commit 99c82e1

Browse files
committed
Initial
1 parent 6247c20 commit 99c82e1

File tree

7 files changed

+216
-8
lines changed

7 files changed

+216
-8
lines changed

src/client/common/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Request as RequestResult } from 'request';
77
import { ConfigurationTarget, DiagnosticSeverity, Disposable, DocumentSymbolProvider, Event, Extension, ExtensionContext, OutputChannel, Uri, WorkspaceEdit } from 'vscode';
88
import { CommandsWithoutArgs } from './application/commands';
99
import { ExtensionChannels } from './insidersBuild/types';
10+
import { InterpreterUri } from './installer/types';
1011
import { EnvironmentVariables } from './variables/types';
1112
export const IOutputChannel = Symbol('IOutputChannel');
1213
export interface IOutputChannel extends OutputChannel { }
@@ -113,9 +114,9 @@ export enum ModuleNamePurpose {
113114
export const IInstaller = Symbol('IInstaller');
114115

115116
export interface IInstaller {
116-
promptToInstall(product: Product, resource?: Uri): Promise<InstallerResponse>;
117-
install(product: Product, resource?: Uri): Promise<InstallerResponse>;
118-
isInstalled(product: Product, resource?: Uri): Promise<boolean | undefined>;
117+
promptToInstall(product: Product, resource?: InterpreterUri): Promise<InstallerResponse>;
118+
install(product: Product, resource?: InterpreterUri): Promise<InstallerResponse>;
119+
isInstalled(product: Product, resource?: InterpreterUri): Promise<boolean | undefined>;
119120
translateProductToModuleName(product: Product, purpose: ModuleNamePurpose): string;
120121
}
121122

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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, QuickPickItem } from 'vscode';
8+
import { IInterpreterSelector } from '../../../interpreter/configuration/types';
9+
import { PythonInterpreter } from '../../../interpreter/contracts';
10+
import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types';
11+
import { KernelService } from './kernelService';
12+
13+
export interface IKernelSpecQuickPickItem extends QuickPickItem {
14+
selection: { kernelSpec: IJupyterKernelSpec } | { interpreter: PythonInterpreter };
15+
}
16+
17+
function getQuickPickFromKernelSpec(kernelSpec: IJupyterKernelSpec): IKernelSpecQuickPickItem {
18+
return {
19+
label: kernelSpec.display_name || kernelSpec.name || '',
20+
description: '',
21+
selection: { kernelSpec: kernelSpec }
22+
};
23+
}
24+
export interface IKernelSelector {
25+
selectKernelSpec(cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined>;
26+
}
27+
28+
// export class kernelSelector implements IKernelSelector {
29+
// public async selectKernelSpec(session?: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined> {
30+
// return;
31+
// }
32+
// private selectKernelSpecFromList(session?: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined> {}
33+
// }
34+
35+
export interface IKernelSelectionListProvider {
36+
getKernelSelections(cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]>;
37+
}
38+
39+
40+
@injectable()
41+
export class KernelSelectionProviderFactory {
42+
constructor(@inject(KernelService) private readonly kernelService: KernelService, @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector) {}
43+
public async getRemoteKernelSelectionProvider(session: IJupyterSessionManager): Promise<IKernelSelectionListProvider> {
44+
// tslint:disable: no-use-before-declare
45+
return new RemoteKernelSelectionProvider(session);
46+
}
47+
public async getLocalKernelSelectionProvider(session?: IJupyterSessionManager): Promise<IKernelSelectionListProvider> {
48+
return new LocalKernelSelectionProvider(this.kernelService, this.interpreterSelector, session);
49+
}
50+
}
51+
export class RemoteKernelSelectionProvider implements IKernelSelectionListProvider {
52+
constructor(private readonly session: IJupyterSessionManager) {}
53+
public async getKernelSelections(cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]> {
54+
return new JupyterSessionKernelSelectionnListProvider(this.session).getKernelSelections(cancelToken);
55+
}
56+
}
57+
58+
// tslint:disable: max-classes-per-file
59+
export class LocalKernelSelectionProvider implements IKernelSelectionListProvider {
60+
constructor(private readonly kernelService: KernelService, private readonly interpreterSelector: IInterpreterSelector, private readonly session?: IJupyterSessionManager) {}
61+
public async getKernelSelections(cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]> {
62+
const activeKernelsPromise = this.session ? new JupyterSessionKernelSelectionnListProvider(this.session).getKernelSelections(cancelToken) : Promise.resolve([]);
63+
const jupyterKernelsPromise = new JupyterKernelSelectionnListProvider(this.kernelService).getKernelSelections(cancelToken);
64+
const interpretersPromise = new InterpreterKernelSelectionnListProvider(this.interpreterSelector).getKernelSelections(cancelToken);
65+
const [activeKernels, jupyterKernels, interprters] = await Promise.all([activeKernelsPromise, jupyterKernelsPromise, interpretersPromise]);
66+
return [...activeKernels!, ...jupyterKernels!, ...interprters];
67+
}
68+
}
69+
70+
export class JupyterSessionKernelSelectionnListProvider implements IKernelSelectionListProvider {
71+
constructor(private readonly session: IJupyterSessionManager) {}
72+
public async getKernelSelections(_cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> {
73+
const items = await this.session.getActiveKernelSpecs();
74+
return items.filter(item => item.display_name || item.name).map(getQuickPickFromKernelSpec);
75+
}
76+
}
77+
78+
export class JupyterKernelSelectionnListProvider implements IKernelSelectionListProvider {
79+
constructor(private readonly kernelService: KernelService) {}
80+
public async getKernelSelections(cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> {
81+
const items = await this.kernelService.getLocalKernelSpecs(cancelToken);
82+
return items.filter(item => item.display_name || item.name).map(getQuickPickFromKernelSpec);
83+
}
84+
}
85+
86+
// tslint:disable: max-classes-per-file
87+
export class InterpreterKernelSelectionnListProvider implements IKernelSelectionListProvider {
88+
constructor(private readonly interpreterSelector: IInterpreterSelector) {}
89+
public async getKernelSelections(_cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> {
90+
const items = await this.interpreterSelector.getSuggestions(undefined);
91+
return items.map(item => {
92+
return {
93+
...item,
94+
selection: { interpreter: item.interpreter }
95+
};
96+
});
97+
}
98+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 { traceWarning } from '../../../common/logger';
10+
import { IInstaller, InstallerResponse, Product } from '../../../common/types';
11+
import { PythonInterpreter } from '../../../interpreter/contracts';
12+
import { JupyterCommands } from '../../constants';
13+
import { IJupyterCommand, IJupyterKernelSpec, IJupyterSessionManager } from '../../types';
14+
import { JupyterCommandFinder, ModuleExistsStatus } from '../jupyterCommandFinder';
15+
import { KernelSelectionProviderFactory } from './kernelSelections';
16+
import { KernelService } from './kernelService';
17+
18+
@injectable()
19+
export class KernelSelector {
20+
constructor(
21+
@inject(KernelSelectionProviderFactory) private readonly providerFactory: KernelSelectionProviderFactory,
22+
@inject(IApplicationShell) private readonly applicationShell: IApplicationShell,
23+
@inject(JupyterCommandFinder) private readonly cmdFinder: JupyterCommandFinder,
24+
@inject(KernelService) private readonly kernelService: KernelService,
25+
@inject(IInstaller) private readonly installer: IInstaller
26+
) {}
27+
public async selectKernel(
28+
options: { session?: IJupyterSessionManager } | { session: IJupyterSessionManager; isRemoteConnection: true },
29+
cancelToken?: CancellationToken
30+
): Promise<IJupyterKernelSpec | undefined> {
31+
const provider =
32+
'isRemoteConnection' in options
33+
? this.providerFactory.getRemoteKernelSelectionProvider(options.session)
34+
: this.providerFactory.getLocalKernelSelectionProvider(options.session);
35+
36+
const suggestions = (await provider).getKernelSelections(cancelToken);
37+
const selection = await this.applicationShell.showQuickPick(suggestions);
38+
39+
if (!selection) {
40+
return;
41+
}
42+
43+
// Nothing to validate if this is a remote connection.
44+
if ('isRemoteConnection' in options) {
45+
if ('kernelSpec' in selection.selection) {
46+
return selection.selection.kernelSpec;
47+
}
48+
// This is not possible.
49+
throw new Error('Invalid Selection in kernel spec');
50+
}
51+
52+
// Check if ipykernel is installed in this kernel.
53+
if ('interpreter' in selection.selection) {
54+
const interpreter = selection.selection.interpreter;
55+
const isValid = await this.isSelectionValid(interpreter, cancelToken);
56+
if (!isValid) {
57+
return;
58+
}
59+
60+
// Try an install this interpreter as a kernel.
61+
return this.kernelService.registerKernel(interpreter, cancelToken);
62+
} else {
63+
return selection.selection.kernelSpec;
64+
}
65+
}
66+
67+
private async isSelectionValid(interpreter: PythonInterpreter, cancelToken?: CancellationToken): Promise<boolean> {
68+
// Do we have the ability to install kernels.
69+
const specCmd = await this.getCreateCmd(cancelToken);
70+
if (!specCmd) {
71+
traceWarning('JupyterCommand not available to install a kernel');
72+
return false;
73+
}
74+
// Is ipykernel installed in this environment.
75+
if (!(await this.installer.isInstalled(Product.ipykernel, interpreter))) {
76+
const response = await this.installer.promptToInstall(Product.ipykernel, interpreter);
77+
if (response !== InstallerResponse.Installed) {
78+
traceWarning(`ipykernel not installed in the interpreter ${interpreter.path}`);
79+
return false;
80+
}
81+
}
82+
return true;
83+
}
84+
private async getCreateCmd(cancelToken?: CancellationToken): Promise<IJupyterCommand | undefined> {
85+
const specCmd = await this.cmdFinder.findBestCommand(JupyterCommands.KernelCreateCommand, cancelToken);
86+
if (specCmd.status === ModuleExistsStatus.NotFound) {
87+
//this.applicationShell.showInformationMessage('Install?');
88+
}
89+
if (specCmd.command) {
90+
return specCmd.command;
91+
}
92+
}
93+
}

src/client/datascience/jupyter/kernels/kernelService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ export class KernelService {
132132
}
133133
}
134134
}
135+
136+
public getLocalKernelSpecs(cancelToken?: CancellationToken): Promise<IJupyterKernelSpec[]> {
137+
return this.enumerateSpecs(cancelToken);
138+
}
135139
/**
136140
* Registers an interprter as a kernel.
137141
* The assumption is that `ipykernel` has been installed in the interpreter.

src/client/interpreter/configuration/interpreterSelector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export class InterpreterSelector implements IInterpreterSelector {
4242
// tslint:disable-next-line:no-non-null-assertion
4343
label: suggestion.displayName!,
4444
detail: `${cachedPrefix}${detail}`,
45-
path: suggestion.path
45+
path: suggestion.path,
46+
interpreter: suggestion
4647
};
4748
}
4849

src/client/interpreter/configuration/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface IInterpreterSelector extends Disposable {
2626

2727
export interface IInterpreterQuickPickItem extends QuickPickItem {
2828
path: string;
29+
interpreter: PythonInterpreter;
2930
}
3031

3132
export const IInterpreterComparer = Symbol('IInterpreterComparer');

src/test/configuration/interpreterSelector.unit.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class InterpreterQuickPickItem implements IInterpreterQuickPickItem {
3131
public label: string;
3232
public description!: string;
3333
public detail?: string;
34+
// tslint:disable-next-line: no-any
35+
public interpreter = {} as any;
3436
constructor(l: string, p: string) {
3537
this.path = p;
3638
this.label = l;
@@ -136,7 +138,9 @@ suite('Interpreters - selector', () => {
136138
pythonSettings.setup(p => p.pythonPath).returns(() => 'python');
137139
const selectedItem: IInterpreterQuickPickItem = {
138140
description: '', detail: '', label: '',
139-
path: 'This is the selected Python path'
141+
path: 'This is the selected Python path',
142+
// tslint:disable-next-line: no-any
143+
interpreter: {} as any
140144
};
141145

142146
workspace.setup(w => w.workspaceFolders).returns(() => undefined);
@@ -166,7 +170,9 @@ suite('Interpreters - selector', () => {
166170
pythonSettings.setup(p => p.pythonPath).returns(() => 'python');
167171
const selectedItem: IInterpreterQuickPickItem = {
168172
description: '', detail: '', label: '',
169-
path: 'This is the selected Python path'
173+
path: 'This is the selected Python path',
174+
// tslint:disable-next-line: no-any
175+
interpreter: {} as any
170176
};
171177

172178
const folder = { name: 'one', uri: Uri.parse('one'), index: 0 };
@@ -197,7 +203,9 @@ suite('Interpreters - selector', () => {
197203
pythonSettings.setup(p => p.pythonPath).returns(() => 'python');
198204
const selectedItem: IInterpreterQuickPickItem = {
199205
description: '', detail: '', label: '',
200-
path: 'This is the selected Python path'
206+
path: 'This is the selected Python path',
207+
// tslint:disable-next-line: no-any
208+
interpreter: {} as any
201209
};
202210

203211
const folder1 = { name: 'one', uri: Uri.parse('one'), index: 1 };
@@ -232,7 +240,9 @@ suite('Interpreters - selector', () => {
232240

233241
const selectedItem: IInterpreterQuickPickItem = {
234242
description: '', detail: '', label: '',
235-
path: 'This is the selected Python path'
243+
path: 'This is the selected Python path',
244+
// tslint:disable-next-line: no-any
245+
interpreter: {} as any
236246
};
237247

238248
const folder1 = { name: 'one', uri: Uri.parse('one'), index: 1 };

0 commit comments

Comments
 (0)