@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