@jupyterlab/apputils
Version:
JupyterLab - Application Utilities
1,225 lines • 45 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { PathExt } from '@jupyterlab/coreutils';
import { nullTranslator } from '@jupyterlab/translation';
import { find } from '@lumino/algorithm';
import { JSONExt, PromiseDelegate, UUID } from '@lumino/coreutils';
import { Signal } from '@lumino/signaling';
import { Widget } from '@lumino/widgets';
import * as React from 'react';
import { Dialog, showDialog } from './dialog';
/**
* The default implementation for a session context object.
*/
export class SessionContext {
/**
* Construct a new session context.
*/
constructor(options) {
var _a, _b, _c, _d;
this._path = '';
this._name = '';
this._type = '';
this._prevKernelName = '';
this._isDisposed = false;
this._disposed = new Signal(this);
this._session = null;
this._ready = new PromiseDelegate();
this._initializing = false;
this._initStarted = new PromiseDelegate();
this._initPromise = new PromiseDelegate();
this._isReady = false;
this._isTerminating = false;
this._isRestarting = false;
this._kernelChanged = new Signal(this);
this._preferenceChanged = new Signal(this);
this._sessionChanged = new Signal(this);
this._statusChanged = new Signal(this);
this._connectionStatusChanged = new Signal(this);
this._pendingInput = false;
this._iopubMessage = new Signal(this);
this._unhandledMessage = new Signal(this);
this._propertyChanged = new Signal(this);
this._dialog = null;
this._busyDisposable = null;
this._pendingKernelName = '';
this._pendingSessionRequest = '';
this.kernelManager = options.kernelManager;
this.sessionManager = options.sessionManager;
this.specsManager = options.specsManager;
this.translator = options.translator || nullTranslator;
this._trans = this.translator.load('jupyterlab');
this._path = (_a = options.path) !== null && _a !== void 0 ? _a : UUID.uuid4();
this._type = (_b = options.type) !== null && _b !== void 0 ? _b : '';
this._name = (_c = options.name) !== null && _c !== void 0 ? _c : '';
this._setBusy = options.setBusy;
this._kernelPreference = (_d = options.kernelPreference) !== null && _d !== void 0 ? _d : {};
}
/**
* The current session connection.
*/
get session() {
var _a;
return (_a = this._session) !== null && _a !== void 0 ? _a : null;
}
/**
* The session path.
*
* #### Notes
* Typically `.session.path` should be used. This attribute is useful if
* there is no current session.
*/
get path() {
return this._path;
}
/**
* The session type.
*
* #### Notes
* Typically `.session.type` should be used. This attribute is useful if
* there is no current session.
*/
get type() {
return this._type;
}
/**
* The session name.
*
* #### Notes
* Typically `.session.name` should be used. This attribute is useful if
* there is no current session.
*/
get name() {
return this._name;
}
/**
* A signal emitted when the kernel connection changes, proxied from the session connection.
*/
get kernelChanged() {
return this._kernelChanged;
}
/**
* A signal emitted when the session connection changes.
*/
get sessionChanged() {
return this._sessionChanged;
}
/**
* A signal emitted when the kernel status changes, proxied from the kernel.
*/
get statusChanged() {
return this._statusChanged;
}
/**
* A flag indicating if the session has pending input, proxied from the kernel.
*/
get pendingInput() {
return this._pendingInput;
}
/**
* A signal emitted when the kernel status changes, proxied from the kernel.
*/
get connectionStatusChanged() {
return this._connectionStatusChanged;
}
/**
* A signal emitted for iopub kernel messages, proxied from the kernel.
*/
get iopubMessage() {
return this._iopubMessage;
}
/**
* A signal emitted for an unhandled kernel message, proxied from the kernel.
*/
get unhandledMessage() {
return this._unhandledMessage;
}
/**
* A signal emitted when a session property changes, proxied from the current session.
*/
get propertyChanged() {
return this._propertyChanged;
}
/**
* The kernel preference of this client session.
*
* This is used when selecting a new kernel, and should reflect the sort of
* kernel the activity prefers.
*/
get kernelPreference() {
return this._kernelPreference;
}
set kernelPreference(value) {
if (!JSONExt.deepEqual(value, this._kernelPreference)) {
const oldValue = this._kernelPreference;
this._kernelPreference = value;
this._preferenceChanged.emit({
name: 'kernelPreference',
oldValue,
newValue: JSONExt.deepCopy(value)
});
}
}
/**
* Signal emitted if the kernel preference changes.
*/
get kernelPreferenceChanged() {
return this._preferenceChanged;
}
/**
* Whether the context is ready.
*/
get isReady() {
return this._isReady;
}
/**
* A promise that is fulfilled when the context is ready.
*/
get ready() {
return this._ready.promise;
}
/**
* Whether the context is terminating.
*/
get isTerminating() {
return this._isTerminating;
}
/**
* Whether the context is restarting.
*/
get isRestarting() {
return this._isRestarting;
}
/**
* Whether the kernel is "No Kernel" or not.
*
* #### Notes
* As the displayed name is translated, this can be used directly.
*/
get hasNoKernel() {
return this.kernelDisplayName === this.noKernelName;
}
/**
* The display name of the current kernel, or a sensible alternative.
*
* #### Notes
* This is a convenience function to have a consistent sensible name for the
* kernel.
*/
get kernelDisplayName() {
var _a, _b, _c, _d, _e, _f, _g;
const kernel = (_a = this.session) === null || _a === void 0 ? void 0 : _a.kernel;
if (this._pendingKernelName === this.noKernelName) {
return this.noKernelName;
}
if (this._pendingKernelName) {
return ((_d = (_c = (_b = this.specsManager.specs) === null || _b === void 0 ? void 0 : _b.kernelspecs[this._pendingKernelName]) === null || _c === void 0 ? void 0 : _c.display_name) !== null && _d !== void 0 ? _d : this._pendingKernelName);
}
if (!kernel) {
return this.noKernelName;
}
return ((_g = (_f = (_e = this.specsManager.specs) === null || _e === void 0 ? void 0 : _e.kernelspecs[kernel.name]) === null || _f === void 0 ? void 0 : _f.display_name) !== null && _g !== void 0 ? _g : kernel.name);
}
/**
* A sensible status to display
*
* #### Notes
* This combines the status and connection status into a single status for
* the user.
*/
get kernelDisplayStatus() {
var _a, _b;
const kernel = (_a = this.session) === null || _a === void 0 ? void 0 : _a.kernel;
if (this._isTerminating) {
return 'terminating';
}
if (this._isRestarting) {
return 'restarting';
}
if (this._pendingKernelName === this.noKernelName) {
return 'unknown';
}
if (!kernel && this._pendingKernelName) {
return 'initializing';
}
if (!kernel &&
!this.isReady &&
this.kernelPreference.canStart !== false &&
this.kernelPreference.shouldStart !== false) {
return 'initializing';
}
return ((_b = ((kernel === null || kernel === void 0 ? void 0 : kernel.connectionStatus) === 'connected'
? kernel === null || kernel === void 0 ? void 0 : kernel.status
: kernel === null || kernel === void 0 ? void 0 : kernel.connectionStatus)) !== null && _b !== void 0 ? _b : 'unknown');
}
/**
* The name of the previously started kernel.
*/
get prevKernelName() {
return this._prevKernelName;
}
/**
* Test whether the context is disposed.
*/
get isDisposed() {
return this._isDisposed;
}
/**
* A signal emitted when the poll is disposed.
*/
get disposed() {
return this._disposed;
}
/**
* Get the constant displayed name for "No Kernel"
*/
get noKernelName() {
return this._trans.__('No Kernel');
}
/**
* Dispose of the resources held by the context.
*/
dispose() {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._disposed.emit();
if (this._session) {
if (this.kernelPreference.shutdownOnDispose) {
// Fire and forget the session shutdown request
this.sessionManager.shutdown(this._session.id).catch(reason => {
console.error(`Kernel not shut down ${reason}`);
});
}
// Dispose the session connection
this._session.dispose();
this._session = null;
}
if (this._dialog) {
this._dialog.dispose();
}
if (this._busyDisposable) {
this._busyDisposable.dispose();
this._busyDisposable = null;
}
Signal.clearData(this);
}
/**
* Starts new Kernel.
*
* @returns Whether to ask the user to pick a kernel.
*/
async startKernel() {
const preference = this.kernelPreference;
if (!preference.autoStartDefault && preference.shouldStart === false) {
return true;
}
let options;
if (preference.id) {
options = { id: preference.id };
}
else {
const name = Private.getDefaultKernel({
specs: this.specsManager.specs,
sessions: this.sessionManager.running(),
preference
});
if (name) {
options = { name };
}
}
if (options) {
try {
await this._changeKernel(options);
return false;
}
catch (err) {
/* no-op */
}
}
// Always fall back to selecting a kernel
return true;
}
/**
* Restart the current Kernel.
*
* @returns A promise that resolves when the kernel is restarted.
*/
async restartKernel() {
var _a, _b, _c, _d, _e, _f;
const kernel = ((_a = this.session) === null || _a === void 0 ? void 0 : _a.kernel) || null;
if (this._isRestarting) {
return;
}
this._isRestarting = true;
this._isReady = false;
this._statusChanged.emit('restarting');
try {
await ((_c = (_b = this.session) === null || _b === void 0 ? void 0 : _b.kernel) === null || _c === void 0 ? void 0 : _c.restart());
this._isReady = true;
}
catch (e) {
console.error(e);
}
this._isRestarting = false;
this._statusChanged.emit(((_e = (_d = this.session) === null || _d === void 0 ? void 0 : _d.kernel) === null || _e === void 0 ? void 0 : _e.status) || 'unknown');
this._kernelChanged.emit({
name: 'kernel',
oldValue: kernel,
newValue: ((_f = this.session) === null || _f === void 0 ? void 0 : _f.kernel) || null
});
}
/**
* Change the current kernel associated with the session.
*/
async changeKernel(options = {}) {
if (this.isDisposed) {
throw new Error('Disposed');
}
// Wait for the initialization method to try
// and start its kernel first to ensure consistent
// ordering.
await this._initStarted.promise;
return this._changeKernel(options);
}
/**
* Kill the kernel and shutdown the session.
*
* @returns A promise that resolves when the session is shut down.
*/
async shutdown() {
if (this.isDisposed || !this._initializing) {
return;
}
await this._initStarted.promise;
this._pendingSessionRequest = '';
this._pendingKernelName = this.noKernelName;
return this._shutdownSession();
}
/**
* Initialize the session context
*
* @returns A promise that resolves with whether to ask the user to select a kernel.
*
* #### 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() {
if (this._initializing) {
return this._initPromise.promise;
}
this._initializing = true;
const needsSelection = await this._initialize();
if (!needsSelection) {
this._isReady = true;
this._ready.resolve(undefined);
}
if (!this._pendingSessionRequest) {
this._initStarted.resolve(void 0);
}
this._initPromise.resolve(needsSelection);
return needsSelection;
}
/**
* Inner initialize function that doesn't handle promises.
* This makes it easier to consolidate promise handling logic.
*/
async _initialize() {
const manager = this.sessionManager;
await manager.ready;
await manager.refreshRunning();
const model = find(manager.running(), item => {
return item.path === this._path;
});
if (model) {
try {
const session = manager.connectTo({ model });
this._handleNewSession(session);
}
catch (err) {
void this._handleSessionError(err);
return Promise.reject(err);
}
}
return await this._startIfNecessary();
}
/**
* Shut down the current session.
*/
async _shutdownSession() {
var _a;
const session = this._session;
// Capture starting values in case an error is raised.
const isTerminating = this._isTerminating;
const isReady = this._isReady;
this._isTerminating = true;
this._isReady = false;
this._statusChanged.emit('terminating');
try {
await (session === null || session === void 0 ? void 0 : session.shutdown());
this._isTerminating = false;
session === null || session === void 0 ? void 0 : session.dispose();
this._session = null;
const kernel = (session === null || session === void 0 ? void 0 : session.kernel) || null;
this._statusChanged.emit('unknown');
this._kernelChanged.emit({
name: 'kernel',
oldValue: kernel,
newValue: null
});
this._sessionChanged.emit({
name: 'session',
oldValue: session,
newValue: null
});
}
catch (err) {
this._isTerminating = isTerminating;
this._isReady = isReady;
const status = (_a = session === null || session === void 0 ? void 0 : session.kernel) === null || _a === void 0 ? void 0 : _a.status;
if (status === undefined) {
this._statusChanged.emit('unknown');
}
else {
this._statusChanged.emit(status);
}
throw err;
}
return;
}
/**
* Start the session if necessary.
*
* @returns Whether to ask the user to pick a kernel.
*/
async _startIfNecessary() {
var _a;
const preference = this.kernelPreference;
if (this.isDisposed ||
((_a = this.session) === null || _a === void 0 ? void 0 : _a.kernel) ||
preference.shouldStart === false ||
preference.canStart === false) {
// Not necessary to start a kernel
return false;
}
return this.startKernel();
}
/**
* Change the kernel.
*/
async _changeKernel(model = {}) {
if (model.name) {
this._pendingKernelName = model.name;
}
if (!this._session) {
this._kernelChanged.emit({
name: 'kernel',
oldValue: null,
newValue: null
});
}
// Guarantee that the initialized kernel
// will be started first.
if (!this._pendingSessionRequest) {
this._initStarted.resolve(void 0);
}
// If we already have a session, just change the kernel.
if (this._session && !this._isTerminating) {
try {
await this._session.changeKernel(model);
return this._session.kernel;
}
catch (err) {
void this._handleSessionError(err);
throw err;
}
}
// Use a UUID for the path to overcome a race condition on the server
// where it will re-use a session for a given path but only after
// the kernel finishes starting.
// We later switch to the real path below.
// Use the correct directory so the kernel will be started in that directory.
const dirName = PathExt.dirname(this._path);
const requestId = (this._pendingSessionRequest = PathExt.join(dirName, UUID.uuid4()));
try {
this._statusChanged.emit('starting');
const session = await this.sessionManager.startNew({
path: requestId,
type: this._type,
name: this._name,
kernel: model
});
// Handle a preempt.
if (this._pendingSessionRequest !== session.path) {
await session.shutdown();
session.dispose();
return null;
}
// Change to the real path.
await session.setPath(this._path);
// Update the name in case it has changed since we launched the session.
await session.setName(this._name);
if (this._session && !this._isTerminating) {
await this._shutdownSession();
}
return this._handleNewSession(session);
}
catch (err) {
void this._handleSessionError(err);
throw err;
}
}
/**
* Handle a new session object.
*/
_handleNewSession(session) {
var _a, _b, _c;
if (this.isDisposed) {
throw Error('Disposed');
}
if (!this._isReady) {
this._isReady = true;
this._ready.resolve(undefined);
}
if (this._session) {
this._session.dispose();
}
this._session = session;
this._pendingKernelName = '';
if (session) {
this._prevKernelName = (_b = (_a = session.kernel) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '';
session.disposed.connect(this._onSessionDisposed, this);
session.propertyChanged.connect(this._onPropertyChanged, this);
session.kernelChanged.connect(this._onKernelChanged, this);
session.statusChanged.connect(this._onStatusChanged, this);
session.connectionStatusChanged.connect(this._onConnectionStatusChanged, this);
session.pendingInput.connect(this._onPendingInput, this);
session.iopubMessage.connect(this._onIopubMessage, this);
session.unhandledMessage.connect(this._onUnhandledMessage, this);
if (session.path !== this._path) {
this._onPropertyChanged(session, 'path');
}
if (session.name !== this._name) {
this._onPropertyChanged(session, 'name');
}
if (session.type !== this._type) {
this._onPropertyChanged(session, 'type');
}
}
// Any existing session/kernel connection was disposed above when the session was
// disposed, so the oldValue should be null.
this._sessionChanged.emit({
name: 'session',
oldValue: null,
newValue: session
});
this._kernelChanged.emit({
oldValue: null,
newValue: (session === null || session === void 0 ? void 0 : session.kernel) || null,
name: 'kernel'
});
this._statusChanged.emit(((_c = session === null || session === void 0 ? void 0 : session.kernel) === null || _c === void 0 ? void 0 : _c.status) || 'unknown');
return (session === null || session === void 0 ? void 0 : session.kernel) || null;
}
/**
* Handle an error in session startup.
*/
async _handleSessionError(err) {
this._handleNewSession(null);
let traceback = '';
let message = '';
try {
traceback = err.traceback;
message = err.message;
}
catch (err) {
// no-op
}
await this._displayKernelError(message, traceback);
}
/**
* Display kernel error
*/
async _displayKernelError(message, traceback) {
const body = (React.createElement("div", null,
message && React.createElement("pre", null, message),
traceback && (React.createElement("details", { className: "jp-mod-wide" },
React.createElement("pre", null, traceback)))));
const dialog = (this._dialog = new Dialog({
title: this._trans.__('Error Starting Kernel'),
body,
buttons: [Dialog.okButton()]
}));
await dialog.launch();
this._dialog = null;
}
/**
* Handle a session termination.
*/
_onSessionDisposed() {
if (this._session) {
const oldValue = this._session;
this._session = null;
const newValue = this._session;
this._sessionChanged.emit({ name: 'session', oldValue, newValue });
}
}
/**
* Handle a change to a session property.
*/
_onPropertyChanged(sender, property) {
switch (property) {
case 'path':
this._path = sender.path;
break;
case 'name':
this._name = sender.name;
break;
case 'type':
this._type = sender.type;
break;
default:
throw new Error(`unrecognized property ${property}`);
}
this._propertyChanged.emit(property);
}
/**
* Handle a change to the kernel.
*/
_onKernelChanged(sender, args) {
this._kernelChanged.emit(args);
}
/**
* Handle a change to the session status.
*/
_onStatusChanged(sender, status) {
var _a;
if (status === 'dead') {
const model = (_a = sender.kernel) === null || _a === void 0 ? void 0 : _a.model;
if (model === null || model === void 0 ? void 0 : model.reason) {
const traceback = model.traceback || '';
void this._displayKernelError(model.reason, traceback);
}
}
// 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 (status === 'busy') {
if (!this._busyDisposable) {
this._busyDisposable = this._setBusy();
}
}
else {
if (this._busyDisposable) {
this._busyDisposable.dispose();
this._busyDisposable = null;
}
}
}
// Proxy the signal
this._statusChanged.emit(status);
}
/**
* Handle a change to the session status.
*/
_onConnectionStatusChanged(sender, status) {
// Proxy the signal
this._connectionStatusChanged.emit(status);
}
/**
* Handle a change to the pending input.
*/
_onPendingInput(sender, value) {
// Set the signal value
this._pendingInput = value;
}
/**
* Handle an iopub message.
*/
_onIopubMessage(sender, message) {
if (message.header.msg_type === 'shutdown_reply') {
this.session.kernel.removeInputGuard();
}
this._iopubMessage.emit(message);
}
/**
* Handle an unhandled message.
*/
_onUnhandledMessage(sender, message) {
this._unhandledMessage.emit(message);
}
}
/**
* A namespace for `SessionContext` statics.
*/
(function (SessionContext) {
/**
* Get the default kernel name given select options.
*/
function getDefaultKernel(options) {
const { preference } = options;
const { shouldStart } = preference;
if (shouldStart === false) {
return null;
}
return Private.getDefaultKernel(options);
}
SessionContext.getDefaultKernel = getDefaultKernel;
})(SessionContext || (SessionContext = {}));
/**
* The default implementation of the client session dialog provider.
*/
export class SessionContextDialogs {
constructor(options = {}) {
var _a;
this._translator = (_a = options.translator) !== null && _a !== void 0 ? _a : nullTranslator;
this._settingRegistry = options.settingRegistry || null;
}
/**
* Select a kernel for the session.
*/
async selectKernel(sessionContext) {
if (sessionContext.isDisposed) {
return Promise.resolve();
}
const translator = this._translator;
const trans = translator.load('jupyterlab');
// If there is no existing kernel, offer the option to keep no kernel.
let label = trans.__('Cancel');
if (sessionContext.hasNoKernel) {
label = sessionContext.kernelDisplayName;
}
const buttons = [
Dialog.cancelButton({ label }),
Dialog.okButton({
label: trans.__('Select'),
ariaLabel: trans.__('Select Kernel')
})
];
const autoStartDefault = sessionContext.kernelPreference.autoStartDefault;
const hasCheckbox = typeof autoStartDefault === 'boolean';
const dialog = new Dialog({
title: trans.__('Select Kernel'),
body: Private.createKernelSelector(sessionContext, translator),
buttons,
checkbox: hasCheckbox
? {
label: trans.__('Always start the preferred kernel'),
caption: trans.__('Remember my choice and always start the preferred kernel'),
checked: autoStartDefault
}
: null
});
const result = await dialog.launch();
if (sessionContext.isDisposed || !result.button.accept) {
return;
}
if (hasCheckbox && result.isChecked !== null) {
sessionContext.kernelPreference = {
...sessionContext.kernelPreference,
autoStartDefault: result.isChecked
};
}
const model = result.value;
if (model === null && !sessionContext.hasNoKernel) {
return sessionContext.shutdown();
}
if (model) {
await sessionContext.changeKernel(model);
}
}
/**
* 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`.
*/
async restart(sessionContext) {
var _a, _b, _c, _d, _e;
const trans = this._translator.load('jupyterlab');
await sessionContext.initialize();
if (sessionContext.isDisposed) {
throw new Error('session already disposed');
}
const kernel = (_a = sessionContext.session) === null || _a === void 0 ? void 0 : _a.kernel;
if (!kernel && sessionContext.prevKernelName) {
await sessionContext.changeKernel({
name: sessionContext.prevKernelName
});
return true;
}
// Bail if there is no previous kernel to start.
if (!kernel) {
throw new Error('No kernel to restart');
}
// Skip the dialog and restart the kernel
const kernelPluginId = '@jupyterlab/apputils-extension:sessionDialogs';
const skipKernelRestartDialog = (_c = (_b = sessionContext.kernelPreference) === null || _b === void 0 ? void 0 : _b.skipKernelRestartDialog) !== null && _c !== void 0 ? _c : false;
const skipKernelRestartDialogSetting = (_e = (await ((_d = this._settingRegistry) === null || _d === void 0 ? void 0 : _d.get(kernelPluginId, 'skipKernelRestartDialog')))) === null || _e === void 0 ? void 0 : _e.composite;
if (skipKernelRestartDialogSetting || skipKernelRestartDialog) {
await sessionContext.restartKernel();
return true;
}
const restartBtn = Dialog.warnButton({
label: trans.__('Restart'),
ariaLabel: trans.__('Confirm Kernel Restart')
});
const result = await showDialog({
title: trans.__('Restart Kernel?'),
body: trans.__('Do you want to restart the kernel of %1? All variables will be lost.', sessionContext.name),
buttons: [
Dialog.cancelButton({ ariaLabel: trans.__('Cancel Kernel Restart') }),
restartBtn
],
checkbox: {
label: trans.__('Do not ask me again.'),
caption: trans.__('If checked, the kernel will restart without confirmation prompt in the future; you can change this back in the settings.')
}
});
if (kernel.isDisposed) {
return false;
}
if (result.button.accept) {
if (typeof result.isChecked === 'boolean' && result.isChecked == true) {
sessionContext.kernelPreference = {
...sessionContext.kernelPreference,
skipKernelRestartDialog: true
};
}
await sessionContext.restartKernel();
return true;
}
return false;
}
}
(function (SessionContextDialogs) {
/**
* Returns available kernel options grouped based on session context.
*
* #### Notes
* If a language preference is set in the given session context, the options
* returned are grouped with the language preference at the top:
*
* - (Start %1 Kernel, language)
* - { all kernelspecs whose language matches in alphabetical order }
* - (Use No Kernel)
* - `No Kernel`
* - (Start Kernel)
* - { all other kernelspecs in alphabetical order }
* - (Connect to Existing %1 Kernel, language)
* - { all running kernels whose language matches in alphabetical order }
* - (Connect to Kernel)
* - { all other running kernels in alphabetical order }
*
* If no language preference is set, these groups and options are returned:
*
* - (Start Kernel)
* - { all kernelspecs in alphabetical order }
* - (Use No Kernel)
* - `No Kernel`
* - (Connect to Existing Kernel)
* - { all running kernels in alphabetical order }
*
* If the session has a kernel ID and a kernel exists with that id, its
* corresponding option has `selected` set to `true`. Otherwise if the session
* context language preference is set, the first kernelspec that matches it is
* selected.
*/
function kernelOptions(sessionContext, translator = null) {
var _a, _b, _c, _d, _e, _f, _g;
const options = { disabled: false, groups: [] };
// Create mapping of sessions and kernel ids.
const kernels = Array.from((_b = (_a = sessionContext.kernelManager) === null || _a === void 0 ? void 0 : _a.running()) !== null && _b !== void 0 ? _b :
// If kernel manager is unavailable use kernels from running sessions.
// TODO: Remove this (next version) when kernel manager is guaranteed.
Array.from(sessionContext.sessionManager.running())
.filter(session => !!session.kernel)
.map(session => session.kernel));
const sessions = Array.from((_c = sessionContext.sessionManager.running()) !== null && _c !== void 0 ? _c : []).reduce((sessions, session) => {
var _a;
if ((_a = session.kernel) === null || _a === void 0 ? void 0 : _a.id)
sessions[session.kernel.id] = session;
return sessions;
}, {});
const preference = {
...sessionContext.kernelPreference,
id: (_e = (_d = sessionContext.session) === null || _d === void 0 ? void 0 : _d.kernel) === null || _e === void 0 ? void 0 : _e.id
};
const currentKernelDisplayName = !sessionContext.hasNoKernel
? sessionContext.kernelDisplayName
: null;
const specs = {
default: '',
kernelspecs: Object.create(null),
...sessionContext.specsManager.specs
};
// Create mapping of languages and kernel names.
const sorted = [];
const languages = Object.create(null);
for (const name in specs.kernelspecs) {
sorted.push(specs.kernelspecs[name]);
languages[name] = specs.kernelspecs[name].language;
}
sorted.sort((a, b) => a.display_name.localeCompare(b.display_name));
translator = translator || nullTranslator;
const trans = translator.load('jupyterlab');
const language = preference.language ||
languages[preference.name] ||
(preference.id ? languages[(_f = sessions[preference.id]) === null || _f === void 0 ? void 0 : _f.name] : '');
const labels = {
connectKernel: trans.__('Connect to Existing Kernel'),
startPreferred: trans.__('Start %1 Kernel', language),
startOther: trans.__('Start Kernel'),
connectToPreferred: trans.__('Connect to Existing %1 Kernel', language),
connectToOther: trans.__('Connect to Other Kernel'),
noKernel: trans.__('No Kernel'),
startKernel: trans.__('Start Kernel'),
useNoKernel: trans.__('Use No Kernel')
};
const noKernel = {
label: labels.useNoKernel,
options: [
{
text: labels.noKernel,
title: labels.noKernel,
value: JSON.stringify(null)
}
]
};
const optionForKernel = (kernel, displayName, session) => {
const sessionName = session
? session.name || PathExt.basename(session.path)
: kernel.name || trans.__('Unknown Kernel');
return {
text: `${sessionName} (${kernel.id.split('-')[0]})`,
title: (session ? `${trans.__('Path: %1', session.path)}\n` : ``) +
`${trans.__('Name: %1', sessionName)}\n` +
`${trans.__('Kernel Name: %1', displayName !== null && displayName !== void 0 ? displayName : kernel.name)}\n` +
`${trans.__('Kernel Id: %1', kernel.id)}`,
value: JSON.stringify({ id: kernel.id })
};
};
const optionForSpec = (spec) => ({
text: spec.display_name,
value: JSON.stringify({ name: spec.name })
});
// If a kernel cannot be started, bail.
if (preference.canStart === false) {
options.disabled = true;
options.groups.push(noKernel);
return options;
}
// Create kernel option groups based on whether language preference exists.
if (language) {
// Add all kernelspecs, separating out the preferred language first.
const preferred = {
label: labels.startPreferred,
options: []
};
const other = {
label: labels.startOther,
options: []
};
const preferredRunning = {
label: labels.connectToPreferred,
options: []
};
const otherRunning = {
label: labels.connectToOther,
options: []
};
for (const spec of sorted) {
(spec.language === language ? preferred : other).options.push(optionForSpec(spec));
}
options.groups.push(preferred);
options.groups.push(noKernel);
options.groups.push(other);
kernels
.map(kernel => {
var _a, _b;
return ({
option: optionForKernel(kernel, (_b = (_a = specs.kernelspecs[kernel.name]) === null || _a === void 0 ? void 0 : _a.display_name) !== null && _b !== void 0 ? _b : '', sessions[kernel.id]),
language: languages[kernel.name]
});
})
.sort((a, b) => a.option.text.localeCompare(b.option.text))
.forEach(kernel => (language === kernel.language
? preferredRunning
: otherRunning).options.push(kernel.option));
if (preferredRunning.options.length)
options.groups.push(preferredRunning);
if (otherRunning.options.length)
options.groups.push(otherRunning);
}
else {
// Add kernelspecs first.
options.groups.push({
label: labels.startKernel,
options: sorted.map(spec => optionForSpec(spec))
});
// Next add the no kernel option.
options.groups.push(noKernel);
// Add running kernels.
options.groups.push({
label: labels.connectKernel,
options: kernels
.map(kernel => {
var _a, _b;
return optionForKernel(kernel, (_b = (_a = specs.kernelspecs[kernel.name]) === null || _a === void 0 ? void 0 : _a.display_name) !== null && _b !== void 0 ? _b : '', sessions[kernel.id]);
})
.sort((a, b) => a.text.localeCompare(b.text))
});
}
// Set the selected option.
if (preference.id || currentKernelDisplayName || preference.name) {
for (const group of options.groups) {
for (const option of group.options) {
const choice = JSON.parse(option.value);
if (!choice)
continue;
if (preference.id) {
if (preference.id === choice.id) {
option.selected = true;
return options;
}
continue;
}
if (currentKernelDisplayName) {
if (currentKernelDisplayName ===
((_g = specs.kernelspecs[choice.name]) === null || _g === void 0 ? void 0 : _g.display_name)) {
option.selected = true;
return options;
}
continue;
}
if (preference.name) {
if (preference.name === choice.name) {
option.selected = true;
return options;
}
continue;
}
}
}
}
return options;
}
SessionContextDialogs.kernelOptions = kernelOptions;
})(SessionContextDialogs || (SessionContextDialogs = {}));
/**
* The namespace for module private data.
*/
var Private;
(function (Private) {
/**
* Return a kernel selector widget.
*/
Private.createKernelSelector = (sessionContext, translator) => new KernelSelector({
node: createSelectorNode(sessionContext, translator)
});
/**
* A widget that provides a kernel selection.
*/
class KernelSelector extends Widget {
/**
* Get the value of the kernel selector widget.
*/
getValue() {
const selector = this.node.querySelector('select');
return JSON.parse(selector.value);
}
}
/**
* Create an HTML node for a kernel selector widget.
*/
function createSelectorNode(sessionContext, translator) {
// Create the dialog body.
translator = translator || nullTranslator;
const trans = translator.load('jupyterlab');
const body = document.createElement('div');
const text = document.createElement('label');
text.textContent = `${trans.__('Select kernel for:')} "${sessionContext.name}"`;
body.appendChild(text);
const select = document.createElement('select');
const options = SessionContextDialogs.kernelOptions(sessionContext, translator);
if (options.disabled)
select.disabled = true;
for (const group of options.groups) {
const { label, options } = group;
const optgroup = document.createElement('optgroup');
optgroup.label = label;
for (const { selected, text, title, value } of options) {
const option = document.createElement('option');
if (selected)
option.selected = true;
if (title)
option.title = title;
option.text = text;
option.value = value;
optgroup.appendChild(option);
}
select.appendChild(optgroup);
}
body.appendChild(select);
return body;
}
/**
* Get the default kernel name given select options.
*/
function getDefaultKernel(options) {
var _a;
const { specs, preference } = options;
const { name, language, canStart, autoStartDefault } = preference;
if (!specs || canStart === false) {
return null;
}
const defaultName = autoStartDefault ? specs.default : null;
if (!name && !language) {
return defaultName;
}
// Look for an exact match of a spec name.
for (const 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.
const matches = [];
for (const specName in specs.kernelspecs) {
const kernelLanguage = (_a = specs.kernelspecs[specName]) === null || _a === void 0 ? void 0 : _a.language;
if (language === kernelLanguage) {
matches.push(specName);
}
}
if (matches.length === 1) {
const specName = matches[0];
console.warn('No exact match found for ' +
specName +
', using kernel ' +
specName +
' that matches ' +
'language=' +
language);
return specName;
}
// No matches found.
return defaultName;
}
Private.getDefaultKernel = getDefaultKernel;
})(Private || (Private = {}));
//# sourceMappingURL=sessioncontext.js.map