diff --git a/src/client/datascience/interactive-common/interactiveBase.ts b/src/client/datascience/interactive-common/interactiveBase.ts index f50e5986d325..7c89937cefb9 100644 --- a/src/client/datascience/interactive-common/interactiveBase.ts +++ b/src/client/datascience/interactive-common/interactiveBase.ts @@ -972,9 +972,9 @@ export abstract class InteractiveBase extends WebViewHost { // Create a new notebook if we need to. if (!this._notebook) { - this._notebook = await server.createNotebook(await this.getNotebookIdentity()); + const [uri, options] = await Promise.all([this.getNotebookIdentity(), this.getNotebookOptions()]); + this._notebook = await server.createNotebook(uri, options.metadata); if (this._notebook) { - const uri: Uri = await this.getNotebookIdentity(); this.postMessage(InteractiveWindowMessages.NotebookExecutionActivated, uri.toString()).ignoreErrors(); const statusChangeHandler = async (status: ServerStatus) => { diff --git a/src/client/datascience/interactive-ipynb/nativeEditor.ts b/src/client/datascience/interactive-ipynb/nativeEditor.ts index 1dc8d4de8b63..79bf289b00cd 100644 --- a/src/client/datascience/interactive-ipynb/nativeEditor.ts +++ b/src/client/datascience/interactive-ipynb/nativeEditor.ts @@ -84,6 +84,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { private savedEvent: EventEmitter = new EventEmitter(); private metadataUpdatedEvent: EventEmitter = new EventEmitter(); private loadedPromise: Deferred = createDeferred(); + private contentsLoadedPromise: Deferred = createDeferred(); private _file: Uri = Uri.file(''); private _dirty: boolean = false; private isPromptingToSaveToDisc: boolean = false; @@ -299,6 +300,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { public async getNotebookOptions(): Promise { const options = await this.ipynbProvider.getNotebookOptions(); + await this.contentsLoadedPromise.promise; const metadata = this.notebookJson.metadata; return { ...options, @@ -587,6 +589,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { if (json) { this.notebookJson = json; } + this.contentsLoadedPromise.resolve(); // Extract cells from the json const cells = contents ? (json.cells as (nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell)[]) : []; diff --git a/src/client/datascience/jupyter/jupyterServer.ts b/src/client/datascience/jupyter/jupyterServer.ts index 2bff18b24a28..df0621010fe5 100644 --- a/src/client/datascience/jupyter/jupyterServer.ts +++ b/src/client/datascience/jupyter/jupyterServer.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { nbformat } from '@jupyterlab/coreutils'; import * as uuid from 'uuid/v4'; import { Disposable, Uri } from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; @@ -39,7 +40,7 @@ export class JupyterServerBase implements INotebookServer { _liveShare: ILiveShareApi, private asyncRegistry: IAsyncDisposableRegistry, private disposableRegistry: IDisposableRegistry, - private configService: IConfigurationService, + protected readonly configService: IConfigurationService, private sessionManagerFactory: IJupyterSessionManagerFactory, private loggers: INotebookExecutionLogger[] ) { @@ -77,7 +78,7 @@ export class JupyterServerBase implements INotebookServer { this.savedSession = session; } - public createNotebook(resource: Uri, cancelToken?: CancellationToken): Promise { + public createNotebook(resource: Uri, notebookMetadata?: nbformat.INotebookMetadata, cancelToken?: CancellationToken): Promise { if (!this.sessionManager) { throw new Error(localize.DataScience.sessionDisposed()); } @@ -86,7 +87,7 @@ export class JupyterServerBase implements INotebookServer { this.savedSession = undefined; // Create a notebook and return it. - return this.createNotebookInstance(resource, this.sessionManager, savedSession, this.disposableRegistry, this.configService, this.loggers, cancelToken); + return this.createNotebookInstance(resource, this.sessionManager, savedSession, this.disposableRegistry, this.configService, this.loggers, notebookMetadata, cancelToken); } public async shutdown(): Promise { @@ -187,6 +188,7 @@ export class JupyterServerBase implements INotebookServer { _disposableRegistry: IDisposableRegistry, _configService: IConfigurationService, _loggers: INotebookExecutionLogger[], + _notebookMetadata?: nbformat.INotebookMetadata, _cancelToken?: CancellationToken ): Promise { throw new Error('You forgot to override createNotebookInstance'); diff --git a/src/client/datascience/jupyter/jupyterServerWrapper.ts b/src/client/datascience/jupyter/jupyterServerWrapper.ts index 7a0d7efb3b40..eb0347801cf1 100644 --- a/src/client/datascience/jupyter/jupyterServerWrapper.ts +++ b/src/client/datascience/jupyter/jupyterServerWrapper.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { nbformat } from '@jupyterlab/coreutils'; import { inject, injectable, multiInject, optional } from 'inversify'; import * as uuid from 'uuid/v4'; import { Uri } from 'vscode'; @@ -12,6 +13,7 @@ import { IFileSystem } from '../../common/platform/types'; import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry } from '../../common/types'; import { IInterpreterService } from '../../interpreter/contracts'; import { IConnection, IDataScience, IJupyterSessionManagerFactory, INotebook, INotebookExecutionLogger, INotebookServer, INotebookServerLaunchInfo } from '../types'; +import { KernelSelector } from './kernels/kernelSelector'; import { GuestJupyterServer } from './liveshare/guestJupyterServer'; import { HostJupyterServer } from './liveshare/hostJupyterServer'; import { IRoleBasedObject, RoleBasedFactory } from './liveshare/roleBasedFactory'; @@ -32,6 +34,7 @@ type JupyterServerClassType = { loggers: INotebookExecutionLogger[], appShell: IApplicationShell, fs: IFileSystem, + kernelSelector: KernelSelector, interpreterService: IInterpreterService ): IJupyterServerInterface; }; @@ -57,7 +60,8 @@ export class JupyterServerWrapper implements INotebookServer, ILiveShareHasRole @multiInject(INotebookExecutionLogger) @optional() loggers: INotebookExecutionLogger[] | undefined, @inject(IApplicationShell) appShell: IApplicationShell, @inject(IFileSystem) fs: IFileSystem, - @inject(IInterpreterService) interpreterService: IInterpreterService + @inject(IInterpreterService) interpreterService: IInterpreterService, + @inject(KernelSelector) kernelSelector: KernelSelector ) { // The server factory will create the appropriate HostJupyterServer or GuestJupyterServer based on // the liveshare state. @@ -75,6 +79,7 @@ export class JupyterServerWrapper implements INotebookServer, ILiveShareHasRole loggers ? loggers : [], appShell, fs, + kernelSelector, interpreterService ); } @@ -93,9 +98,9 @@ export class JupyterServerWrapper implements INotebookServer, ILiveShareHasRole return server.connect(launchInfo, cancelToken); } - public async createNotebook(resource: Uri): Promise { + public async createNotebook(resource: Uri, notebookMetadata?: nbformat.INotebookMetadata, cancelToken?: CancellationToken): Promise { const server = await this.serverFactory.get(); - return server.createNotebook(resource); + return server.createNotebook(resource, notebookMetadata, cancelToken); } public async shutdown(): Promise { diff --git a/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts b/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts index e4e1cfc79d8e..cec3de412ca8 100644 --- a/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts +++ b/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts @@ -8,6 +8,7 @@ import * as vscode from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; import * as vsls from 'vsls/vscode'; +import { nbformat } from '@jupyterlab/coreutils'; import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../common/application/types'; import { traceInfo } from '../../../common/logger'; import { IFileSystem } from '../../../common/platform/types'; @@ -25,6 +26,7 @@ import { INotebookServerLaunchInfo } from '../../types'; import { JupyterServerBase } from '../jupyterServer'; +import { KernelSelector } from '../kernels/kernelSelector'; import { HostJupyterNotebook } from './hostJupyterNotebook'; import { LiveShareParticipantHost } from './liveShareParticipantMixin'; import { IRoleBasedObject } from './roleBasedFactory'; @@ -46,7 +48,8 @@ export class HostJupyterServer extends LiveShareParticipantHost(JupyterServerBas private workspaceService: IWorkspaceService, loggers: INotebookExecutionLogger[], private appService: IApplicationShell, - private fs: IFileSystem + private fs: IFileSystem, + private readonly kernelSelector: KernelSelector ) { super(liveShare, asyncRegistry, disposableRegistry, configService, sessionManager, loggers); } @@ -86,7 +89,7 @@ export class HostJupyterServer extends LiveShareParticipantHost(JupyterServerBas const uri = vscode.Uri.parse(args[0]); const resource = uri.scheme && uri.scheme !== Identifiers.InteractiveWindowIdentityScheme ? this.finishedApi!.convertSharedUriToLocal(uri) : uri; // Don't return the notebook. We don't want it to be serialized. We just want its live share server to be started. - const notebook = (await this.createNotebook(resource, cancellation)) as HostJupyterNotebook; + const notebook = (await this.createNotebook(resource, undefined, cancellation)) as HostJupyterNotebook; await notebook.onAttach(api); }); @@ -137,6 +140,7 @@ export class HostJupyterServer extends LiveShareParticipantHost(JupyterServerBas disposableRegistry: IDisposableRegistry, configService: IConfigurationService, loggers: INotebookExecutionLogger[], + notebookMetadata?: nbformat.INotebookMetadata, cancelToken?: CancellationToken ): Promise { // See if already exists. @@ -159,8 +163,25 @@ export class HostJupyterServer extends LiveShareParticipantHost(JupyterServerBas throw this.getDisposedError(); } + // Find a kernel that can be used. + // Do this only if kernel information has been provided in the metadata, else use the default. + let defaultKernelInfoToUse = launchInfo.kernelSpec; + if (notebookMetadata?.kernelspec) { + const kernelInfo = await (launchInfo.connectionInfo.localLaunch + ? this.kernelSelector.getKernelForLocalConnection(sessionManager, notebookMetadata, false, cancelToken) + : this.kernelSelector.getKernelForRemoteConnection(sessionManager, notebookMetadata, cancelToken)); + + const kernelInfoToUse = kernelInfo?.kernelSpec || kernelInfo?.kernelModel; + if (kernelInfoToUse) { + defaultKernelInfoToUse = kernelInfoToUse; + } + if (possibleSession && kernelInfoToUse) { + await possibleSession.changeKernel(kernelInfoToUse, this.configService.getSettings().datascience.jupyterLaunchTimeout); + } + launchInfo.kernelSpec = defaultKernelInfoToUse; + } // Start a session (or use the existing one) - const session = possibleSession || (await sessionManager.startNew(launchInfo.kernelSpec, cancelToken)); + const session = possibleSession || (await sessionManager.startNew(defaultKernelInfoToUse, cancelToken)); traceInfo(`Started session ${this.id}`); if (session) { diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 8a6c0f4d883b..86eb0c0fd185 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -81,7 +81,7 @@ export interface INotebookCompletion { export const INotebookServer = Symbol('INotebookServer'); export interface INotebookServer extends IAsyncDisposable { readonly id: string; - createNotebook(resource: Uri, cancelToken?: CancellationToken): Promise; + createNotebook(resource: Uri, notebookMetadata?: nbformat.INotebookMetadata, cancelToken?: CancellationToken): Promise; getNotebook(resource: Uri): Promise; connect(launchInfo: INotebookServerLaunchInfo, cancelToken?: CancellationToken): Promise; getConnectionInfo(): IConnection | undefined;