UNPKG

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