UNPKG

91.2 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, isMarkdownCellModel, isRawCellModel, MarkdownCell } from '@jupyterlab/cells';
5import { signalToPromise } from '@jupyterlab/coreutils';
6import { nullTranslator } from '@jupyterlab/translation';
7import { every, findIndex } from '@lumino/algorithm';
8import { JSONExt } from '@lumino/coreutils';
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 when one notebook's cells are all executed.
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 // We force the notebook back in edit mode as splitting a cell
99 // requires using the cursor position within a cell (aka it was recently in edit mode)
100 // However the focus may be stolen if the action is triggered
101 // from the menu entry; switching the notebook in command mode.
102 notebook.mode = 'edit';
103 notebook.deselectAll();
104 const nbModel = notebook.model;
105 const index = notebook.activeCellIndex;
106 const child = notebook.widgets[index];
107 const editor = child.editor;
108 if (!editor) {
109 // TODO
110 return;
111 }
112 const selections = editor.getSelections();
113 const orig = child.model.sharedModel.getSource();
114 const offsets = [0];
115 let start = -1;
116 let end = -1;
117 for (let i = 0; i < selections.length; i++) {
118 // append start and end to handle selections
119 // cursors will have same start and end
120 start = editor.getOffsetAt(selections[i].start);
121 end = editor.getOffsetAt(selections[i].end);
122 if (start < end) {
123 offsets.push(start);
124 offsets.push(end);
125 }
126 else if (end < start) {
127 offsets.push(end);
128 offsets.push(start);
129 }
130 else {
131 offsets.push(start);
132 }
133 }
134 offsets.push(orig.length);
135 const clones = offsets.slice(0, -1).map((offset, offsetIdx) => {
136 const { cell_type, metadata } = child.model.sharedModel.toJSON();
137 return {
138 cell_type,
139 metadata,
140 source: orig
141 .slice(offset, offsets[offsetIdx + 1])
142 .replace(/^\n+/, '')
143 .replace(/\n+$/, '')
144 };
145 });
146 nbModel.sharedModel.transact(() => {
147 nbModel.sharedModel.deleteCell(index);
148 nbModel.sharedModel.insertCells(index, clones);
149 });
150 // If there is a selection the selected cell will be activated
151 const activeCellDelta = start !== end ? 2 : 1;
152 notebook.activeCellIndex = index + clones.length - activeCellDelta;
153 notebook
154 .scrollToItem(notebook.activeCellIndex)
155 .then(() => {
156 var _a;
157 (_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.editor.focus();
158 })
159 .catch(reason => {
160 // no-op
161 });
162 Private.handleState(notebook, state);
163 }
164 NotebookActions.splitCell = splitCell;
165 /**
166 * Merge the selected cells.
167 *
168 * @param notebook - The target notebook widget.
169 *
170 * @param mergeAbove - If only one cell is selected, indicates whether to merge it
171 * with the cell above (true) or below (false, default).
172 *
173 * #### Notes
174 * The widget mode will be preserved.
175 * If only one cell is selected and `mergeAbove` is true, the above cell will be selected.
176 * If only one cell is selected and `mergeAbove` is false, the below cell will be selected.
177 * If the active cell is a code cell, its outputs will be cleared.
178 * This action can be undone.
179 * The final cell will have the same type as the active cell.
180 * If the active cell is a markdown cell, it will be unrendered.
181 */
182 function mergeCells(notebook, mergeAbove = false) {
183 if (!notebook.model || !notebook.activeCell) {
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.sharedModel.getSource());
198 if (index !== active) {
199 toDelete.push(index);
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.sharedModel.getSource());
221 toDelete.push(active - 1);
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.sharedModel.getSource());
231 toDelete.push(active + 1);
232 }
233 }
234 notebook.deselectAll();
235 const primaryModel = primary.model.sharedModel;
236 const { cell_type, metadata } = primaryModel.toJSON();
237 if (primaryModel.cell_type === 'code') {
238 // We can trust this cell because the outputs will be removed.
239 metadata.trusted = true;
240 }
241 const newModel = {
242 cell_type,
243 metadata,
244 source: toMerge.join('\n\n'),
245 attachments: primaryModel.cell_type === 'markdown' ||
246 primaryModel.cell_type === 'raw'
247 ? attachments
248 : undefined
249 };
250 // Make the changes while preserving history.
251 model.sharedModel.transact(() => {
252 model.sharedModel.deleteCell(active);
253 model.sharedModel.insertCell(active, newModel);
254 toDelete
255 .sort((a, b) => b - a)
256 .forEach(index => {
257 model.sharedModel.deleteCell(index);
258 });
259 });
260 // If the original cell is a markdown cell, make sure
261 // the new cell is unrendered.
262 if (primary instanceof MarkdownCell) {
263 notebook.activeCell.rendered = false;
264 }
265 Private.handleState(notebook, state);
266 }
267 NotebookActions.mergeCells = mergeCells;
268 /**
269 * Delete the selected cells.
270 *
271 * @param notebook - The target notebook widget.
272 *
273 * #### Notes
274 * The cell after the last selected cell will be activated.
275 * It will add a code cell if all cells are deleted.
276 * This action can be undone.
277 */
278 function deleteCells(notebook) {
279 if (!notebook.model || !notebook.activeCell) {
280 return;
281 }
282 const state = Private.getState(notebook);
283 Private.deleteCells(notebook);
284 Private.handleState(notebook, state, true);
285 }
286 NotebookActions.deleteCells = deleteCells;
287 /**
288 * Insert a new code cell above the active cell or in index 0 if the notebook is empty.
289 *
290 * @param notebook - The target notebook widget.
291 *
292 * #### Notes
293 * The widget mode will be preserved.
294 * This action can be undone.
295 * The existing selection will be cleared.
296 * The new cell will the active cell.
297 */
298 function insertAbove(notebook) {
299 if (!notebook.model) {
300 return;
301 }
302 const state = Private.getState(notebook);
303 const model = notebook.model;
304 const newIndex = notebook.activeCell ? notebook.activeCellIndex : 0;
305 model.sharedModel.insertCell(newIndex, {
306 cell_type: notebook.notebookConfig.defaultCell,
307 metadata: notebook.notebookConfig.defaultCell === 'code'
308 ? {
309 // This is an empty cell created by user, thus is trusted
310 trusted: true
311 }
312 : {}
313 });
314 // Make the newly inserted cell active.
315 notebook.activeCellIndex = newIndex;
316 notebook.deselectAll();
317 Private.handleState(notebook, state, true);
318 }
319 NotebookActions.insertAbove = insertAbove;
320 /**
321 * Insert a new code cell below the active cell or in index 0 if the notebook is empty.
322 *
323 * @param notebook - The target notebook widget.
324 *
325 * #### Notes
326 * The widget mode will be preserved.
327 * This action can be undone.
328 * The existing selection will be cleared.
329 * The new cell will be the active cell.
330 */
331 function insertBelow(notebook) {
332 if (!notebook.model) {
333 return;
334 }
335 const state = Private.getState(notebook);
336 const model = notebook.model;
337 const newIndex = notebook.activeCell ? notebook.activeCellIndex + 1 : 0;
338 model.sharedModel.insertCell(newIndex, {
339 cell_type: notebook.notebookConfig.defaultCell,
340 metadata: notebook.notebookConfig.defaultCell === 'code'
341 ? {
342 // This is an empty cell created by user, thus is trusted
343 trusted: true
344 }
345 : {}
346 });
347 // Make the newly inserted cell active.
348 notebook.activeCellIndex = newIndex;
349 notebook.deselectAll();
350 Private.handleState(notebook, state, true);
351 }
352 NotebookActions.insertBelow = insertBelow;
353 function move(notebook, shift) {
354 if (!notebook.model || !notebook.activeCell) {
355 return;
356 }
357 const state = Private.getState(notebook);
358 const firstIndex = notebook.widgets.findIndex(w => notebook.isSelectedOrActive(w));
359 let lastIndex = notebook.widgets
360 .slice(firstIndex + 1)
361 .findIndex(w => !notebook.isSelectedOrActive(w));
362 if (lastIndex >= 0) {
363 lastIndex += firstIndex + 1;
364 }
365 else {
366 lastIndex = notebook.model.cells.length;
367 }
368 if (shift > 0) {
369 notebook.moveCell(firstIndex, lastIndex, lastIndex - firstIndex);
370 }
371 else {
372 notebook.moveCell(firstIndex, firstIndex + shift, lastIndex - firstIndex);
373 }
374 Private.handleState(notebook, state, true);
375 }
376 /**
377 * Move the selected cell(s) down.
378 *
379 * @param notebook = The target notebook widget.
380 */
381 function moveDown(notebook) {
382 move(notebook, 1);
383 }
384 NotebookActions.moveDown = moveDown;
385 /**
386 * Move the selected cell(s) up.
387 *
388 * @param notebook - The target notebook widget.
389 */
390 function moveUp(notebook) {
391 move(notebook, -1);
392 }
393 NotebookActions.moveUp = moveUp;
394 /**
395 * Change the selected cell type(s).
396 *
397 * @param notebook - The target notebook widget.
398 *
399 * @param value - The target cell type.
400 *
401 * #### Notes
402 * It should preserve the widget mode.
403 * This action can be undone.
404 * The existing selection will be cleared.
405 * Any cells converted to markdown will be unrendered.
406 */
407 function changeCellType(notebook, value) {
408 if (!notebook.model || !notebook.activeCell) {
409 return;
410 }
411 const state = Private.getState(notebook);
412 Private.changeCellType(notebook, value);
413 Private.handleState(notebook, state);
414 }
415 NotebookActions.changeCellType = changeCellType;
416 /**
417 * Run the selected cell(s).
418 *
419 * @param notebook - The target notebook widget.
420 * @param sessionContext - The client session object.
421 * @param sessionDialogs - The session dialogs.
422 * @param translator - The application translator.
423 *
424 * #### Notes
425 * The last selected cell will be activated, but not scrolled into view.
426 * The existing selection will be cleared.
427 * An execution error will prevent the remaining code cells from executing.
428 * All markdown cells will be rendered.
429 */
430 function run(notebook, sessionContext, sessionDialogs, translator) {
431 if (!notebook.model || !notebook.activeCell) {
432 return Promise.resolve(false);
433 }
434 const state = Private.getState(notebook);
435 const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
436 Private.handleRunState(notebook, state, false);
437 return promise;
438 }
439 NotebookActions.run = run;
440 /**
441 * Run the selected cell(s) and advance to the next cell.
442 *
443 * @param notebook - The target notebook widget.
444 * @param sessionContext - The client session object.
445 * @param sessionDialogs - The session dialogs.
446 * @param translator - The application translator.
447 *
448 * #### Notes
449 * The existing selection will be cleared.
450 * The cell after the last selected cell will be activated and scrolled into view.
451 * An execution error will prevent the remaining code cells from executing.
452 * All markdown cells will be rendered.
453 * If the last selected cell is the last cell, a new code cell
454 * will be created in `'edit'` mode. The new cell creation can be undone.
455 */
456 async function runAndAdvance(notebook, sessionContext, sessionDialogs, translator) {
457 var _a;
458 if (!notebook.model || !notebook.activeCell) {
459 return Promise.resolve(false);
460 }
461 const state = Private.getState(notebook);
462 const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
463 const model = notebook.model;
464 if (notebook.activeCellIndex === notebook.widgets.length - 1) {
465 // Do not use push here, as we want an widget insertion
466 // to make sure no placeholder widget is rendered.
467 model.sharedModel.insertCell(notebook.widgets.length, {
468 cell_type: notebook.notebookConfig.defaultCell,
469 metadata: notebook.notebookConfig.defaultCell === 'code'
470 ? {
471 // This is an empty cell created by user, thus is trusted
472 trusted: true
473 }
474 : {}
475 });
476 notebook.activeCellIndex++;
477 if (((_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.inViewport) === false) {
478 await signalToPromise(notebook.activeCell.inViewportChanged, 200).catch(() => {
479 // no-op
480 });
481 }
482 notebook.mode = 'edit';
483 }
484 else {
485 notebook.activeCellIndex++;
486 }
487 Private.handleState(notebook, state, true);
488 return promise;
489 }
490 NotebookActions.runAndAdvance = runAndAdvance;
491 /**
492 * Run the selected cell(s) and insert a new code cell.
493 *
494 * @param notebook - The target notebook widget.
495 * @param sessionContext - The client session object.
496 * @param sessionDialogs - The session dialogs.
497 * @param translator - The application translator.
498 *
499 * #### Notes
500 * An execution error will prevent the remaining code cells from executing.
501 * All markdown cells will be rendered.
502 * The widget mode will be set to `'edit'` after running.
503 * The existing selection will be cleared.
504 * The cell insert can be undone.
505 * The new cell will be scrolled into view.
506 */
507 async function runAndInsert(notebook, sessionContext, sessionDialogs, translator) {
508 var _a;
509 if (!notebook.model || !notebook.activeCell) {
510 return Promise.resolve(false);
511 }
512 const state = Private.getState(notebook);
513 const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
514 const model = notebook.model;
515 model.sharedModel.insertCell(notebook.activeCellIndex + 1, {
516 cell_type: notebook.notebookConfig.defaultCell,
517 metadata: notebook.notebookConfig.defaultCell === 'code'
518 ? {
519 // This is an empty cell created by user, thus is trusted
520 trusted: true
521 }
522 : {}
523 });
524 notebook.activeCellIndex++;
525 if (((_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.inViewport) === false) {
526 await signalToPromise(notebook.activeCell.inViewportChanged, 200).catch(() => {
527 // no-op
528 });
529 }
530 notebook.mode = 'edit';
531 Private.handleState(notebook, state, true);
532 return promise;
533 }
534 NotebookActions.runAndInsert = runAndInsert;
535 /**
536 * Run all of the cells in the notebook.
537 *
538 * @param notebook - The target notebook widget.
539 * @param sessionContext - The client session object.
540 * @param sessionDialogs - The session dialogs.
541 * @param translator - The application translator.
542 *
543 * #### Notes
544 * The existing selection will be cleared.
545 * An execution error will prevent the remaining code cells from executing.
546 * All markdown cells will be rendered.
547 * The last cell in the notebook will be activated and scrolled into view.
548 */
549 function runAll(notebook, sessionContext, sessionDialogs, translator) {
550 if (!notebook.model || !notebook.activeCell) {
551 return Promise.resolve(false);
552 }
553 const state = Private.getState(notebook);
554 notebook.widgets.forEach(child => {
555 notebook.select(child);
556 });
557 const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
558 Private.handleRunState(notebook, state, true);
559 return promise;
560 }
561 NotebookActions.runAll = runAll;
562 function renderAllMarkdown(notebook) {
563 if (!notebook.model || !notebook.activeCell) {
564 return Promise.resolve(false);
565 }
566 const previousIndex = notebook.activeCellIndex;
567 const state = Private.getState(notebook);
568 notebook.widgets.forEach((child, index) => {
569 if (child.model.type === 'markdown') {
570 notebook.select(child);
571 // This is to make sure that the activeCell
572 // does not get executed
573 notebook.activeCellIndex = index;
574 }
575 });
576 if (notebook.activeCell.model.type !== 'markdown') {
577 return Promise.resolve(true);
578 }
579 const promise = Private.runSelected(notebook);
580 notebook.activeCellIndex = previousIndex;
581 Private.handleRunState(notebook, state, true);
582 return promise;
583 }
584 NotebookActions.renderAllMarkdown = renderAllMarkdown;
585 /**
586 * Run all of the cells before the currently active cell (exclusive).
587 *
588 * @param notebook - The target notebook widget.
589 * @param sessionContext - The client session object.
590 * @param sessionDialogs - The session dialogs.
591 * @param translator - The application translator.
592 *
593 * #### Notes
594 * The existing selection will be cleared.
595 * An execution error will prevent the remaining code cells from executing.
596 * All markdown cells will be rendered.
597 * The currently active cell will remain selected.
598 */
599 function runAllAbove(notebook, sessionContext, sessionDialogs, translator) {
600 const { activeCell, activeCellIndex, model } = notebook;
601 if (!model || !activeCell || activeCellIndex < 1) {
602 return Promise.resolve(false);
603 }
604 const state = Private.getState(notebook);
605 notebook.activeCellIndex--;
606 notebook.deselectAll();
607 for (let i = 0; i < notebook.activeCellIndex; ++i) {
608 notebook.select(notebook.widgets[i]);
609 }
610 const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
611 notebook.activeCellIndex++;
612 Private.handleRunState(notebook, state, true);
613 return promise;
614 }
615 NotebookActions.runAllAbove = runAllAbove;
616 /**
617 * Run all of the cells after the currently active cell (inclusive).
618 *
619 * @param notebook - The target notebook widget.
620 * @param sessionContext - The client session object.
621 * @param sessionDialogs - The session dialogs.
622 * @param translator - The application translator.
623 *
624 * #### Notes
625 * The existing selection will be cleared.
626 * An execution error will prevent the remaining code cells from executing.
627 * All markdown cells will be rendered.
628 * The last cell in the notebook will be activated and scrolled into view.
629 */
630 function runAllBelow(notebook, sessionContext, sessionDialogs, translator) {
631 if (!notebook.model || !notebook.activeCell) {
632 return Promise.resolve(false);
633 }
634 const state = Private.getState(notebook);
635 notebook.deselectAll();
636 for (let i = notebook.activeCellIndex; i < notebook.widgets.length; ++i) {
637 notebook.select(notebook.widgets[i]);
638 }
639 const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
640 Private.handleRunState(notebook, state, true);
641 return promise;
642 }
643 NotebookActions.runAllBelow = runAllBelow;
644 /**
645 * Replaces the selection in the active cell of the notebook.
646 *
647 * @param notebook - The target notebook widget.
648 * @param text - The text to replace the selection.
649 */
650 function replaceSelection(notebook, text) {
651 var _a, _b, _c;
652 if (!notebook.model || !((_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.editor)) {
653 return;
654 }
655 (_c = (_b = notebook.activeCell.editor).replaceSelection) === null || _c === void 0 ? void 0 : _c.call(_b, text);
656 }
657 NotebookActions.replaceSelection = replaceSelection;
658 /**
659 * Select the above the active cell.
660 *
661 * @param notebook - The target notebook widget.
662 *
663 * #### Notes
664 * The widget mode will be preserved.
665 * This is a no-op if the first cell is the active cell.
666 * This will skip any collapsed cells.
667 * The existing selection will be cleared.
668 */
669 function selectAbove(notebook) {
670 if (!notebook.model || !notebook.activeCell) {
671 return;
672 }
673 if (notebook.activeCellIndex === 0) {
674 return;
675 }
676 let possibleNextCellIndex = notebook.activeCellIndex - 1;
677 // find first non hidden cell above current cell
678 while (possibleNextCellIndex >= 0) {
679 const possibleNextCell = notebook.widgets[possibleNextCellIndex];
680 if (!possibleNextCell.inputHidden && !possibleNextCell.isHidden) {
681 break;
682 }
683 possibleNextCellIndex -= 1;
684 }
685 const state = Private.getState(notebook);
686 notebook.activeCellIndex = possibleNextCellIndex;
687 notebook.deselectAll();
688 Private.handleState(notebook, state, true);
689 }
690 NotebookActions.selectAbove = selectAbove;
691 /**
692 * Select the cell below the active cell.
693 *
694 * @param notebook - The target notebook widget.
695 *
696 * #### Notes
697 * The widget mode will be preserved.
698 * This is a no-op if the last cell is the active cell.
699 * This will skip any collapsed cells.
700 * The existing selection will be cleared.
701 */
702 function selectBelow(notebook) {
703 if (!notebook.model || !notebook.activeCell) {
704 return;
705 }
706 let maxCellIndex = notebook.widgets.length - 1;
707 // Find last non-hidden cell
708 while (notebook.widgets[maxCellIndex].isHidden ||
709 notebook.widgets[maxCellIndex].inputHidden) {
710 maxCellIndex -= 1;
711 }
712 if (notebook.activeCellIndex === maxCellIndex) {
713 const footer = notebook.layout.footer;
714 footer === null || footer === void 0 ? void 0 : footer.node.focus();
715 return;
716 }
717 let possibleNextCellIndex = notebook.activeCellIndex + 1;
718 // find first non hidden cell below current cell
719 while (possibleNextCellIndex < maxCellIndex) {
720 let possibleNextCell = notebook.widgets[possibleNextCellIndex];
721 if (!possibleNextCell.inputHidden && !possibleNextCell.isHidden) {
722 break;
723 }
724 possibleNextCellIndex += 1;
725 }
726 const state = Private.getState(notebook);
727 notebook.activeCellIndex = possibleNextCellIndex;
728 notebook.deselectAll();
729 Private.handleState(notebook, state, true);
730 }
731 NotebookActions.selectBelow = selectBelow;
732 /** Insert new heading of same level above active cell.
733 *
734 * @param notebook - The target notebook widget
735 */
736 async function insertSameLevelHeadingAbove(notebook) {
737 if (!notebook.model || !notebook.activeCell) {
738 return;
739 }
740 let headingLevel = Private.Headings.determineHeadingLevel(notebook.activeCell, notebook);
741 if (headingLevel == -1) {
742 await Private.Headings.insertHeadingAboveCellIndex(0, 1, notebook);
743 }
744 else {
745 await Private.Headings.insertHeadingAboveCellIndex(notebook.activeCellIndex, headingLevel, notebook);
746 }
747 }
748 NotebookActions.insertSameLevelHeadingAbove = insertSameLevelHeadingAbove;
749 /** Insert new heading of same level at end of current section.
750 *
751 * @param notebook - The target notebook widget
752 */
753 async function insertSameLevelHeadingBelow(notebook) {
754 if (!notebook.model || !notebook.activeCell) {
755 return;
756 }
757 let headingLevel = Private.Headings.determineHeadingLevel(notebook.activeCell, notebook);
758 headingLevel = headingLevel > -1 ? headingLevel : 1;
759 let cellIdxOfHeadingBelow = Private.Headings.findLowerEqualLevelHeadingBelow(notebook.activeCell, notebook, true);
760 await Private.Headings.insertHeadingAboveCellIndex(cellIdxOfHeadingBelow == -1
761 ? notebook.model.cells.length
762 : cellIdxOfHeadingBelow, headingLevel, notebook);
763 }
764 NotebookActions.insertSameLevelHeadingBelow = insertSameLevelHeadingBelow;
765 /**
766 * Select the heading above the active cell or, if already at heading, collapse it.
767 *
768 * @param notebook - The target notebook widget.
769 *
770 * #### Notes
771 * The widget mode will be preserved.
772 * This is a no-op if the active cell is the topmost heading in collapsed state
773 * The existing selection will be cleared.
774 */
775 function selectHeadingAboveOrCollapseHeading(notebook) {
776 if (!notebook.model || !notebook.activeCell) {
777 return;
778 }
779 const state = Private.getState(notebook);
780 let hInfoActiveCell = getHeadingInfo(notebook.activeCell);
781 // either collapse or find the right heading to jump to
782 if (hInfoActiveCell.isHeading && !hInfoActiveCell.collapsed) {
783 setHeadingCollapse(notebook.activeCell, true, notebook);
784 }
785 else {
786 let targetHeadingCellIdx = Private.Headings.findLowerEqualLevelParentHeadingAbove(notebook.activeCell, notebook, true);
787 if (targetHeadingCellIdx > -1) {
788 notebook.activeCellIndex = targetHeadingCellIdx;
789 }
790 }
791 // clear selection and handle state
792 notebook.deselectAll();
793 Private.handleState(notebook, state, true);
794 }
795 NotebookActions.selectHeadingAboveOrCollapseHeading = selectHeadingAboveOrCollapseHeading;
796 /**
797 * Select the heading below the active cell or, if already at heading, expand it.
798 *
799 * @param notebook - The target notebook widget.
800 *
801 * #### Notes
802 * The widget mode will be preserved.
803 * This is a no-op if the active cell is the last heading in expanded state
804 * The existing selection will be cleared.
805 */
806 function selectHeadingBelowOrExpandHeading(notebook) {
807 if (!notebook.model || !notebook.activeCell) {
808 return;
809 }
810 const state = Private.getState(notebook);
811 let hInfo = getHeadingInfo(notebook.activeCell);
812 if (hInfo.isHeading && hInfo.collapsed) {
813 setHeadingCollapse(notebook.activeCell, false, notebook);
814 }
815 else {
816 let targetHeadingCellIdx = Private.Headings.findHeadingBelow(notebook.activeCell, notebook, true // return index of heading cell
817 );
818 if (targetHeadingCellIdx > -1) {
819 notebook.activeCellIndex = targetHeadingCellIdx;
820 }
821 }
822 notebook.deselectAll();
823 Private.handleState(notebook, state, true);
824 }
825 NotebookActions.selectHeadingBelowOrExpandHeading = selectHeadingBelowOrExpandHeading;
826 /**
827 * Extend the selection to the cell above.
828 *
829 * @param notebook - The target notebook widget.
830 * @param toTop - If true, denotes selection to extend to the top.
831 *
832 * #### Notes
833 * This is a no-op if the first cell is the active cell.
834 * The new cell will be activated.
835 */
836 function extendSelectionAbove(notebook, toTop = false) {
837 if (!notebook.model || !notebook.activeCell) {
838 return;
839 }
840 // Do not wrap around.
841 if (notebook.activeCellIndex === 0) {
842 return;
843 }
844 const state = Private.getState(notebook);
845 notebook.mode = 'command';
846 // Check if toTop is true, if yes, selection is made to the top.
847 if (toTop) {
848 notebook.extendContiguousSelectionTo(0);
849 }
850 else {
851 notebook.extendContiguousSelectionTo(notebook.activeCellIndex - 1);
852 }
853 Private.handleState(notebook, state, true);
854 }
855 NotebookActions.extendSelectionAbove = extendSelectionAbove;
856 /**
857 * Extend the selection to the cell below.
858 *
859 * @param notebook - The target notebook widget.
860 * @param toBottom - If true, denotes selection to extend to the bottom.
861 *
862 * #### Notes
863 * This is a no-op if the last cell is the active cell.
864 * The new cell will be activated.
865 */
866 function extendSelectionBelow(notebook, toBottom = false) {
867 if (!notebook.model || !notebook.activeCell) {
868 return;
869 }
870 // Do not wrap around.
871 if (notebook.activeCellIndex === notebook.widgets.length - 1) {
872 return;
873 }
874 const state = Private.getState(notebook);
875 notebook.mode = 'command';
876 // Check if toBottom is true, if yes selection is made to the bottom.
877 if (toBottom) {
878 notebook.extendContiguousSelectionTo(notebook.widgets.length - 1);
879 }
880 else {
881 notebook.extendContiguousSelectionTo(notebook.activeCellIndex + 1);
882 }
883 Private.handleState(notebook, state, true);
884 }
885 NotebookActions.extendSelectionBelow = extendSelectionBelow;
886 /**
887 * Select all of the cells of the notebook.
888 *
889 * @param notebook - the target notebook widget.
890 */
891 function selectAll(notebook) {
892 if (!notebook.model || !notebook.activeCell) {
893 return;
894 }
895 notebook.widgets.forEach(child => {
896 notebook.select(child);
897 });
898 }
899 NotebookActions.selectAll = selectAll;
900 /**
901 * Deselect all of the cells of the notebook.
902 *
903 * @param notebook - the target notebook widget.
904 */
905 function deselectAll(notebook) {
906 if (!notebook.model || !notebook.activeCell) {
907 return;
908 }
909 notebook.deselectAll();
910 }
911 NotebookActions.deselectAll = deselectAll;
912 /**
913 * Copy the selected cell(s) data to a clipboard.
914 *
915 * @param notebook - The target notebook widget.
916 */
917 function copy(notebook) {
918 Private.copyOrCut(notebook, false);
919 }
920 NotebookActions.copy = copy;
921 /**
922 * Cut the selected cell data to a clipboard.
923 *
924 * @param notebook - The target notebook widget.
925 *
926 * #### Notes
927 * This action can be undone.
928 * A new code cell is added if all cells are cut.
929 */
930 function cut(notebook) {
931 Private.copyOrCut(notebook, true);
932 }
933 NotebookActions.cut = cut;
934 /**
935 * Paste cells from the application clipboard.
936 *
937 * @param notebook - The target notebook widget.
938 *
939 * @param mode - the mode of adding cells:
940 * 'below' (default) adds cells below the active cell,
941 * 'belowSelected' adds cells below all selected cells,
942 * 'above' adds cells above the active cell, and
943 * 'replace' removes the currently selected cells and adds cells in their place.
944 *
945 * #### Notes
946 * The last pasted cell becomes the active cell.
947 * This is a no-op if there is no cell data on the clipboard.
948 * This action can be undone.
949 */
950 function paste(notebook, mode = 'below') {
951 const clipboard = Clipboard.getInstance();
952 if (!clipboard.hasData(JUPYTER_CELL_MIME)) {
953 return;
954 }
955 const values = clipboard.getData(JUPYTER_CELL_MIME);
956 addCells(notebook, mode, values, true);
957 }
958 NotebookActions.paste = paste;
959 /**
960 * Duplicate selected cells in the notebook without using the application clipboard.
961 *
962 * @param notebook - The target notebook widget.
963 *
964 * @param mode - the mode of adding cells:
965 * 'below' (default) adds cells below the active cell,
966 * 'belowSelected' adds cells below all selected cells,
967 * 'above' adds cells above the active cell, and
968 * 'replace' removes the currently selected cells and adds cells in their place.
969 *
970 * #### Notes
971 * The last pasted cell becomes the active cell.
972 * This is a no-op if there is no cell data on the clipboard.
973 * This action can be undone.
974 */
975 function duplicate(notebook, mode = 'below') {
976 const values = Private.selectedCells(notebook);
977 if (!values || values.length === 0) {
978 return;
979 }
980 addCells(notebook, mode, values, false); // Cells not from the clipboard
981 }
982 NotebookActions.duplicate = duplicate;
983 /**
984 * Adds cells to the notebook.
985 *
986 * @param notebook - The target notebook widget.
987 *
988 * @param mode - the mode of adding cells:
989 * 'below' (default) adds cells below the active cell,
990 * 'belowSelected' adds cells below all selected cells,
991 * 'above' adds cells above the active cell, and
992 * 'replace' removes the currently selected cells and adds cells in their place.
993 *
994 * @param values — The cells to add to the notebook.
995 *
996 * @param cellsFromClipboard — True if the cells were sourced from the clipboard.
997 *
998 * #### Notes
999 * The last added cell becomes the active cell.
1000 * This is a no-op if values is an empty array.
1001 * This action can be undone.
1002 */
1003 function addCells(notebook, mode = 'below', values, cellsFromClipboard = false) {
1004 if (!notebook.model || !notebook.activeCell) {
1005 return;
1006 }
1007 const state = Private.getState(notebook);
1008 const model = notebook.model;
1009 notebook.mode = 'command';
1010 let index = 0;
1011 const prevActiveCellIndex = notebook.activeCellIndex;
1012 model.sharedModel.transact(() => {
1013 // Set the starting index of the paste operation depending upon the mode.
1014 switch (mode) {
1015 case 'below':
1016 index = notebook.activeCellIndex + 1;
1017 break;
1018 case 'belowSelected':
1019 notebook.widgets.forEach((child, childIndex) => {
1020 if (notebook.isSelectedOrActive(child)) {
1021 index = childIndex + 1;
1022 }
1023 });
1024 break;
1025 case 'above':
1026 index = notebook.activeCellIndex;
1027 break;
1028 case 'replace': {
1029 // Find the cells to delete.
1030 const toDelete = [];
1031 notebook.widgets.forEach((child, index) => {
1032 const deletable = child.model.sharedModel.getMetadata('deletable') !== false;
1033 if (notebook.isSelectedOrActive(child) && deletable) {
1034 toDelete.push(index);
1035 }
1036 });
1037 // If cells are not deletable, we may not have anything to delete.
1038 if (toDelete.length > 0) {
1039 // Delete the cells as one undo event.
1040 toDelete.reverse().forEach(i => {
1041 model.sharedModel.deleteCell(i);
1042 });
1043 }
1044 index = toDelete[0];
1045 break;
1046 }
1047 default:
1048 break;
1049 }
1050 model.sharedModel.insertCells(index, values.map(cell => {
1051 cell.id =
1052 cell.cell_type === 'code' &&
1053 notebook.lastClipboardInteraction === 'cut' &&
1054 typeof cell.id === 'string'
1055 ? cell.id
1056 : undefined;
1057 return cell;
1058 }));
1059 });
1060 notebook.activeCellIndex = prevActiveCellIndex + values.length;
1061 notebook.deselectAll();
1062 if (cellsFromClipboard) {
1063 notebook.lastClipboardInteraction = 'paste';
1064 }
1065 Private.handleState(notebook, state, true);
1066 }
1067 /**
1068 * Undo a cell action.
1069 *
1070 * @param notebook - The target notebook widget.
1071 *
1072 * #### Notes
1073 * This is a no-op if there are no cell actions to undo.
1074 */
1075 function undo(notebook) {
1076 if (!notebook.model) {
1077 return;
1078 }
1079 const state = Private.getState(notebook);
1080 notebook.mode = 'command';
1081 notebook.model.sharedModel.undo();
1082 notebook.deselectAll();
1083 Private.handleState(notebook, state);
1084 }
1085 NotebookActions.undo = undo;
1086 /**
1087 * Redo a cell action.
1088 *
1089 * @param notebook - The target notebook widget.
1090 *
1091 * #### Notes
1092 * This is a no-op if there are no cell actions to redo.
1093 */
1094 function redo(notebook) {
1095 if (!notebook.model || !notebook.activeCell) {
1096 return;
1097 }
1098 const state = Private.getState(notebook);
1099 notebook.mode = 'command';
1100 notebook.model.sharedModel.redo();
1101 notebook.deselectAll();
1102 Private.handleState(notebook, state);
1103 }
1104 NotebookActions.redo = redo;
1105 /**
1106 * Toggle the line number of all cells.
1107 *
1108 * @param notebook - The target notebook widget.
1109 *
1110 * #### Notes
1111 * The original state is based on the state of the active cell.
1112 * The `mode` of the widget will be preserved.
1113 */
1114 function toggleAllLineNumbers(notebook) {
1115 if (!notebook.model || !notebook.activeCell) {
1116 return;
1117 }
1118 const state = Private.getState(notebook);
1119 const config = notebook.editorConfig;
1120 const lineNumbers = !(config.code.lineNumbers &&
1121 config.markdown.lineNumbers &&
1122 config.raw.lineNumbers);
1123 const newConfig = {
1124 code: { ...config.code, lineNumbers },
1125 markdown: { ...config.markdown, lineNumbers },
1126 raw: { ...config.raw, lineNumbers }
1127 };
1128 notebook.editorConfig = newConfig;
1129 Private.handleState(notebook, state);
1130 }
1131 NotebookActions.toggleAllLineNumbers = toggleAllLineNumbers;
1132 /**
1133 * Clear the code outputs of the selected cells.
1134 *
1135 * @param notebook - The target notebook widget.
1136 *
1137 * #### Notes
1138 * The widget `mode` will be preserved.
1139 */
1140 function clearOutputs(notebook) {
1141 if (!notebook.model || !notebook.activeCell) {
1142 return;
1143 }
1144 const state = Private.getState(notebook);
1145 let index = -1;
1146 for (const cell of notebook.model.cells) {
1147 const child = notebook.widgets[++index];
1148 if (notebook.isSelectedOrActive(child) && cell.type === 'code') {
1149 cell.sharedModel.transact(() => {
1150 cell.clearExecution();
1151 child.outputHidden = false;
1152 }, false);
1153 }
1154 }
1155 Private.handleState(notebook, state, true);
1156 }
1157 NotebookActions.clearOutputs = clearOutputs;
1158 /**
1159 * Clear all the code outputs on the widget.
1160 *
1161 * @param notebook - The target notebook widget.
1162 *
1163 * #### Notes
1164 * The widget `mode` will be preserved.
1165 */
1166 function clearAllOutputs(notebook) {
1167 if (!notebook.model || !notebook.activeCell) {
1168 return;
1169 }
1170 const state = Private.getState(notebook);
1171 let index = -1;
1172 for (const cell of notebook.model.cells) {
1173 const child = notebook.widgets[++index];
1174 if (cell.type === 'code') {
1175 cell.sharedModel.transact(() => {
1176 cell.clearExecution();
1177 child.outputHidden = false;
1178 }, false);
1179 }
1180 }
1181 Private.handleState(notebook, state, true);
1182 }
1183 NotebookActions.clearAllOutputs = clearAllOutputs;
1184 /**
1185 * Hide the code on selected code cells.
1186 *
1187 * @param notebook - The target notebook widget.
1188 */
1189 function hideCode(notebook) {
1190 if (!notebook.model || !notebook.activeCell) {
1191 return;
1192 }
1193 const state = Private.getState(notebook);
1194 notebook.widgets.forEach(cell => {
1195 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1196 cell.inputHidden = true;
1197 }
1198 });
1199 Private.handleState(notebook, state);
1200 }
1201 NotebookActions.hideCode = hideCode;
1202 /**
1203 * Show the code on selected code cells.
1204 *
1205 * @param notebook - The target notebook widget.
1206 */
1207 function showCode(notebook) {
1208 if (!notebook.model || !notebook.activeCell) {
1209 return;
1210 }
1211 const state = Private.getState(notebook);
1212 notebook.widgets.forEach(cell => {
1213 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1214 cell.inputHidden = false;
1215 }
1216 });
1217 Private.handleState(notebook, state);
1218 }
1219 NotebookActions.showCode = showCode;
1220 /**
1221 * Hide the code on all code cells.
1222 *
1223 * @param notebook - The target notebook widget.
1224 */
1225 function hideAllCode(notebook) {
1226 if (!notebook.model || !notebook.activeCell) {
1227 return;
1228 }
1229 const state = Private.getState(notebook);
1230 notebook.widgets.forEach(cell => {
1231 if (cell.model.type === 'code') {
1232 cell.inputHidden = true;
1233 }
1234 });
1235 Private.handleState(notebook, state);
1236 }
1237 NotebookActions.hideAllCode = hideAllCode;
1238 /**
1239 * Show the code on all code cells.
1240 *
1241 * @param widget - The target notebook widget.
1242 */
1243 function showAllCode(notebook) {
1244 if (!notebook.model || !notebook.activeCell) {
1245 return;
1246 }
1247 const state = Private.getState(notebook);
1248 notebook.widgets.forEach(cell => {
1249 if (cell.model.type === 'code') {
1250 cell.inputHidden = false;
1251 }
1252 });
1253 Private.handleState(notebook, state);
1254 }
1255 NotebookActions.showAllCode = showAllCode;
1256 /**
1257 * Hide the output on selected code cells.
1258 *
1259 * @param notebook - The target notebook widget.
1260 */
1261 function hideOutput(notebook) {
1262 if (!notebook.model || !notebook.activeCell) {
1263 return;
1264 }
1265 const state = Private.getState(notebook);
1266 notebook.widgets.forEach(cell => {
1267 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1268 cell.outputHidden = true;
1269 }
1270 });
1271 Private.handleState(notebook, state, true);
1272 }
1273 NotebookActions.hideOutput = hideOutput;
1274 /**
1275 * Show the output on selected code cells.
1276 *
1277 * @param notebook - The target notebook widget.
1278 */
1279 function showOutput(notebook) {
1280 if (!notebook.model || !notebook.activeCell) {
1281 return;
1282 }
1283 const state = Private.getState(notebook);
1284 notebook.widgets.forEach(cell => {
1285 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1286 cell.outputHidden = false;
1287 }
1288 });
1289 Private.handleState(notebook, state);
1290 }
1291 NotebookActions.showOutput = showOutput;
1292 /**
1293 * Hide the output on all code cells.
1294 *
1295 * @param notebook - The target notebook widget.
1296 */
1297 function hideAllOutputs(notebook) {
1298 if (!notebook.model || !notebook.activeCell) {
1299 return;
1300 }
1301 const state = Private.getState(notebook);
1302 notebook.widgets.forEach(cell => {
1303 if (cell.model.type === 'code') {
1304 cell.outputHidden = true;
1305 }
1306 });
1307 Private.handleState(notebook, state, true);
1308 }
1309 NotebookActions.hideAllOutputs = hideAllOutputs;
1310 /**
1311 * Render side-by-side.
1312 *
1313 * @param notebook - The target notebook widget.
1314 */
1315 function renderSideBySide(notebook) {
1316 notebook.renderingLayout = 'side-by-side';
1317 }
1318 NotebookActions.renderSideBySide = renderSideBySide;
1319 /**
1320 * Render not side-by-side.
1321 *
1322 * @param notebook - The target notebook widget.
1323 */
1324 function renderDefault(notebook) {
1325 notebook.renderingLayout = 'default';
1326 }
1327 NotebookActions.renderDefault = renderDefault;
1328 /**
1329 * Show the output on all code cells.
1330 *
1331 * @param notebook - The target notebook widget.
1332 */
1333 function showAllOutputs(notebook) {
1334 if (!notebook.model || !notebook.activeCell) {
1335 return;
1336 }
1337 const state = Private.getState(notebook);
1338 notebook.widgets.forEach(cell => {
1339 if (cell.model.type === 'code') {
1340 cell.outputHidden = false;
1341 }
1342 });
1343 Private.handleState(notebook, state);
1344 }
1345 NotebookActions.showAllOutputs = showAllOutputs;
1346 /**
1347 * Enable output scrolling for all selected cells.
1348 *
1349 * @param notebook - The target notebook widget.
1350 */
1351 function enableOutputScrolling(notebook) {
1352 if (!notebook.model || !notebook.activeCell) {
1353 return;
1354 }
1355 const state = Private.getState(notebook);
1356 notebook.widgets.forEach(cell => {
1357 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1358 cell.outputsScrolled = true;
1359 }
1360 });
1361 Private.handleState(notebook, state, true);
1362 }
1363 NotebookActions.enableOutputScrolling = enableOutputScrolling;
1364 /**
1365 * Disable output scrolling for all selected cells.
1366 *
1367 * @param notebook - The target notebook widget.
1368 */
1369 function disableOutputScrolling(notebook) {
1370 if (!notebook.model || !notebook.activeCell) {
1371 return;
1372 }
1373 const state = Private.getState(notebook);
1374 notebook.widgets.forEach(cell => {
1375 if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
1376 cell.outputsScrolled = false;
1377 }
1378 });
1379 Private.handleState(notebook, state);
1380 }
1381 NotebookActions.disableOutputScrolling = disableOutputScrolling;
1382 /**
1383 * Go to the last cell that is run or current if it is running.
1384 *
1385 * Note: This requires execution timing to be toggled on or this will have
1386 * no effect.
1387 *
1388 * @param notebook - The target notebook widget.
1389 */
1390 function selectLastRunCell(notebook) {
1391 let latestTime = null;
1392 let latestCellIdx = null;
1393 notebook.widgets.forEach((cell, cellIndx) => {
1394 if (cell.model.type === 'code') {
1395 const execution = cell.model.getMetadata('execution');
1396 if (execution &&
1397 JSONExt.isObject(execution) &&
1398 execution['iopub.status.busy'] !== undefined) {
1399 // The busy status is used as soon as a request is received:
1400 // https://jupyter-client.readthedocs.io/en/stable/messaging.html
1401 const timestamp = execution['iopub.status.busy'].toString();
1402 if (timestamp) {
1403 const startTime = new Date(timestamp);
1404 if (!latestTime || startTime >= latestTime) {
1405 latestTime = startTime;
1406 latestCellIdx = cellIndx;
1407 }
1408 }
1409 }
1410 }
1411 });
1412 if (latestCellIdx !== null) {
1413 notebook.activeCellIndex = latestCellIdx;
1414 }
1415 }
1416 NotebookActions.selectLastRunCell = selectLastRunCell;
1417 /**
1418 * Set the markdown header level.
1419 *
1420 * @param notebook - The target notebook widget.
1421 *
1422 * @param level - The header level.
1423 *
1424 * #### Notes
1425 * All selected cells will be switched to markdown.
1426 * The level will be clamped between 1 and 6.
1427 * If there is an existing header, it will be replaced.
1428 * There will always be one blank space after the header.
1429 * The cells will be unrendered.
1430 */
1431 function setMarkdownHeader(notebook, level) {
1432 if (!notebook.model || !notebook.activeCell) {
1433 return;
1434 }
1435 const state = Private.getState(notebook);
1436 const cells = notebook.model.cells;
1437 level = Math.min(Math.max(level, 1), 6);
1438 notebook.widgets.forEach((child, index) => {
1439 if (notebook.isSelectedOrActive(child)) {
1440 Private.setMarkdownHeader(cells.get(index), level);
1441 }
1442 });
1443 Private.changeCellType(notebook, 'markdown');
1444 Private.handleState(notebook, state);
1445 }
1446 NotebookActions.setMarkdownHeader = setMarkdownHeader;
1447 /**
1448 * Collapse all cells in given notebook.
1449 *
1450 * @param notebook - The target notebook widget.
1451 */
1452 function collapseAllHeadings(notebook) {
1453 for (const cell of notebook.widgets) {
1454 if (NotebookActions.getHeadingInfo(cell).isHeading) {
1455 NotebookActions.setHeadingCollapse(cell, true, notebook);
1456 NotebookActions.setCellCollapse(cell, true);
1457 }
1458 }
1459 }
1460 NotebookActions.collapseAllHeadings = collapseAllHeadings;
1461 /**
1462 * Un-collapse all cells in given notebook.
1463 *
1464 * @param notebook - The target notebook widget.
1465 */
1466 function expandAllHeadings(notebook) {
1467 for (const cell of notebook.widgets) {
1468 if (NotebookActions.getHeadingInfo(cell).isHeading) {
1469 NotebookActions.setHeadingCollapse(cell, false, notebook);
1470 // similar to collapseAll.
1471 NotebookActions.setCellCollapse(cell, false);
1472 }
1473 }
1474 }
1475 NotebookActions.expandAllHeadings = expandAllHeadings;
1476 function findNearestParentHeader(cell, notebook) {
1477 const index = findIndex(notebook.widgets, (possibleCell, index) => {
1478 return cell.model.id === possibleCell.model.id;
1479 });
1480 if (index === -1) {
1481 return;
1482 }
1483 // Finds the nearest header above the given cell. If the cell is a header itself, it does not return itself;
1484 // this can be checked directly by calling functions.
1485 if (index >= notebook.widgets.length) {
1486 return;
1487 }
1488 let childHeaderInfo = getHeadingInfo(notebook.widgets[index]);
1489 for (let cellN = index - 1; cellN >= 0; cellN--) {
1490 if (cellN < notebook.widgets.length) {
1491 let hInfo = getHeadingInfo(notebook.widgets[cellN]);
1492 if (hInfo.isHeading &&
1493 hInfo.headingLevel < childHeaderInfo.headingLevel) {
1494 return notebook.widgets[cellN];
1495 }
1496 }
1497 }
1498 // else no parent header found.
1499 return;
1500 }
1501 /**
1502 * Finds the "parent" heading of the given cell and expands.
1503 * Used for the case that a cell becomes active that is within a collapsed heading.
1504 * @param cell - "Child" cell that has become the active cell
1505 * @param notebook - The target notebook widget.
1506 */
1507 function expandParent(cell, notebook) {
1508 let nearestParentCell = findNearestParentHeader(cell, notebook);
1509 if (!nearestParentCell) {
1510 return;
1511 }
1512 if (!getHeadingInfo(nearestParentCell).collapsed &&
1513 !nearestParentCell.isHidden) {
1514 return;
1515 }
1516 if (nearestParentCell.isHidden) {
1517 expandParent(nearestParentCell, notebook);
1518 }
1519 if (getHeadingInfo(nearestParentCell).collapsed) {
1520 setHeadingCollapse(nearestParentCell, false, notebook);
1521 }
1522 }
1523 NotebookActions.expandParent = expandParent;
1524 /**
1525 * Finds the next heading that isn't a child of the given markdown heading.
1526 * @param cell - "Child" cell that has become the active cell
1527 * @param notebook - The target notebook widget.
1528 */
1529 function findNextParentHeading(cell, notebook) {
1530 let index = findIndex(notebook.widgets, (possibleCell, index) => {
1531 return cell.model.id === possibleCell.model.id;
1532 });
1533 if (index === -1) {
1534 return -1;
1535 }
1536 let childHeaderInfo = getHeadingInfo(cell);
1537 for (index = index + 1; index < notebook.widgets.length; index++) {
1538 let hInfo = getHeadingInfo(notebook.widgets[index]);
1539 if (hInfo.isHeading &&
1540 hInfo.headingLevel <= childHeaderInfo.headingLevel) {
1541 return index;
1542 }
1543 }
1544 // else no parent header found. return the index of the last cell
1545 return notebook.widgets.length;
1546 }
1547 NotebookActions.findNextParentHeading = findNextParentHeading;
1548 /**
1549 * Set the given cell and ** all "child" cells **
1550 * to the given collapse / expand if cell is
1551 * a markdown header.
1552 *
1553 * @param cell - The cell
1554 * @param collapsing - Whether to collapse or expand the cell
1555 * @param notebook - The target notebook widget.
1556 */
1557 function setHeadingCollapse(cell, collapsing, notebook) {
1558 const which = findIndex(notebook.widgets, (possibleCell, index) => {
1559 return cell.model.id === possibleCell.model.id;
1560 });
1561 if (which === -1) {
1562 return -1;
1563 }
1564 if (!notebook.widgets.length) {
1565 return which + 1;
1566 }
1567 let selectedHeadingInfo = NotebookActions.getHeadingInfo(cell);
1568 if (cell.isHidden ||
1569 !(cell instanceof MarkdownCell) ||
1570 !selectedHeadingInfo.isHeading) {
1571 // otherwise collapsing and uncollapsing already hidden stuff can
1572 // cause some funny looking bugs.
1573 return which + 1;
1574 }
1575 let localCollapsed = false;
1576 let localCollapsedLevel = 0;
1577 // iterate through all cells after the active cell.
1578 let cellNum;
1579 for (cellNum = which + 1; cellNum < notebook.widgets.length; cellNum++) {
1580 let subCell = notebook.widgets[cellNum];
1581 let subCellHeadingInfo = NotebookActions.getHeadingInfo(subCell);
1582 if (subCellHeadingInfo.isHeading &&
1583 subCellHeadingInfo.headingLevel <= selectedHeadingInfo.headingLevel) {
1584 // then reached an equivalent or higher heading level than the
1585 // original the end of the collapse.
1586 cellNum -= 1;
1587 break;
1588 }
1589 if (localCollapsed &&
1590 subCellHeadingInfo.isHeading &&
1591 subCellHeadingInfo.headingLevel <= localCollapsedLevel) {
1592 // then reached the end of the local collapsed, so unset NotebookActions.
1593 localCollapsed = false;
1594 }
1595 if (collapsing || localCollapsed) {
1596 // then no extra handling is needed for further locally collapsed
1597 // headings.
1598 subCell.setHidden(true);
1599 continue;
1600 }
1601 if (subCellHeadingInfo.collapsed && subCellHeadingInfo.isHeading) {
1602 localCollapsed = true;
1603 localCollapsedLevel = subCellHeadingInfo.headingLevel;
1604 // but don't collapse the locally collapsed heading, so continue to
1605 // expand the heading. This will get noticed in the next round.
1606 }
1607 subCell.setHidden(false);
1608 }
1609 if (cellNum === notebook.widgets.length) {
1610 cell.numberChildNodes = cellNum - which - 1;
1611 }
1612 else {
1613 cell.numberChildNodes = cellNum - which;
1614 }
1615 NotebookActions.setCellCollapse(cell, collapsing);
1616 return cellNum + 1;
1617 }
1618 NotebookActions.setHeadingCollapse = setHeadingCollapse;
1619 /**
1620 * Toggles the collapse state of the active cell of the given notebook
1621 * and ** all of its "child" cells ** if the cell is a heading.
1622 *
1623 * @param notebook - The target notebook widget.
1624 */
1625 function toggleCurrentHeadingCollapse(notebook) {
1626 if (!notebook.activeCell || notebook.activeCellIndex === undefined) {
1627 return;
1628 }
1629 let headingInfo = NotebookActions.getHeadingInfo(notebook.activeCell);
1630 if (headingInfo.isHeading) {
1631 // Then toggle!
1632 NotebookActions.setHeadingCollapse(notebook.activeCell, !headingInfo.collapsed, notebook);
1633 }
1634 notebook.scrollToItem(notebook.activeCellIndex).catch(reason => {
1635 // no-op
1636 });
1637 }
1638 NotebookActions.toggleCurrentHeadingCollapse = toggleCurrentHeadingCollapse;
1639 /**
1640 * If cell is a markdown heading, sets the headingCollapsed field,
1641 * and otherwise hides the cell.
1642 *
1643 * @param cell - The cell to collapse / expand
1644 * @param collapsing - Whether to collapse or expand the given cell
1645 */
1646 function setCellCollapse(cell, collapsing) {
1647 if (cell instanceof MarkdownCell) {
1648 cell.headingCollapsed = collapsing;
1649 }
1650 else {
1651 cell.setHidden(collapsing);
1652 }
1653 }
1654 NotebookActions.setCellCollapse = setCellCollapse;
1655 /**
1656 * If given cell is a markdown heading, returns the heading level.
1657 * If given cell is not markdown, returns 7 (there are only 6 levels of markdown headings)
1658 *
1659 * @param cell - The target cell widget.
1660 */
1661 function getHeadingInfo(cell) {
1662 if (!(cell instanceof MarkdownCell)) {
1663 return { isHeading: false, headingLevel: 7 };
1664 }
1665 let level = cell.headingInfo.level;
1666 let collapsed = cell.headingCollapsed;
1667 return { isHeading: level > 0, headingLevel: level, collapsed: collapsed };
1668 }
1669 NotebookActions.getHeadingInfo = getHeadingInfo;
1670 /**
1671 * Trust the notebook after prompting the user.
1672 *
1673 * @param notebook - The target notebook widget.
1674 *
1675 * @returns a promise that resolves when the transaction is finished.
1676 *
1677 * #### Notes
1678 * No dialog will be presented if the notebook is already trusted.
1679 */
1680 function trust(notebook, translator) {
1681 translator = translator || nullTranslator;
1682 const trans = translator.load('jupyterlab');
1683 if (!notebook.model) {
1684 return Promise.resolve();
1685 }
1686 // Do nothing if already trusted.
1687 const trusted = every(notebook.model.cells, cell => cell.trusted);
1688 // FIXME
1689 const trustMessage = (React.createElement("p", null,
1690 trans.__('A trusted Jupyter notebook may execute hidden malicious code when you open it.'),
1691 React.createElement("br", null),
1692 trans.__('Selecting "Trust" will re-render this notebook in a trusted state.'),
1693 React.createElement("br", null),
1694 trans.__('For more information, see'),
1695 ' ',
1696 React.createElement("a", { href: "https://jupyter-server.readthedocs.io/en/stable/operators/security.html", target: "_blank", rel: "noopener noreferrer" }, trans.__('the Jupyter security documentation')),
1697 "."));
1698 if (trusted) {
1699 return showDialog({
1700 body: trans.__('Notebook is already trusted'),
1701 buttons: [Dialog.okButton()]
1702 }).then(() => undefined);
1703 }
1704 return showDialog({
1705 body: trustMessage,
1706 title: trans.__('Trust this notebook?'),
1707 buttons: [
1708 Dialog.cancelButton(),
1709 Dialog.warnButton({
1710 label: trans.__('Trust'),
1711 ariaLabel: trans.__('Confirm Trusting this notebook')
1712 })
1713 ] // FIXME?
1714 }).then(result => {
1715 if (result.button.accept) {
1716 if (notebook.model) {
1717 for (const cell of notebook.model.cells) {
1718 cell.trusted = true;
1719 }
1720 }
1721 }
1722 });
1723 }
1724 NotebookActions.trust = trust;
1725})(NotebookActions || (NotebookActions = {}));
1726/**
1727 * A namespace for private data.
1728 */
1729var Private;
1730(function (Private) {
1731 /**
1732 * A signal that emits whenever a cell completes execution.
1733 */
1734 Private.executed = new Signal({});
1735 /**
1736 * A signal that emits whenever a cell execution is scheduled.
1737 */
1738 Private.executionScheduled = new Signal({});
1739 /**
1740 * A signal that emits when one notebook's cells are all executed.
1741 */
1742 Private.selectionExecuted = new Signal({});
1743 /**
1744 * Get the state of a widget before running an action.
1745 */
1746 function getState(notebook) {
1747 var _a, _b;
1748 return {
1749 wasFocused: notebook.node.contains(document.activeElement),
1750 activeCellId: (_b = (_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.model.id) !== null && _b !== void 0 ? _b : null
1751 };
1752 }
1753 Private.getState = getState;
1754 /**
1755 * Handle the state of a widget after running an action.
1756 */
1757 function handleState(notebook, state, scrollIfNeeded = false) {
1758 const { activeCell, activeCellIndex } = notebook;
1759 if (state.wasFocused || notebook.mode === 'edit') {
1760 notebook.activate();
1761 }
1762 if (scrollIfNeeded && activeCell) {
1763 notebook.scrollToItem(activeCellIndex, 'smart', 0.05).catch(reason => {
1764 // no-op
1765 });
1766 }
1767 }
1768 Private.handleState = handleState;
1769 /**
1770 * Handle the state of a widget after running a run action.
1771 */
1772 function handleRunState(notebook, state, scroll = false) {
1773 var _a;
1774 if (state.wasFocused || notebook.mode === 'edit') {
1775 notebook.activate();
1776 }
1777 if (scroll && state.activeCellId) {
1778 const index = notebook.widgets.findIndex(w => w.model.id === state.activeCellId);
1779 if ((_a = notebook.widgets[index]) === null || _a === void 0 ? void 0 : _a.inputArea) {
1780 notebook.scrollToItem(index).catch(reason => {
1781 // no-op
1782 });
1783 }
1784 }
1785 }
1786 Private.handleRunState = handleRunState;
1787 /**
1788 * Run the selected cells.
1789 *
1790 * @param notebook Notebook
1791 * @param sessionContext Notebook session context
1792 * @param sessionDialogs Session dialogs
1793 * @param translator Application translator
1794 */
1795 function runSelected(notebook, sessionContext, sessionDialogs, translator) {
1796 notebook.mode = 'command';
1797 let lastIndex = notebook.activeCellIndex;
1798 const selected = notebook.widgets.filter((child, index) => {
1799 const active = notebook.isSelectedOrActive(child);
1800 if (active) {
1801 lastIndex = index;
1802 }
1803 return active;
1804 });
1805 notebook.activeCellIndex = lastIndex;
1806 notebook.deselectAll();
1807 return Promise.all(selected.map(child => runCell(notebook, child, sessionContext, sessionDialogs, translator)))
1808 .then(results => {
1809 if (notebook.isDisposed) {
1810 return false;
1811 }
1812 Private.selectionExecuted.emit({
1813 notebook,
1814 lastCell: notebook.widgets[lastIndex]
1815 });
1816 // Post an update request.
1817 notebook.update();
1818 return results.every(result => result);
1819 })
1820 .catch(reason => {
1821 if (reason.message.startsWith('KernelReplyNotOK')) {
1822 selected.map(cell => {
1823 // Remove '*' prompt from cells that didn't execute
1824 if (cell.model.type === 'code' &&
1825 cell.model.executionCount == null) {
1826 cell.setPrompt('');
1827 }
1828 });
1829 }
1830 else {
1831 throw reason;
1832 }
1833 Private.selectionExecuted.emit({
1834 notebook,
1835 lastCell: notebook.widgets[lastIndex]
1836 });
1837 notebook.update();
1838 return false;
1839 });
1840 }
1841 Private.runSelected = runSelected;
1842 /**
1843 * Run a cell.
1844 */
1845 async function runCell(notebook, cell, sessionContext, sessionDialogs, translator) {
1846 var _a, _b, _c;
1847 translator = translator || nullTranslator;
1848 const trans = translator.load('jupyterlab');
1849 switch (cell.model.type) {
1850 case 'markdown':
1851 cell.rendered = true;
1852 cell.inputHidden = false;
1853 Private.executed.emit({ notebook, cell, success: true });
1854 break;
1855 case 'code':
1856 if (sessionContext) {
1857 if (sessionContext.isTerminating) {
1858 await showDialog({
1859 title: trans.__('Kernel Terminating'),
1860 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),
1861 buttons: [Dialog.okButton()]
1862 });
1863 break;
1864 }
1865 if (sessionContext.pendingInput) {
1866 await showDialog({
1867 title: trans.__('Cell not executed due to pending input'),
1868 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.'),
1869 buttons: [Dialog.okButton()]
1870 });
1871 return false;
1872 }
1873 if (sessionContext.hasNoKernel) {
1874 const shouldSelect = await sessionContext.startKernel();
1875 if (shouldSelect && sessionDialogs) {
1876 await sessionDialogs.selectKernel(sessionContext);
1877 }
1878 }
1879 if (sessionContext.hasNoKernel) {
1880 cell.model.sharedModel.transact(() => {
1881 cell.model.clearExecution();
1882 });
1883 return true;
1884 }
1885 const deletedCells = (_c = (_b = notebook.model) === null || _b === void 0 ? void 0 : _b.deletedCells) !== null && _c !== void 0 ? _c : [];
1886 Private.executionScheduled.emit({ notebook, cell });
1887 let ran = false;
1888 try {
1889 const reply = await CodeCell.execute(cell, sessionContext, {
1890 deletedCells,
1891 recordTiming: notebook.notebookConfig.recordTiming
1892 });
1893 deletedCells.splice(0, deletedCells.length);
1894 ran = (() => {
1895 if (cell.isDisposed) {
1896 return false;
1897 }
1898 if (!reply) {
1899 return true;
1900 }
1901 if (reply.content.status === 'ok') {
1902 const content = reply.content;
1903 if (content.payload && content.payload.length) {
1904 handlePayload(content, notebook, cell);
1905 }
1906 return true;
1907 }
1908 else {
1909 throw new KernelError(reply.content);
1910 }
1911 })();
1912 }
1913 catch (reason) {
1914 if (cell.isDisposed || reason.message.startsWith('Canceled')) {
1915 ran = false;
1916 }
1917 else {
1918 Private.executed.emit({
1919 notebook,
1920 cell,
1921 success: false,
1922 error: reason
1923 });
1924 throw reason;
1925 }
1926 }
1927 if (ran) {
1928 Private.executed.emit({ notebook, cell, success: true });
1929 }
1930 return ran;
1931 }
1932 cell.model.sharedModel.transact(() => {
1933 cell.model.clearExecution();
1934 }, false);
1935 break;
1936 default:
1937 break;
1938 }
1939 return Promise.resolve(true);
1940 }
1941 /**
1942 * Handle payloads from an execute reply.
1943 *
1944 * #### Notes
1945 * Payloads are deprecated and there are no official interfaces for them in
1946 * the kernel type definitions.
1947 * See [Payloads (DEPRECATED)](https://jupyter-client.readthedocs.io/en/latest/messaging.html#payloads-deprecated).
1948 */
1949 function handlePayload(content, notebook, cell) {
1950 var _a;
1951 const setNextInput = (_a = content.payload) === null || _a === void 0 ? void 0 : _a.filter(i => {
1952 return i.source === 'set_next_input';
1953 })[0];
1954 if (!setNextInput) {
1955 return;
1956 }
1957 const text = setNextInput.text;
1958 const replace = setNextInput.replace;
1959 if (replace) {
1960 cell.model.sharedModel.setSource(text);
1961 return;
1962 }
1963 // Create a new code cell and add as the next cell.
1964 const notebookModel = notebook.model.sharedModel;
1965 const cells = notebook.model.cells;
1966 const index = findIndex(cells, model => model === cell.model);
1967 // While this cell has no outputs and could be trusted following the letter
1968 // of Jupyter trust model, its content comes from kernel and hence is not
1969 // necessarily controlled by the user; if we set it as trusted, a user
1970 // executing cells in succession could end up with unwanted trusted output.
1971 if (index === -1) {
1972 notebookModel.insertCell(notebookModel.cells.length, {
1973 cell_type: 'code',
1974 source: text,
1975 metadata: {
1976 trusted: false
1977 }
1978 });
1979 }
1980 else {
1981 notebookModel.insertCell(index + 1, {
1982 cell_type: 'code',
1983 source: text,
1984 metadata: {
1985 trusted: false
1986 }
1987 });
1988 }
1989 }
1990 /**
1991 * Get the selected cell(s) without affecting the clipboard.
1992 *
1993 * @param notebook - The target notebook widget.
1994 *
1995 * @returns A list of 0 or more selected cells
1996 */
1997 function selectedCells(notebook) {
1998 return notebook.widgets
1999 .filter(cell => notebook.isSelectedOrActive(cell))
2000 .map(cell => cell.model.toJSON())
2001 .map(cellJSON => {
2002 if (cellJSON.metadata.deletable !== undefined) {
2003 delete cellJSON.metadata.deletable;
2004 }
2005 return cellJSON;
2006 });
2007 }
2008 Private.selectedCells = selectedCells;
2009 /**
2010 * Copy or cut the selected cell data to the application clipboard.
2011 *
2012 * @param notebook - The target notebook widget.
2013 *
2014 * @param cut - True if the cells should be cut, false if they should be copied.
2015 */
2016 function copyOrCut(notebook, cut) {
2017 if (!notebook.model || !notebook.activeCell) {
2018 return;
2019 }
2020 const state = getState(notebook);
2021 const clipboard = Clipboard.getInstance();
2022 notebook.mode = 'command';
2023 clipboard.clear();
2024 const data = Private.selectedCells(notebook);
2025 clipboard.setData(JUPYTER_CELL_MIME, data);
2026 if (cut) {
2027 deleteCells(notebook);
2028 }
2029 else {
2030 notebook.deselectAll();
2031 }
2032 if (cut) {
2033 notebook.lastClipboardInteraction = 'cut';
2034 }
2035 else {
2036 notebook.lastClipboardInteraction = 'copy';
2037 }
2038 handleState(notebook, state);
2039 }
2040 Private.copyOrCut = copyOrCut;
2041 /**
2042 * Change the selected cell type(s).
2043 *
2044 * @param notebook - The target notebook widget.
2045 *
2046 * @param value - The target cell type.
2047 *
2048 * #### Notes
2049 * It should preserve the widget mode.
2050 * This action can be undone.
2051 * The existing selection will be cleared.
2052 * Any cells converted to markdown will be unrendered.
2053 */
2054 function changeCellType(notebook, value) {
2055 const notebookSharedModel = notebook.model.sharedModel;
2056 notebook.widgets.forEach((child, index) => {
2057 if (!notebook.isSelectedOrActive(child)) {
2058 return;
2059 }
2060 if (child.model.type !== value) {
2061 const raw = child.model.toJSON();
2062 notebookSharedModel.transact(() => {
2063 notebookSharedModel.deleteCell(index);
2064 if (value === 'code') {
2065 // After change of type outputs are deleted so cell can be trusted.
2066 raw.metadata.trusted = true;
2067 }
2068 else {
2069 // Otherwise clear the metadata as trusted is only "valid" on code
2070 // cells (since other cell types cannot have outputs).
2071 raw.metadata.trusted = undefined;
2072 }
2073 const newCell = notebookSharedModel.insertCell(index, {
2074 cell_type: value,
2075 source: raw.source,
2076 metadata: raw.metadata
2077 });
2078 if (raw.attachments && ['markdown', 'raw'].includes(value)) {
2079 newCell.attachments =
2080 raw.attachments;
2081 }
2082 });
2083 }
2084 if (value === 'markdown') {
2085 // Fetch the new widget and unrender it.
2086 child = notebook.widgets[index];
2087 child.rendered = false;
2088 }
2089 });
2090 notebook.deselectAll();
2091 }
2092 Private.changeCellType = changeCellType;
2093 /**
2094 * Delete the selected cells.
2095 *
2096 * @param notebook - The target notebook widget.
2097 *
2098 * #### Notes
2099 * The cell after the last selected cell will be activated.
2100 * If the last cell is deleted, then the previous one will be activated.
2101 * It will add a code cell if all cells are deleted.
2102 * This action can be undone.
2103 */
2104 function deleteCells(notebook) {
2105 const model = notebook.model;
2106 const sharedModel = model.sharedModel;
2107 const toDelete = [];
2108 notebook.mode = 'command';
2109 // Find the cells to delete.
2110 notebook.widgets.forEach((child, index) => {
2111 var _a;
2112 const deletable = child.model.getMetadata('deletable') !== false;
2113 if (notebook.isSelectedOrActive(child) && deletable) {
2114 toDelete.push(index);
2115 (_a = notebook.model) === null || _a === void 0 ? void 0 : _a.deletedCells.push(child.model.id);
2116 }
2117 });
2118 // If cells are not deletable, we may not have anything to delete.
2119 if (toDelete.length > 0) {
2120 // Delete the cells as one undo event.
2121 sharedModel.transact(() => {
2122 // Delete cells in reverse order to maintain the correct indices.
2123 toDelete.reverse().forEach(index => {
2124 sharedModel.deleteCell(index);
2125 });
2126 // Add a new cell if the notebook is empty. This is done
2127 // within the compound operation to make the deletion of
2128 // a notebook's last cell undoable.
2129 if (sharedModel.cells.length == toDelete.length) {
2130 sharedModel.insertCell(0, {
2131 cell_type: notebook.notebookConfig.defaultCell,
2132 metadata: notebook.notebookConfig.defaultCell === 'code'
2133 ? {
2134 // This is an empty cell created in empty notebook, thus is trusted
2135 trusted: true
2136 }
2137 : {}
2138 });
2139 }
2140 });
2141 // Select the *first* interior cell not deleted or the cell
2142 // *after* the last selected cell.
2143 // Note: The activeCellIndex is clamped to the available cells,
2144 // so if the last cell is deleted the previous cell will be activated.
2145 // The *first* index is the index of the last cell in the initial
2146 // toDelete list due to the `reverse` operation above.
2147 notebook.activeCellIndex = toDelete[0] - toDelete.length + 1;
2148 }
2149 // Deselect any remaining, undeletable cells. Do this even if we don't
2150 // delete anything so that users are aware *something* happened.
2151 notebook.deselectAll();
2152 }
2153 Private.deleteCells = deleteCells;
2154 /**
2155 * Set the markdown header level of a cell.
2156 */
2157 function setMarkdownHeader(cell, level) {
2158 // Remove existing header or leading white space.
2159 let source = cell.sharedModel.getSource();
2160 const regex = /^(#+\s*)|^(\s*)/;
2161 const newHeader = Array(level + 1).join('#') + ' ';
2162 const matches = regex.exec(source);
2163 if (matches) {
2164 source = source.slice(matches[0].length);
2165 }
2166 cell.sharedModel.setSource(newHeader + source);
2167 }
2168 Private.setMarkdownHeader = setMarkdownHeader;
2169 /** Functionality related to collapsible headings */
2170 let Headings;
2171 (function (Headings) {
2172 /** Find the heading that is parent to cell.
2173 *
2174 * @param childCell - The cell that is child to the sought heading
2175 * @param notebook - The target notebook widget
2176 * @param includeChildCell [default=false] - if set to true and childCell is a heading itself, the childCell will be returned
2177 * @param returnIndex [default=false] - if set to true, the cell index is returned rather than the cell object.
2178 *
2179 * @returns the (index | Cell object) of the parent heading or (-1 | null) if there is no parent heading.
2180 */
2181 function findParentHeading(childCell, notebook, includeChildCell = false, returnIndex = false) {
2182 let cellIdx = notebook.widgets.indexOf(childCell) - (includeChildCell ? 1 : 0);
2183 while (cellIdx >= 0) {
2184 let headingInfo = NotebookActions.getHeadingInfo(notebook.widgets[cellIdx]);
2185 if (headingInfo.isHeading) {
2186 return returnIndex ? cellIdx : notebook.widgets[cellIdx];
2187 }
2188 cellIdx--;
2189 }
2190 return returnIndex ? -1 : null;
2191 }
2192 Headings.findParentHeading = findParentHeading;
2193 /** Find heading above with leq level than baseCell heading level.
2194 *
2195 * @param baseCell - cell relative to which so search
2196 * @param notebook - target notebook widget
2197 * @param returnIndex [default=false] - if set to true, the cell index is returned rather than the cell object.
2198 *
2199 * @returns the (index | Cell object) of the found heading or (-1 | null) if no heading found.
2200 */
2201 function findLowerEqualLevelParentHeadingAbove(baseCell, notebook, returnIndex = false) {
2202 let baseHeadingLevel = Private.Headings.determineHeadingLevel(baseCell, notebook);
2203 if (baseHeadingLevel == -1) {
2204 baseHeadingLevel = 1; // if no heading level can be determined, assume we're on level 1
2205 }
2206 // find the heading above with heading level <= baseHeadingLevel and return its index
2207 let cellIdx = notebook.widgets.indexOf(baseCell) - 1;
2208 while (cellIdx >= 0) {
2209 let cell = notebook.widgets[cellIdx];
2210 let headingInfo = NotebookActions.getHeadingInfo(cell);
2211 if (headingInfo.isHeading &&
2212 headingInfo.headingLevel <= baseHeadingLevel) {
2213 return returnIndex ? cellIdx : cell;
2214 }
2215 cellIdx--;
2216 }
2217 return returnIndex ? -1 : null; // no heading found
2218 }
2219 Headings.findLowerEqualLevelParentHeadingAbove = findLowerEqualLevelParentHeadingAbove;
2220 /** Find next heading with equal or lower level.
2221 *
2222 * @param baseCell - cell relative to which so search
2223 * @param notebook - target notebook widget
2224 * @param returnIndex [default=false] - if set to true, the cell index is returned rather than the cell object.
2225 *
2226 * @returns the (index | Cell object) of the found heading or (-1 | null) if no heading found.
2227 */
2228 function findLowerEqualLevelHeadingBelow(baseCell, notebook, returnIndex = false) {
2229 let baseHeadingLevel = Private.Headings.determineHeadingLevel(baseCell, notebook);
2230 if (baseHeadingLevel == -1) {
2231 baseHeadingLevel = 1; // if no heading level can be determined, assume we're on level 1
2232 }
2233 let cellIdx = notebook.widgets.indexOf(baseCell) + 1;
2234 while (cellIdx < notebook.widgets.length) {
2235 let cell = notebook.widgets[cellIdx];
2236 let headingInfo = NotebookActions.getHeadingInfo(cell);
2237 if (headingInfo.isHeading &&
2238 headingInfo.headingLevel <= baseHeadingLevel) {
2239 return returnIndex ? cellIdx : cell;
2240 }
2241 cellIdx++;
2242 }
2243 return returnIndex ? -1 : null;
2244 }
2245 Headings.findLowerEqualLevelHeadingBelow = findLowerEqualLevelHeadingBelow;
2246 /** Find next heading.
2247 *
2248 * @param baseCell - cell relative to which so search
2249 * @param notebook - target notebook widget
2250 * @param returnIndex [default=false] - if set to true, the cell index is returned rather than the cell object.
2251 *
2252 * @returns the (index | Cell object) of the found heading or (-1 | null) if no heading found.
2253 */
2254 function findHeadingBelow(baseCell, notebook, returnIndex = false) {
2255 let cellIdx = notebook.widgets.indexOf(baseCell) + 1;
2256 while (cellIdx < notebook.widgets.length) {
2257 let cell = notebook.widgets[cellIdx];
2258 let headingInfo = NotebookActions.getHeadingInfo(cell);
2259 if (headingInfo.isHeading) {
2260 return returnIndex ? cellIdx : cell;
2261 }
2262 cellIdx++;
2263 }
2264 return returnIndex ? -1 : null;
2265 }
2266 Headings.findHeadingBelow = findHeadingBelow;
2267 /** Determine the heading level of a cell.
2268 *
2269 * @param baseCell - The cell of which the heading level shall be determined
2270 * @param notebook - The target notebook widget
2271 *
2272 * @returns the heading level or -1 if there is no parent heading
2273 *
2274 * #### Notes
2275 * If the baseCell is a heading itself, the heading level of baseCell is returned.
2276 * If the baseCell is not a heading itself, the level of the parent heading is returned.
2277 * If there is no parent heading, -1 is returned.
2278 */
2279 function determineHeadingLevel(baseCell, notebook) {
2280 let headingInfoBaseCell = NotebookActions.getHeadingInfo(baseCell);
2281 // fill baseHeadingLevel or return null if there is no heading at or above baseCell
2282 if (headingInfoBaseCell.isHeading) {
2283 return headingInfoBaseCell.headingLevel;
2284 }
2285 else {
2286 let parentHeading = findParentHeading(baseCell, notebook, true);
2287 if (parentHeading == null) {
2288 return -1;
2289 }
2290 return NotebookActions.getHeadingInfo(parentHeading).headingLevel;
2291 }
2292 }
2293 Headings.determineHeadingLevel = determineHeadingLevel;
2294 /** Insert a new heading cell at given position.
2295 *
2296 * @param cellIndex - where to insert
2297 * @param headingLevel - level of the new heading
2298 * @param notebook - target notebook
2299 *
2300 * #### Notes
2301 * Enters edit mode after insert.
2302 */
2303 async function insertHeadingAboveCellIndex(cellIndex, headingLevel, notebook) {
2304 var _a;
2305 headingLevel = Math.min(Math.max(headingLevel, 1), 6);
2306 const state = Private.getState(notebook);
2307 const model = notebook.model;
2308 const sharedModel = model.sharedModel;
2309 sharedModel.insertCell(cellIndex, {
2310 cell_type: 'markdown',
2311 source: '#'.repeat(headingLevel) + ' '
2312 });
2313 notebook.activeCellIndex = cellIndex;
2314 if (((_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.inViewport) === false) {
2315 await signalToPromise(notebook.activeCell.inViewportChanged, 200).catch(() => {
2316 // no-op
2317 });
2318 }
2319 notebook.deselectAll();
2320 Private.handleState(notebook, state, true);
2321 notebook.mode = 'edit';
2322 notebook.widgets[cellIndex].setHidden(false);
2323 }
2324 Headings.insertHeadingAboveCellIndex = insertHeadingAboveCellIndex;
2325 })(Headings = Private.Headings || (Private.Headings = {}));
2326})(Private || (Private = {}));
2327//# sourceMappingURL=actions.js.map
\No newline at end of file