UNPKG

39.6 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { PathExt } from '@jupyterlab/coreutils';
4import { nullTranslator } from '@jupyterlab/translation';
5import { each, find } from '@lumino/algorithm';
6import { PromiseDelegate, UUID } from '@lumino/coreutils';
7import { Signal } from '@lumino/signaling';
8import { Widget } from '@lumino/widgets';
9import * as React from 'react';
10import { Dialog, showDialog } from './dialog';
11/**
12 * The default implementation for a session context object.
13 */
14export class SessionContext {
15 /**
16 * Construct a new session context.
17 */
18 constructor(options) {
19 var _a, _b, _c, _d;
20 this._path = '';
21 this._name = '';
22 this._type = '';
23 this._prevKernelName = '';
24 this._isDisposed = false;
25 this._disposed = new Signal(this);
26 this._session = null;
27 this._ready = new PromiseDelegate();
28 this._initializing = false;
29 this._initStarted = new PromiseDelegate();
30 this._initPromise = new PromiseDelegate();
31 this._isReady = false;
32 this._isTerminating = false;
33 this._isRestarting = false;
34 this._kernelChanged = new Signal(this);
35 this._sessionChanged = new Signal(this);
36 this._statusChanged = new Signal(this);
37 this._connectionStatusChanged = new Signal(this);
38 this._pendingInput = false;
39 this._iopubMessage = new Signal(this);
40 this._unhandledMessage = new Signal(this);
41 this._propertyChanged = new Signal(this);
42 this._dialog = null;
43 this._busyDisposable = null;
44 this._pendingKernelName = '';
45 this._pendingSessionRequest = '';
46 this.sessionManager = options.sessionManager;
47 this.specsManager = options.specsManager;
48 this.translator = options.translator || nullTranslator;
49 this._trans = this.translator.load('jupyterlab');
50 this._path = (_a = options.path) !== null && _a !== void 0 ? _a : UUID.uuid4();
51 this._type = (_b = options.type) !== null && _b !== void 0 ? _b : '';
52 this._name = (_c = options.name) !== null && _c !== void 0 ? _c : '';
53 this._setBusy = options.setBusy;
54 this._kernelPreference = (_d = options.kernelPreference) !== null && _d !== void 0 ? _d : {};
55 }
56 /**
57 * The current session connection.
58 */
59 get session() {
60 var _a;
61 return (_a = this._session) !== null && _a !== void 0 ? _a : null;
62 }
63 /**
64 * The session path.
65 *
66 * #### Notes
67 * Typically `.session.path` should be used. This attribute is useful if
68 * there is no current session.
69 */
70 get path() {
71 return this._path;
72 }
73 /**
74 * The session type.
75 *
76 * #### Notes
77 * Typically `.session.type` should be used. This attribute is useful if
78 * there is no current session.
79 */
80 get type() {
81 return this._type;
82 }
83 /**
84 * The session name.
85 *
86 * #### Notes
87 * Typically `.session.name` should be used. This attribute is useful if
88 * there is no current session.
89 */
90 get name() {
91 return this._name;
92 }
93 /**
94 * A signal emitted when the kernel connection changes, proxied from the session connection.
95 */
96 get kernelChanged() {
97 return this._kernelChanged;
98 }
99 /**
100 * A signal emitted when the session connection changes.
101 */
102 get sessionChanged() {
103 return this._sessionChanged;
104 }
105 /**
106 * A signal emitted when the kernel status changes, proxied from the kernel.
107 */
108 get statusChanged() {
109 return this._statusChanged;
110 }
111 /**
112 * A flag indicating if the session has ending input, proxied from the kernel.
113 */
114 get pendingInput() {
115 return this._pendingInput;
116 }
117 /**
118 * A signal emitted when the kernel status changes, proxied from the kernel.
119 */
120 get connectionStatusChanged() {
121 return this._connectionStatusChanged;
122 }
123 /**
124 * A signal emitted for iopub kernel messages, proxied from the kernel.
125 */
126 get iopubMessage() {
127 return this._iopubMessage;
128 }
129 /**
130 * A signal emitted for an unhandled kernel message, proxied from the kernel.
131 */
132 get unhandledMessage() {
133 return this._unhandledMessage;
134 }
135 /**
136 * A signal emitted when a session property changes, proxied from the current session.
137 */
138 get propertyChanged() {
139 return this._propertyChanged;
140 }
141 /**
142 * The kernel preference of this client session.
143 *
144 * This is used when selecting a new kernel, and should reflect the sort of
145 * kernel the activity prefers.
146 */
147 get kernelPreference() {
148 return this._kernelPreference;
149 }
150 set kernelPreference(value) {
151 this._kernelPreference = value;
152 }
153 /**
154 * Whether the context is ready.
155 */
156 get isReady() {
157 return this._isReady;
158 }
159 /**
160 * A promise that is fulfilled when the context is ready.
161 */
162 get ready() {
163 return this._ready.promise;
164 }
165 /**
166 * Whether the context is terminating.
167 */
168 get isTerminating() {
169 return this._isTerminating;
170 }
171 /**
172 * Whether the context is restarting.
173 */
174 get isRestarting() {
175 return this._isRestarting;
176 }
177 /**
178 * Whether the kernel is "No Kernel" or not.
179 *
180 * #### Notes
181 * As the displayed name is translated, this can be used directly.
182 */
183 get hasNoKernel() {
184 return this.kernelDisplayName === this.noKernelName;
185 }
186 /**
187 * The display name of the current kernel, or a sensible alternative.
188 *
189 * #### Notes
190 * This is a convenience function to have a consistent sensible name for the
191 * kernel.
192 */
193 get kernelDisplayName() {
194 var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
195 const kernel = (_a = this.session) === null || _a === void 0 ? void 0 : _a.kernel;
196 if (this._pendingKernelName === this.noKernelName) {
197 return this.noKernelName;
198 }
199 if (!kernel &&
200 !this.isReady &&
201 this.kernelPreference.canStart !== false &&
202 this.kernelPreference.shouldStart !== false) {
203 let name = this._pendingKernelName ||
204 SessionContext.getDefaultKernel({
205 specs: this.specsManager.specs,
206 sessions: this.sessionManager.running(),
207 preference: this.kernelPreference
208 }) ||
209 '';
210 if (name) {
211 name = (_d = (_c = (_b = this.specsManager.specs) === null || _b === void 0 ? void 0 : _b.kernelspecs[name]) === null || _c === void 0 ? void 0 : _c.display_name) !== null && _d !== void 0 ? _d : name;
212 return name;
213 }
214 return this.noKernelName;
215 }
216 if (this._pendingKernelName) {
217 return ((_g = (_f = (_e = this.specsManager.specs) === null || _e === void 0 ? void 0 : _e.kernelspecs[this._pendingKernelName]) === null || _f === void 0 ? void 0 : _f.display_name) !== null && _g !== void 0 ? _g : this._pendingKernelName);
218 }
219 if (!kernel) {
220 return this.noKernelName;
221 }
222 return ((_k = (_j = (_h = this.specsManager.specs) === null || _h === void 0 ? void 0 : _h.kernelspecs[kernel.name]) === null || _j === void 0 ? void 0 : _j.display_name) !== null && _k !== void 0 ? _k : kernel.name);
223 }
224 /**
225 * A sensible status to display
226 *
227 * #### Notes
228 * This combines the status and connection status into a single status for
229 * the user.
230 */
231 get kernelDisplayStatus() {
232 var _a, _b;
233 const kernel = (_a = this.session) === null || _a === void 0 ? void 0 : _a.kernel;
234 if (this._isTerminating) {
235 return 'terminating';
236 }
237 if (this._isRestarting) {
238 return 'restarting';
239 }
240 if (this._pendingKernelName === this.noKernelName) {
241 return 'unknown';
242 }
243 if (!kernel && this._pendingKernelName) {
244 return 'initializing';
245 }
246 if (!kernel &&
247 !this.isReady &&
248 this.kernelPreference.canStart !== false &&
249 this.kernelPreference.shouldStart !== false) {
250 return 'initializing';
251 }
252 return ((_b = ((kernel === null || kernel === void 0 ? void 0 : kernel.connectionStatus) === 'connected'
253 ? kernel === null || kernel === void 0 ? void 0 : kernel.status : kernel === null || kernel === void 0 ? void 0 : kernel.connectionStatus)) !== null && _b !== void 0 ? _b : 'unknown');
254 }
255 /**
256 * The name of the previously started kernel.
257 */
258 get prevKernelName() {
259 return this._prevKernelName;
260 }
261 /**
262 * Test whether the context is disposed.
263 */
264 get isDisposed() {
265 return this._isDisposed;
266 }
267 /**
268 * A signal emitted when the poll is disposed.
269 */
270 get disposed() {
271 return this._disposed;
272 }
273 /**
274 * Get the constant displayed name for "No Kernel"
275 */
276 get noKernelName() {
277 return this._trans.__('No Kernel');
278 }
279 /**
280 * Dispose of the resources held by the context.
281 */
282 dispose() {
283 if (this._isDisposed) {
284 return;
285 }
286 this._isDisposed = true;
287 this._disposed.emit();
288 if (this._session) {
289 if (this.kernelPreference.shutdownOnDispose) {
290 // Fire and forget the session shutdown request
291 this.sessionManager.shutdown(this._session.id).catch(reason => {
292 console.error(`Kernel not shut down ${reason}`);
293 });
294 }
295 // Dispose the session connection
296 this._session.dispose();
297 this._session = null;
298 }
299 if (this._dialog) {
300 this._dialog.dispose();
301 }
302 if (this._busyDisposable) {
303 this._busyDisposable.dispose();
304 this._busyDisposable = null;
305 }
306 Signal.clearData(this);
307 }
308 /**
309 * Restart the current Kernel.
310 *
311 * @returns A promise that resolves when the kernel is restarted.
312 */
313 async restartKernel() {
314 var _a, _b, _c, _d, _e, _f;
315 const kernel = ((_a = this.session) === null || _a === void 0 ? void 0 : _a.kernel) || null;
316 if (this._isRestarting) {
317 return;
318 }
319 this._isRestarting = true;
320 this._isReady = false;
321 this._statusChanged.emit('restarting');
322 try {
323 await ((_c = (_b = this.session) === null || _b === void 0 ? void 0 : _b.kernel) === null || _c === void 0 ? void 0 : _c.restart());
324 this._isReady = true;
325 }
326 catch (e) {
327 console.error(e);
328 }
329 this._isRestarting = false;
330 this._statusChanged.emit(((_e = (_d = this.session) === null || _d === void 0 ? void 0 : _d.kernel) === null || _e === void 0 ? void 0 : _e.status) || 'unknown');
331 this._kernelChanged.emit({
332 name: 'kernel',
333 oldValue: kernel,
334 newValue: ((_f = this.session) === null || _f === void 0 ? void 0 : _f.kernel) || null
335 });
336 }
337 /**
338 * Change the current kernel associated with the session.
339 */
340 async changeKernel(options = {}) {
341 if (this.isDisposed) {
342 throw new Error('Disposed');
343 }
344 // Wait for the initialization method to try
345 // and start its kernel first to ensure consistent
346 // ordering.
347 await this._initStarted.promise;
348 return this._changeKernel(options);
349 }
350 /**
351 * Kill the kernel and shutdown the session.
352 *
353 * @returns A promise that resolves when the session is shut down.
354 */
355 async shutdown() {
356 if (this.isDisposed || !this._initializing) {
357 return;
358 }
359 await this._initStarted.promise;
360 this._pendingSessionRequest = '';
361 this._pendingKernelName = this.noKernelName;
362 return this._shutdownSession();
363 }
364 /**
365 * Initialize the session context
366 *
367 * @returns A promise that resolves with whether to ask the user to select a kernel.
368 *
369 * #### Notes
370 * If a server session exists on the current path, we will connect to it.
371 * If preferences include disabling `canStart` or `shouldStart`, no
372 * server session will be started.
373 * If a kernel id is given, we attempt to start a session with that id.
374 * If a default kernel is available, we connect to it.
375 * Otherwise we ask the user to select a kernel.
376 */
377 async initialize() {
378 if (this._initializing) {
379 return this._initPromise.promise;
380 }
381 this._initializing = true;
382 const needsSelection = await this._initialize();
383 if (!needsSelection) {
384 this._isReady = true;
385 this._ready.resolve(undefined);
386 }
387 if (!this._pendingSessionRequest) {
388 this._initStarted.resolve(void 0);
389 }
390 this._initPromise.resolve(needsSelection);
391 return needsSelection;
392 }
393 /**
394 * Inner initialize function that doesn't handle promises.
395 * This makes it easier to consolidate promise handling logic.
396 */
397 async _initialize() {
398 const manager = this.sessionManager;
399 await manager.ready;
400 await manager.refreshRunning();
401 const model = find(manager.running(), item => {
402 return item.path === this._path;
403 });
404 if (model) {
405 try {
406 const session = manager.connectTo({ model });
407 this._handleNewSession(session);
408 }
409 catch (err) {
410 void this._handleSessionError(err);
411 return Promise.reject(err);
412 }
413 }
414 return await this._startIfNecessary();
415 }
416 /**
417 * Shut down the current session.
418 */
419 async _shutdownSession() {
420 var _a;
421 const session = this._session;
422 // Capture starting values in case an error is raised.
423 const isTerminating = this._isTerminating;
424 const isReady = this._isReady;
425 this._isTerminating = true;
426 this._isReady = false;
427 this._statusChanged.emit('terminating');
428 try {
429 await (session === null || session === void 0 ? void 0 : session.shutdown());
430 this._isTerminating = false;
431 session === null || session === void 0 ? void 0 : session.dispose();
432 this._session = null;
433 const kernel = (session === null || session === void 0 ? void 0 : session.kernel) || null;
434 this._statusChanged.emit('unknown');
435 this._kernelChanged.emit({
436 name: 'kernel',
437 oldValue: kernel,
438 newValue: null
439 });
440 this._sessionChanged.emit({
441 name: 'session',
442 oldValue: session,
443 newValue: null
444 });
445 }
446 catch (err) {
447 this._isTerminating = isTerminating;
448 this._isReady = isReady;
449 const status = (_a = session === null || session === void 0 ? void 0 : session.kernel) === null || _a === void 0 ? void 0 : _a.status;
450 if (status === undefined) {
451 this._statusChanged.emit('unknown');
452 }
453 else {
454 this._statusChanged.emit(status);
455 }
456 throw err;
457 }
458 return;
459 }
460 /**
461 * Start the session if necessary.
462 *
463 * @returns Whether to ask the user to pick a kernel.
464 */
465 async _startIfNecessary() {
466 var _a;
467 const preference = this.kernelPreference;
468 if (this.isDisposed || ((_a = this.session) === null || _a === void 0 ? void 0 : _a.kernel) ||
469 preference.shouldStart === false ||
470 preference.canStart === false) {
471 // Not necessary to start a kernel
472 return false;
473 }
474 let options;
475 if (preference.id) {
476 options = { id: preference.id };
477 }
478 else {
479 const name = SessionContext.getDefaultKernel({
480 specs: this.specsManager.specs,
481 sessions: this.sessionManager.running(),
482 preference
483 });
484 if (name) {
485 options = { name };
486 }
487 }
488 if (options) {
489 try {
490 await this._changeKernel(options);
491 return false;
492 }
493 catch (err) {
494 /* no-op */
495 }
496 }
497 // Always fall back to selecting a kernel
498 return true;
499 }
500 /**
501 * Change the kernel.
502 */
503 async _changeKernel(model = {}, isInit = false) {
504 if (model.name) {
505 this._pendingKernelName = model.name;
506 }
507 if (!this._session) {
508 this._kernelChanged.emit({
509 name: 'kernel',
510 oldValue: null,
511 newValue: null
512 });
513 }
514 // Guarantee that the initialized kernel
515 // will be started first.
516 if (!this._pendingSessionRequest) {
517 this._initStarted.resolve(void 0);
518 }
519 // If we already have a session, just change the kernel.
520 if (this._session && !this._isTerminating) {
521 try {
522 await this._session.changeKernel(model);
523 return this._session.kernel;
524 }
525 catch (err) {
526 void this._handleSessionError(err);
527 throw err;
528 }
529 }
530 // Use a UUID for the path to overcome a race condition on the server
531 // where it will re-use a session for a given path but only after
532 // the kernel finishes starting.
533 // We later switch to the real path below.
534 // Use the correct directory so the kernel will be started in that directory.
535 const dirName = PathExt.dirname(this._path);
536 const requestId = (this._pendingSessionRequest = PathExt.join(dirName, UUID.uuid4()));
537 try {
538 this._statusChanged.emit('starting');
539 const session = await this.sessionManager.startNew({
540 path: requestId,
541 type: this._type,
542 name: this._name,
543 kernel: model
544 });
545 // Handle a preempt.
546 if (this._pendingSessionRequest !== session.path) {
547 await session.shutdown();
548 session.dispose();
549 return null;
550 }
551 // Change to the real path.
552 await session.setPath(this._path);
553 // Update the name in case it has changed since we launched the session.
554 await session.setName(this._name);
555 if (this._session && !this._isTerminating) {
556 await this._shutdownSession();
557 }
558 return this._handleNewSession(session);
559 }
560 catch (err) {
561 void this._handleSessionError(err);
562 throw err;
563 }
564 }
565 /**
566 * Handle a new session object.
567 */
568 _handleNewSession(session) {
569 var _a, _b, _c;
570 if (this.isDisposed) {
571 throw Error('Disposed');
572 }
573 if (!this._isReady) {
574 this._isReady = true;
575 this._ready.resolve(undefined);
576 }
577 if (this._session) {
578 this._session.dispose();
579 }
580 this._session = session;
581 this._pendingKernelName = '';
582 if (session) {
583 this._prevKernelName = (_b = (_a = session.kernel) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '';
584 session.disposed.connect(this._onSessionDisposed, this);
585 session.propertyChanged.connect(this._onPropertyChanged, this);
586 session.kernelChanged.connect(this._onKernelChanged, this);
587 session.statusChanged.connect(this._onStatusChanged, this);
588 session.connectionStatusChanged.connect(this._onConnectionStatusChanged, this);
589 session.pendingInput.connect(this._onPendingInput, this);
590 session.iopubMessage.connect(this._onIopubMessage, this);
591 session.unhandledMessage.connect(this._onUnhandledMessage, this);
592 if (session.path !== this._path) {
593 this._onPropertyChanged(session, 'path');
594 }
595 if (session.name !== this._name) {
596 this._onPropertyChanged(session, 'name');
597 }
598 if (session.type !== this._type) {
599 this._onPropertyChanged(session, 'type');
600 }
601 }
602 // Any existing session/kernel connection was disposed above when the session was
603 // disposed, so the oldValue should be null.
604 this._sessionChanged.emit({
605 name: 'session',
606 oldValue: null,
607 newValue: session
608 });
609 this._kernelChanged.emit({
610 oldValue: null,
611 newValue: (session === null || session === void 0 ? void 0 : session.kernel) || null,
612 name: 'kernel'
613 });
614 this._statusChanged.emit(((_c = session === null || session === void 0 ? void 0 : session.kernel) === null || _c === void 0 ? void 0 : _c.status) || 'unknown');
615 return (session === null || session === void 0 ? void 0 : session.kernel) || null;
616 }
617 /**
618 * Handle an error in session startup.
619 */
620 async _handleSessionError(err) {
621 this._handleNewSession(null);
622 let traceback = '';
623 let message = '';
624 try {
625 traceback = err.traceback;
626 message = err.message;
627 }
628 catch (err) {
629 // no-op
630 }
631 await this._displayKernelError(message, traceback);
632 }
633 /**
634 * Display kernel error
635 */
636 async _displayKernelError(message, traceback) {
637 const body = (React.createElement("div", null,
638 message && React.createElement("pre", null, message),
639 traceback && (React.createElement("details", { className: "jp-mod-wide" },
640 React.createElement("pre", null, traceback)))));
641 const dialog = (this._dialog = new Dialog({
642 title: this._trans.__('Error Starting Kernel'),
643 body,
644 buttons: [Dialog.okButton()]
645 }));
646 await dialog.launch();
647 this._dialog = null;
648 }
649 /**
650 * Handle a session termination.
651 */
652 _onSessionDisposed() {
653 if (this._session) {
654 const oldValue = this._session;
655 this._session = null;
656 const newValue = this._session;
657 this._sessionChanged.emit({ name: 'session', oldValue, newValue });
658 }
659 }
660 /**
661 * Handle a change to a session property.
662 */
663 _onPropertyChanged(sender, property) {
664 switch (property) {
665 case 'path':
666 this._path = sender.path;
667 break;
668 case 'name':
669 this._name = sender.name;
670 break;
671 case 'type':
672 this._type = sender.type;
673 break;
674 default:
675 throw new Error(`unrecognized property ${property}`);
676 }
677 this._propertyChanged.emit(property);
678 }
679 /**
680 * Handle a change to the kernel.
681 */
682 _onKernelChanged(sender, args) {
683 this._kernelChanged.emit(args);
684 }
685 /**
686 * Handle a change to the session status.
687 */
688 _onStatusChanged(sender, status) {
689 var _a;
690 if (status === 'dead') {
691 const model = (_a = sender.kernel) === null || _a === void 0 ? void 0 : _a.model;
692 if (model === null || model === void 0 ? void 0 : model.reason) {
693 const traceback = model.traceback || '';
694 void this._displayKernelError(model.reason, traceback);
695 }
696 }
697 // Set that this kernel is busy, if we haven't already
698 // If we have already, and now we aren't busy, dispose
699 // of the busy disposable.
700 if (this._setBusy) {
701 if (status === 'busy') {
702 if (!this._busyDisposable) {
703 this._busyDisposable = this._setBusy();
704 }
705 }
706 else {
707 if (this._busyDisposable) {
708 this._busyDisposable.dispose();
709 this._busyDisposable = null;
710 }
711 }
712 }
713 // Proxy the signal
714 this._statusChanged.emit(status);
715 }
716 /**
717 * Handle a change to the session status.
718 */
719 _onConnectionStatusChanged(sender, status) {
720 // Proxy the signal
721 this._connectionStatusChanged.emit(status);
722 }
723 /**
724 * Handle a change to the pending input.
725 */
726 _onPendingInput(sender, value) {
727 // Set the signal value
728 this._pendingInput = value;
729 }
730 /**
731 * Handle an iopub message.
732 */
733 _onIopubMessage(sender, message) {
734 if (message.header.msg_type === 'shutdown_reply') {
735 this.session.kernel.removeInputGuard();
736 }
737 this._iopubMessage.emit(message);
738 }
739 /**
740 * Handle an unhandled message.
741 */
742 _onUnhandledMessage(sender, message) {
743 this._unhandledMessage.emit(message);
744 }
745}
746/**
747 * A namespace for `SessionContext` statics.
748 */
749(function (SessionContext) {
750 /**
751 * Get the default kernel name given select options.
752 */
753 function getDefaultKernel(options) {
754 return Private.getDefaultKernel(options);
755 }
756 SessionContext.getDefaultKernel = getDefaultKernel;
757})(SessionContext || (SessionContext = {}));
758/**
759 * The default implementation of the client session dialog provider.
760 */
761export const sessionContextDialogs = {
762 /**
763 * Select a kernel for the session.
764 */
765 async selectKernel(sessionContext, translator) {
766 if (sessionContext.isDisposed) {
767 return Promise.resolve();
768 }
769 translator = translator || nullTranslator;
770 const trans = translator.load('jupyterlab');
771 // If there is no existing kernel, offer the option
772 // to keep no kernel.
773 let label = trans.__('Cancel');
774 if (sessionContext.hasNoKernel) {
775 label = sessionContext.kernelDisplayName;
776 }
777 const buttons = [
778 Dialog.cancelButton({ label }),
779 Dialog.okButton({ label: trans.__('Select') })
780 ];
781 const dialog = new Dialog({
782 title: trans.__('Select Kernel'),
783 body: new Private.KernelSelector(sessionContext, translator),
784 buttons
785 });
786 const result = await dialog.launch();
787 if (sessionContext.isDisposed || !result.button.accept) {
788 return;
789 }
790 const model = result.value;
791 if (model === null && !sessionContext.hasNoKernel) {
792 return sessionContext.shutdown();
793 }
794 if (model) {
795 await sessionContext.changeKernel(model);
796 }
797 },
798 /**
799 * Restart the session.
800 *
801 * @returns A promise that resolves with whether the kernel has restarted.
802 *
803 * #### Notes
804 * If there is a running kernel, present a dialog.
805 * If there is no kernel, we start a kernel with the last run
806 * kernel name and resolves with `true`.
807 */
808 async restart(sessionContext, translator) {
809 var _a;
810 translator = translator || nullTranslator;
811 const trans = translator.load('jupyterlab');
812 await sessionContext.initialize();
813 if (sessionContext.isDisposed) {
814 throw new Error('session already disposed');
815 }
816 const kernel = (_a = sessionContext.session) === null || _a === void 0 ? void 0 : _a.kernel;
817 if (!kernel && sessionContext.prevKernelName) {
818 await sessionContext.changeKernel({
819 name: sessionContext.prevKernelName
820 });
821 return true;
822 }
823 // Bail if there is no previous kernel to start.
824 if (!kernel) {
825 throw new Error('No kernel to restart');
826 }
827 const restartBtn = Dialog.warnButton({ label: 'Restart' });
828 const result = await showDialog({
829 title: trans.__('Restart Kernel?'),
830 body: trans.__('Do you want to restart the current kernel? All variables will be lost.'),
831 buttons: [Dialog.cancelButton(), restartBtn]
832 });
833 if (kernel.isDisposed) {
834 return false;
835 }
836 if (result.button.accept) {
837 await sessionContext.restartKernel();
838 return true;
839 }
840 return false;
841 }
842};
843/**
844 * The namespace for module private data.
845 */
846var Private;
847(function (Private) {
848 /**
849 * A widget that provides a kernel selection.
850 */
851 class KernelSelector extends Widget {
852 /**
853 * Create a new kernel selector widget.
854 */
855 constructor(sessionContext, translator) {
856 super({ node: createSelectorNode(sessionContext, translator) });
857 }
858 /**
859 * Get the value of the kernel selector widget.
860 */
861 getValue() {
862 const selector = this.node.querySelector('select');
863 return JSON.parse(selector.value);
864 }
865 }
866 Private.KernelSelector = KernelSelector;
867 /**
868 * Create a node for a kernel selector widget.
869 */
870 function createSelectorNode(sessionContext, translator) {
871 // Create the dialog body.
872 translator = translator || nullTranslator;
873 const trans = translator.load('jupyterlab');
874 const body = document.createElement('div');
875 const text = document.createElement('label');
876 text.textContent = `${trans.__('Select kernel for:')} "${sessionContext.name}"`;
877 body.appendChild(text);
878 const options = getKernelSearch(sessionContext);
879 const selector = document.createElement('select');
880 populateKernelSelect(selector, options, translator, !sessionContext.hasNoKernel ? sessionContext.kernelDisplayName : null);
881 body.appendChild(selector);
882 return body;
883 }
884 /**
885 * Get the default kernel name given select options.
886 */
887 function getDefaultKernel(options) {
888 var _a;
889 const { specs, preference } = options;
890 const { name, language, shouldStart, canStart, autoStartDefault } = preference;
891 if (!specs || shouldStart === false || canStart === false) {
892 return null;
893 }
894 const defaultName = autoStartDefault ? specs.default : null;
895 if (!name && !language) {
896 return defaultName;
897 }
898 // Look for an exact match of a spec name.
899 for (const specName in specs.kernelspecs) {
900 if (specName === name) {
901 return name;
902 }
903 }
904 // Bail if there is no language.
905 if (!language) {
906 return defaultName;
907 }
908 // Check for a single kernel matching the language.
909 const matches = [];
910 for (const specName in specs.kernelspecs) {
911 const kernelLanguage = (_a = specs.kernelspecs[specName]) === null || _a === void 0 ? void 0 : _a.language;
912 if (language === kernelLanguage) {
913 matches.push(specName);
914 }
915 }
916 if (matches.length === 1) {
917 const specName = matches[0];
918 console.warn('No exact match found for ' +
919 specName +
920 ', using kernel ' +
921 specName +
922 ' that matches ' +
923 'language=' +
924 language);
925 return specName;
926 }
927 // No matches found.
928 return defaultName;
929 }
930 Private.getDefaultKernel = getDefaultKernel;
931 /**
932 * Populate a kernel select node for the session.
933 */
934 function populateKernelSelect(node, options, translator, currentKernelDisplayName = null) {
935 while (node.firstChild) {
936 node.removeChild(node.firstChild);
937 }
938 const { preference, sessions, specs } = options;
939 const { name, id, language, canStart, shouldStart } = preference;
940 translator = translator || nullTranslator;
941 const trans = translator.load('jupyterlab');
942 if (!specs || canStart === false) {
943 node.appendChild(optionForNone(translator));
944 node.value = 'null';
945 node.disabled = true;
946 return;
947 }
948 node.disabled = false;
949 // Create mappings of display names and languages for kernel name.
950 const displayNames = Object.create(null);
951 const languages = Object.create(null);
952 for (const name in specs.kernelspecs) {
953 const spec = specs.kernelspecs[name];
954 displayNames[name] = spec.display_name;
955 languages[name] = spec.language;
956 }
957 // Handle a kernel by name.
958 const names = [];
959 if (name && name in specs.kernelspecs) {
960 names.push(name);
961 }
962 // Then look by language.
963 if (language) {
964 for (const specName in specs.kernelspecs) {
965 if (name !== specName && languages[specName] === language) {
966 names.push(specName);
967 }
968 }
969 }
970 // Use the default kernel if no kernels were found.
971 if (!names.length) {
972 names.push(specs.default);
973 }
974 // Handle a preferred kernels in order of display name.
975 const preferred = document.createElement('optgroup');
976 preferred.label = trans.__('Start Preferred Kernel');
977 names.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
978 for (const name of names) {
979 preferred.appendChild(optionForName(name, displayNames[name]));
980 }
981 if (preferred.firstChild) {
982 node.appendChild(preferred);
983 }
984 // Add an option for no kernel
985 node.appendChild(optionForNone());
986 const other = document.createElement('optgroup');
987 other.label = trans.__('Start Other Kernel');
988 // Add the rest of the kernel names in alphabetical order.
989 const otherNames = [];
990 for (const specName in specs.kernelspecs) {
991 if (names.indexOf(specName) !== -1) {
992 continue;
993 }
994 otherNames.push(specName);
995 }
996 otherNames.sort((a, b) => displayNames[a].localeCompare(displayNames[b]));
997 for (const otherName of otherNames) {
998 other.appendChild(optionForName(otherName, displayNames[otherName]));
999 }
1000 // Add a separator option if there were any other names.
1001 if (otherNames.length) {
1002 node.appendChild(other);
1003 }
1004 // Handle the default value.
1005 if (shouldStart === false) {
1006 node.value = 'null';
1007 }
1008 else {
1009 let selectedIndex = 0;
1010 if (currentKernelDisplayName) {
1011 // Select current kernel by default.
1012 selectedIndex = Array.from(node.options).findIndex(option => option.text === currentKernelDisplayName);
1013 selectedIndex = Math.max(selectedIndex, 0);
1014 }
1015 node.selectedIndex = selectedIndex;
1016 }
1017 // Bail if there are no sessions.
1018 if (!sessions) {
1019 return;
1020 }
1021 // Add the sessions using the preferred language first.
1022 const matchingSessions = [];
1023 const otherSessions = [];
1024 each(sessions, session => {
1025 var _a;
1026 if (language &&
1027 session.kernel &&
1028 languages[session.kernel.name] === language &&
1029 session.kernel.id !== id) {
1030 matchingSessions.push(session);
1031 }
1032 else if (((_a = session.kernel) === null || _a === void 0 ? void 0 : _a.id) !== id) {
1033 otherSessions.push(session);
1034 }
1035 });
1036 const matching = document.createElement('optgroup');
1037 matching.label = trans.__('Use Kernel from Preferred Session');
1038 node.appendChild(matching);
1039 if (matchingSessions.length) {
1040 matchingSessions.sort((a, b) => {
1041 return a.path.localeCompare(b.path);
1042 });
1043 each(matchingSessions, session => {
1044 const name = session.kernel ? displayNames[session.kernel.name] : '';
1045 matching.appendChild(optionForSession(session, name, translator));
1046 });
1047 }
1048 const otherSessionsNode = document.createElement('optgroup');
1049 otherSessionsNode.label = trans.__('Use Kernel from Other Session');
1050 node.appendChild(otherSessionsNode);
1051 if (otherSessions.length) {
1052 otherSessions.sort((a, b) => {
1053 return a.path.localeCompare(b.path);
1054 });
1055 each(otherSessions, session => {
1056 const name = session.kernel
1057 ? displayNames[session.kernel.name] || session.kernel.name
1058 : '';
1059 otherSessionsNode.appendChild(optionForSession(session, name, translator));
1060 });
1061 }
1062 }
1063 Private.populateKernelSelect = populateKernelSelect;
1064 /**
1065 * Get the kernel search options given a session context and session manager.
1066 */
1067 function getKernelSearch(sessionContext) {
1068 return {
1069 specs: sessionContext.specsManager.specs,
1070 sessions: sessionContext.sessionManager.running(),
1071 preference: sessionContext.kernelPreference
1072 };
1073 }
1074 /**
1075 * Create an option element for a kernel name.
1076 */
1077 function optionForName(name, displayName) {
1078 const option = document.createElement('option');
1079 option.text = displayName;
1080 option.value = JSON.stringify({ name });
1081 return option;
1082 }
1083 /**
1084 * Create an option for no kernel.
1085 */
1086 function optionForNone(translator) {
1087 translator = translator || nullTranslator;
1088 const trans = translator.load('jupyterlab');
1089 const group = document.createElement('optgroup');
1090 group.label = trans.__('Use No Kernel');
1091 const option = document.createElement('option');
1092 option.text = trans.__('No Kernel');
1093 option.value = 'null';
1094 group.appendChild(option);
1095 return group;
1096 }
1097 /**
1098 * Create an option element for a session.
1099 */
1100 function optionForSession(session, displayName, translator) {
1101 var _a, _b;
1102 translator = translator || nullTranslator;
1103 const trans = translator.load('jupyterlab');
1104 const option = document.createElement('option');
1105 const sessionName = session.name || PathExt.basename(session.path);
1106 option.text = sessionName;
1107 option.value = JSON.stringify({ id: (_a = session.kernel) === null || _a === void 0 ? void 0 : _a.id });
1108 option.title =
1109 `${trans.__('Path:')} ${session.path}\n` +
1110 `${trans.__('Name:')} ${sessionName}\n` +
1111 `${trans.__('Kernel Name:')} ${displayName}\n` +
1112 `${trans.__('Kernel Id:')} ${(_b = session.kernel) === null || _b === void 0 ? void 0 : _b.id}`;
1113 return option;
1114 }
1115})(Private || (Private = {}));
1116//# sourceMappingURL=sessioncontext.js.map
\No newline at end of file