UNPKG

76.5 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { Clipboard, Dialog, sessionContextDialogs, showDialog } from '@jupyterlab/apputils';
4import { CodeCell, isCodeCellModel, isMarkdownCellModel, isRawCellModel, MarkdownCell } from '@jupyterlab/cells';
5import { nullTranslator } from '@jupyterlab/translation';
6import { ArrayExt, each, findIndex, toArray } from '@lumino/algorithm';
7import { JSONExt } from '@lumino/coreutils';
8import { ElementExt } from '@lumino/domutils';
9import { Signal } from '@lumino/signaling';
10import * as React from 'react';
11/**
12 * The mimetype used for Jupyter cell data.
13 */
14const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
15export class KernelError extends Error {
16 /**
17 * Construct the kernel error.
18 */
19 constructor(content) {
20 const errorContent = content;
21 const errorName = errorContent.ename;
22 const errorValue = errorContent.evalue;
23 super(`KernelReplyNotOK: ${errorName} ${errorValue}`);
24 this.errorName = errorName;
25 this.errorValue = errorValue;
26 this.traceback = errorContent.traceback;
27 Object.setPrototypeOf(this, KernelError.prototype);
28 }
29}
30/**
31 * A collection of actions that run against notebooks.
32 *
33 * #### Notes
34 * All of the actions are a no-op if there is no model on the notebook.
35 * The actions set the widget `mode` to `'command'` unless otherwise specified.
36 * The actions will preserve the selection on the notebook widget unless
37 * otherwise specified.
38 */
39export class NotebookActions {
40 /**
41 * A signal that emits whenever a cell completes execution.
42 */
43 static get executed() {
44 return Private.executed;
45 }
46 /**
47 * A signal that emits whenever a cell execution is scheduled.
48 */
49 static get executionScheduled() {
50 return Private.executionScheduled;
51 }
52 /**
53 * A signal that emits whenever a cell execution is scheduled.
54 */
55 static get selectionExecuted() {
56 return Private.selectionExecuted;
57 }
58 /**
59 * A private constructor for the `NotebookActions` class.
60 *
61 * #### Notes
62 * This class can never be instantiated. Its static member `executed` will be
63 * merged with the `NotebookActions` namespace. The reason it exists as a
64 * standalone class is because at run time, the `Private.executed` variable
65 * does not yet exist, so it needs to be referenced via a getter.
66 */
67 constructor() {
68 // Intentionally empty.
69 }
70}
71/**
72 * A namespace for `NotebookActions` static methods.
73 */
74(function (NotebookActions) {
75 /**
76 * Split the active cell into two or more cells.
77 *
78 * @param notebook The target notebook widget.
79 *
80 * #### Notes
81 * It will preserve the existing mode.
82 * The last cell will be activated if no selection is found.
83 * If text was selected, the cell containing the selection will
84 * be activated.
85 * The existing selection will be cleared.
86 * The activated cell will have focus and the cursor will
87 * remain in the initial position.
88 * The leading whitespace in the second cell will be removed.
89 * If there is no content, two empty cells will be created.
90 * Both cells will have the same type as the original cell.
91 * This action can be undone.
92 */
93 function splitCell(notebook) {
94 if (!notebook.model || !notebook.activeCell) {
95 return;
96 }
97 if (!Private.isNotebookRendered(notebook)) {
98 return;
99 }
100 const state = Private.getState(notebook);
101 notebook.deselectAll();
102 const nbModel = notebook.model;
103 const index = notebook.activeCellIndex;
104 const child = notebook.widgets[index];
105 const editor = child.editor;
106 const selections = editor.getSelections();
107 const orig = child.model.value.text;
108 const offsets = [0];
109 let start = -1;
110 let end = -1;
111 for (let i = 0; i < selections.length; i++) {
112 // append start and end to handle selections
113 // cursors will have same start and end
114 start = editor.getOffsetAt(selections[i].start);
115 end = editor.getOffsetAt(selections[i].end);
116 if (start < end) {
117 offsets.push(start);
118 offsets.push(end);
119 }
120 else if (end < start) {
121 offsets.push(end);
122 offsets.push(start);
123 }
124 else {
125 offsets.push(start);
126 }
127 }
128 offsets.push(orig.length);
129 const clones = [];
130 for (let i = 0; i + 1 < offsets.length; i++) {
131 const clone = Private.cloneCell(nbModel, child.model);
132 clones.push(clone);
133 }
134 for (let i = 0; i < clones.length; i++) {
135 if (i !== clones.length - 1 && clones[i].type === 'code') {
136 clones[i].outputs.clear();
137 }
138 clones[i].value.text = orig
139 .slice(offsets[i], offsets[i + 1])
140 .replace(/^\n+/, '')
141 .replace(/\n+$/, '');
142 }
143 const cells = nbModel.cells;
144 cells.beginCompoundOperation();
145 for (let i = 0; i < clones.length; i++) {
146 if (i === 0) {
147 cells.set(index, clones[i]);
148 }
149 else {
150 cells.insert(index + i, clones[i]);
151 }
152 }
153 cells.endCompoundOperation();
154 // If there is a selection the selected cell will be activated
155 const activeCellDelta = start !== end ? 2 : 1;
156 notebook.activeCellIndex = index + clones.length - activeCellDelta;
157 const focusedEditor = notebook.activeCell.editor;
158 focusedEditor.focus();
159 Private.handleState(notebook, state);
160 }
161 NotebookActions.splitCell = splitCell;
162 /**
163 * Merge the selected cells.
164 *
165 * @param notebook - The target notebook widget.
166 *
167 * @param mergeAbove - If only one cell is selected, indicates whether to merge it
168 * with the cell above (true) or below (false, default).
169 *
170 * #### Notes
171 * The widget mode will be preserved.
172 * If only one cell is selected and `mergeAbove` is true, the above cell will be selected.
173 * If only one cell is selected and `mergeAbove` is false, the below cell will be selected.
174 * If the active cell is a code cell, its outputs will be cleared.
175 * This action can be undone.
176 * The final cell will have the same type as the active cell.
177 * If the active cell is a markdown cell, it will be unrendered.
178 */
179 function mergeCells(notebook, mergeAbove = false) {
180 if (!notebook.model || !notebook.activeCell) {
181 return;
182 }
183 if (!Private.isNotebookRendered(notebook)) {
184 return;
185 }
186 const state = Private.getState(notebook);
187 const toMerge = [];
188 const toDelete = [];
189 const model = notebook.model;
190 const cells = model.cells;
191 const primary = notebook.activeCell;
192 const active = notebook.activeCellIndex;
193 const attachments = {};
194 // Get the cells to merge.
195 notebook.widgets.forEach((child, index) => {
196 if (notebook.isSelectedOrActive(child)) {
197 toMerge.push(child.model.value.text);
198 if (index !== active) {
199 toDelete.push(child.model);
200 }
201 // Collect attachments if the cell is a markdown cell or a raw cell
202 const model = child.model;
203 if (isRawCellModel(model) || isMarkdownCellModel(model)) {
204 for (const key of model.attachments.keys) {
205 attachments[key] = model.attachments.get(key).toJSON();
206 }
207 }
208 }
209 });
210 // Check for only a single cell selected.
211 if (toMerge.length === 1) {
212 // Merge with the cell above when mergeAbove is true
213 if (mergeAbove === true) {
214 // Bail if it is the first cell.
215 if (active === 0) {
216 return;
217 }
218 // Otherwise merge with the previous cell.
219 const cellModel = cells.get(active - 1);
220 toMerge.unshift(cellModel.value.text);
221 toDelete.push(cellModel);
222 }
223 else if (mergeAbove === false) {
224 // Bail if it is the last cell.
225 if (active === cells.length - 1) {
226 return;
227 }
228 // Otherwise merge with the next cell.
229 const cellModel = cells.get(active + 1);
230 toMerge.push(cellModel.value.text);
231 toDelete.push(cellModel);
232 }
233 }
234 notebook.deselectAll();
235 // Create a new cell for the source to preserve history.
236 const newModel = Private.cloneCell(model, primary.model);
237 newModel.value.text = toMerge.join('\n\n');
238 if (isCodeCellModel(newModel)) {
239 newModel.outputs.clear();
240 }
241 else if (isMarkdownCellModel(newModel) || isRawCellModel(newModel)) {
242 newModel.attachments.fromJSON(attachments);
243 }
244 // Make the changes while preserving history.
245 cells.beginCompoundOperation();
246 cells.set(active, newModel);
247 toDelete.forEach(cell => {
248 cells.removeValue(cell);
249 });
250 cells.endCompoundOperation();
251 // If the original cell is a markdown cell, make sure
252 // the new cell is unrendered.
253 if (primary instanceof MarkdownCell) {
254 notebook.activeCell.rendered = false;
255 }
256 Private.handleState(notebook, state);
257 }
258 NotebookActions.mergeCells = mergeCells;
259 /**
260 * Delete the selected cells.
261 *
262 * @param notebook - The target notebook widget.
263 *
264 * #### Notes
265 * The cell after the last selected cell will be activated.
266 * It will add a code cell if all cells are deleted.
267 * This action can be undone.
268 */
269 function deleteCells(notebook) {
270 if (!notebook.model || !notebook.activeCell) {
271 return;
272 }
273 if (!Private.isNotebookRendered(notebook)) {
274 return;
275 }
276 const state = Private.getState(notebook);
277 Private.deleteCells(notebook);
278 Private.handleState(notebook, state, true);
279 }
280 NotebookActions.deleteCells = deleteCells;
281 /**
282 * Insert a new code cell above the active cell.
283 *
284 * @param notebook - The target notebook widget.
285 *
286 * #### Notes
287 * The widget mode will be preserved.
288 * This action can be undone.
289 * The existing selection will be cleared.
290 * The new cell will the active cell.
291 */
292 function insertAbove(notebook) {
293 if (!notebook.model || !notebook.activeCell) {
294 return;
295 }
296 if (!Private.isNotebookRendered(notebook)) {
297 return;
298 }
299 const state = Private.getState(notebook);
300 const model = notebook.model;
301 const cell = model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {});
302 const active = notebook.activeCellIndex;
303 model.cells.insert(active, cell);
304 // Make the newly inserted cell active.
305 notebook.activeCellIndex = active;
306 notebook.deselectAll();
307 Private.handleState(notebook, state, true);
308 }
309 NotebookActions.insertAbove = insertAbove;
310 /**
311 * Insert a new code cell below the active cell.
312 *
313 * @param notebook - The target notebook widget.
314 *
315 * #### Notes
316 * The widget mode will be preserved.
317 * This action can be undone.
318 * The existing selection will be cleared.
319 * The new cell will be the active cell.
320 */
321 function insertBelow(notebook) {
322 if (!notebook.model || !notebook.activeCell) {
323 return;
324 }
325 if (!Private.isNotebookRendered(notebook)) {
326 return;
327 }
328 const state = Private.getState(notebook);
329 const model = notebook.model;
330 const cell = model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {});
331 model.cells.insert(notebook.activeCellIndex + 1, cell);
332 // Make the newly inserted cell active.
333 notebook.activeCellIndex++;
334 notebook.deselectAll();
335 Private.handleState(notebook, state, true);
336 }
337 NotebookActions.insertBelow = insertBelow;
338 /**
339 * Move the selected cell(s) down.
340 *
341 * @param notebook = The target notebook widget.
342 */
343 function moveDown(notebook) {
344 if (!notebook.model || !notebook.activeCell) {
345 return;
346 }
347 if (!Private.isNotebookRendered(notebook)) {
348 return;
349 }
350 const state = Private.getState(notebook);
351 const cells = notebook.model.cells;
352 const widgets = notebook.widgets;
353 cells.beginCompoundOperation();
354 for (let i = cells.length - 2; i > -1; i--) {
355 if (notebook.isSelectedOrActive(widgets[i])) {
356 if (!notebook.isSelectedOrActive(widgets[i + 1])) {
357 cells.move(i, i + 1);
358 if (notebook.activeCellIndex === i) {
359 notebook.activeCellIndex++;
360 }
361 notebook.select(widgets[i + 1]);
362 notebook.deselect(widgets[i]);
363 }
364 }
365 }
366 cells.endCompoundOperation();
367 Private.handleState(notebook, state, true);
368 }
369 NotebookActions.moveDown = moveDown;
370 /**
371 * Move the selected cell(s) up.
372 *
373 * @param widget - The target notebook widget.
374 */
375 function moveUp(notebook) {
376 if (!notebook.model || !notebook.activeCell) {
377 return;
378 }
379 if (!Private.isNotebookRendered(notebook)) {
380 return;
381 }
382 const state = Private.getState(notebook);
383 const cells = notebook.model.cells;
384 const widgets = notebook.widgets;
385 cells.beginCompoundOperation();
386 for (let i = 1; i < cells.length; i++) {
387 if (notebook.isSelectedOrActive(widgets[i])) {
388 if (!notebook.isSelectedOrActive(widgets[i - 1])) {
389 cells.move(i, i - 1);
390 if (notebook.activeCellIndex === i) {
391 notebook.activeCellIndex--;
392 }
393 notebook.select(widgets[i - 1]);
394 notebook.deselect(widgets[i]);
395 }
396 }
397 }
398 cells.endCompoundOperation();
399 Private.handleState(notebook, state, true);
400 }
401 NotebookActions.moveUp = moveUp;
402 /**
403 * Change the selected cell type(s).
404 *
405 * @param notebook - The target notebook widget.
406 *
407 * @param value - The target cell type.
408 *
409 * #### Notes
410 * It should preserve the widget mode.
411 * This action can be undone.
412 * The existing selection will be cleared.
413 * Any cells converted to markdown will be unrendered.
414 */
415 function changeCellType(notebook, value) {
416 if (!notebook.model || !notebook.activeCell) {
417 return;
418 }
419 const state = Private.getState(notebook);
420 Private.changeCellType(notebook, value);
421 Private.handleState(notebook, state);
422 }
423 NotebookActions.changeCellType = changeCellType;
424 /**
425 * Run the selected cell(s).
426 *
427 * @param notebook - The target notebook widget.
428 *
429 * @param sessionContext - The optional client session object.
430 *
431 * #### Notes
432 * The last selected cell will be activated, but not scrolled into view.
433 * The existing selection will be cleared.
434 * An execution error will prevent the remaining code cells from executing.
435 * All markdown cells will be rendered.
436 */
437 function run(notebook, sessionContext) {
438 if (!notebook.model || !notebook.activeCell) {
439 return Promise.resolve(false);
440 }
441 const state = Private.getState(notebook);
442 const promise = Private.runSelected(notebook, sessionContext);
443 Private.handleRunState(notebook, state, false);
444 return promise;
445 }
446 NotebookActions.run = run;
447 /**
448 * Run the selected cell(s) and advance to the next cell.
449 *
450 * @param notebook - The target notebook widget.
451 *
452 * @param sessionContext - The optional client session object.
453 *
454 * #### Notes
455 * The existing selection will be cleared.
456 * The cell after the last selected cell will be activated and scrolled into view.
457 * An execution error will prevent the remaining code cells from executing.
458 * All markdown cells will be rendered.
459 * If the last selected cell is the last cell, a new code cell
460 * will be created in `'edit'` mode. The new cell creation can be undone.
461 */
462 function runAndAdvance(notebook, sessionContext) {
463 if (!notebook.model || !notebook.activeCell) {
464 return Promise.resolve(false);
465 }
466 const state = Private.getState(notebook);
467 const promise = Private.runSelected(notebook, sessionContext);
468 const model = notebook.model;
469 if (notebook.activeCellIndex === notebook.widgets.length - 1) {
470 const cell = model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {});
471 // Do not use push here, as we want an widget insertion
472 // to make sure no placeholder widget is rendered.
473 model.cells.insert(notebook.widgets.length, cell);
474 notebook.activeCellIndex++;
475 notebook.mode = 'edit';
476 }
477 else {
478 notebook.activeCellIndex++;
479 }
480 Private.handleRunState(notebook, state, true);
481 return promise;
482 }
483 NotebookActions.runAndAdvance = runAndAdvance;
484 /**
485 * Run the selected cell(s) and insert a new code cell.
486 *
487 * @param notebook - The target notebook widget.
488 *
489 * @param sessionContext - The optional client session object.
490 *
491 * #### Notes
492 * An execution error will prevent the remaining code cells from executing.
493 * All markdown cells will be rendered.
494 * The widget mode will be set to `'edit'` after running.
495 * The existing selection will be cleared.
496 * The cell insert can be undone.
497 * The new cell will be scrolled into view.
498 */
499 function runAndInsert(notebook, sessionContext) {
500 if (!notebook.model || !notebook.activeCell) {
501 return Promise.resolve(false);
502 }
503 if (!Private.isNotebookRendered(notebook)) {
504 return Promise.resolve(false);
505 }
506 const state = Private.getState(notebook);
507 const promise = Private.runSelected(notebook, sessionContext);
508 const model = notebook.model;
509 const cell = model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {});
510 model.cells.insert(notebook.activeCellIndex + 1, cell);
511 notebook.activeCellIndex++;
512 notebook.mode = 'edit';
513 Private.handleRunState(notebook, state, true);
514 return promise;
515 }
516 NotebookActions.runAndInsert = runAndInsert;
517 /**
518 * Run all of the cells in the notebook.
519 *
520 * @param notebook - The target notebook widget.
521 *
522 * @param sessionContext - The optional client session object.
523 *
524 * #### Notes
525 * The existing selection will be cleared.
526 * An execution error will prevent the remaining code cells from executing.
527 * All markdown cells will be rendered.
528 * The last cell in the notebook will be activated and scrolled into view.
529 */
530 function runAll(notebook, sessionContext) {
531 if (!notebook.model || !notebook.activeCell) {
532 return Promise.resolve(false);
533 }
534 const state = Private.getState(notebook);
535 notebook.widgets.forEach(child => {
536 notebook.select(child);
537 });
538 const promise = Private.runSelected(notebook, sessionContext);
539 Private.handleRunState(notebook, state, true);
540 return promise;
541 }
542 NotebookActions.runAll = runAll;
543 function renderAllMarkdown(notebook, sessionContext) {
544 if (!notebook.model || !notebook.activeCell) {
545 return Promise.resolve(false);
546 }
547 const previousIndex = notebook.activeCellIndex;
548 const state = Private.getState(notebook);
549 notebook.widgets.forEach((child, index) => {
550 if (child.model.type === 'markdown') {
551 notebook.select(child);
552 // This is to make sure that the activeCell
553 // does not get executed
554 notebook.activeCellIndex = index;
555 }
556 });
557 if (notebook.activeCell.model.type !== 'markdown') {
558 return Promise.resolve(true);
559 }
560 const promise = Private.runSelected(notebook, sessionContext);
561 notebook.activeCellIndex = previousIndex;
562 Private.handleRunState(notebook, state, true);
563 return promise;
564 }
565 NotebookActions.renderAllMarkdown = renderAllMarkdown;
566 /**
567 * Run all of the cells before the currently active cell (exclusive).
568 *
569 * @param notebook - The target notebook widget.
570 *
571 * @param sessionContext - The optional client session object.
572 *
573 * #### Notes
574 * The existing selection will be cleared.
575 * An execution error will prevent the remaining code cells from executing.
576 * All markdown cells will be rendered.
577 * The currently active cell will remain selected.
578 */
579 function runAllAbove(notebook, sessionContext) {
580 const { activeCell, activeCellIndex, model } = notebook;
581 if (!model || !activeCell || activeCellIndex < 1) {
582 return Promise.resolve(false);
583 }
584 const state = Private.getState(notebook);
585 notebook.activeCellIndex--;
586 notebook.deselectAll();
587 for (let i = 0; i < notebook.activeCellIndex; ++i) {
588 notebook.select(notebook.widgets[i]);
589 }
590 const promise = Private.runSelected(notebook, sessionContext);
591 notebook.activeCellIndex++;
592 Private.handleRunState(notebook, state, true);
593 return promise;
594 }
595 NotebookActions.runAllAbove = runAllAbove;
596 /**
597 * Run all of the cells after the currently active cell (inclusive).
598 *
599 * @param notebook - The target notebook widget.
600 *
601 * @param sessionContext - The optional client session object.
602 *
603 * #### Notes
604 * The existing selection will be cleared.
605 * An execution error will prevent the remaining code cells from executing.
606 * All markdown cells will be rendered.
607 * The last cell in the notebook will be activated and scrolled into view.
608 */
609 function runAllBelow(notebook, sessionContext) {
610 if (!notebook.model || !notebook.activeCell) {
611 return Promise.resolve(false);
612 }
613 const state = Private.getState(notebook);
614 notebook.deselectAll();
615 for (let i = notebook.activeCellIndex; i < notebook.widgets.length; ++i) {
616 notebook.select(notebook.widgets[i]);
617 }
618 const promise = Private.runSelected(notebook, sessionContext);
619 Private.handleRunState(notebook, state, true);
620 return promise;
621 }
622 NotebookActions.runAllBelow = runAllBelow;
623 /**
624 * Replaces the selection in the active cell of the notebook.
625 *
626 * @param notebook - The target notebook widget.
627 * @param text - The text to replace the selection.
628 */
629 function replaceSelection(notebook, text) {
630 var _a, _b;
631 if (!notebook.model || !notebook.activeCell) {
632 return;
633 }
634 (_b = (_a = notebook.activeCell.editor).replaceSelection) === null || _b === void 0 ? void 0 : _b.call(_a, text);
635 }
636 NotebookActions.replaceSelection = replaceSelection;
637 /**
638 * Select the above the active cell.
639 *
640 * @param notebook - The target notebook widget.
641 *
642 * #### Notes
643 * The widget mode will be preserved.
644 * This is a no-op if the first cell is the active cell.
645 * This will skip any collapsed cells.
646 * The existing selection will be cleared.
647 */
648 function selectAbove(notebook) {
649 if (!notebook.model || !notebook.activeCell) {
650 return;
651 }
652 if (notebook.activeCellIndex === 0) {
653 return;
654 }
655 let possibleNextCellIndex = notebook.activeCellIndex - 1;
656 // find first non hidden cell above current cell
657 while (possibleNextCellIndex >= 0) {
658 const possibleNextCell = notebook.widgets[possibleNextCellIndex];
659 if (!possibleNextCell.inputHidden && !possibleNextCell.isHidden) {
660 break;
661 }
662 possibleNextCellIndex -= 1;
663 }
664 const state = Private.getState(notebook);
665 notebook.activeCellIndex = possibleNextCellIndex;
666 notebook.deselectAll();
667 Private.handleState(notebook, state, true);
668 }
669 NotebookActions.selectAbove = selectAbove;
670 /**
671 * Select the cell below the active cell.
672 *
673 * @param notebook - The target notebook widget.
674 *
675 * #### Notes
676 * The widget mode will be preserved.
677 * This is a no-op if the last cell is the active cell.
678 * This will skip any collapsed cells.
679 * The existing selection will be cleared.
680 */
681 function selectBelow(notebook) {
682 if (!notebook.model || !notebook.activeCell) {
683 return;
684 }
685 let maxCellIndex = notebook.widgets.length - 1;
686 // Find last non-hidden cell
687 while (notebook.widgets[maxCellIndex].isHidden ||
688 notebook.widgets[maxCellIndex].inputHidden) {
689 maxCellIndex -= 1;
690 }
691 if (notebook.activeCellIndex === maxCellIndex) {
692 return;
693 }
694 let possibleNextCellIndex = notebook.activeCellIndex + 1;
695 // find first non hidden cell below current cell
696 while (possibleNextCellIndex < maxCellIndex) {
697 let possibleNextCell = notebook.widgets[possibleNextCellIndex];
698 if (!possibleNextCell.inputHidden && !possibleNextCell.isHidden) {
699 break;
700 }
701 possibleNextCellIndex += 1;
702 }
703 const state = Private.getState(notebook);
704 notebook.activeCellIndex = possibleNextCellIndex;
705 notebook.deselectAll();
706 Private.handleState(notebook, state, true);
707 }
708 NotebookActions.selectBelow = selectBelow;
709 /**
710 * Extend the selection to the cell above.
711 *
712 * @param notebook - The target notebook widget.
713 * @param toTop - If true, denotes selection to extend to the top.
714 *
715 * #### Notes
716 * This is a no-op if the first cell is the active cell.
717 * The new cell will be activated.
718 */
719 function extendSelectionAbove(notebook, toTop = false) {
720 if (!notebook.model || !notebook.activeCell) {
721 return;
722 }
723 // Do not wrap around.
724 if (notebook.activeCellIndex === 0) {
725 return;
726 }
727 const state = Private.getState(notebook);
728 notebook.mode = 'command';
729 // Check if toTop is true, if yes, selection is made to the top.
730 if (toTop) {
731 notebook.extendContiguousSelectionTo(0);
732 }
733 else {
734 notebook.extendContiguousSelectionTo(notebook.activeCellIndex - 1);
735 }
736 Private.handleState(notebook, state, true);
737 }
738 NotebookActions.extendSelectionAbove = extendSelectionAbove;
739 /**
740 * Extend the selection to the cell below.
741 *
742 * @param notebook - The target notebook widget.
743 * @param toBottom - If true, denotes selection to extend to the bottom.
744 *
745 * #### Notes
746 * This is a no-op if the last cell is the active cell.
747 * The new cell will be activated.
748 */
749 function extendSelectionBelow(notebook, toBottom = false) {
750 if (!notebook.model || !notebook.activeCell) {
751 return;
752 }
753 // Do not wrap around.
754 if (notebook.activeCellIndex === notebook.widgets.length - 1) {
755 return;
756 }
757 const state = Private.getState(notebook);
758 notebook.mode = 'command';
759 // Check if toBottom is true, if yes selection is made to the bottom.
760 if (toBottom) {
761 notebook.extendContiguousSelectionTo(notebook.widgets.length - 1);
762 }
763 else {
764 notebook.extendContiguousSelectionTo(notebook.activeCellIndex + 1);
765 }
766 Private.handleState(notebook, state, true);
767 }
768 NotebookActions.extendSelectionBelow = extendSelectionBelow;
769 /**
770 * Select all of the cells of the notebook.
771 *
772 * @param notebook - the target notebook widget.
773 */
774 function selectAll(notebook) {
775 if (!notebook.model || !notebook.activeCell) {
776 return;
777 }
778 notebook.widgets.forEach(child => {
779 notebook.select(child);
780 });
781 }
782 NotebookActions.selectAll = selectAll;
783 /**
784 * Deselect all of the cells of the notebook.
785 *
786 * @param notebook - the target notebook widget.
787 */
788 function deselectAll(notebook) {
789 if (!notebook.model || !notebook.activeCell) {
790 return;
791 }
792 notebook.deselectAll();
793 }
794 NotebookActions.deselectAll = deselectAll;
795 /**
796 * Copy the selected cell(s) data to a clipboard.
797 *
798 * @param notebook - The target notebook widget.
799 */
800 function copy(notebook) {
801 Private.copyOrCut(notebook, false);
802 }
803 NotebookActions.copy = copy;
804 /**
805 * Cut the selected cell data to a clipboard.
806 *
807 * @param notebook - The target notebook widget.
808 *
809 * #### Notes
810 * This action can be undone.
811 * A new code cell is added if all cells are cut.
812 */
813 function cut(notebook) {
814 if (!Private.isNotebookRendered(notebook)) {
815 return;
816 }
817 Private.copyOrCut(notebook, true);
818 }
819 NotebookActions.cut = cut;
820 /**
821 * Paste cells from the application clipboard.
822 *
823 * @param notebook - The target notebook widget.
824 *
825 * @param mode - the mode of adding cells:
826 * 'below' (default) adds cells below the active cell,
827 * 'belowSelected' adds cells below all selected cells,
828 * 'above' adds cells above the active cell, and
829 * 'replace' removes the currently selected cells and adds cells in their place.
830 *
831 * #### Notes
832 * The last pasted cell becomes the active cell.
833 * This is a no-op if there is no cell data on the clipboard.
834 * This action can be undone.
835 */
836 function paste(notebook, mode = 'below') {
837 if (!Private.isNotebookRendered(notebook)) {
838 return;
839 }
840 const clipboard = Clipboard.getInstance();
841 if (!clipboard.hasData(JUPYTER_CELL_MIME)) {
842 return;
843 }
844 const values = clipboard.getData(JUPYTER_CELL_MIME);
845 addCells(notebook, mode, values, true);
846 }
847 NotebookActions.paste = paste;
848 /**
849 * Duplicate selected cells in the notebook without using the application clipboard.
850 *
851 * @param notebook - The target notebook widget.
852 *
853 * @param mode - the mode of adding cells:
854 * 'below' (default) adds cells below the active cell,
855 * 'belowSelected' adds cells below all selected cells,
856 * 'above' adds cells above the active cell, and
857 * 'replace' removes the currently selected cells and adds cells in their place.
858 *
859 * #### Notes
860 * The last pasted cell becomes the active cell.
861 * This is a no-op if there is no cell data on the clipboard.
862 * This action can be undone.
863 */
864 function duplicate(notebook, mode = 'below') {
865 const values = Private.selectedCells(notebook);
866 if (!values || values.length === 0) {
867 return;
868 }
869 addCells(notebook, mode, values, false); // Cells not from the clipboard
870 }
871 NotebookActions.duplicate = duplicate;
872 /**
873 * Adds cells to the notebook.
874 *
875 * @param notebook - The target notebook widget.
876 *
877 * @param mode - the mode of adding cells:
878 * 'below' (default) adds cells below the active cell,
879 * 'belowSelected' adds cells below all selected cells,
880 * 'above' adds cells above the active cell, and
881 * 'replace' removes the currently selected cells and adds cells in their place.
882 *
883 * @param values — The cells to add to the notebook.
884 *
885 * @param cellsFromClipboard — True if the cells were sourced from the clipboard.
886 *
887 * #### Notes
888 * The last added cell becomes the active cell.
889 * This is a no-op if values is an empty array.
890 * This action can be undone.
891 */
892 function addCells(notebook, mode = 'below', values, cellsFromClipboard = false) {
893 if (!notebook.model || !notebook.activeCell) {
894 return;
895 }
896 const state = Private.getState(notebook);
897 const model = notebook.model;
898 notebook.mode = 'command';
899 const newCells = values.map(cell => {
900 switch (cell.cell_type) {
901 case 'code':
902 if (notebook.lastClipboardInteraction === 'cut' &&
903 typeof cell.id === 'string') {
904 let cell_id = cell.id;
905 return model.contentFactory.createCodeCell({
906 id: cell_id,
907 cell: cell
908 });
909 }
910 else {
911 return model.contentFactory.createCodeCell({ cell });
912 }
913 case 'markdown':
914 return model.contentFactory.createMarkdownCell({ cell });
915 default:
916 return model.contentFactory.createRawCell({ cell });
917 }
918 });
919 const cells = notebook.model.cells;
920 let index;
921 cells.beginCompoundOperation();
922 // Set the starting index of the paste operation depending upon the mode.
923 switch (mode) {
924 case 'below':
925 index = notebook.activeCellIndex;
926 break;
927 case 'belowSelected':
928 notebook.widgets.forEach((child, childIndex) => {
929 if (notebook.isSelectedOrActive(child)) {
930 index = childIndex;
931 }
932 });
933 break;
934 case 'above':
935 index = notebook.activeCellIndex - 1;
936 break;
937 case 'replace': {
938 // Find the cells to delete.
939 const toDelete = [];
940 notebook.widgets.forEach((child, index) => {
941 const deletable = child.model.metadata.get('deletable') !== false;
942 if (notebook.isSelectedOrActive(child) && deletable) {
943 toDelete.push(index);
944 }
945 });
946 // If cells are not deletable, we may not have anything to delete.
947 if (toDelete.length > 0) {
948 // Delete the cells as one undo event.
949 toDelete.reverse().forEach(i => {
950 cells.remove(i);
951 });
952 }
953 index = toDelete[0];
954 break;
955 }
956 default:
957 break;
958 }
959 newCells.forEach(cell => {
960 cells.insert(++index, cell);
961 });
962 cells.endCompoundOperation();
963 notebook.activeCellIndex += newCells.length;
964 notebook.deselectAll();
965 if (cellsFromClipboard) {
966 notebook.lastClipboardInteraction = 'paste';
967 }
968 Private.handleState(notebook, state);
969 }
970 /**
971 * Undo a cell action.
972 *
973 * @param notebook - The target notebook widget.
974 *
975 * #### Notes
976 * This is a no-op if if there are no cell actions to undo.
977 */
978 function undo(notebook) {
979 if (!notebook.model || !notebook.activeCell) {
980 return;
981 }
982 if (!Private.isNotebookRendered(notebook)) {
983 return;
984 }
985 const state = Private.getState(notebook);
986 notebook.mode = 'command';
987 notebook.model.sharedModel.undo();
988 notebook.deselectAll();
989 Private.handleState(notebook, state);
990 }
991 NotebookActions.undo = undo;
992 /**
993 * Redo a cell action.
994 *
995 * @param notebook - The target notebook widget.
996 *
997 * #### Notes
998 * This is a no-op if there are no cell actions to redo.
999 */
1000 function redo(notebook) {
1001 if (!notebook.model || !notebook.activeCell) {
1002 return;
1003 }
1004 const state = Private.getState(notebook);
1005 notebook.mode = 'command';
1006 notebook.model.sharedModel.redo();
1007 notebook.deselectAll();
1008 Private.handleState(notebook, state);
1009 }
1010 NotebookActions.redo = redo;
1011 /**
1012 * Toggle the line number of all cells.
1013 *
1014 * @param notebook - The target notebook widget.
1015 *
1016 * #### Notes
1017 * The original state is based on the state of the active cell.
1018 * The `mode` of the widget will be preserved.
1019 */
1020 function toggleAllLineNumbers(notebook) {
1021 if (!notebook.model || !notebook.activeCell) {
1022 return;
1023 }
1024 const state = Private.getState(notebook);
1025 const config = notebook.editorConfig;
1026 const lineNumbers = !(config.code.lineNumbers &&
1027 config.markdown.lineNumbers &&
1028 config.raw.lineNumbers);
1029 const newConfig = {
1030 code: Object.assign(Object.assign({}, config.code), { lineNumbers }),
1031 markdown: Object.assign(Object.assign({}, config.markdown), { lineNumbers }),
1032 raw: Object.assign(Object.assign({}, config.raw), { lineNumbers })
1033 };
1034 notebook.editorConfig = newConfig;
1035 Private.handleState(notebook, state);
1036 }
1037 NotebookActions.toggleAllLineNumbers = toggleAllLineNumbers;
1038 /**
1039 * Clear the code outputs of the selected cells.
1040 *
1041 * @param notebook - The target notebook widget.
1042 *
1043 * #### Notes
1044 * The widget `mode` will be preserved.
1045 */
1046 function clearOutputs(notebook) {
1047 if (!notebook.model || !notebook.activeCell) {
1048 return;
1049 }
1050 const state = Private.getState(notebook);
1051 each(notebook.model.cells, (cell, index) => {
1052 const child = notebook.widgets[index];
1053 if (notebook.isSelectedOrActive(child) && cell.type === 'code') {
1054 cell.clearExecution();
1055 child.outputHidden = false;
1056 }
1057 });
1058 Private.handleState(notebook, state, true);
1059 }
1060 NotebookActions.clearOutputs = clearOutputs;
1061 /**
1062 * Clear all the code outputs on the widget.
1063 *
1064 * @param notebook - The target notebook widget.
1065 *
1066 * #### Notes
1067 * The widget `mode` will be preserved.
1068 */
1069 function clearAllOutputs(notebook) {
1070 if (!notebook.model || !notebook.activeCell) {
1071 return;
1072 }
1073 const state = Private.getState(notebook);
1074 each(notebook.model.cells, (cell, index) => {
1075 const child = notebook.widgets[index];
1076 if (cell.type === 'code') {
1077 cell.clearExecution();
1078 child.outputHidden = false;
1079 }
1080 });
1081 Private.handleState(notebook, state, true);
1082 }
1083 NotebookActions.clearAllOutputs = clearAllOutputs;
1084 /**
1085 * Hide the code on selected code cells.
1086 *
1087 * @param notebook - The target notebook widget.
1088 */
1089 function hideCode(notebook) {
1090 if (!notebook.model || !notebook.activeCell) {
1091 return;
1092 }
1093 const state = Private.getState(notebook);
1094 notebook.widgets.forEach(cell => {
1095 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1096 cell.inputHidden = true;
1097 }
1098 });
1099 Private.handleState(notebook, state);
1100 }
1101 NotebookActions.hideCode = hideCode;
1102 /**
1103 * Show the code on selected code cells.
1104 *
1105 * @param notebook - The target notebook widget.
1106 */
1107 function showCode(notebook) {
1108 if (!notebook.model || !notebook.activeCell) {
1109 return;
1110 }
1111 const state = Private.getState(notebook);
1112 notebook.widgets.forEach(cell => {
1113 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1114 cell.inputHidden = false;
1115 }
1116 });
1117 Private.handleState(notebook, state);
1118 }
1119 NotebookActions.showCode = showCode;
1120 /**
1121 * Hide the code on all code cells.
1122 *
1123 * @param notebook - The target notebook widget.
1124 */
1125 function hideAllCode(notebook) {
1126 if (!notebook.model || !notebook.activeCell) {
1127 return;
1128 }
1129 const state = Private.getState(notebook);
1130 notebook.widgets.forEach(cell => {
1131 if (cell.model.type === 'code') {
1132 cell.inputHidden = true;
1133 }
1134 });
1135 Private.handleState(notebook, state);
1136 }
1137 NotebookActions.hideAllCode = hideAllCode;
1138 /**
1139 * Show the code on all code cells.
1140 *
1141 * @param widget - The target notebook widget.
1142 */
1143 function showAllCode(notebook) {
1144 if (!notebook.model || !notebook.activeCell) {
1145 return;
1146 }
1147 const state = Private.getState(notebook);
1148 notebook.widgets.forEach(cell => {
1149 if (cell.model.type === 'code') {
1150 cell.inputHidden = false;
1151 }
1152 });
1153 Private.handleState(notebook, state);
1154 }
1155 NotebookActions.showAllCode = showAllCode;
1156 /**
1157 * Hide the output on selected code cells.
1158 *
1159 * @param notebook - The target notebook widget.
1160 */
1161 function hideOutput(notebook) {
1162 if (!notebook.model || !notebook.activeCell) {
1163 return;
1164 }
1165 const state = Private.getState(notebook);
1166 notebook.widgets.forEach(cell => {
1167 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1168 cell.outputHidden = true;
1169 }
1170 });
1171 Private.handleState(notebook, state, true);
1172 }
1173 NotebookActions.hideOutput = hideOutput;
1174 /**
1175 * Show the output on selected code cells.
1176 *
1177 * @param notebook - The target notebook widget.
1178 */
1179 function showOutput(notebook) {
1180 if (!notebook.model || !notebook.activeCell) {
1181 return;
1182 }
1183 const state = Private.getState(notebook);
1184 notebook.widgets.forEach(cell => {
1185 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1186 cell.outputHidden = false;
1187 }
1188 });
1189 Private.handleState(notebook, state);
1190 }
1191 NotebookActions.showOutput = showOutput;
1192 /**
1193 * Hide the output on all code cells.
1194 *
1195 * @param notebook - The target notebook widget.
1196 */
1197 function hideAllOutputs(notebook) {
1198 if (!notebook.model || !notebook.activeCell) {
1199 return;
1200 }
1201 const state = Private.getState(notebook);
1202 notebook.widgets.forEach(cell => {
1203 if (cell.model.type === 'code') {
1204 cell.outputHidden = true;
1205 }
1206 });
1207 Private.handleState(notebook, state, true);
1208 }
1209 NotebookActions.hideAllOutputs = hideAllOutputs;
1210 /**
1211 * Render side-by-side.
1212 *
1213 * @param notebook - The target notebook widget.
1214 */
1215 function renderSideBySide(notebook) {
1216 notebook.renderingLayout = 'side-by-side';
1217 }
1218 NotebookActions.renderSideBySide = renderSideBySide;
1219 /**
1220 * Render not side-by-side.
1221 *
1222 * @param notebook - The target notebook widget.
1223 */
1224 function renderDefault(notebook) {
1225 notebook.renderingLayout = 'default';
1226 }
1227 NotebookActions.renderDefault = renderDefault;
1228 /**
1229 * Show the output on all code cells.
1230 *
1231 * @param notebook - The target notebook widget.
1232 */
1233 function showAllOutputs(notebook) {
1234 if (!notebook.model || !notebook.activeCell) {
1235 return;
1236 }
1237 const state = Private.getState(notebook);
1238 notebook.widgets.forEach(cell => {
1239 if (cell.model.type === 'code') {
1240 cell.outputHidden = false;
1241 }
1242 });
1243 Private.handleState(notebook, state);
1244 }
1245 NotebookActions.showAllOutputs = showAllOutputs;
1246 /**
1247 * Enable output scrolling for all selected cells.
1248 *
1249 * @param notebook - The target notebook widget.
1250 */
1251 function enableOutputScrolling(notebook) {
1252 if (!notebook.model || !notebook.activeCell) {
1253 return;
1254 }
1255 const state = Private.getState(notebook);
1256 notebook.widgets.forEach(cell => {
1257 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1258 cell.outputsScrolled = true;
1259 }
1260 });
1261 Private.handleState(notebook, state, true);
1262 }
1263 NotebookActions.enableOutputScrolling = enableOutputScrolling;
1264 /**
1265 * Disable output scrolling for all selected cells.
1266 *
1267 * @param notebook - The target notebook widget.
1268 */
1269 function disableOutputScrolling(notebook) {
1270 if (!notebook.model || !notebook.activeCell) {
1271 return;
1272 }
1273 const state = Private.getState(notebook);
1274 notebook.widgets.forEach(cell => {
1275 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1276 cell.outputsScrolled = false;
1277 }
1278 });
1279 Private.handleState(notebook, state);
1280 }
1281 NotebookActions.disableOutputScrolling = disableOutputScrolling;
1282 /**
1283 * Go to the last cell that is run or current if it is running.
1284 *
1285 * Note: This requires execution timing to be toggled on or this will have
1286 * no effect.
1287 *
1288 * @param notebook - The target notebook widget.
1289 */
1290 function selectLastRunCell(notebook) {
1291 let latestTime = null;
1292 let latestCellIdx = null;
1293 notebook.widgets.forEach((cell, cellIndx) => {
1294 if (cell.model.type === 'code') {
1295 const execution = cell.model.metadata.get('execution');
1296 if (execution &&
1297 JSONExt.isObject(execution) &&
1298 execution['iopub.status.busy'] !== undefined) {
1299 // The busy status is used as soon as a request is received:
1300 // https://jupyter-client.readthedocs.io/en/stable/messaging.html
1301 const timestamp = execution['iopub.status.busy'].toString();
1302 if (timestamp) {
1303 const startTime = new Date(timestamp);
1304 if (!latestTime || startTime >= latestTime) {
1305 latestTime = startTime;
1306 latestCellIdx = cellIndx;
1307 }
1308 }
1309 }
1310 }
1311 });
1312 if (latestCellIdx !== null) {
1313 notebook.activeCellIndex = latestCellIdx;
1314 }
1315 }
1316 NotebookActions.selectLastRunCell = selectLastRunCell;
1317 /**
1318 * Set the markdown header level.
1319 *
1320 * @param notebook - The target notebook widget.
1321 *
1322 * @param level - The header level.
1323 *
1324 * #### Notes
1325 * All selected cells will be switched to markdown.
1326 * The level will be clamped between 1 and 6.
1327 * If there is an existing header, it will be replaced.
1328 * There will always be one blank space after the header.
1329 * The cells will be unrendered.
1330 */
1331 function setMarkdownHeader(notebook, level) {
1332 if (!notebook.model || !notebook.activeCell) {
1333 return;
1334 }
1335 const state = Private.getState(notebook);
1336 const cells = notebook.model.cells;
1337 level = Math.min(Math.max(level, 1), 6);
1338 notebook.widgets.forEach((child, index) => {
1339 if (notebook.isSelectedOrActive(child)) {
1340 Private.setMarkdownHeader(cells.get(index), level);
1341 }
1342 });
1343 Private.changeCellType(notebook, 'markdown');
1344 Private.handleState(notebook, state);
1345 }
1346 NotebookActions.setMarkdownHeader = setMarkdownHeader;
1347 /**
1348 * Collapse all cells in given notebook.
1349 *
1350 * @param notebook - The target notebook widget.
1351 */
1352 function collapseAll(notebook) {
1353 for (const cell of notebook.widgets) {
1354 if (NotebookActions.getHeadingInfo(cell).isHeading) {
1355 NotebookActions.setHeadingCollapse(cell, true, notebook);
1356 NotebookActions.setCellCollapse(cell, true);
1357 }
1358 }
1359 }
1360 NotebookActions.collapseAll = collapseAll;
1361 /**
1362 * Un-collapse all cells in given notebook.
1363 *
1364 * @param notebook - The target notebook widget.
1365 */
1366 function expandAllHeadings(notebook) {
1367 for (const cell of notebook.widgets) {
1368 if (NotebookActions.getHeadingInfo(cell).isHeading) {
1369 NotebookActions.setHeadingCollapse(cell, false, notebook);
1370 // similar to collapseAll.
1371 NotebookActions.setCellCollapse(cell, false);
1372 }
1373 }
1374 }
1375 NotebookActions.expandAllHeadings = expandAllHeadings;
1376 function findNearestParentHeader(cell, notebook) {
1377 const index = findIndex(notebook.widgets, (possibleCell, index) => {
1378 return cell.model.id === possibleCell.model.id;
1379 });
1380 if (index === -1) {
1381 return;
1382 }
1383 // Finds the nearest header above the given cell. If the cell is a header itself, it does not return itself;
1384 // this can be checked directly by calling functions.
1385 if (index >= notebook.widgets.length) {
1386 return;
1387 }
1388 let childHeaderInfo = getHeadingInfo(notebook.widgets[index]);
1389 for (let cellN = index - 1; cellN >= 0; cellN--) {
1390 if (cellN < notebook.widgets.length) {
1391 let hInfo = getHeadingInfo(notebook.widgets[cellN]);
1392 if (hInfo.isHeading &&
1393 hInfo.headingLevel < childHeaderInfo.headingLevel) {
1394 return notebook.widgets[cellN];
1395 }
1396 }
1397 }
1398 // else no parent header found.
1399 return;
1400 }
1401 /**
1402 * Finds the "parent" heading of the given cell and expands.
1403 * Used for the case that a cell becomes active that is within a collapsed heading.
1404 * @param cell - "Child" cell that has become the active cell
1405 * @param notebook - The target notebook widget.
1406 */
1407 function expandParent(cell, notebook) {
1408 let nearestParentCell = findNearestParentHeader(cell, notebook);
1409 if (!nearestParentCell) {
1410 return;
1411 }
1412 if (!getHeadingInfo(nearestParentCell).collapsed &&
1413 !nearestParentCell.isHidden) {
1414 return;
1415 }
1416 if (nearestParentCell.isHidden) {
1417 expandParent(nearestParentCell, notebook);
1418 }
1419 if (getHeadingInfo(nearestParentCell).collapsed) {
1420 setHeadingCollapse(nearestParentCell, false, notebook);
1421 }
1422 }
1423 NotebookActions.expandParent = expandParent;
1424 /**
1425 * Finds the next heading that isn't a child of the given markdown heading.
1426 * @param cell - "Child" cell that has become the active cell
1427 * @param notebook - The target notebook widget.
1428 */
1429 function findNextParentHeading(cell, notebook) {
1430 let index = findIndex(notebook.widgets, (possibleCell, index) => {
1431 return cell.model.id === possibleCell.model.id;
1432 });
1433 if (index === -1) {
1434 return -1;
1435 }
1436 let childHeaderInfo = getHeadingInfo(cell);
1437 for (index = index + 1; index < notebook.widgets.length; index++) {
1438 let hInfo = getHeadingInfo(notebook.widgets[index]);
1439 if (hInfo.isHeading &&
1440 hInfo.headingLevel <= childHeaderInfo.headingLevel) {
1441 return index;
1442 }
1443 }
1444 // else no parent header found. return the index of the last cell
1445 return notebook.widgets.length;
1446 }
1447 NotebookActions.findNextParentHeading = findNextParentHeading;
1448 /**
1449 * Set the given cell and ** all "child" cells **
1450 * to the given collapse / expand if cell is
1451 * a markdown header.
1452 *
1453 * @param cell - The cell
1454 * @param collapsing - Whether to collapse or expand the cell
1455 * @param notebook - The target notebook widget.
1456 */
1457 function setHeadingCollapse(cell, collapsing, notebook) {
1458 const which = findIndex(notebook.widgets, (possibleCell, index) => {
1459 return cell.model.id === possibleCell.model.id;
1460 });
1461 if (which === -1) {
1462 return -1;
1463 }
1464 if (!notebook.widgets.length) {
1465 return which + 1;
1466 }
1467 let selectedHeadingInfo = NotebookActions.getHeadingInfo(cell);
1468 if (cell.isHidden ||
1469 !(cell instanceof MarkdownCell) ||
1470 !selectedHeadingInfo.isHeading) {
1471 // otherwise collapsing and uncollapsing already hidden stuff can
1472 // cause some funny looking bugs.
1473 return which + 1;
1474 }
1475 let localCollapsed = false;
1476 let localCollapsedLevel = 0;
1477 // iterate through all cells after the active cell.
1478 let cellNum;
1479 for (cellNum = which + 1; cellNum < notebook.widgets.length; cellNum++) {
1480 let subCell = notebook.widgets[cellNum];
1481 let subCellHeadingInfo = NotebookActions.getHeadingInfo(subCell);
1482 if (subCellHeadingInfo.isHeading &&
1483 subCellHeadingInfo.headingLevel <= selectedHeadingInfo.headingLevel) {
1484 // then reached an equivalent or higher heading level than the
1485 // original the end of the collapse.
1486 cellNum -= 1;
1487 break;
1488 }
1489 if (localCollapsed &&
1490 subCellHeadingInfo.isHeading &&
1491 subCellHeadingInfo.headingLevel <= localCollapsedLevel) {
1492 // then reached the end of the local collapsed, so unset NotebookActions.
1493 localCollapsed = false;
1494 }
1495 if (collapsing || localCollapsed) {
1496 // then no extra handling is needed for further locally collapsed
1497 // headings.
1498 subCell.setHidden(true);
1499 continue;
1500 }
1501 if (subCellHeadingInfo.collapsed && subCellHeadingInfo.isHeading) {
1502 localCollapsed = true;
1503 localCollapsedLevel = subCellHeadingInfo.headingLevel;
1504 // but don't collapse the locally collapsed heading, so continue to
1505 // expand the heading. This will get noticed in the next round.
1506 }
1507 subCell.setHidden(false);
1508 }
1509 if (cellNum === notebook.widgets.length) {
1510 cell.numberChildNodes = cellNum - which - 1;
1511 }
1512 else {
1513 cell.numberChildNodes = cellNum - which;
1514 }
1515 NotebookActions.setCellCollapse(cell, collapsing);
1516 return cellNum + 1;
1517 }
1518 NotebookActions.setHeadingCollapse = setHeadingCollapse;
1519 /**
1520 * Toggles the collapse state of the active cell of the given notebook
1521 * and ** all of its "child" cells ** if the cell is a heading.
1522 *
1523 * @param notebook - The target notebook widget.
1524 */
1525 function toggleCurrentHeadingCollapse(notebook) {
1526 if (!notebook.activeCell || notebook.activeCellIndex === undefined) {
1527 return;
1528 }
1529 let headingInfo = NotebookActions.getHeadingInfo(notebook.activeCell);
1530 if (headingInfo.isHeading) {
1531 // Then toggle!
1532 NotebookActions.setHeadingCollapse(notebook.activeCell, !headingInfo.collapsed, notebook);
1533 }
1534 ElementExt.scrollIntoViewIfNeeded(notebook.node, notebook.activeCell.node);
1535 }
1536 NotebookActions.toggleCurrentHeadingCollapse = toggleCurrentHeadingCollapse;
1537 /**
1538 * If cell is a markdown heading, sets the headingCollapsed field,
1539 * and otherwise hides the cell.
1540 *
1541 * @param cell - The cell to collapse / expand
1542 * @param collapsing - Whether to collapse or expand the given cell
1543 */
1544 function setCellCollapse(cell, collapsing) {
1545 if (cell instanceof MarkdownCell) {
1546 cell.headingCollapsed = collapsing;
1547 }
1548 else {
1549 cell.setHidden(collapsing);
1550 }
1551 }
1552 NotebookActions.setCellCollapse = setCellCollapse;
1553 /**
1554 * If given cell is a markdown heading, returns the heading level.
1555 * If given cell is not markdown, returns 7 (there are only 6 levels of markdown headings)
1556 *
1557 * @param cell - The target cell widget.
1558 */
1559 function getHeadingInfo(cell) {
1560 if (!(cell instanceof MarkdownCell)) {
1561 return { isHeading: false, headingLevel: 7 };
1562 }
1563 let level = cell.headingInfo.level;
1564 let collapsed = cell.headingCollapsed;
1565 return { isHeading: level > 0, headingLevel: level, collapsed: collapsed };
1566 }
1567 NotebookActions.getHeadingInfo = getHeadingInfo;
1568 /**
1569 * Trust the notebook after prompting the user.
1570 *
1571 * @param notebook - The target notebook widget.
1572 *
1573 * @returns a promise that resolves when the transaction is finished.
1574 *
1575 * #### Notes
1576 * No dialog will be presented if the notebook is already trusted.
1577 */
1578 function trust(notebook, translator) {
1579 translator = translator || nullTranslator;
1580 const trans = translator.load('jupyterlab');
1581 if (!notebook.model) {
1582 return Promise.resolve();
1583 }
1584 // Do nothing if already trusted.
1585 const cells = toArray(notebook.model.cells);
1586 const trusted = cells.every(cell => cell.trusted);
1587 // FIXME
1588 const trustMessage = (React.createElement("p", null,
1589 trans.__('A trusted Jupyter notebook may execute hidden malicious code when you open it.'),
1590 React.createElement("br", null),
1591 trans.__('Selecting trust will re-render this notebook in a trusted state.'),
1592 React.createElement("br", null),
1593 trans.__('For more information, see'),
1594 ' ',
1595 React.createElement("a", { href: "https://jupyter-server.readthedocs.io/en/stable/operators/security.html", target: "_blank", rel: "noopener noreferrer" }, trans.__('the Jupyter security documentation'))));
1596 if (trusted) {
1597 return showDialog({
1598 body: trans.__('Notebook is already trusted'),
1599 buttons: [Dialog.okButton({ label: trans.__('Ok') })]
1600 }).then(() => undefined);
1601 }
1602 return showDialog({
1603 body: trustMessage,
1604 title: trans.__('Trust this notebook?'),
1605 buttons: [
1606 Dialog.cancelButton({ label: trans.__('Cancel') }),
1607 Dialog.warnButton({ label: trans.__('Ok') })
1608 ] // FIXME?
1609 }).then(result => {
1610 if (result.button.accept) {
1611 cells.forEach(cell => {
1612 cell.trusted = true;
1613 });
1614 }
1615 });
1616 }
1617 NotebookActions.trust = trust;
1618})(NotebookActions || (NotebookActions = {}));
1619/**
1620 * A namespace for private data.
1621 */
1622var Private;
1623(function (Private) {
1624 /**
1625 * A signal that emits whenever a cell completes execution.
1626 */
1627 Private.executed = new Signal({});
1628 /**
1629 * A signal that emits whenever a cell execution is scheduled.
1630 */
1631 Private.executionScheduled = new Signal({});
1632 /**
1633 * A signal that emits when one notebook's cells are all executed.
1634 */
1635 Private.selectionExecuted = new Signal({});
1636 function isNotebookRendered(notebook) {
1637 const translator = notebook.translator;
1638 const trans = translator.load('jupyterlab');
1639 if (notebook.remainingCellToRenderCount !== 0) {
1640 showDialog({
1641 body: trans.__(`Notebook is still rendering and has for now (%1) remaining cells to render.
1642
1643Please wait for the complete rendering before invoking that action.`, notebook.remainingCellToRenderCount),
1644 buttons: [Dialog.okButton({ label: trans.__('Ok') })]
1645 }).catch(reason => {
1646 console.error('An error occurred when displaying notebook rendering warning', reason);
1647 });
1648 return false;
1649 }
1650 return true;
1651 }
1652 Private.isNotebookRendered = isNotebookRendered;
1653 /**
1654 * Get the state of a widget before running an action.
1655 */
1656 function getState(notebook) {
1657 return {
1658 wasFocused: notebook.node.contains(document.activeElement),
1659 activeCell: notebook.activeCell
1660 };
1661 }
1662 Private.getState = getState;
1663 /**
1664 * Handle the state of a widget after running an action.
1665 */
1666 function handleState(notebook, state, scrollIfNeeded = false) {
1667 const { activeCell, node } = notebook;
1668 if (state.wasFocused || notebook.mode === 'edit') {
1669 notebook.activate();
1670 }
1671 if (scrollIfNeeded && activeCell) {
1672 ElementExt.scrollIntoViewIfNeeded(node, activeCell.node);
1673 }
1674 }
1675 Private.handleState = handleState;
1676 /**
1677 * Handle the state of a widget after running a run action.
1678 */
1679 function handleRunState(notebook, state, scroll = false) {
1680 if (state.wasFocused || notebook.mode === 'edit') {
1681 notebook.activate();
1682 }
1683 if (scroll && state.activeCell) {
1684 // Scroll to the top of the previous active cell output.
1685 const rect = state.activeCell.inputArea.node.getBoundingClientRect();
1686 notebook.scrollToPosition(rect.bottom, 45);
1687 }
1688 }
1689 Private.handleRunState = handleRunState;
1690 /**
1691 * Clone a cell model.
1692 */
1693 function cloneCell(model, cell) {
1694 switch (cell.type) {
1695 case 'code':
1696 // TODO why isn't modeldb or id passed here?
1697 return model.contentFactory.createCodeCell({ cell: cell.toJSON() });
1698 case 'markdown':
1699 // TODO why isn't modeldb or id passed here?
1700 return model.contentFactory.createMarkdownCell({ cell: cell.toJSON() });
1701 default:
1702 // TODO why isn't modeldb or id passed here?
1703 return model.contentFactory.createRawCell({ cell: cell.toJSON() });
1704 }
1705 }
1706 Private.cloneCell = cloneCell;
1707 /**
1708 * Run the selected cells.
1709 */
1710 function runSelected(notebook, sessionContext) {
1711 notebook.mode = 'command';
1712 let lastIndex = notebook.activeCellIndex;
1713 const selected = notebook.widgets.filter((child, index) => {
1714 const active = notebook.isSelectedOrActive(child);
1715 if (active) {
1716 lastIndex = index;
1717 }
1718 return active;
1719 });
1720 notebook.activeCellIndex = lastIndex;
1721 notebook.deselectAll();
1722 return Promise.all(selected.map(child => runCell(notebook, child, sessionContext)))
1723 .then(results => {
1724 if (notebook.isDisposed) {
1725 return false;
1726 }
1727 Private.selectionExecuted.emit({
1728 notebook,
1729 lastCell: notebook.widgets[lastIndex]
1730 });
1731 // Post an update request.
1732 notebook.update();
1733 return results.every(result => result);
1734 })
1735 .catch(reason => {
1736 if (reason.message.startsWith('KernelReplyNotOK')) {
1737 selected.map(cell => {
1738 // Remove '*' prompt from cells that didn't execute
1739 if (cell.model.type === 'code' &&
1740 cell.model.executionCount == null) {
1741 cell.setPrompt('');
1742 }
1743 });
1744 }
1745 else {
1746 throw reason;
1747 }
1748 Private.selectionExecuted.emit({
1749 notebook,
1750 lastCell: notebook.widgets[lastIndex]
1751 });
1752 notebook.update();
1753 return false;
1754 });
1755 }
1756 Private.runSelected = runSelected;
1757 /**
1758 * Run a cell.
1759 */
1760 function runCell(notebook, cell, sessionContext, translator) {
1761 var _a, _b, _c;
1762 translator = translator || nullTranslator;
1763 const trans = translator.load('jupyterlab');
1764 switch (cell.model.type) {
1765 case 'markdown':
1766 cell.rendered = true;
1767 cell.inputHidden = false;
1768 Private.executed.emit({ notebook, cell, success: true });
1769 break;
1770 case 'code':
1771 if (sessionContext) {
1772 if (sessionContext.isTerminating) {
1773 void showDialog({
1774 title: trans.__('Kernel Terminating'),
1775 body: trans.__('The kernel for %1 appears to be terminating. You can not run any cell for now.', (_a = sessionContext.session) === null || _a === void 0 ? void 0 : _a.path),
1776 buttons: [Dialog.okButton({ label: trans.__('Ok') })]
1777 });
1778 break;
1779 }
1780 if (sessionContext.pendingInput) {
1781 void showDialog({
1782 title: trans.__('Cell not executed due to pending input'),
1783 body: trans.__('The cell has not been executed to avoid kernel deadlock as there is another pending input! Submit your pending input and try again.'),
1784 buttons: [Dialog.okButton({ label: trans.__('Ok') })]
1785 });
1786 return Promise.resolve(false);
1787 }
1788 if (sessionContext.hasNoKernel) {
1789 void sessionContextDialogs.selectKernel(sessionContext);
1790 return Promise.resolve(false);
1791 }
1792 const deletedCells = (_c = (_b = notebook.model) === null || _b === void 0 ? void 0 : _b.deletedCells) !== null && _c !== void 0 ? _c : [];
1793 Private.executionScheduled.emit({ notebook, cell });
1794 return CodeCell.execute(cell, sessionContext, {
1795 deletedCells,
1796 recordTiming: notebook.notebookConfig.recordTiming
1797 })
1798 .then(reply => {
1799 deletedCells.splice(0, deletedCells.length);
1800 if (cell.isDisposed) {
1801 return false;
1802 }
1803 if (!reply) {
1804 return true;
1805 }
1806 if (reply.content.status === 'ok') {
1807 const content = reply.content;
1808 if (content.payload && content.payload.length) {
1809 handlePayload(content, notebook, cell);
1810 }
1811 return true;
1812 }
1813 else {
1814 throw new KernelError(reply.content);
1815 }
1816 })
1817 .catch(reason => {
1818 if (cell.isDisposed || reason.message.startsWith('Canceled')) {
1819 return false;
1820 }
1821 Private.executed.emit({ notebook, cell, success: false, error: reason });
1822 throw reason;
1823 })
1824 .then(ran => {
1825 if (ran) {
1826 Private.executed.emit({ notebook, cell, success: true });
1827 }
1828 return ran;
1829 });
1830 }
1831 cell.model.clearExecution();
1832 break;
1833 default:
1834 break;
1835 }
1836 return Promise.resolve(true);
1837 }
1838 /**
1839 * Handle payloads from an execute reply.
1840 *
1841 * #### Notes
1842 * Payloads are deprecated and there are no official interfaces for them in
1843 * the kernel type definitions.
1844 * See [Payloads (DEPRECATED)](https://jupyter-client.readthedocs.io/en/latest/messaging.html#payloads-deprecated).
1845 */
1846 function handlePayload(content, notebook, cell) {
1847 var _a;
1848 const setNextInput = (_a = content.payload) === null || _a === void 0 ? void 0 : _a.filter(i => {
1849 return i.source === 'set_next_input';
1850 })[0];
1851 if (!setNextInput) {
1852 return;
1853 }
1854 const text = setNextInput.text;
1855 const replace = setNextInput.replace;
1856 if (replace) {
1857 cell.model.value.text = text;
1858 return;
1859 }
1860 // Create a new code cell and add as the next cell.
1861 const newCell = notebook.model.contentFactory.createCodeCell({});
1862 const cells = notebook.model.cells;
1863 const index = ArrayExt.firstIndexOf(toArray(cells), cell.model);
1864 newCell.value.text = text;
1865 if (index === -1) {
1866 cells.push(newCell);
1867 }
1868 else {
1869 cells.insert(index + 1, newCell);
1870 }
1871 }
1872 /**
1873 * Get the selected cell(s) without affecting the clipboard.
1874 *
1875 * @param notebook - The target notebook widget.
1876 *
1877 * @returns A list of 0 or more selected cells
1878 */
1879 function selectedCells(notebook) {
1880 return notebook.widgets
1881 .filter(cell => notebook.isSelectedOrActive(cell))
1882 .map(cell => cell.model.toJSON())
1883 .map(cellJSON => {
1884 if (cellJSON.metadata.deletable !== undefined) {
1885 delete cellJSON.metadata.deletable;
1886 }
1887 return cellJSON;
1888 });
1889 }
1890 Private.selectedCells = selectedCells;
1891 /**
1892 * Copy or cut the selected cell data to the application clipboard.
1893 *
1894 * @param notebook - The target notebook widget.
1895 *
1896 * @param cut - True if the cells should be cut, false if they should be copied.
1897 */
1898 function copyOrCut(notebook, cut) {
1899 if (!notebook.model || !notebook.activeCell) {
1900 return;
1901 }
1902 const state = getState(notebook);
1903 const clipboard = Clipboard.getInstance();
1904 notebook.mode = 'command';
1905 clipboard.clear();
1906 const data = Private.selectedCells(notebook);
1907 clipboard.setData(JUPYTER_CELL_MIME, data);
1908 if (cut) {
1909 deleteCells(notebook);
1910 }
1911 else {
1912 notebook.deselectAll();
1913 }
1914 if (cut) {
1915 notebook.lastClipboardInteraction = 'cut';
1916 }
1917 else {
1918 notebook.lastClipboardInteraction = 'copy';
1919 }
1920 handleState(notebook, state);
1921 }
1922 Private.copyOrCut = copyOrCut;
1923 /**
1924 * Change the selected cell type(s).
1925 *
1926 * @param notebook - The target notebook widget.
1927 *
1928 * @param value - The target cell type.
1929 *
1930 * #### Notes
1931 * It should preserve the widget mode.
1932 * This action can be undone.
1933 * The existing selection will be cleared.
1934 * Any cells converted to markdown will be unrendered.
1935 */
1936 function changeCellType(notebook, value) {
1937 const model = notebook.model;
1938 const cells = model.cells;
1939 cells.beginCompoundOperation();
1940 notebook.widgets.forEach((child, index) => {
1941 if (!notebook.isSelectedOrActive(child)) {
1942 return;
1943 }
1944 if (child.model.type !== value) {
1945 const cell = child.model.toJSON();
1946 let newCell;
1947 switch (value) {
1948 case 'code':
1949 newCell = model.contentFactory.createCodeCell({ cell });
1950 break;
1951 case 'markdown':
1952 newCell = model.contentFactory.createMarkdownCell({ cell });
1953 if (child.model.type === 'code') {
1954 newCell.trusted = false;
1955 }
1956 break;
1957 default:
1958 newCell = model.contentFactory.createRawCell({ cell });
1959 if (child.model.type === 'code') {
1960 newCell.trusted = false;
1961 }
1962 }
1963 cells.set(index, newCell);
1964 }
1965 if (value === 'markdown') {
1966 // Fetch the new widget and unrender it.
1967 child = notebook.widgets[index];
1968 child.rendered = false;
1969 }
1970 });
1971 cells.endCompoundOperation();
1972 notebook.deselectAll();
1973 }
1974 Private.changeCellType = changeCellType;
1975 /**
1976 * Delete the selected cells.
1977 *
1978 * @param notebook - The target notebook widget.
1979 *
1980 * #### Notes
1981 * The cell after the last selected cell will be activated.
1982 * If the last cell is deleted, then the previous one will be activated.
1983 * It will add a code cell if all cells are deleted.
1984 * This action can be undone.
1985 */
1986 function deleteCells(notebook) {
1987 const model = notebook.model;
1988 const cells = model.cells;
1989 const toDelete = [];
1990 notebook.mode = 'command';
1991 // Find the cells to delete.
1992 notebook.widgets.forEach((child, index) => {
1993 const deletable = child.model.metadata.get('deletable') !== false;
1994 if (notebook.isSelectedOrActive(child) && deletable) {
1995 toDelete.push(index);
1996 model.deletedCells.push(child.model.id);
1997 }
1998 });
1999 // If cells are not deletable, we may not have anything to delete.
2000 if (toDelete.length > 0) {
2001 // Delete the cells as one undo event.
2002 cells.beginCompoundOperation();
2003 // Delete cells in reverse order to maintain the correct indices.
2004 toDelete.reverse().forEach(index => {
2005 cells.remove(index);
2006 });
2007 // Add a new cell if the notebook is empty. This is done
2008 // within the compound operation to make the deletion of
2009 // a notebook's last cell undoable.
2010 if (!cells.length) {
2011 cells.push(model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {}));
2012 }
2013 cells.endCompoundOperation();
2014 // Select the *first* interior cell not deleted or the cell
2015 // *after* the last selected cell.
2016 // Note: The activeCellIndex is clamped to the available cells,
2017 // so if the last cell is deleted the previous cell will be activated.
2018 // The *first* index is the index of the last cell in the initial
2019 // toDelete list due to the `reverse` operation above.
2020 notebook.activeCellIndex = toDelete[0] - toDelete.length + 1;
2021 }
2022 // Deselect any remaining, undeletable cells. Do this even if we don't
2023 // delete anything so that users are aware *something* happened.
2024 notebook.deselectAll();
2025 }
2026 Private.deleteCells = deleteCells;
2027 /**
2028 * Set the markdown header level of a cell.
2029 */
2030 function setMarkdownHeader(cell, level) {
2031 // Remove existing header or leading white space.
2032 let source = cell.value.text;
2033 const regex = /^(#+\s*)|^(\s*)/;
2034 const newHeader = Array(level + 1).join('#') + ' ';
2035 const matches = regex.exec(source);
2036 if (matches) {
2037 source = source.slice(matches[0].length);
2038 }
2039 cell.value.text = newHeader + source;
2040 }
2041 Private.setMarkdownHeader = setMarkdownHeader;
2042})(Private || (Private = {}));
2043//# sourceMappingURL=actions.js.map
\No newline at end of file