12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import { PathExt, IChangedArgs } from '@jupyterlab/coreutils';
- import { UUID } from '@phosphor/coreutils';
- import {
- Kernel,
- KernelMessage,
- ServerConnection,
- Session
- } from '@jupyterlab/services';
- import { IterableOrArrayLike, each, find } from '@phosphor/algorithm';
- import { PromiseDelegate } from '@phosphor/coreutils';
- import { IDisposable } from '@phosphor/disposable';
- import { ISignal, Signal } from '@phosphor/signaling';
- import { Widget } from '@phosphor/widgets';
- import * as React from 'react';
- import { showDialog, Dialog } from './dialog';
- /**
- * The interface of client session object.
- *
- * The client session represents the link between
- * a path and its kernel for the duration of the lifetime
- * of the session object. The session can have no current
- * kernel, and can start a new kernel at any time.
- */
- export interface IClientSession extends IDisposable {
- /**
- * A signal emitted when the session is shut down.
- */
- readonly terminated: ISignal<this, void>;
- /**
- * A signal emitted when the kernel changes.
- */
- readonly sessionChanged: ISignal<
- this,
- IChangedArgs<Session.ISession | null, 'session'>
- >;
- session: Session.ISession | null;
- /**
- * A signal emitted when the kernel changes.
- */
- readonly kernelChanged: ISignal<
- this,
- IChangedArgs<Kernel.IKernelConnection | null, 'kernel'>
- >;
- /**
- * A signal emitted when the kernel status changes.
- */
- readonly statusChanged: ISignal<this, Kernel.Status>;
- /**
- * A signal emitted for a kernel messages.
- */
- readonly iopubMessage: ISignal<this, KernelMessage.IMessage>;
- /**
- * A signal emitted for an unhandled kernel message.
- */
- readonly unhandledMessage: ISignal<this, KernelMessage.IMessage>;
- /**
- * A signal emitted when a session property changes.
- */
- readonly propertyChanged: ISignal<this, 'path' | 'name' | 'type'>;
- /**
- * The current kernel associated with the document.
- */
- readonly kernel: Kernel.IKernelConnection | null;
- /**
- * The current path associated with the client session.
- */
- readonly path: string;
- /**
- * The current name associated with the client session.
- */
- readonly name: string;
- /**
- * The type of the client session.
- */
- readonly type: string;
- /**
- * Whether the session is ready.
- */
- readonly isReady: boolean;
- /**
- * A promise that is fulfilled when the session is ready.
- */
- readonly ready: Promise<void>;
- /**
- * The kernel preference.
- */
- kernelPreference: IClientSession.IKernelPreference;
- /**
- * The display name of the kernel.
- */
- readonly kernelDisplayName: string;
- /**
- * Change the current kernel associated with the document.
- */
- changeKernel(
- options: Partial<Kernel.IModel>
- ): Promise<Kernel.IKernelConnection>;
- /**
- * Kill the kernel and shutdown the session.
- *
- * @returns A promise that resolves when the session is shut down.
- */
- shutdown(): Promise<void>;
- /**
- * Select a kernel for the session.
- */
- selectKernel(): Promise<void>;
- /**
- * Restart the session.
- *
- * @returns A promise that resolves with whether the kernel has restarted.
- *
- * #### Notes
- * If there is a running kernel, present a dialog.
- * If there is no kernel, we start a kernel with the last run
- * kernel name and resolves with `true`. If no kernel has been started,
- * this is a no-op, and resolves with `false`.
- */
- restart(): Promise<boolean>;
- /**
- * Change the session path.
- *
- * @param path - The new session path.
- *
- * @returns A promise that resolves when the session has renamed.
- *
- * #### Notes
- * This uses the Jupyter REST API, and the response is validated.
- * The promise is fulfilled on a valid response and rejected otherwise.
- */
- setPath(path: string): Promise<void>;
- /**
- * Change the session name.
- */
- setName(name: string): Promise<void>;
- /**
- * Change the session type.
- */
- setType(type: string): Promise<void>;
- }
- /**
- * The namespace for Client Session related interfaces.
- */
- export namespace IClientSession {
- /**
- * A kernel preference.
- */
- export interface IKernelPreference {
- /**
- * The name of the kernel.
- */
- readonly name?: string;
- /**
- * The preferred kernel language.
- */
- readonly language?: string;
- /**
- * The id of an existing kernel.
- */
- readonly id?: string;
- /**
- * Whether to prefer starting a kernel.
- */
- readonly shouldStart?: boolean;
- /**
- * Whether a kernel can be started.
- */
- readonly canStart?: boolean;
- /**
- * Whether a kernel needs to be close with the associated session
- */
- readonly shutdownOnClose?: boolean;
- /**
- * Whether to auto-start the default kernel if no matching kernel is found.
- */
- readonly autoStartDefault?: boolean;
- }
- }
- /**
- * The default implementation of client session object.
- */
- export class ClientSession implements IClientSession {
- /**
- * Construct a new client session.
- */
- constructor(options: ClientSession.IOptions) {
- this.manager = options.manager;
- this._path = options.path || UUID.uuid4();
- this._type = options.type || '';
- this._name = options.name || '';
- this._setBusy = options.setBusy;
- this._kernelPreference = options.kernelPreference || {};
- }
- /**
- * A signal emitted when the session is shut down.
- */
- get terminated(): ISignal<this, void> {
- return this._terminated;
- }
- /**
- * A signal emitted when the kernel changes.
- */
- get kernelChanged(): ISignal<this, Session.IKernelChangedArgs> {
- return this._kernelChanged;
- }
- /**
- * A signal emitted when the status changes.
- */
- get statusChanged(): ISignal<this, Kernel.Status> {
- return this._statusChanged;
- }
- /**
- * A signal emitted for iopub kernel messages.
- */
- get iopubMessage(): ISignal<this, KernelMessage.IIOPubMessage> {
- return this._iopubMessage;
- }
- /**
- * A signal emitted for an unhandled kernel message.
- */
- get unhandledMessage(): ISignal<this, KernelMessage.IMessage> {
- return this._unhandledMessage;
- }
- /**
- * A signal emitted when a session property changes.
- */
- get propertyChanged(): ISignal<this, 'path' | 'name' | 'type'> {
- return this._propertyChanged;
- }
- /**
- * The current kernel of the session.
- */
- get kernel(): Kernel.IKernelConnection | null {
- return this._session ? this._session.kernel : null;
- }
- /**
- * The current path of the session.
- */
- get path(): string {
- return this._path;
- }
- /**
- * The current name of the session.
- */
- get name(): string {
- return this._name;
- }
- /**
- * The type of the client session.
- */
- get type(): string {
- return this._type;
- }
- /**
- * The kernel preference of the session.
- */
- get kernelPreference(): IClientSession.IKernelPreference {
- return this._kernelPreference;
- }
- set kernelPreference(value: IClientSession.IKernelPreference) {
- this._kernelPreference = value;
- }
- /**
- * The session manager used by the session.
- */
- readonly manager: Session.IManager;
- /**
- * Whether the session is ready.
- */
- get isReady(): boolean {
- return this._isReady;
- }
- /**
- * A promise that is fulfilled when the session is ready.
- */
- get ready(): Promise<void> {
- return this._ready.promise;
- }
- /**
- * The display name of the current kernel.
- */
- get kernelDisplayName(): string {
- let kernel = this.kernel;
- if (!kernel) {
- return 'No Kernel!';
- }
- let specs = this.manager.specs;
- if (!specs) {
- return 'Unknown!';
- }
- let spec = specs.kernelspecs[kernel.name];
- return spec ? spec.display_name : kernel.name;
- }
- /**
- * Test whether the context is disposed.
- */
- get isDisposed(): boolean {
- return this._isDisposed;
- }
- /**
- * Dispose of the resources held by the context.
- */
- dispose(): void {
- if (this._isDisposed) {
- return;
- }
- this._isDisposed = true;
- if (this._session) {
- if (this.kernelPreference.shutdownOnClose) {
- this._session.shutdown().catch(reason => {
- console.error(`Kernel not shut down ${reason}`);
- });
- }
- this._session.dispose();
- this._session = null;
- }
- if (this._dialog) {
- this._dialog.dispose();
- }
- if (this._busyDisposable) {
- this._busyDisposable.dispose();
- this._busyDisposable = null;
- }
- Signal.clearData(this);
- }
- /**
- * Change the current kernel associated with the document.
- */
- changeKernel(
- options: Partial<Kernel.IModel>
- ): Promise<Kernel.IKernelConnection> {
- return this.initialize().then(() => {
- if (this.isDisposed) {
- return Promise.reject('Disposed');
- }
- return this._changeKernel(options);
- });
- }
- /**
- * Select a kernel for the session.
- */
- selectKernel(): Promise<void> {
- return this.initialize().then(() => {
- if (this.isDisposed) {
- return Promise.reject('Disposed');
- }
- return this._selectKernel(true);
- });
- }
- /**
- * Kill the kernel and shutdown the session.
- *
- * @returns A promise that resolves when the session is shut down.
- */
- shutdown(): Promise<void> {
- const session = this._session;
- if (this.isDisposed || !session) {
- return Promise.resolve();
- }
- this._session = null;
- return session.shutdown();
- }
- /**
- * Restart the session.
- *
- * @returns A promise that resolves with whether the kernel has restarted.
- *
- * #### Notes
- * If there is a running kernel, present a dialog.
- * If there is no kernel, we start a kernel with the last run
- * kernel name and resolves with `true`.
- */
- restart(): Promise<boolean> {
- return this.initialize().then(() => {
- if (this.isDisposed) {
- return Promise.reject('session already disposed');
- }
- let kernel = this.kernel;
- if (!kernel) {
- if (this._prevKernelName) {
- return this.changeKernel({ name: this._prevKernelName }).then(
- () => true
- );
- }
- // Bail if there is no previous kernel to start.
- return Promise.reject('No kernel to restart');
- }
- return ClientSession.restartKernel(kernel);
- });
- }
- /**
- * Change the session path.
- *
- * @param path - The new session path.
- *
- * @returns A promise that resolves when the session has renamed.
- *
- * #### Notes
- * This uses the Jupyter REST API, and the response is validated.
- * The promise is fulfilled on a valid response and rejected otherwise.
- */
- setPath(path: string): Promise<void> {
- if (this.isDisposed || this._path === path) {
- return Promise.resolve();
- }
- this._path = path;
- if (this._session) {
- return this._session.setPath(path);
- }
- this._propertyChanged.emit('path');
- return Promise.resolve();
- }
- /**
- * Change the session name.
- */
- setName(name: string): Promise<void> {
- if (this.isDisposed || this._name === name) {
- return Promise.resolve();
- }
- this._name = name;
- if (this._session) {
- return this._session.setName(name);
- }
- this._propertyChanged.emit('name');
- return Promise.resolve();
- }
- /**
- * Change the session type.
- */
- setType(type: string): Promise<void> {
- if (this.isDisposed || this._type === type) {
- return Promise.resolve();
- }
- this._type = type;
- if (this._session) {
- return this._session.setType(name);
- }
- this._propertyChanged.emit('type');
- return Promise.resolve();
- }
- /**
- * Initialize the session.
- *
- * #### Notes
- * If a server session exists on the current path, we will connect to it.
- * If preferences include disabling `canStart` or `shouldStart`, no
- * server session will be started.
- * If a kernel id is given, we attempt to start a session with that id.
- * If a default kernel is available, we connect to it.
- * Otherwise we ask the user to select a kernel.
- */
- async initialize(): Promise<void> {
- if (this._initializing || this._isReady) {
- return this._ready.promise;
- }
- this._initializing = true;
- let manager = this.manager;
- await manager.ready;
- let model = find(manager.running(), item => {
- return item.path === this._path;
- });
- if (model) {
- try {
- let session = manager.connectTo(model);
- this._handleNewSession(session);
- } catch (err) {
- void this._handleSessionError(err);
- return Promise.reject(err);
- }
- }
- await this._startIfNecessary();
- this._isReady = true;
- this._ready.resolve(undefined);
- }
- /**
- * Start the session if necessary.
- */
- private _startIfNecessary(): Promise<void> {
- let preference = this.kernelPreference;
- if (
- this.isDisposed ||
- this.kernel ||
- preference.shouldStart === false ||
- preference.canStart === false
- ) {
- return Promise.resolve();
- }
- // Try to use an existing kernel.
- if (preference.id) {
- return this._changeKernel({ id: preference.id })
- .then(() => undefined)
- .catch(() => this._selectKernel(false));
- }
- let name = ClientSession.getDefaultKernel({
- specs: this.manager.specs,
- sessions: this.manager.running(),
- preference
- });
- if (name) {
- return this._changeKernel({ name })
- .then(() => undefined)
- .catch(() => this._selectKernel(false));
- }
- return this._selectKernel(false);
- }
- /**
- * Change the kernel.
- */
- private _changeKernel(
- options: Partial<Kernel.IModel>
- ): Promise<Kernel.IKernelConnection> {
- if (this.isDisposed) {
- return Promise.reject('Disposed');
- }
- let session = this._session;
- if (session && session.kernel.status !== 'dead') {
- return session.changeKernel(options).catch(err => {
- void this._handleSessionError(err);
- return Promise.reject(err);
- });
- } else {
- return this._startSession(options);
- }
- }
- /**
- * Select a kernel.
- *
- * @param cancelable: whether the dialog should have a cancel button.
- */
- private _selectKernel(cancelable: boolean): Promise<void> {
- if (this.isDisposed) {
- return Promise.resolve();
- }
- const buttons = cancelable
- ? [Dialog.cancelButton(), Dialog.okButton({ label: 'Select' })]
- : [Dialog.okButton({ label: 'Select' })];
- let dialog = (this._dialog = new Dialog({
- title: 'Select Kernel',
- body: new Private.KernelSelector(this),
- buttons
- }));
- return dialog
- .launch()
- .then(result => {
- if (this.isDisposed || !result.button.accept) {
- return;
- }
- let model = result.value;
- if (model === null && this._session) {
- return this.shutdown().then(() => {
- this._kernelChanged.emit({ oldValue: null, newValue: null });
- });
- }
- if (model) {
- return this._changeKernel(model).then(() => undefined);
- }
- })
- .then(() => {
- this._dialog = null;
- });
- }
- /**
- * Start a session and set up its signals.
- */
- private _startSession(
- model: Partial<Kernel.IModel>
- ): Promise<Kernel.IKernelConnection> {
- if (this.isDisposed) {
- return Promise.reject('Session is disposed.');
- }
- return this.manager
- .startNew({
- path: this._path,
- type: this._type,
- name: this._name,
- kernelName: model ? model.name : undefined,
- kernelId: model ? model.id : undefined
- })
- .then(session => {
- return this._handleNewSession(session);
- })
- .catch(err => {
- void this._handleSessionError(err);
- return Promise.reject(err);
- });
- }
- /**
- * Handle a new session object.
- */
- private _handleNewSession(
- session: Session.ISession
- ): Kernel.IKernelConnection {
- if (this.isDisposed) {
- throw Error('Disposed');
- }
- if (this._session) {
- this._session.dispose();
- }
- this._session = session;
- if (session.path !== this._path) {
- this._path = session.path;
- this._propertyChanged.emit('path');
- }
- if (session.name !== this._name) {
- this._name = session.name;
- this._propertyChanged.emit('name');
- }
- if (session.type !== this._type) {
- this._type = session.type;
- this._propertyChanged.emit('type');
- }
- session.disposed.connect(this._onTerminated, this);
- session.propertyChanged.connect(this._onPropertyChanged, this);
- session.kernelChanged.connect(this._onKernelChanged, this);
- session.statusChanged.connect(this._onStatusChanged, this);
- session.iopubMessage.connect(this._onIopubMessage, this);
- session.unhandledMessage.connect(this._onUnhandledMessage, this);
- this._prevKernelName = session.kernel.name;
- // The session kernel was disposed above when the session was disposed, so
- // the oldValue should be null.
- this._kernelChanged.emit({ oldValue: null, newValue: session.kernel });
- return session.kernel;
- }
- /**
- * Handle an error in session startup.
- */
- private _handleSessionError(
- err: ServerConnection.ResponseError
- ): Promise<void> {
- return err.response
- .text()
- .then(text => {
- let message = err.message;
- try {
- message = JSON.parse(text)['traceback'];
- } catch (err) {
- // no-op
- }
- let dialog = (this._dialog = new Dialog({
- title: 'Error Starting Kernel',
- body: <pre>{message}</pre>,
- buttons: [Dialog.okButton()]
- }));
- return dialog.launch();
- })
- .then(() => {
- this._dialog = null;
- });
- }
- /**
- * Handle a session termination.
- */
- private _onTerminated(): void {
- let kernel = this.kernel;
- if (this._session) {
- this._session.dispose();
- }
- this._session = null;
- this._terminated.emit(undefined);
- if (kernel) {
- this._kernelChanged.emit({ oldValue: null, newValue: null });
- }
- }
- /**
- * Handle a change to a session property.
- */
- private _onPropertyChanged(
- sender: Session.ISession,
- property: 'path' | 'name' | 'type'
- ) {
- switch (property) {
- case 'path':
- this._path = sender.path;
- break;
- case 'name':
- this._name = sender.name;
- break;
- default:
- this._type = sender.type;
- break;
- }
- this._propertyChanged.emit(property);
- }
- /**
- * Handle a change to the kernel.
- */
- private _onKernelChanged(
- sender: Session.ISession,
- args: Session.IKernelChangedArgs
- ): void {
- this._kernelChanged.emit(args);
- }
- /**
- * Handle a change to the session status.
- */
- private _onStatusChanged(): void {
- // Set that this kernel is busy, if we haven't already
- // If we have already, and now we aren't busy, dispose
- // of the busy disposable.
- if (this._setBusy) {
- if (this.kernel.status === 'busy') {
- if (!this._busyDisposable) {
- this._busyDisposable = this._setBusy();
- }
- } else {
- if (this._busyDisposable) {
- this._busyDisposable.dispose();
- this._busyDisposable = null;
- }
- }
- }
- this._statusChanged.emit(this.kernel.status);
- }
- /**
- * Handle an iopub message.
- */
- private _onIopubMessage(
- sender: Session.ISession,
- message: KernelMessage.IIOPubMessage
- ): void {
- this._iopubMessage.emit(message);
- }
- /**
- * Handle an unhandled message.
- */
- private _onUnhandledMessage(
- sender: Session.ISession,
- message: KernelMessage.IMessage
- ): void {
- this._unhandledMessage.emit(message);
- }
- private _path = '';
- private _name = '';
- private _type = '';
- private _prevKernelName = '';
- private _kernelPreference: IClientSession.IKernelPreference;
- private _isDisposed = false;
- private _session: Session.ISession | null = null;
- private _ready = new PromiseDelegate<void>();
- private _initializing = false;
- private _isReady = false;
- private _terminated = new Signal<this, void>(this);
- private _kernelChanged = new Signal<this, Session.IKernelChangedArgs>(this);
- private _statusChanged = new Signal<this, Kernel.Status>(this);
- private _iopubMessage = new Signal<this, KernelMessage.IIOPubMessage>(this);
- private _unhandledMessage = new Signal<this, KernelMessage.IMessage>(this);
- private _propertyChanged = new Signal<this, 'path' | 'name' | 'type'>(this);
- private _dialog: Dialog<any> | null = null;
- private _setBusy: () => IDisposable | undefined;
- private _busyDisposable: IDisposable | null = null;
- }
- /**
- * A namespace for `ClientSession` statics.
- */
- export namespace ClientSession {
- /**
- * The options used to initialize a context.
- */
- export interface IOptions {
- /**
- * A session manager instance.
- */
- manager: Session.IManager;
- /**
- * The initial path of the file.
- */
- path?: string;
- /**
- * The name of the session.
- */
- name?: string;
- /**
- * The type of the session.
- */
- type?: string;
- /**
- * A kernel preference.
- */
- kernelPreference?: IClientSession.IKernelPreference;
- /**
- * A function to call when the session becomes busy.
- */
- setBusy?: () => IDisposable;
- }
- /**
- * Restart a kernel if the user accepts the risk.
- *
- * Returns a promise resolving with whether the kernel was restarted.
- */
- export async function restartKernel(
- kernel: Kernel.IKernelConnection
- ): Promise<boolean> {
- let restartBtn = Dialog.warnButton({ label: 'Restart' });
- const result = await showDialog({
- title: 'Restart Kernel?',
- body:
- 'Do you want to restart the current kernel? All variables will be lost.',
- buttons: [Dialog.cancelButton(), restartBtn]
- });
- if (kernel.isDisposed) {
- return false;
- }
- if (result.button.accept) {
- await kernel.restart();
- return true;
- }
- return false;
- }
- /**
- * An interface for populating a kernel selector.
- */
- export interface IKernelSearch {
- /**
- * The Kernel specs.
- */
- specs: Kernel.ISpecModels | null;
- /**
- * The kernel preference.
- */
- preference: IClientSession.IKernelPreference;
- /**
- * The current running sessions.
- */
- sessions?: IterableOrArrayLike<Session.IModel>;
- }
- /**
- * Get the default kernel name given select options.
- */
- export function getDefaultKernel(options: IKernelSearch): string | null {
- return Private.getDefaultKernel(options);
- }
- /**
- * Populate a kernel dropdown list.
- *
- * @param node - The node to populate.
- *
- * @param options - The options used to populate the kernels.
- *
- * #### Notes
- * Populates the list with separated sections:
- * - Kernels matching the preferred language (display names).
- * - "None" signifying no kernel.
- * - The remaining kernels.
- * - Sessions matching the preferred language (file names).
- * - The remaining sessions.
- * If no preferred language is given or no kernels are found using
- * the preferred language, the default kernel is used in the first
- * section. Kernels are sorted by display name. Sessions display the
- * base name of the file with an ellipsis overflow and a tooltip with
- * the explicit session information.
- */
- export function populateKernelSelect(
- node: HTMLSelectElement,
- options: IKernelSearch
- ): void {
- return Private.populateKernelSelect(node, options);
- }
- }
- /**
- * The namespace for module private data.
- */
- namespace Private {
- /**
- * A widget that provides a kernel selection.
- */
- export class KernelSelector extends Widget {
- /**
- * Create a new kernel selector widget.
- */
- constructor(session: ClientSession) {
- super({ node: createSelectorNode(session) });
- }
- /**
- * Get the value of the kernel selector widget.
- */
- getValue(): Kernel.IModel {
- let selector = this.node.querySelector('select') as HTMLSelectElement;
- return JSON.parse(selector.value) as Kernel.IModel;
- }
- }
- /**
- * Create a node for a kernel selector widget.
- */
- function createSelectorNode(session: ClientSession) {
- // Create the dialog body.
- let body = document.createElement('div');
- let text = document.createElement('label');
- text.textContent = `Select kernel for: "${session.name}"`;
- body.appendChild(text);
- let options = getKernelSearch(session);
- let selector = document.createElement('select');
- ClientSession.populateKernelSelect(selector, options);
- body.appendChild(selector);
- return body;
- }
- /**
- * Get the default kernel name given select options.
- */
- export function getDefaultKernel(
- options: ClientSession.IKernelSearch
- ): string | null {
- let { specs, preference } = options;
- let {
- name,
- language,
- shouldStart,
- canStart,
- autoStartDefault
- } = preference;
- if (!specs || shouldStart === false || canStart === false) {
- return null;
- }
- let defaultName = autoStartDefault ? specs.default : null;
- if (!name && !language) {
- return defaultName;
- }
- // Look for an exact match of a spec name.
- for (let specName in specs.kernelspecs) {
- if (specName === name) {
- return name;
- }
- }
- // Bail if there is no language.
- if (!language) {
- return defaultName;
- }
- // Check for a single kernel matching the language.
- let matches: string[] = [];
- for (let specName in specs.kernelspecs) {
- let kernelLanguage = specs.kernelspecs[specName].language;
- if (language === kernelLanguage) {
- matches.push(specName);
- }
- }
- if (matches.length === 1) {
- let specName = matches[0];
- console.log(
- 'No exact match found for ' +
- specName +
- ', using kernel ' +
- specName +
- ' that matches ' +
- 'language=' +
- language
- );
- return specName;
- }
- // No matches found.
- return defaultName;
- }
- /**
- * Populate a kernel select node for the session.
- */
- export function populateKernelSelect(
- node: HTMLSelectElement,
- options: ClientSession.IKernelSearch
- ): void {
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
- let { preference, sessions, specs } = options;
- let { name, id, language, canStart, shouldStart } = preference;
- if (!specs || canStart === false) {
- node.appendChild(optionForNone());
- node.value = 'null';
- node.disabled = true;
- return;
- }
- node.disabled = false;
- // Create mappings of display names and languages for kernel name.
- let displayNames: { [key: string]: string } = Object.create(null);
- let languages: { [key: string]: string } = Object.create(null);
- for (let name in specs.kernelspecs) {
- let spec = specs.kernelspecs[name];
- displayNames[name] = spec.display_name;
- languages[name] = spec.language;
- }
- // Handle a kernel by name.
- let names: string[] = [];
- if (name && name in specs.kernelspecs) {
- names.push(name);
- }
- // Then look by language.
- if (language) {
- for (let specName in specs.kernelspecs) {
- if (name !== specName && languages[specName] === language) {
- names.push(specName);
- }
- }
- }
- // Use the default kernel if no kernels were found.
- if (!names.length) {
- names.push(specs.default);
- }
- // Handle a preferred kernels in order of display name.
- let preferred = document.createElement('optgroup');
- preferred.label = 'Start Preferred Kernel';
- names.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
- for (let name of names) {
- preferred.appendChild(optionForName(name, displayNames[name]));
- }
- if (preferred.firstChild) {
- node.appendChild(preferred);
- }
- // Add an option for no kernel
- node.appendChild(optionForNone());
- let other = document.createElement('optgroup');
- other.label = 'Start Other Kernel';
- // Add the rest of the kernel names in alphabetical order.
- let otherNames: string[] = [];
- for (let specName in specs.kernelspecs) {
- if (names.indexOf(specName) !== -1) {
- continue;
- }
- otherNames.push(specName);
- }
- otherNames.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
- for (let otherName of otherNames) {
- other.appendChild(optionForName(otherName, displayNames[otherName]));
- }
- // Add a separator option if there were any other names.
- if (otherNames.length) {
- node.appendChild(other);
- }
- // Handle the default value.
- if (shouldStart === false) {
- node.value = 'null';
- } else {
- node.selectedIndex = 0;
- }
- // Bail if there are no sessions.
- if (!sessions) {
- return;
- }
- // Add the sessions using the preferred language first.
- let matchingSessions: Session.IModel[] = [];
- let otherSessions: Session.IModel[] = [];
- each(sessions, session => {
- if (
- language &&
- languages[session.kernel.name] === language &&
- session.kernel.id !== id
- ) {
- matchingSessions.push(session);
- } else if (session.kernel.id !== id) {
- otherSessions.push(session);
- }
- });
- let matching = document.createElement('optgroup');
- matching.label = 'Use Kernel from Preferred Session';
- node.appendChild(matching);
- if (matchingSessions.length) {
- matchingSessions.sort((a, b) => {
- return a.path.localeCompare(b.path);
- });
- each(matchingSessions, session => {
- let name = displayNames[session.kernel.name];
- matching.appendChild(optionForSession(session, name));
- });
- }
- let otherSessionsNode = document.createElement('optgroup');
- otherSessionsNode.label = 'Use Kernel from Other Session';
- node.appendChild(otherSessionsNode);
- if (otherSessions.length) {
- otherSessions.sort((a, b) => {
- return a.path.localeCompare(b.path);
- });
- each(otherSessions, session => {
- let name = displayNames[session.kernel.name] || session.kernel.name;
- otherSessionsNode.appendChild(optionForSession(session, name));
- });
- }
- }
- /**
- * Get the kernel search options given a client session and sesion manager.
- */
- function getKernelSearch(
- session: ClientSession
- ): ClientSession.IKernelSearch {
- return {
- specs: session.manager.specs,
- sessions: session.manager.running(),
- preference: session.kernelPreference
- };
- }
- /**
- * Create an option element for a kernel name.
- */
- function optionForName(name: string, displayName: string): HTMLOptionElement {
- let option = document.createElement('option');
- option.text = displayName;
- option.value = JSON.stringify({ name });
- return option;
- }
- /**
- * Create an option for no kernel.
- */
- function optionForNone(): HTMLOptGroupElement {
- let group = document.createElement('optgroup');
- group.label = 'Use No Kernel';
- let option = document.createElement('option');
- option.text = 'No Kernel';
- option.value = 'null';
- group.appendChild(option);
- return group;
- }
- /**
- * Create an option element for a session.
- */
- function optionForSession(
- session: Session.IModel,
- displayName: string
- ): HTMLOptionElement {
- let option = document.createElement('option');
- let sessionName = session.name || PathExt.basename(session.path);
- option.text = sessionName;
- option.value = JSON.stringify({ id: session.kernel.id });
- option.title =
- `Path: ${session.path}\n` +
- `Name: ${sessionName}\n` +
- `Kernel Name: ${displayName}\n` +
- `Kernel Id: ${session.kernel.id}`;
- return option;
- }
- }
|