UNPKG

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