UNPKG

@jupyterlab/notebook

Version:
2,881 lines 117 kB
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Clipboard, Dialog, showDialog, SystemClipboard } from '@jupyterlab/apputils';
import { CodeCell, CodeCellModel, isMarkdownCellModel, isRawCellModel, MarkdownCell } from '@jupyterlab/cells';
import { Notification } from '@jupyterlab/apputils';
import { signalToPromise } from '@jupyterlab/coreutils';
import * as nbformat from '@jupyterlab/nbformat';
import { nullTranslator } from '@jupyterlab/translation';
import { every, findIndex } from '@lumino/algorithm';
import { JSONExt } from '@lumino/coreutils';
import { Signal } from '@lumino/signaling';
import * as React from 'react';
import { runCell as defaultRunCell } from './cellexecutor';
/**
 * The mimetype used for Jupyter cell data.
 */
const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
export class KernelError extends Error {
    /**
     * Construct the kernel error.
     */
    constructor(content) {
        const errorContent = content;
        const errorName = errorContent.ename;
        const errorValue = errorContent.evalue;
        super(`KernelReplyNotOK: ${errorName} ${errorValue}`);
        this.errorName = errorName;
        this.errorValue = errorValue;
        this.traceback = errorContent.traceback;
        Object.setPrototypeOf(this, KernelError.prototype);
    }
}
/**
 * A collection of actions that run against notebooks.
 *
 * #### Notes
 * All of the actions are a no-op if there is no model on the notebook.
 * The actions set the widget `mode` to `'command'` unless otherwise specified.
 * The actions will preserve the selection on the notebook widget unless
 * otherwise specified.
 */
export class NotebookActions {
    /**
     * A signal that emits whenever a cell completes execution.
     */
    static get executed() {
        return Private.executed;
    }
    /**
     * A signal that emits whenever a cell execution is scheduled.
     */
    static get executionScheduled() {
        return Private.executionScheduled;
    }
    /**
     * A signal that emits when one notebook's cells are all executed.
     */
    static get selectionExecuted() {
        return Private.selectionExecuted;
    }
    /**
     * A signal that emits when a cell's output is cleared.
     */
    static get outputCleared() {
        return Private.outputCleared;
    }
    /**
     * A private constructor for the `NotebookActions` class.
     *
     * #### Notes
     * This class can never be instantiated. Its static member `executed` will be
     * merged with the `NotebookActions` namespace. The reason it exists as a
     * standalone class is because at run time, the `Private.executed` variable
     * does not yet exist, so it needs to be referenced via a getter.
     */
    constructor() {
        // Intentionally empty.
    }
}
/**
 * A namespace for `NotebookActions` static methods.
 */
(function (NotebookActions) {
    const READ_ONLY_ACTION_AUTO_CLOSE = 5000;
    function notifySplitReadOnlyAction(translator) {
        const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
        Notification.error(trans.__('The cell is read-only and cannot be split.'), {
            autoClose: READ_ONLY_ACTION_AUTO_CLOSE
        });
    }
    function notifyMergeReadOnlyAction(translator) {
        const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
        Notification.error(trans.__('The cell is read-only and cannot be merged.'), {
            autoClose: READ_ONLY_ACTION_AUTO_CLOSE
        });
    }
    /**
     * Split the active cell into two or more cells.
     *
     * @param notebook The target notebook widget.
     *
     * @param translator - Application translator.
     *
     * #### Notes
     * It will preserve the existing mode.
     * The last cell will be activated if no selection is found.
     * If text was selected, the cell containing the selection will
     * be activated.
     * The existing selection will be cleared.
     * The activated cell will have focus and the cursor will
     * remain in the initial position.
     * The leading whitespace in the second cell will be removed.
     * If there is no content, two empty cells will be created.
     * Both cells will have the same type as the original cell.
     * This action can be undone.
     * The original cell is preserved to maintain kernel connections.
     */
    function splitCell(notebook, translator) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        if (notebook.activeCell.model.getMetadata('editable') === false) {
            notifySplitReadOnlyAction(translator);
            return;
        }
        const state = Private.getState(notebook);
        // We force the notebook back in edit mode as splitting a cell
        // requires using the cursor position within a cell (aka it was recently in edit mode)
        // However the focus may be stolen if the action is triggered
        // from the menu entry; switching the notebook in command mode.
        notebook.mode = 'edit';
        notebook.deselectAll();
        const nbModel = notebook.model;
        const index = notebook.activeCellIndex;
        const child = notebook.widgets[index];
        const editor = child.editor;
        if (!editor) {
            // TODO
            return;
        }
        const selections = editor.getSelections();
        const orig = child.model.sharedModel.getSource();
        const offsets = [0];
        let start = -1;
        let end = -1;
        for (let i = 0; i < selections.length; i++) {
            // append start and end to handle selections
            // cursors will have same start and end
            start = editor.getOffsetAt(selections[i].start);
            end = editor.getOffsetAt(selections[i].end);
            if (start < end) {
                offsets.push(start);
                offsets.push(end);
            }
            else if (end < start) {
                offsets.push(end);
                offsets.push(start);
            }
            else {
                offsets.push(start);
            }
        }
        offsets.push(orig.length);
        const { cell_type, metadata } = child.model.sharedModel.toJSON();
        const baseMetadata = JSON.parse(JSON.stringify(metadata !== null && metadata !== void 0 ? metadata : {}));
        // If execution metadata is present but missing execute_reply (i.e., it is in running state),
        // remove execution metadata entirely for new cells
        if (cell_type === 'code' &&
            baseMetadata.execution &&
            baseMetadata.execution['iopub.execute_input'] &&
            !baseMetadata.execution['shell.execute_reply']) {
            delete baseMetadata.execution;
        }
        // Create new cells for all content pieces EXCEPT the last one
        // The last piece will remain in the original cell to preserve kernel connection
        const newCells = offsets.slice(0, -2).map((offset, offsetIdx) => ({
            cell_type,
            metadata: JSON.parse(JSON.stringify(baseMetadata)),
            source: orig
                .slice(offset, offsets[offsetIdx + 1])
                .replace(/^\n+/, '')
                .replace(/\n+$/, ''),
            outputs: undefined
        }));
        // Prepare the content for the original cell (last piece)
        const lastPieceStart = offsets[offsets.length - 2];
        const lastPieceEnd = offsets[offsets.length - 1];
        const lastPieceContent = orig
            .slice(lastPieceStart, lastPieceEnd)
            .replace(/^\n+/, '')
            .replace(/\n+$/, '');
        nbModel.sharedModel.transact(() => {
            // Insert new cells above the current cell (if any)
            if (newCells.length > 0) {
                nbModel.sharedModel.insertCells(index, newCells);
            }
            // Update the original cell with the last piece of content
            child.model.sharedModel.setSource(lastPieceContent);
            // Mark cell as dirty if it is running
            if (child.model instanceof CodeCellModel) {
                const codeCellModel = child.model;
                if (codeCellModel.executionState === 'running') {
                    codeCellModel.isDirty = true;
                }
            }
        });
        // If there was a selection, activate the cell containing the selection
        let targetCellIndex;
        if (start !== end) {
            // Find which piece contains the selection
            let selectionPieceIndex = 0;
            for (let i = 0; i < offsets.length - 1; i++) {
                if (start >= offsets[i] && start < offsets[i + 1]) {
                    selectionPieceIndex = i;
                    break;
                }
            }
            targetCellIndex = index + selectionPieceIndex;
        }
        else {
            // No selection, activate the original cell (now at the end)
            targetCellIndex = index + newCells.length;
        }
        notebook.activeCellIndex = targetCellIndex;
        notebook
            .scrollToItem(notebook.activeCellIndex)
            .then(() => {
            var _a;
            (_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.editor.focus();
        })
            .catch(reason => {
            // no-op
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.splitCell = splitCell;
    /**
     * Merge the selected cells.
     *
     * @param notebook - The target notebook widget.
     *
     * @param mergeAbove - If only one cell is selected, indicates whether to merge it
     *    with the cell above (true) or below (false, default).
     *
     * @param addExtraLine - Whether to add an extra newline between merged cell contents
     *    (true, default) or use only a single newline (false).
     *
     * @param translator - Application translator.
     *
     * #### Notes
     * The widget mode will be preserved.
     * If only one cell is selected and `mergeAbove` is true, the above cell will be selected.
     * If only one cell is selected and `mergeAbove` is false, the below cell will be selected.
     * If the active cell is a code cell, its outputs will be cleared.
     * This action can be undone.
     * The final cell will have the same type as the active cell.
     * If the active cell is a markdown cell, it will be unrendered.
     */
    function mergeCells(notebook, mergeAbove = false, addExtraLine = true, translator) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        const toMerge = [];
        const toDelete = [];
        const model = notebook.model;
        const cells = model.cells;
        const primary = notebook.activeCell;
        const active = notebook.activeCellIndex;
        const attachments = {};
        let hasReadOnlyCell = false;
        // Get the cells to merge.
        notebook.widgets.forEach((child, index) => {
            if (notebook.isSelectedOrActive(child)) {
                if (child.model.getMetadata('editable') === false) {
                    hasReadOnlyCell = true;
                    return;
                }
                toMerge.push(child.model.sharedModel.getSource());
                if (index !== active) {
                    toDelete.push(index);
                }
                // Collect attachments if the cell is a markdown cell or a raw cell
                const model = child.model;
                if (isRawCellModel(model) || isMarkdownCellModel(model)) {
                    for (const key of model.attachments.keys) {
                        attachments[key] = model.attachments.get(key).toJSON();
                    }
                }
            }
        });
        if (hasReadOnlyCell) {
            notifyMergeReadOnlyAction(translator);
            return;
        }
        // Check for only a single cell selected.
        if (toMerge.length === 1) {
            // Merge with the cell above when mergeAbove is true
            if (mergeAbove === true) {
                // Bail if it is the first cell.
                if (active === 0) {
                    return;
                }
                if (notebook.widgets[active - 1].model.getMetadata('editable') === false) {
                    notifyMergeReadOnlyAction(translator);
                    return;
                }
                // Otherwise merge with the previous cell.
                const cellModel = cells.get(active - 1);
                toMerge.unshift(cellModel.sharedModel.getSource());
                toDelete.push(active - 1);
            }
            else if (mergeAbove === false) {
                // Bail if it is the last cell.
                if (active === cells.length - 1) {
                    return;
                }
                if (notebook.widgets[active + 1].model.getMetadata('editable') === false) {
                    notifyMergeReadOnlyAction(translator);
                    return;
                }
                // Otherwise merge with the next cell.
                const cellModel = cells.get(active + 1);
                toMerge.push(cellModel.sharedModel.getSource());
                toDelete.push(active + 1);
            }
        }
        notebook.deselectAll();
        const primaryModel = primary.model.sharedModel;
        const { cell_type, metadata } = primaryModel.toJSON();
        if (primaryModel.cell_type === 'code') {
            // We can trust this cell because the outputs will be removed.
            metadata.trusted = true;
        }
        const newModel = {
            cell_type,
            metadata,
            source: toMerge.join(addExtraLine ? '\n\n' : '\n'),
            attachments: primaryModel.cell_type === 'markdown' ||
                primaryModel.cell_type === 'raw'
                ? attachments
                : undefined
        };
        // Detach kernel futures from cells about to be deleted so OutputArea.dispose()
        // does not terminate them - they stay live in kernel._futures for reconnection
        // after undo. Handlers are cleared by detachFuture() so the future no
        // longer holds references to the (soon-to-be-disposed) output area.
        const storedExecutions = [];
        [active, ...toDelete].forEach(index => {
            const cell = notebook.widgets[index];
            if (!(cell instanceof CodeCell)) {
                return;
            }
            const stored = Private.captureExecution(cell);
            if (stored) {
                storedExecutions.push(stored);
            }
        });
        // Make the changes while preserving history.
        model.sharedModel.transact(() => {
            model.sharedModel.deleteCell(active);
            model.sharedModel.insertCell(active, newModel);
            toDelete
                .sort((a, b) => b - a)
                .forEach(index => {
                model.sharedModel.deleteCell(index);
            });
        });
        // Store execution context in the undo stack item so undo() can restore state.
        if (storedExecutions.length > 0) {
            const undoManager = model.sharedModel.undoManager;
            const lastItem = undoManager.undoStack[undoManager.undoStack.length - 1];
            lastItem === null || lastItem === void 0 ? void 0 : lastItem.meta.set(Private.CELL_EXECUTION_META_KEY, storedExecutions);
        }
        // If the original cell is a markdown cell, make sure
        // the new cell is unrendered.
        if (primary instanceof MarkdownCell) {
            notebook.activeCell.rendered = false;
        }
        void Private.handleState(notebook, state);
    }
    NotebookActions.mergeCells = mergeCells;
    /**
     * Delete the selected cells.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The cell after the last selected cell will be activated.
     * It will add a code cell if all cells are deleted.
     * This action can be undone.
     */
    function deleteCells(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        Private.deleteCells(notebook);
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.deleteCells = deleteCells;
    /**
     * Insert a new code cell above the active cell or in index 0 if the notebook is empty.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The widget mode will be preserved.
     * This action can be undone.
     * The existing selection will be cleared.
     * The new cell will the active cell.
     */
    function insertAbove(notebook) {
        if (!notebook.model) {
            return;
        }
        const state = Private.getState(notebook);
        const model = notebook.model;
        const newIndex = notebook.activeCell ? notebook.activeCellIndex : 0;
        model.sharedModel.insertCell(newIndex, {
            cell_type: notebook.notebookConfig.defaultCell,
            metadata: notebook.notebookConfig.defaultCell === 'code'
                ? {
                    // This is an empty cell created by user, thus is trusted
                    trusted: true
                }
                : {}
        });
        // Make the newly inserted cell active.
        notebook.activeCellIndex = newIndex;
        notebook.deselectAll();
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.insertAbove = insertAbove;
    /**
     * Insert a new code cell below the active cell or in index 0 if the notebook is empty.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The widget mode will be preserved.
     * This action can be undone.
     * The existing selection will be cleared.
     * The new cell will be the active cell.
     */
    function insertBelow(notebook) {
        if (!notebook.model) {
            return;
        }
        const state = Private.getState(notebook);
        const model = notebook.model;
        const newIndex = notebook.activeCell ? notebook.activeCellIndex + 1 : 0;
        model.sharedModel.insertCell(newIndex, {
            cell_type: notebook.notebookConfig.defaultCell,
            metadata: notebook.notebookConfig.defaultCell === 'code'
                ? {
                    // This is an empty cell created by user, thus is trusted
                    trusted: true
                }
                : {}
        });
        // Make the newly inserted cell active.
        notebook.activeCellIndex = newIndex;
        notebook.deselectAll();
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.insertBelow = insertBelow;
    function move(notebook, shift) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        const firstIndex = notebook.widgets.findIndex(w => notebook.isSelectedOrActive(w));
        let lastIndex = notebook.widgets
            .slice(firstIndex + 1)
            .findIndex(w => !notebook.isSelectedOrActive(w));
        if (lastIndex >= 0) {
            lastIndex += firstIndex + 1;
        }
        else {
            lastIndex = notebook.model.cells.length;
        }
        const toIndex = shift > 0 ? lastIndex : firstIndex + shift;
        moveCells(notebook, firstIndex, toIndex, lastIndex - firstIndex);
        void Private.handleState(notebook, state, true);
    }
    /**
     * Move cells while preserving in-flight kernel futures.
     *
     * The underlying `jupyter-ydoc` `moveCells` implementation currently
     * serializes cells to JSON and recreates them via a delete + insert
     * transaction, which disposes any active kernel futures attached to the
     * old widgets. This wrapper detaches futures before the move and
     * reattaches them to the new widgets afterwards, and stores them in the
     * undo stack so that undoing the move also restores execution state.
     *
     * @param notebook - The target notebook.
     * @param from - Index of the first cell to move.
     * @param to - Target index (as passed to `notebook.moveCell`).
     * @param n - Number of cells to move.
     */
    function moveCells(notebook, from, to, n = 1) {
        if (!notebook.model) {
            return;
        }
        // Mirror `Notebook.moveCell`'s bounding/no-op logic so that we do not
        // capture futures or touch the undo stack when the move won't happen.
        // Otherwise we could attach execution metadata to an unrelated previous
        // undo item, corrupting subsequent undo behavior.
        const boundedTo = Math.min(notebook.model.cells.length - 1, Math.max(0, to));
        if (boundedTo === from) {
            return;
        }
        // moveCells serializes cells to JSON and recreates widgets (delete+insert),
        // which would dispose any in-flight futures. Capture them first.
        const storedExecutions = [];
        notebook.widgets.slice(from, from + n).forEach(child => {
            if (!(child instanceof CodeCell)) {
                return;
            }
            const stored = Private.captureExecution(child);
            if (stored) {
                storedExecutions.push(stored);
            }
        });
        notebook.moveCell(from, to, n);
        // Immediately reconnect futures to the newly created widgets.
        for (const stored of storedExecutions) {
            Private.restoreExecution(notebook, stored);
        }
        // Store in the undo stack so that undoing the move can also restore state.
        if (storedExecutions.length > 0) {
            const undoManager = notebook.model.sharedModel.undoManager;
            const lastItem = undoManager.undoStack[undoManager.undoStack.length - 1];
            lastItem === null || lastItem === void 0 ? void 0 : lastItem.meta.set(Private.CELL_EXECUTION_META_KEY, storedExecutions);
        }
    }
    NotebookActions.moveCells = moveCells;
    /**
     * Move the selected cell(s) down.
     *
     * @param notebook = The target notebook widget.
     */
    function moveDown(notebook) {
        move(notebook, 1);
    }
    NotebookActions.moveDown = moveDown;
    /**
     * Move the selected cell(s) up.
     *
     * @param notebook - The target notebook widget.
     */
    function moveUp(notebook) {
        move(notebook, -1);
    }
    NotebookActions.moveUp = moveUp;
    /**
     * Change the selected cell type(s).
     *
     * @param notebook - The target notebook widget.
     * @param value - The target cell type.
     * @param translator - The application translator.
     *
     * #### Notes
     * It should preserve the widget mode.
     * This action can be undone.
     * The existing selection will be cleared.
     * Any cells converted to markdown will be unrendered.
     */
    function changeCellType(notebook, value, translator) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        Private.changeCellType(notebook, value, { translator });
        void Private.handleState(notebook, state);
    }
    NotebookActions.changeCellType = changeCellType;
    /**
     * Run the selected cell(s).
     *
     * @param notebook - The target notebook widget.
     * @param sessionContext - The client session object.
     * @param sessionDialogs - The session dialogs.
     * @param translator - The application translator.
     *
     * #### Notes
     * The last selected cell will be activated, but not scrolled into view.
     * The existing selection will be cleared.
     * An execution error will prevent the remaining code cells from executing.
     * All markdown cells will be rendered.
     */
    function run(notebook, sessionContext, sessionDialogs, translator) {
        if (!notebook.model || !notebook.activeCell) {
            return Promise.resolve(false);
        }
        const state = Private.getState(notebook);
        const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
        void Private.handleRunState(notebook, state);
        return promise;
    }
    NotebookActions.run = run;
    /**
     * Run specified cells.
     *
     * @param notebook - The target notebook widget.
     * @param cells - The cells to run.
     * @param sessionContext - The client session object.
     * @param sessionDialogs - The session dialogs.
     * @param translator - The application translator.
     *
     * #### Notes
     * The existing selection will be preserved.
     * The mode will be changed to command.
     * An execution error will prevent the remaining code cells from executing.
     * All markdown cells will be rendered.
     */
    function runCells(notebook, cells, sessionContext, sessionDialogs, translator) {
        if (!notebook.model) {
            return Promise.resolve(false);
        }
        const state = Private.getState(notebook);
        const promise = Private.runCells(notebook, cells, sessionContext, sessionDialogs, translator);
        void Private.handleRunState(notebook, state);
        return promise;
    }
    NotebookActions.runCells = runCells;
    /**
     * Run the selected cell(s) and advance to the next cell.
     *
     * @param notebook - The target notebook widget.
     * @param sessionContext - The client session object.
     * @param sessionDialogs - The session dialogs.
     * @param translator - The application translator.
     *
     * #### Notes
     * The existing selection will be cleared.
     * The cell after the last selected cell will be activated and scrolled into view.
     * An execution error will prevent the remaining code cells from executing.
     * All markdown cells will be rendered.
     * If the last selected cell is the last cell, a new code cell
     * will be created in `'edit'` mode.  The new cell creation can be undone.
     */
    async function runAndAdvance(notebook, sessionContext, sessionDialogs, translator) {
        var _a;
        if (!notebook.model || !notebook.activeCell) {
            return Promise.resolve(false);
        }
        const state = Private.getState(notebook);
        const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
        const model = notebook.model;
        if (notebook.activeCellIndex === notebook.widgets.length - 1) {
            // Do not use push here, as we want an widget insertion
            // to make sure no placeholder widget is rendered.
            model.sharedModel.insertCell(notebook.widgets.length, {
                cell_type: notebook.notebookConfig.defaultCell,
                metadata: notebook.notebookConfig.defaultCell === 'code'
                    ? {
                        // This is an empty cell created by user, thus is trusted
                        trusted: true
                    }
                    : {}
            });
            notebook.activeCellIndex++;
            if (((_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.inViewport) === false) {
                await signalToPromise(notebook.activeCell.inViewportChanged, 200).catch(() => {
                    // no-op
                });
            }
            notebook.mode = 'edit';
        }
        else {
            notebook.activeCellIndex++;
        }
        // If a cell is outside of viewport and scrolling is needed, the `smart`
        // logic in `handleRunState` will choose appropriate alignment, except
        // for the case of a small cell less than one viewport away for which it
        // would use the `auto` heuristic, for which we set the preferred alignment
        // to `center` as in most cases there will be space below and above a cell
        // that is smaller than (or approximately equal to) the viewport size.
        void Private.handleRunState(notebook, state, 'center');
        return promise;
    }
    NotebookActions.runAndAdvance = runAndAdvance;
    /**
     * Run the selected cell(s) and insert a new code cell.
     *
     * @param notebook - The target notebook widget.
     * @param sessionContext - The client session object.
     * @param sessionDialogs - The session dialogs.
     * @param translator - The application translator.
     *
     * #### Notes
     * An execution error will prevent the remaining code cells from executing.
     * All markdown cells will be rendered.
     * The widget mode will be set to `'edit'` after running.
     * The existing selection will be cleared.
     * The cell insert can be undone.
     * The new cell will be scrolled into view.
     */
    async function runAndInsert(notebook, sessionContext, sessionDialogs, translator) {
        var _a;
        if (!notebook.model || !notebook.activeCell) {
            return Promise.resolve(false);
        }
        const state = Private.getState(notebook);
        const promise = Private.runSelected(notebook, sessionContext, sessionDialogs, translator);
        const model = notebook.model;
        model.sharedModel.insertCell(notebook.activeCellIndex + 1, {
            cell_type: notebook.notebookConfig.defaultCell,
            metadata: notebook.notebookConfig.defaultCell === 'code'
                ? {
                    // This is an empty cell created by user, thus is trusted
                    trusted: true
                }
                : {}
        });
        notebook.activeCellIndex++;
        if (((_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.inViewport) === false) {
            await signalToPromise(notebook.activeCell.inViewportChanged, 200).catch(() => {
                // no-op
            });
        }
        notebook.mode = 'edit';
        void Private.handleRunState(notebook, state, 'center');
        return promise;
    }
    NotebookActions.runAndInsert = runAndInsert;
    /**
     * Run all of the cells in the notebook.
     *
     * @param notebook - The target notebook widget.
     * @param sessionContext - The client session object.
     * @param sessionDialogs - The session dialogs.
     * @param translator - The application translator.
     *
     * #### Notes
     * The existing selection will be cleared.
     * An execution error will prevent the remaining code cells from executing.
     * All markdown cells will be rendered.
     * The last cell in the notebook will be activated and scrolled into view.
     */
    function runAll(notebook, sessionContext, sessionDialogs, translator) {
        if (!notebook.model || !notebook.activeCell) {
            return Promise.resolve(false);
        }
        const state = Private.getState(notebook);
        const lastIndex = notebook.widgets.length;
        const promise = Private.runCells(notebook, notebook.widgets, sessionContext, sessionDialogs, translator);
        notebook.activeCellIndex = lastIndex;
        notebook.deselectAll();
        void Private.handleRunState(notebook, state);
        return promise;
    }
    NotebookActions.runAll = runAll;
    function renderAllMarkdown(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return Promise.resolve(false);
        }
        const previousIndex = notebook.activeCellIndex;
        const state = Private.getState(notebook);
        notebook.widgets.forEach((child, index) => {
            if (child.model.type === 'markdown') {
                notebook.select(child);
                // This is to make sure that the activeCell
                // does not get executed
                notebook.activeCellIndex = index;
            }
        });
        if (notebook.activeCell.model.type !== 'markdown') {
            return Promise.resolve(true);
        }
        const promise = Private.runSelected(notebook);
        notebook.activeCellIndex = previousIndex;
        void Private.handleRunState(notebook, state);
        return promise;
    }
    NotebookActions.renderAllMarkdown = renderAllMarkdown;
    /**
     * Run all of the cells before the currently active cell (exclusive).
     *
     * @param notebook - The target notebook widget.
     * @param sessionContext - The client session object.
     * @param sessionDialogs - The session dialogs.
     * @param translator - The application translator.
     *
     * #### Notes
     * The existing selection will be cleared.
     * An execution error will prevent the remaining code cells from executing.
     * All markdown cells will be rendered.
     * The currently active cell will remain selected.
     */
    function runAllAbove(notebook, sessionContext, sessionDialogs, translator) {
        const { activeCell, activeCellIndex, model } = notebook;
        if (!model || !activeCell || activeCellIndex < 1) {
            return Promise.resolve(false);
        }
        const state = Private.getState(notebook);
        const promise = Private.runCells(notebook, notebook.widgets.slice(0, notebook.activeCellIndex), sessionContext, sessionDialogs, translator);
        notebook.deselectAll();
        void Private.handleRunState(notebook, state);
        return promise;
    }
    NotebookActions.runAllAbove = runAllAbove;
    /**
     * Run all of the cells after the currently active cell (inclusive).
     *
     * @param notebook - The target notebook widget.
     * @param sessionContext - The client session object.
     * @param sessionDialogs - The session dialogs.
     * @param translator - The application translator.
     *
     * #### Notes
     * The existing selection will be cleared.
     * An execution error will prevent the remaining code cells from executing.
     * All markdown cells will be rendered.
     * The last cell in the notebook will be activated and scrolled into view.
     */
    function runAllBelow(notebook, sessionContext, sessionDialogs, translator) {
        if (!notebook.model || !notebook.activeCell) {
            return Promise.resolve(false);
        }
        const state = Private.getState(notebook);
        const lastIndex = notebook.widgets.length;
        const promise = Private.runCells(notebook, notebook.widgets.slice(notebook.activeCellIndex), sessionContext, sessionDialogs, translator);
        notebook.activeCellIndex = lastIndex;
        notebook.deselectAll();
        void Private.handleRunState(notebook, state);
        return promise;
    }
    NotebookActions.runAllBelow = runAllBelow;
    /**
     * Replaces the selection in the active cell of the notebook.
     *
     * @param notebook - The target notebook widget.
     * @param text - The text to replace the selection.
     */
    function replaceSelection(notebook, text) {
        var _a, _b, _c;
        if (!notebook.model || !((_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.editor)) {
            return;
        }
        (_c = (_b = notebook.activeCell.editor).replaceSelection) === null || _c === void 0 ? void 0 : _c.call(_b, text);
    }
    NotebookActions.replaceSelection = replaceSelection;
    /**
     * Select the above the active cell.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The widget mode will be preserved.
     * This is a no-op if the first cell is the active cell.
     * This will skip any collapsed cells.
     * The existing selection will be cleared.
     */
    function selectAbove(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const footer = notebook.layout.footer;
        if (footer && document.activeElement === footer.node) {
            footer.node.blur();
            notebook.mode = 'command';
            return;
        }
        if (notebook.activeCellIndex === 0) {
            return;
        }
        let possibleNextCellIndex = notebook.activeCellIndex - 1;
        // find first non hidden cell above current cell
        while (possibleNextCellIndex >= 0) {
            const possibleNextCell = notebook.widgets[possibleNextCellIndex];
            if (!possibleNextCell.inputHidden && !possibleNextCell.isHidden) {
                break;
            }
            possibleNextCellIndex -= 1;
        }
        const state = Private.getState(notebook);
        notebook.activeCellIndex = possibleNextCellIndex;
        notebook.deselectAll();
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.selectAbove = selectAbove;
    /**
     * Select the cell below the active cell.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The widget mode will be preserved.
     * This is a no-op if the last cell is the active cell.
     * This will skip any collapsed cells.
     * The existing selection will be cleared.
     */
    function selectBelow(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        let maxCellIndex = notebook.widgets.length - 1;
        // Find last non-hidden cell
        while (notebook.widgets[maxCellIndex].isHidden ||
            notebook.widgets[maxCellIndex].inputHidden) {
            maxCellIndex -= 1;
        }
        if (notebook.activeCellIndex === maxCellIndex) {
            const footer = notebook.layout.footer;
            footer === null || footer === void 0 ? void 0 : footer.node.focus();
            return;
        }
        let possibleNextCellIndex = notebook.activeCellIndex + 1;
        // find first non hidden cell below current cell
        while (possibleNextCellIndex < maxCellIndex) {
            let possibleNextCell = notebook.widgets[possibleNextCellIndex];
            if (!possibleNextCell.inputHidden && !possibleNextCell.isHidden) {
                break;
            }
            possibleNextCellIndex += 1;
        }
        const state = Private.getState(notebook);
        notebook.activeCellIndex = possibleNextCellIndex;
        notebook.deselectAll();
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.selectBelow = selectBelow;
    /** Insert new heading of same level above active cell.
     *
     * @param notebook - The target notebook widget
     */
    async function insertSameLevelHeadingAbove(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        let headingLevel = Private.Headings.determineHeadingLevel(notebook.activeCell, notebook);
        if (headingLevel == -1) {
            await Private.Headings.insertHeadingAboveCellIndex(0, 1, notebook);
        }
        else {
            await Private.Headings.insertHeadingAboveCellIndex(notebook.activeCellIndex, headingLevel, notebook);
        }
    }
    NotebookActions.insertSameLevelHeadingAbove = insertSameLevelHeadingAbove;
    /** Insert new heading of same level at end of current section.
     *
     * @param notebook - The target notebook widget
     */
    async function insertSameLevelHeadingBelow(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        let headingLevel = Private.Headings.determineHeadingLevel(notebook.activeCell, notebook);
        headingLevel = headingLevel > -1 ? headingLevel : 1;
        let cellIdxOfHeadingBelow = Private.Headings.findLowerEqualLevelHeadingBelow(notebook.activeCell, notebook, true);
        await Private.Headings.insertHeadingAboveCellIndex(cellIdxOfHeadingBelow == -1
            ? notebook.model.cells.length
            : cellIdxOfHeadingBelow, headingLevel, notebook);
    }
    NotebookActions.insertSameLevelHeadingBelow = insertSameLevelHeadingBelow;
    /**
     * Select the heading above the active cell or, if already at heading, collapse it.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The widget mode will be preserved.
     * This is a no-op if the active cell is the topmost heading in collapsed state
     * The existing selection will be cleared.
     */
    function selectHeadingAboveOrCollapseHeading(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        let hInfoActiveCell = getHeadingInfo(notebook.activeCell);
        // either collapse or find the right heading to jump to
        if (hInfoActiveCell.isHeading && !hInfoActiveCell.collapsed) {
            setHeadingCollapse(notebook.activeCell, true, notebook);
        }
        else {
            let targetHeadingCellIdx = Private.Headings.findLowerEqualLevelParentHeadingAbove(notebook.activeCell, notebook, true);
            if (targetHeadingCellIdx > -1) {
                notebook.activeCellIndex = targetHeadingCellIdx;
            }
        }
        // clear selection and handle state
        notebook.deselectAll();
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.selectHeadingAboveOrCollapseHeading = selectHeadingAboveOrCollapseHeading;
    /**
     * Select the heading below the active cell or, if already at heading, expand it.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The widget mode will be preserved.
     * This is a no-op if the active cell is the last heading in expanded state
     * The existing selection will be cleared.
     */
    function selectHeadingBelowOrExpandHeading(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        let hInfo = getHeadingInfo(notebook.activeCell);
        if (hInfo.isHeading && hInfo.collapsed) {
            setHeadingCollapse(notebook.activeCell, false, notebook);
        }
        else {
            let targetHeadingCellIdx = Private.Headings.findHeadingBelow(notebook.activeCell, notebook, true // return index of heading cell
            );
            if (targetHeadingCellIdx > -1) {
                notebook.activeCellIndex = targetHeadingCellIdx;
            }
        }
        notebook.deselectAll();
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.selectHeadingBelowOrExpandHeading = selectHeadingBelowOrExpandHeading;
    /**
     * Extend the selection to the cell above.
     *
     * @param notebook - The target notebook widget.
     * @param toTop - If true, denotes selection to extend to the top.
     *
     * #### Notes
     * This is a no-op if the first cell is the active cell.
     * The new cell will be activated.
     */
    function extendSelectionAbove(notebook, toTop = false) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        // Do not wrap around.
        if (notebook.activeCellIndex === 0) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.mode = 'command';
        // Check if toTop is true, if yes, selection is made to the top.
        if (toTop) {
            notebook.extendContiguousSelectionTo(0);
        }
        else {
            notebook.extendContiguousSelectionTo(notebook.activeCellIndex - 1);
        }
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.extendSelectionAbove = extendSelectionAbove;
    /**
     * Extend the selection to the cell below.
     *
     * @param notebook - The target notebook widget.
     * @param toBottom - If true, denotes selection to extend to the bottom.
     *
     * #### Notes
     * This is a no-op if the last cell is the active cell.
     * The new cell will be activated.
     */
    function extendSelectionBelow(notebook, toBottom = false) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        // Do not wrap around.
        if (notebook.activeCellIndex === notebook.widgets.length - 1) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.mode = 'command';
        // Check if toBottom is true, if yes selection is made to the bottom.
        if (toBottom) {
            notebook.extendContiguousSelectionTo(notebook.widgets.length - 1);
        }
        else {
            notebook.extendContiguousSelectionTo(notebook.activeCellIndex + 1);
        }
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.extendSelectionBelow = extendSelectionBelow;
    /**
     * Select all of the cells of the notebook.
     *
     * @param notebook - the target notebook widget.
     */
    function selectAll(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        notebook.widgets.forEach(child => {
            notebook.select(child);
        });
    }
    NotebookActions.selectAll = selectAll;
    /**
     * Deselect all of the cells of the notebook.
     *
     * @param notebook - the target notebook widget.
     */
    function deselectAll(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        notebook.deselectAll();
    }
    NotebookActions.deselectAll = deselectAll;
    /**
     * Copy the selected cell(s) data to a clipboard.
     *
     * @param notebook - The target notebook widget.
     */
    function copy(notebook) {
        Private.copyOrCut(notebook, false);
    }
    NotebookActions.copy = copy;
    /**
     * Copy the selected cell(s) data to the system clipboard.
     *
     * @param notebook - The target notebook widget.
     */
    async function copyToSystemClipboard(notebook) {
        await Private.copyOrCutToSystemClipboard(notebook, false);
    }
    NotebookActions.copyToSystemClipboard = copyToSystemClipboard;
    /**
     * Cut the selected cell data to a clipboard.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * This action can be undone.
     * A new code cell is added if all cells are cut.
     */
    function cut(notebook) {
        Private.copyOrCut(notebook, true);
    }
    NotebookActions.cut = cut;
    /**
     * Cut the selected cell data to the system clipboard.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * This action can be undone.
     * A new code cell is added if all cells are cut.
     */
    async function cutToSystemClipboard(notebook) {
        await Private.copyOrCutToSystemClipboard(notebook, true);
    }
    NotebookActions.cutToSystemClipboard = cutToSystemClipboard;
    /**
     * Paste cells from the application clipboard.
     *
     * @param notebook - The target notebook widget.
     *
     * @param mode - the mode of adding cells:
     *   'below' (default) adds cells below the active cell,
     *   'belowSelected' adds cells below all selected cells,
     *   'above' adds cells above the active cell, and
     *   'replace' removes the currently selected cells and adds cells in their place.
     *
     * @param options - Optional. Set `stripOutputs: true` to paste code cells without their outputs.
     *
     * #### Notes
     * The last pasted cell becomes the active cell.
     * This is a no-op if there is no cell data on the clipboard.
     * This action can be undone.
     */
    function paste(notebook, mode = 'below', options) {
        const clipboard = Clipboard.getInstance();
        if (!clipboard.hasData(JUPYTER_CELL_MIME)) {
            return;
        }
        let values = clipboard.getData(JUPYTER_CELL_MIME);
        if (options === null || options === void 0 ? void 0 : options.stripOutputs) {
            values = Private.stripCodeCellOutputs(values);
        }
        addCells(notebook, mode, values, true);
        void focusActiveCell(notebook);
    }
    NotebookActions.paste = paste;
    /**
     * Paste cells from the system clipboard.
     *
     * @param notebook - The target notebook widget.
     *
     * @param mode - the mode of adding cells:
     *   'below' (default) adds cells below the active cell,
     *   'belowSelected' adds cells below all selected cells,
     *   'above' adds cells above the active cell, and
     *   'replace' removes the currently selected cells and adds cells in their place.
     *
     * @param options - Optional. Set `stripOutputs: true` to paste code cells without their outputs.
     *
     * #### Notes
     * The last pasted cell becomes the active cell.
     * This is a no-op if there is no cell data on the clipboard.
     * This action can be undone.
     */
    async function pasteFromSystemClipboard(notebook, mode = 'below', options) {
        const clipboard = SystemClipboard.getInstance();
        const stored = await clipboard.getData(JUPYTER_CELL_MIME);
        if (stored === null || stored === undefined) {
            return;
        }
        let values = stored;
        if (options === null || options === void 0 ? void 0 : options.stripOutputs) {
            values = Private.stripCodeCellOutputs(values);
        }
        addCells(notebook, mode, values, true);
        void focusActiveCell(notebook);
    }
    NotebookActions.pasteFromSystemClipboard = pasteFromSystemClipboard;
    /**
     * Duplicate selected cells in the notebook without using the application clipboard.
     *
     * @param notebook - The target notebook widget.
     *
     * @param mode - the mode of adding cells:
     *   'below' (default) adds cells below the active cell,
     *   'belowSelected' adds cells below all selected cells,
     *   'above' adds cells above the active cell, and
     *   'replace' removes the currently selected cells and adds cells in their place.
     *
     * #### Notes
     * The last pasted cell becomes the active cell.
     * This is a no-op if there is no cell data on the clipboard.
     * This action can be undone.
     */
    function duplicate(notebook, mode = 'below') {
        const values = Private.selectedCells(notebook);
        if (!values || values.length === 0) {
            return;
        }
        addCells(notebook, mode, values, false); // Cells not from the clipboard
    }
    NotebookActions.duplicate = duplicate;
    /**
     * Adds cells to the notebook.
     *
     * @param notebook - The target notebook widget.
     *
     * @param mode - the mode of adding cells:
     *   'below' (default) adds cells below the active cell,
     *   'belowSelected' adds cells below all selected cells,
     *   'above' adds cells above the active cell, and
     *   'replace' removes the currently selected cells and adds cells in their place.
     *
     * @param values — The cells to add to the notebook.
     *
     * @param cellsFromClipboard — True if the cells were sourced from the clipboard.
     *
     * #### Notes
     * The last added cell becomes the active cell.
     * This is a no-op if values is an empty array.
     * This action can be undone.
     */
    function addCells(notebook, mode = 'below', values, cellsFromClipboard = false) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        const model = notebook.model;
        notebook.mode = 'command';
        let index = 0;
        const prevActiveCellIndex = notebook.activeCellIndex;
        model.sharedModel.transact(() => {
            // Set the starting index of the paste operation depending upon the mode.
            switch (mode) {
                case 'below':
                    index = notebook.activeCellIndex + 1;
                    break;
                case 'belowSelected':
                    notebook.widgets.forEach((child, childIndex) => {
                        if (notebook.isSelectedOrActive(child)) {
                            index = childIndex + 1;
                        }
                    });
                    break;
                case 'above':
                    index = notebook.activeCellIndex;
                    break;
                case 'replace': {
                    // Find the cells to delete.
                    const toDelete = [];
                    notebook.widgets.forEach((child, index) => {
                        const deletable = child.model.sharedModel.getMetadata('deletable') !== false;
                        if (notebook.isSelectedOrActive(child) && deletable) {
                            toDelete.push(index);
                        }
                    });
                    // If cells are not deletable, we may not have anything to delete.
                    if (toDelete.length > 0) {
                        // Delete the cells as one undo event.
                        toDelete.reverse().forEach(i => {
                            model.sharedModel.deleteCell(i);
                        });
                    }
                    index = toDelete[0];
                    break;
                }
                default:
                    break;
            }
            model.sharedModel.insertCells(index, values.map(cell => {
                cell.id =
                    cell.cell_type === 'code' &&
                        notebook.lastClipboardInteraction === 'cut' &&
                        typeof cell.id === 'string'
                        ? cell.id
                        : undefined;
                return cell;
            }));
        });
        notebook.activeCellIndex = prevActiveCellIndex + values.length;
        notebook.deselectAll();
        if (cellsFromClipboard) {
            notebook.recordCellClipboardInteraction('paste', values);
        }
        void Private.handleState(notebook, state, true);
    }
    /**
     * Undo a cell action.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * This is a no-op if there are no cell actions to undo.
     */
    function undo(notebook) {
        if (!notebook.model) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.mode = 'command';
        const undoManager = notebook.model.sharedModel.undoManager;
        // For cells that will be MOVED by the undo (i.e. they still exist in the
        // notebook at their current position), pre-capture their futures now before
        // sharedModel.undo() destroys those widgets. This prevents OutputArea.dispose()
        // from cancelling the future during the Y.js delete+insert that implements the move.
        const topItem = undoManager.undoStack[undoManager.undoStack.length - 1];
        const pendingExecutions = topItem === null || topItem === void 0 ? void 0 : topItem.meta.get(Private.CELL_EXECUTION_META_KEY);
        const preCaptured = new Map();
        pendingExecutions === null || pendingExecutions === void 0 ? void 0 : pendingExecutions.forEach(stored => {
            var _a;
            const cell = notebook.widgets.find(w => w.model.id === stored.cellId);
            if (!(cell instanceof CodeCell)) {
                return; // cell was deleted (not moved) — handled via stored future below
            }
            // Cell still present → move undo. Fresh capture protects the future.
            const fresh = (_a = Private.captureExecution(cell)) !== null && _a !== void 0 ? _a : {
                ...stored,
                isDone: () => true,
                buffered: []
            };
            // The undo will roll the outputs back to their state at the time of
            // the move; snapshot the current outputs so anything received since
            // then can be re-applied after the undo.
            fresh.outputs = cell.model.outputs.toJSON();
            preCaptured.set(stored.cellId, fresh);
        });
        // Capture execution context from the stack item being popped.
        let storedExecutions;
        const onStackItemPopped = ({ stackItem }) => {
            storedExecutions = stackItem.meta.get(Private.CELL_EXECUTION_META_KEY);
        };
        undoManager.on('stack-item-popped', onStackItemPopped);
        notebook.model.sharedModel.undo();
        undoManager.off('stack-item-popped', onStackItemPopped);
        // Restore execution state on resurrected/moved cell widgets.
        // For move-undo: use freshly pre-captured data (stored data is stale).
        // For delete-undo: stored data has the futures captured at deletion time.
        storedExecutions === null || storedExecutions === void 0 ? void 0 : storedExecutions.forEach(stored => {
            var _a;
            Private.restoreExecution(notebook, (_a = preCaptured.get(stored.cellId)) !== null && _a !== void 0 ? _a : stored);
        });
        notebook.deselectAll();
        void Private.handleState(notebook, state);
    }
    NotebookActions.undo = undo;
    /**
     * Redo a cell action.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * This is a no-op if there are no cell actions to redo.
     */
    function redo(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.mode = 'command';
        notebook.model.sharedModel.redo();
        notebook.deselectAll();
        void Private.handleState(notebook, state);
    }
    NotebookActions.redo = redo;
    /**
     * Toggle the line number of all cells.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The original state is based on the state of the active cell.
     * The `mode` of the widget will be preserved.
     */
    function toggleAllLineNumbers(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        const config = notebook.editorConfig;
        const lineNumbers = !(config.code.lineNumbers &&
            config.markdown.lineNumbers &&
            config.raw.lineNumbers);
        const newConfig = {
            code: { ...config.code, lineNumbers },
            markdown: { ...config.markdown, lineNumbers },
            raw: { ...config.raw, lineNumbers }
        };
        notebook.editorConfig = newConfig;
        void Private.handleState(notebook, state);
    }
    NotebookActions.toggleAllLineNumbers = toggleAllLineNumbers;
    /**
     * Clear the code outputs of the selected cells.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The widget `mode` will be preserved.
     */
    function clearOutputs(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        let index = -1;
        for (const cell of notebook.model.cells) {
            const child = notebook.widgets[++index];
            if (notebook.isSelectedOrActive(child) && cell.type === 'code') {
                cell.sharedModel.transact(() => {
                    cell.clearExecution();
                    child.outputHidden = false;
                }, false);
                Private.outputCleared.emit({ notebook, cell: child });
            }
        }
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.clearOutputs = clearOutputs;
    /**
     * Clear all the code outputs on the widget.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The widget `mode` will be preserved.
     */
    function clearAllOutputs(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        let index = -1;
        for (const cell of notebook.model.cells) {
            const child = notebook.widgets[++index];
            if (cell.type === 'code') {
                cell.sharedModel.transact(() => {
                    cell.clearExecution();
                    child.outputHidden = false;
                }, false);
                Private.outputCleared.emit({ notebook, cell: child });
            }
        }
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.clearAllOutputs = clearAllOutputs;
    /**
     * Hide the code on selected code cells.
     *
     * @param notebook - The target notebook widget.
     */
    function hideCode(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
                cell.inputHidden = true;
            }
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.hideCode = hideCode;
    /**
     * Show the code on selected code cells.
     *
     * @param notebook - The target notebook widget.
     */
    function showCode(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
                cell.inputHidden = false;
            }
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.showCode = showCode;
    /**
     * Hide the code on all code cells.
     *
     * @param notebook - The target notebook widget.
     */
    function hideAllCode(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (cell.model.type === 'code') {
                cell.inputHidden = true;
            }
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.hideAllCode = hideAllCode;
    /**
     * Show the code on all code cells.
     *
     * @param notebook The target notebook widget.
     */
    function showAllCode(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (cell.model.type === 'code') {
                cell.inputHidden = false;
            }
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.showAllCode = showAllCode;
    /**
     * Hide the output on selected code cells.
     *
     * @param notebook - The target notebook widget.
     */
    function hideOutput(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
                cell.outputHidden = true;
            }
        });
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.hideOutput = hideOutput;
    /**
     * Show the output on selected code cells.
     *
     * @param notebook - The target notebook widget.
     */
    function showOutput(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
                cell.outputHidden = false;
            }
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.showOutput = showOutput;
    /**
     * Toggle output visibility on selected code cells.
     * If at least one output is visible, all outputs are hidden.
     * If no outputs are visible, all outputs are made visible.
     *
     * @param notebook - The target notebook widget.
     */
    function toggleOutput(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        for (const cell of notebook.widgets) {
            if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
                if (cell.outputHidden === false) {
                    // We found at least one visible output; hide outputs for this cell
                    return hideOutput(notebook);
                }
            }
        }
        // We found no selected cells or no selected cells with visible output;
        // show outputs for selected cells
        return showOutput(notebook);
    }
    NotebookActions.toggleOutput = toggleOutput;
    /**
     * Hide the output on all code cells.
     *
     * @param notebook - The target notebook widget.
     */
    function hideAllOutputs(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (cell.model.type === 'code') {
                cell.outputHidden = true;
            }
        });
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.hideAllOutputs = hideAllOutputs;
    /**
     * Render side-by-side.
     *
     * @param notebook - The target notebook widget.
     */
    function renderSideBySide(notebook) {
        notebook.renderingLayout = 'side-by-side';
    }
    NotebookActions.renderSideBySide = renderSideBySide;
    /**
     * Render not side-by-side.
     *
     * @param notebook - The target notebook widget.
     */
    function renderDefault(notebook) {
        notebook.renderingLayout = 'default';
    }
    NotebookActions.renderDefault = renderDefault;
    /**
     * Show the output on all code cells.
     *
     * @param notebook - The target notebook widget.
     */
    function showAllOutputs(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (cell.model.type === 'code') {
                cell.outputHidden = false;
            }
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.showAllOutputs = showAllOutputs;
    /**
     * Enable output scrolling for all selected cells.
     *
     * @param notebook - The target notebook widget.
     */
    function enableOutputScrolling(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
                cell.outputsScrolled = true;
            }
        });
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.enableOutputScrolling = enableOutputScrolling;
    /**
     * Disable output scrolling for all selected cells.
     *
     * @param notebook - The target notebook widget.
     */
    function disableOutputScrolling(notebook) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        notebook.widgets.forEach(cell => {
            if (notebook.isSelectedOrActive(cell) && cell.model.type === 'code') {
                cell.outputsScrolled = false;
            }
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.disableOutputScrolling = disableOutputScrolling;
    /**
     * Go to the last cell that is run or current if it is running.
     *
     * Note: This requires execution timing to be toggled on or this will have
     * no effect.
     *
     * @param notebook - The target notebook widget.
     */
    function selectLastRunCell(notebook) {
        let latestTime = null;
        let latestCellIdx = null;
        notebook.widgets.forEach((cell, cellIndx) => {
            if (cell.model.type === 'code') {
                const execution = cell.model.getMetadata('execution');
                if (execution &&
                    JSONExt.isObject(execution) &&
                    execution['iopub.status.busy'] !== undefined) {
                    // The busy status is used as soon as a request is received:
                    // https://jupyter-client.readthedocs.io/en/stable/messaging.html
                    const timestamp = execution['iopub.status.busy'].toString();
                    if (timestamp) {
                        const startTime = new Date(timestamp);
                        if (!latestTime || startTime >= latestTime) {
                            latestTime = startTime;
                            latestCellIdx = cellIndx;
                        }
                    }
                }
            }
        });
        if (latestCellIdx !== null) {
            notebook.activeCellIndex = latestCellIdx;
        }
    }
    NotebookActions.selectLastRunCell = selectLastRunCell;
    /**
     * Select the last modified cell and pop it from the back stack
     *
     * @param notebook - The target notebook widget.
     */
    async function selectLastModifiedCell(notebook) {
        const cell = notebook.popLastModifiedCell();
        if (cell && cell !== notebook.activeCell && !cell.isDisposed) {
            notebook.activeCellIndex = notebook.widgets.indexOf(cell);
            await notebook.scrollToCell(cell);
        }
    }
    NotebookActions.selectLastModifiedCell = selectLastModifiedCell;
    /**
     * Select the next modified cell and pop it from the forward stack
     *
     * @param notebook - The target notebook widget.
     */
    async function selectNextModifiedCell(notebook) {
        const cell = notebook.popNextModifiedCell();
        if (cell && cell !== notebook.activeCell && !cell.isDisposed) {
            notebook.activeCellIndex = notebook.widgets.indexOf(cell);
            await notebook.scrollToCell(cell);
        }
    }
    NotebookActions.selectNextModifiedCell = selectNextModifiedCell;
    /**
     * Set the markdown header level.
     *
     * @param notebook - The target notebook widget.
     * @param level - The header level.
     * @param translator - The application translator.
     *
     * #### Notes
     * All selected cells will be switched to markdown.
     * The level will be clamped between 1 and 6.
     * If there is an existing header, it will be replaced.
     * There will always be one blank space after the header.
     * The cells will be unrendered.
     */
    function setMarkdownHeader(notebook, level, translator) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = Private.getState(notebook);
        level = Math.min(Math.max(level, 1), 6);
        Private.changeCellType(notebook, 'markdown', {
            translator,
            headingLevel: level
        });
        void Private.handleState(notebook, state);
    }
    NotebookActions.setMarkdownHeader = setMarkdownHeader;
    /**
     * Collapse all cells in given notebook.
     *
     * @param notebook - The target notebook widget.
     */
    function collapseAllHeadings(notebook) {
        const state = Private.getState(notebook);
        for (const cell of notebook.widgets) {
            if (NotebookActions.getHeadingInfo(cell).isHeading) {
                NotebookActions.setHeadingCollapse(cell, true, notebook);
                NotebookActions.setCellCollapse(cell, true);
            }
        }
        notebook.activeCellIndex = 0;
        void Private.handleState(notebook, state, true);
    }
    NotebookActions.collapseAllHeadings = collapseAllHeadings;
    /**
     * Un-collapse all cells in given notebook.
     *
     * @param notebook - The target notebook widget.
     */
    function expandAllHeadings(notebook) {
        for (const cell of notebook.widgets) {
            if (NotebookActions.getHeadingInfo(cell).isHeading) {
                NotebookActions.setHeadingCollapse(cell, false, notebook);
                // similar to collapseAll.
                NotebookActions.setCellCollapse(cell, false);
            }
        }
    }
    NotebookActions.expandAllHeadings = expandAllHeadings;
    function findNearestParentHeader(cell, notebook) {
        const index = findIndex(notebook.widgets, (possibleCell, index) => {
            return cell.model.id === possibleCell.model.id;
        });
        if (index === -1) {
            return;
        }
        // Finds the nearest header above the given cell. If the cell is a header itself, it does not return itself;
        // this can be checked directly by calling functions.
        if (index >= notebook.widgets.length) {
            return;
        }
        let childHeaderInfo = getHeadingInfo(notebook.widgets[index]);
        for (let cellN = index - 1; cellN >= 0; cellN--) {
            if (cellN < notebook.widgets.length) {
                let hInfo = getHeadingInfo(notebook.widgets[cellN]);
                if (hInfo.isHeading &&
                    hInfo.headingLevel < childHeaderInfo.headingLevel) {
                    return notebook.widgets[cellN];
                }
            }
        }
        // else no parent header found.
        return;
    }
    /**
     * Finds the "parent" heading of the given cell and expands.
     * Used for the case that a cell becomes active that is within a collapsed heading.
     * @param cell - "Child" cell that has become the active cell
     * @param notebook - The target notebook widget.
     */
    function expandParent(cell, notebook) {
        let nearestParentCell = findNearestParentHeader(cell, notebook);
        if (!nearestParentCell) {
            return;
        }
        if (!getHeadingInfo(nearestParentCell).collapsed &&
            !nearestParentCell.isHidden) {
            return;
        }
        if (nearestParentCell.isHidden) {
            expandParent(nearestParentCell, notebook);
        }
        if (getHeadingInfo(nearestParentCell).collapsed) {
            setHeadingCollapse(nearestParentCell, false, notebook);
        }
    }
    NotebookActions.expandParent = expandParent;
    /**
     * Finds the next heading that isn't a child of the given markdown heading.
     * @param cell - "Child" cell that has become the active cell
     * @param notebook - The target notebook widget.
     */
    function findNextParentHeading(cell, notebook) {
        let index = findIndex(notebook.widgets, (possibleCell, index) => {
            return cell.model.id === possibleCell.model.id;
        });
        if (index === -1) {
            return -1;
        }
        let childHeaderInfo = getHeadingInfo(cell);
        for (index = index + 1; index < notebook.widgets.length; index++) {
            let hInfo = getHeadingInfo(notebook.widgets[index]);
            if (hInfo.isHeading &&
                hInfo.headingLevel <= childHeaderInfo.headingLevel) {
                return index;
            }
        }
        // else no parent header found. return the index of the last cell
        return notebook.widgets.length;
    }
    NotebookActions.findNextParentHeading = findNextParentHeading;
    /**
     * Set the given cell and ** all "child" cells **
     * to the given collapse / expand if cell is
     * a markdown header.
     *
     * @param cell - The cell
     * @param collapsing - Whether to collapse or expand the cell
     * @param notebook - The target notebook widget.
     */
    function setHeadingCollapse(cell, collapsing, notebook) {
        const which = findIndex(notebook.widgets, (possibleCell, index) => {
            return cell.model.id === possibleCell.model.id;
        });
        if (which === -1) {
            return -1;
        }
        if (!notebook.widgets.length) {
            return which + 1;
        }
        let selectedHeadingInfo = NotebookActions.getHeadingInfo(cell);
        if (cell.isHidden ||
            !(cell instanceof MarkdownCell) ||
            !selectedHeadingInfo.isHeading) {
            // otherwise collapsing and uncollapsing already hidden stuff can
            // cause some funny looking bugs.
            return which + 1;
        }
        let localCollapsed = false;
        let localCollapsedLevel = 0;
        // iterate through all cells after the active cell.
        let cellNum;
        for (cellNum = which + 1; cellNum < notebook.widgets.length; cellNum++) {
            let subCell = notebook.widgets[cellNum];
            let subCellHeadingInfo = NotebookActions.getHeadingInfo(subCell);
            const isHeadingResolved = subCell
                .headingsResolved;
            // Defer until subCell's asynchronous heading parsing is complete
            if (subCellHeadingInfo.headingLevel === -1 && !isHeadingResolved) {
                requestAnimationFrame(() => {
                    setHeadingCollapse(cell, collapsing, notebook);
                });
                break;
            }
            if (subCellHeadingInfo.isHeading &&
                subCellHeadingInfo.headingLevel <= selectedHeadingInfo.headingLevel) {
                // then reached an equivalent or higher heading level than the
                // original the end of the collapse.
                cellNum -= 1;
                break;
            }
            if (localCollapsed &&
                subCellHeadingInfo.isHeading &&
                subCellHeadingInfo.headingLevel <= localCollapsedLevel) {
                // then reached the end of the local collapsed, so unset NotebookActions.
                localCollapsed = false;
            }
            if (collapsing || localCollapsed) {
                // then no extra handling is needed for further locally collapsed
                // headings.
                subCell.setHidden(true);
                continue;
            }
            if (subCellHeadingInfo.collapsed && subCellHeadingInfo.isHeading) {
                localCollapsed = true;
                localCollapsedLevel = subCellHeadingInfo.headingLevel;
                // but don't collapse the locally collapsed heading, so continue to
                // expand the heading. This will get noticed in the next round.
            }
            subCell.setHidden(false);
        }
        if (cellNum === notebook.widgets.length) {
            cell.numberChildNodes = cellNum - which - 1;
        }
        else {
            cell.numberChildNodes = cellNum - which;
        }
        NotebookActions.setCellCollapse(cell, collapsing);
        return cellNum + 1;
    }
    NotebookActions.setHeadingCollapse = setHeadingCollapse;
    /**
     * Toggles the collapse state of the active cell of the given notebook
     * and ** all of its "child" cells ** if the cell is a heading.
     *
     * @param notebook - The target notebook widget.
     */
    function toggleCurrentHeadingCollapse(notebook) {
        if (!notebook.activeCell || notebook.activeCellIndex === undefined) {
            return;
        }
        let headingInfo = NotebookActions.getHeadingInfo(notebook.activeCell);
        if (headingInfo.isHeading) {
            // Then toggle!
            NotebookActions.setHeadingCollapse(notebook.activeCell, !headingInfo.collapsed, notebook);
        }
        notebook.scrollToItem(notebook.activeCellIndex).catch(reason => {
            // no-op
        });
    }
    NotebookActions.toggleCurrentHeadingCollapse = toggleCurrentHeadingCollapse;
    /**
     * If cell is a markdown heading, sets the headingCollapsed field,
     * and otherwise hides the cell.
     *
     * @param cell - The cell to collapse / expand
     * @param collapsing - Whether to collapse or expand the given cell
     */
    function setCellCollapse(cell, collapsing) {
        if (cell instanceof MarkdownCell) {
            cell.headingCollapsed = collapsing;
        }
        else {
            cell.setHidden(collapsing);
        }
    }
    NotebookActions.setCellCollapse = setCellCollapse;
    /**
     * If given cell is a markdown heading, returns the heading level.
     * If given cell is not markdown, returns 7 (there are only 6 levels of markdown headings)
     *
     * @param cell - The target cell widget.
     */
    function getHeadingInfo(cell) {
        if (!(cell instanceof MarkdownCell)) {
            return { isHeading: false, headingLevel: 7 };
        }
        let level = cell.headingInfo.level;
        let collapsed = cell.headingCollapsed;
        return { isHeading: level > 0, headingLevel: level, collapsed: collapsed };
    }
    NotebookActions.getHeadingInfo = getHeadingInfo;
    /**
     * Trust the notebook after prompting the user.
     *
     * @param notebook - The target notebook widget.
     * @param translator - The application translator.
     *
     * @returns a promise that resolves when the transaction is finished.
     *
     * #### Notes
     * No dialog will be presented if the notebook is already trusted.
     */
    function trust(notebook, translator) {
        translator = translator || nullTranslator;
        const trans = translator.load('jupyterlab');
        if (!notebook.model) {
            return Promise.resolve({ trusted: false });
        }
        // Do nothing if already trusted.
        const trusted = every(notebook.model.cells, cell => cell.trusted);
        // FIXME
        const trustMessage = (React.createElement("p", null,
            trans.__('A trusted Jupyter notebook may execute hidden malicious code when you open it.'),
            React.createElement("br", null),
            trans.__('Selecting "Trust" will re-render this notebook in a trusted state.'),
            React.createElement("br", null),
            trans.__('For more information, see'),
            ' ',
            React.createElement("a", { href: "https://jupyter-server.readthedocs.io/en/stable/operators/security.html", target: "_blank", rel: "noopener noreferrer" }, trans.__('the Jupyter security documentation')),
            "."));
        if (trusted) {
            return showDialog({
                body: trans.__('Notebook is already trusted'),
                buttons: [Dialog.okButton()]
            }).then(() => ({ trusted: true }));
        }
        return showDialog({
            body: trustMessage,
            title: trans.__('Trust this notebook?'),
            buttons: [
                Dialog.cancelButton(),
                Dialog.warnButton({
                    label: trans.__('Trust'),
                    ariaLabel: trans.__('Confirm Trusting this notebook')
                })
            ] // FIXME?
        }).then(result => {
            if (result.button.accept) {
                if (notebook.model) {
                    for (const cell of notebook.model.cells) {
                        cell.trusted = true;
                    }
                }
                return { trusted: true };
            }
            return { trusted: false };
        });
    }
    NotebookActions.trust = trust;
    /**
     * If the notebook has an active cell, focus it.
     *
     * @param notebook The target notebook widget
     * @param options Optional options to change the behavior of this function
     * @param options.waitUntilReady If true, do not call focus until activeCell.ready is resolved
     * @param options.preventScroll If true, do not scroll the active cell into view
     *
     * @returns a promise that resolves when focus has been called on the active
     * cell's node.
     *
     * #### Notes
     * By default, waits until after the active cell has been attached unless
     * called with { waitUntilReady: false }
     */
    async function focusActiveCell(notebook, options = { waitUntilReady: true, preventScroll: false }) {
        const { activeCell } = notebook;
        const { waitUntilReady, preventScroll } = options;
        if (!activeCell) {
            return;
        }
        if (waitUntilReady) {
            await activeCell.ready;
        }
        if (notebook.isDisposed || activeCell.isDisposed) {
            return;
        }
        activeCell.node.focus({
            preventScroll
        });
    }
    NotebookActions.focusActiveCell = focusActiveCell;
    /*
     * Access last notebook history.
     *
     * @param notebook - The target notebook widget.
     */
    async function accessPreviousHistory(notebook) {
        if (!notebook.notebookConfig.accessKernelHistory) {
            return;
        }
        const activeCell = notebook.activeCell;
        if (activeCell) {
            if (notebook.kernelHistory) {
                const previousHistory = await notebook.kernelHistory.back(activeCell);
                notebook.kernelHistory.updateEditor(activeCell, previousHistory);
            }
        }
    }
    NotebookActions.accessPreviousHistory = accessPreviousHistory;
    /**
     * Access next notebook history.
     *
     * @param notebook - The target notebook widget.
     */
    async function accessNextHistory(notebook) {
        if (!notebook.notebookConfig.accessKernelHistory) {
            return;
        }
        const activeCell = notebook.activeCell;
        if (activeCell) {
            if (notebook.kernelHistory) {
                const nextHistory = await notebook.kernelHistory.forward(activeCell);
                notebook.kernelHistory.updateEditor(activeCell, nextHistory);
            }
        }
    }
    NotebookActions.accessNextHistory = accessNextHistory;
})(NotebookActions || (NotebookActions = {}));
/**
 * Set the notebook cell executor and the related signals.
 */
export function setCellExecutor(executor) {
    if (Private.executor) {
        throw new Error('Cell executor can only be set once.');
    }
    Private.executor = executor;
}
/**
 * A namespace for private data.
 */
var Private;
(function (Private) {
    /** Key used to store cell execution state in Y.js undo stack item metadata. */
    Private.CELL_EXECUTION_META_KEY = Symbol('cellExecutionState');
    /**
     * Detach the kernel future from a code cell, buffering any messages that
     * arrive while it is detached so they can be replayed on reattach.
     *
     * `detachFuture` clears the IOPub, stdin and reply handlers, so all three
     * channels are buffered here; otherwise a message arriving during the
     * detached window (e.g. an `input()` request on stdin) would be dropped.
     *
     * Returns null if the cell has no active future.
     */
    function captureExecution(cell) {
        const future = cell.outputArea.detachFuture();
        if (!future) {
            return null;
        }
        let done = false;
        void future.done.finally(() => {
            done = true;
        });
        const buffered = [];
        future.onIOPub = msg => {
            buffered.push({ channel: 'iopub', msg });
        };
        future.onStdin = msg => {
            buffered.push({ channel: 'stdin', msg });
        };
        future.onReply = msg => {
            buffered.push({ channel: 'reply', msg });
        };
        return { cellId: cell.model.id, future, isDone: () => done, buffered };
    }
    Private.captureExecution = captureExecution;
    /**
     * Reconnect a captured execution to the cell widget that now holds the model.
     *
     * Handles both the "still running" and "already finished" cases.
     */
    function restoreExecution(notebook, { cellId, future, isDone, buffered, outputs }) {
        const cell = notebook.widgets.find(w => w.model.id === cellId);
        if (!(cell instanceof CodeCell)) {
            return;
        }
        if (outputs && !JSONExt.deepEqual(outputs, cell.model.outputs.toJSON())) {
            // Re-apply the output snapshot taken just before the undo: the Y.js
            // undo rolled the outputs back to their state at the time of the
            // undone action. Going through the output area model keeps the
            // in-memory model and the shared model in sync (output changes are
            // not tracked by the undo manager, so this does not pollute history).
            cell.model.outputs.fromJSON(outputs);
        }
        // Reattach the future (without clearing existing outputs) and replay any
        // messages buffered while it was detached, in arrival order. This is done
        // whether or not the execution has already finished, so that final outputs
        // (or a pending stdin request) that arrived while detached are not lost
        // (e.g. if the kernel completed in the brief window during the undo).
        cell.outputArea.reattachFuture(future);
        for (const buf of buffered) {
            switch (buf.channel) {
                case 'iopub':
                    void future.onIOPub(buf.msg);
                    break;
                case 'stdin':
                    void future.onStdin(buf.msg);
                    break;
                case 'reply':
                    void future.onReply(buf.msg);
                    break;
            }
        }
        if (isDone()) {
            // The execution already finished (e.g. it completed or was interrupted
            // during the undo). The resurrected cell may carry a stale 'running'
            // state from its restored snapshot, so reset it synchronously rather
            // than relying solely on the asynchronous `future.done` handler below.
            cell.model.executionState = 'idle';
        }
        else {
            // Restore the running state on the recreated cell widget.
            cell.model.executionState = 'running';
        }
        const cellRef = cell;
        void future.done.then(reply => {
            if (!cellRef.isDisposed) {
                // The future is authoritative for the prompt number; a snapshot
                // taken before completion would be stale (still null). Setting a
                // non-null execution count also flips the state back to 'idle'.
                cellRef.model.executionCount = reply.content.execution_count;
                cellRef.model.executionState = 'idle';
            }
        }, () => {
            if (!cellRef.isDisposed) {
                cellRef.model.executionState = 'idle';
            }
        });
    }
    Private.restoreExecution = restoreExecution;
    /**
     * A signal that emits whenever a cell completes execution.
     */
    Private.executed = new Signal({});
    /**
     * A signal that emits whenever a cell execution is scheduled.
     */
    Private.executionScheduled = new Signal({});
    /**
     * A signal that emits when one notebook's cells are all executed.
     */
    Private.selectionExecuted = new Signal({});
    /**
     * A signal that emits when one notebook's cells are all executed.
     */
    Private.outputCleared = new Signal({});
    /**
     * Get the state of a widget before running an action.
     */
    function getState(notebook) {
        var _a, _b;
        return {
            wasFocused: notebook.node.contains(document.activeElement),
            activeCellId: (_b = (_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.model.id) !== null && _b !== void 0 ? _b : null
        };
    }
    Private.getState = getState;
    /**
     * Handle the state of a widget after running an action.
     */
    async function handleState(notebook, state, scrollIfNeeded = false) {
        const { activeCell, activeCellIndex } = notebook;
        if (scrollIfNeeded && activeCell) {
            await notebook.scrollToItem(activeCellIndex, 'auto', 0).catch(reason => {
                // no-op
            });
        }
        if (state.wasFocused || notebook.mode === 'edit') {
            notebook.activate();
        }
    }
    Private.handleState = handleState;
    /**
     * Handle the state of a widget after running a run action.
     */
    async function handleRunState(notebook, state, alignPreference) {
        const { activeCell, activeCellIndex } = notebook;
        if (activeCell) {
            await notebook
                .scrollToItem(activeCellIndex, 'smart', 0, alignPreference)
                .catch(reason => {
                // no-op
            });
        }
        if (state.wasFocused || notebook.mode === 'edit') {
            notebook.activate();
        }
    }
    Private.handleRunState = handleRunState;
    /**
     * Run the selected cells.
     *
     * @param notebook Notebook
     * @param cells Cells to run
     * @param sessionContext Notebook session context
     * @param sessionDialogs Session dialogs
     * @param translator Application translator
     */
    function runCells(notebook, cells, sessionContext, sessionDialogs, translator) {
        const lastCell = cells[cells.length - 1];
        notebook.mode = 'command';
        let initializingDialogShown = false;
        return Promise.all(cells.map(cell => {
            if (cell.model.type === 'code' &&
                notebook.notebookConfig.enableKernelInitNotification &&
                sessionContext &&
                sessionContext.kernelDisplayStatus === 'initializing' &&
                !initializingDialogShown) {
                initializingDialogShown = true;
                translator = translator || nullTranslator;
                const trans = translator.load('jupyterlab');
                Notification.emit(trans.__(`Kernel '${sessionContext.kernelDisplayName}' for '${sessionContext.path}' is still initializing. You can run code cells when the kernel has initialized.`), 'warning', { autoClose: false });
                return Promise.resolve(false);
            }
            if (cell.model.type === 'code' &&
                notebook.notebookConfig.enableKernelInitNotification &&
                initializingDialogShown) {
                return Promise.resolve(false);
            }
            return runCell(notebook, cell, sessionContext, sessionDialogs, translator);
        }))
            .then(results => {
            if (notebook.isDisposed) {
                return false;
            }
            Private.selectionExecuted.emit({
                notebook,
                lastCell
            });
            // Post an update request.
            notebook.update();
            return results.every(result => result);
        })
            .catch(reason => {
            if (reason.message.startsWith('KernelReplyNotOK')) {
                cells.map(cell => {
                    // Remove '*' prompt from cells that didn't execute
                    if (cell.model.type === 'code' &&
                        cell.model.executionCount == null) {
                        cell.model.executionState = 'idle';
                    }
                });
            }
            else {
                throw reason;
            }
            Private.selectionExecuted.emit({
                notebook,
                lastCell
            });
            notebook.update();
            return false;
        });
    }
    Private.runCells = runCells;
    /**
     * Run the selected cells.
     *
     * @param notebook Notebook
     * @param sessionContext Notebook session context
     * @param sessionDialogs Session dialogs
     * @param translator Application translator
     */
    function runSelected(notebook, sessionContext, sessionDialogs, translator) {
        notebook.mode = 'command';
        let lastIndex = notebook.activeCellIndex;
        const selected = notebook.widgets.filter((child, index) => {
            const active = notebook.isSelectedOrActive(child);
            if (active) {
                lastIndex = index;
            }
            return active;
        });
        notebook.activeCellIndex = lastIndex;
        notebook.deselectAll();
        return runCells(notebook, selected, sessionContext, sessionDialogs, translator);
    }
    Private.runSelected = runSelected;
    /**
     * Run a cell.
     */
    async function runCell(notebook, cell, sessionContext, sessionDialogs, translator) {
        if (!Private.executor) {
            console.warn('Requesting cell execution without any cell executor defined. Falling back to default execution.');
        }
        const options = {
            cell,
            notebook: notebook.model,
            notebookConfig: notebook.notebookConfig,
            onCellExecuted: args => {
                Private.executed.emit({ notebook, ...args });
            },
            onCellExecutionScheduled: args => {
                Private.executionScheduled.emit({ notebook, ...args });
            },
            sessionContext,
            sessionDialogs,
            translator
        };
        return Private.executor ? Private.executor.runCell(options) : defaultRunCell(options);
    }
    /**
     * Return a deep copy of cells with code cell outputs and execution_count cleared.
     *
     * @param cells - The cells to process.
     * @returns New cell objects.
     */
    function stripCodeCellOutputs(cells) {
        return cells.map(cell => {
            const copy = JSONExt.deepCopy(cell);
            if (copy && nbformat.isCode(copy)) {
                copy.outputs = [];
                copy.execution_count = null;
            }
            return copy;
        });
    }
    Private.stripCodeCellOutputs = stripCodeCellOutputs;
    /**
     * Get the selected cell(s) without affecting the clipboard.
     *
     * @param notebook - The target notebook widget.
     *
     * @returns A list of 0 or more selected cells
     */
    function selectedCells(notebook) {
        return notebook.widgets
            .filter(cell => notebook.isSelectedOrActive(cell))
            .map(cell => cell.model.toJSON())
            .map(cellJSON => {
            if (cellJSON.metadata.deletable !== undefined) {
                delete cellJSON.metadata.deletable;
            }
            return cellJSON;
        });
    }
    Private.selectedCells = selectedCells;
    /**
     * Copy or cut the selected cell data to the application clipboard.
     *
     * @param notebook - The target notebook widget.
     *
     * @param cut - True if the cells should be cut, false if they should be copied.
     */
    function copyOrCut(notebook, cut) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = getState(notebook);
        const clipboard = Clipboard.getInstance();
        notebook.mode = 'command';
        clipboard.clear();
        const data = Private.selectedCells(notebook);
        clipboard.setData(JUPYTER_CELL_MIME, data);
        if (cut) {
            deleteCells(notebook);
        }
        else {
            notebook.deselectAll();
        }
        if (cut) {
            notebook.recordCellClipboardInteraction('cut', data);
        }
        else {
            notebook.recordCellClipboardInteraction('copy', data);
        }
        void handleState(notebook, state);
    }
    Private.copyOrCut = copyOrCut;
    /**
     * Copy or cut the selected cell data to the system clipboard.
     *
     * @param notebook - The target notebook widget.
     *
     * @param cut - True if the cells should be cut, false if they should be copied.
     */
    async function copyOrCutToSystemClipboard(notebook, cut) {
        if (!notebook.model || !notebook.activeCell) {
            return;
        }
        const state = getState(notebook);
        const clipboard = SystemClipboard.getInstance();
        notebook.mode = 'command';
        clipboard.clear();
        const data = Private.selectedCells(notebook);
        await clipboard.setData(JUPYTER_CELL_MIME, data);
        if (cut) {
            deleteCells(notebook);
        }
        else {
            notebook.deselectAll();
        }
        if (cut) {
            notebook.recordCellClipboardInteraction('cut', data);
        }
        else {
            notebook.recordCellClipboardInteraction('copy', data);
        }
        void handleState(notebook, state);
    }
    Private.copyOrCutToSystemClipboard = copyOrCutToSystemClipboard;
    /**
     * Change the selected cell type(s).
     *
     * @param notebook - The target notebook widget.
     *
     * @param value - The target cell type.
     *
     * #### Notes
     * It should preserve the widget mode.
     * This action can be undone.
     * The existing selection will be cleared.
     * Any cells converted to markdown will be unrendered.
     */
    function changeCellType(notebook, value, options) {
        const { translator, headingLevel } = options !== null && options !== void 0 ? options : {};
        const notebookSharedModel = notebook.model.sharedModel;
        notebook.widgets.forEach((child, index) => {
            if (!notebook.isSelectedOrActive(child)) {
                return;
            }
            if (child.model.type === 'code' &&
                child.outputArea.pendingInput) {
                const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
                // Do not permit changing cell type when input is pending
                void showDialog({
                    title: trans.__('Cell type not changed due to pending input'),
                    body: trans.__('The cell type has not been changed to avoid kernel deadlock as this cell has pending input! Submit your pending input and try again.'),
                    buttons: [Dialog.okButton()]
                });
                return;
            }
            if (child.model.getMetadata('editable') == false) {
                const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
                // Do not permit changing cell type when the cell is readonly
                void showDialog({
                    title: trans.__('Cell is read-only'),
                    body: trans.__('The cell is read-only, its type cannot be changed!'),
                    buttons: [Dialog.okButton()]
                });
                return;
            }
            if (child.model.type !== value) {
                const raw = child.model.toJSON();
                let newSource = raw.source;
                if (headingLevel !== undefined) {
                    newSource = Private.setMarkdownHeader(newSource, headingLevel);
                }
                // Detach future before the transaction so dispose() does not cancel it.
                const storedExecution = child instanceof CodeCell
                    ? Private.captureExecution(child)
                    : undefined;
                notebookSharedModel.transact(() => {
                    notebookSharedModel.deleteCell(index);
                    if (value === 'code') {
                        // After change of type outputs are deleted so cell can be trusted.
                        raw.metadata.trusted = true;
                    }
                    else {
                        // Otherwise clear the metadata as trusted is only "valid" on code
                        // cells (since other cell types cannot have outputs).
                        raw.metadata.trusted = undefined;
                    }
                    const newCell = notebookSharedModel.insertCell(index, {
                        id: raw.id,
                        cell_type: value,
                        source: newSource,
                        metadata: raw.metadata
                    });
                    if (raw.attachments && ['markdown', 'raw'].includes(value)) {
                        newCell.attachments =
                            raw.attachments;
                    }
                });
                if (storedExecution) {
                    const undoManager = notebookSharedModel.undoManager;
                    const lastItem = undoManager.undoStack[undoManager.undoStack.length - 1];
                    lastItem === null || lastItem === void 0 ? void 0 : lastItem.meta.set(Private.CELL_EXECUTION_META_KEY, [
                        storedExecution
                    ]);
                }
            }
            else if (value === 'markdown' && headingLevel !== undefined) {
                notebookSharedModel.transact(() => {
                    child.model.sharedModel.setSource(Private.setMarkdownHeader(child.model.sharedModel.getSource(), headingLevel));
                });
            }
            if (value === 'markdown') {
                // Fetch the new widget and unrender it.
                child = notebook.widgets[index];
                child.rendered = false;
            }
        });
        notebook.deselectAll();
    }
    Private.changeCellType = changeCellType;
    /**
     * Delete the selected cells.
     *
     * @param notebook - The target notebook widget.
     *
     * #### Notes
     * The cell after the last selected cell will be activated.
     * If the last cell is deleted, then the previous one will be activated.
     * It will add a code cell if all cells are deleted.
     * This action can be undone.
     */
    function deleteCells(notebook) {
        const model = notebook.model;
        const sharedModel = model.sharedModel;
        const toDelete = [];
        notebook.mode = 'command';
        // Find the cells to delete.
        notebook.widgets.forEach((child, index) => {
            var _a;
            const deletable = child.model.getMetadata('deletable') !== false;
            if (notebook.isSelectedOrActive(child) && deletable) {
                toDelete.push(index);
                (_a = notebook.model) === null || _a === void 0 ? void 0 : _a.deletedCells.push(child.model.id);
            }
        });
        // If cells are not deletable, we may not have anything to delete.
        if (toDelete.length > 0) {
            // Detach futures before the transaction so dispose() does not cancel them.
            const storedExecutions = [];
            toDelete.forEach(index => {
                const cell = notebook.widgets[index];
                if (!(cell instanceof CodeCell)) {
                    return;
                }
                const stored = Private.captureExecution(cell);
                if (stored) {
                    storedExecutions.push(stored);
                }
            });
            // Delete the cells as one undo event.
            sharedModel.transact(() => {
                // Delete cells in reverse order to maintain the correct indices.
                toDelete.reverse().forEach(index => {
                    sharedModel.deleteCell(index);
                });
                // Add a new cell if the notebook is empty. This is done
                // within the compound operation to make the deletion of
                // a notebook's last cell undoable.
                if (sharedModel.cells.length == toDelete.length) {
                    sharedModel.insertCell(0, {
                        cell_type: notebook.notebookConfig.defaultCell,
                        metadata: notebook.notebookConfig.defaultCell === 'code'
                            ? {
                                // This is an empty cell created in empty notebook, thus is trusted
                                trusted: true
                            }
                            : {}
                    });
                }
            });
            if (storedExecutions.length > 0) {
                const undoManager = sharedModel.undoManager;
                const lastItem = undoManager.undoStack[undoManager.undoStack.length - 1];
                lastItem === null || lastItem === void 0 ? void 0 : lastItem.meta.set(Private.CELL_EXECUTION_META_KEY, storedExecutions);
            }
            // Select the *first* interior cell not deleted or the cell
            // *after* the last selected cell.
            // Note: The activeCellIndex is clamped to the available cells,
            // so if the last cell is deleted the previous cell will be activated.
            // The *first* index is the index of the last cell in the initial
            // toDelete list due to the `reverse` operation above.
            notebook.activeCellIndex = toDelete[0] - toDelete.length + 1;
        }
        // Deselect any remaining, undeletable cells. Do this even if we don't
        // delete anything so that users are aware *something* happened.
        notebook.deselectAll();
    }
    Private.deleteCells = deleteCells;
    /**
     * Set the markdown header level of a cell source.
     */
    function setMarkdownHeader(source, level) {
        // Remove existing header or leading white space.
        const regex = /^#+\s*|^\s*/;
        const newHeader = Array(level + 1).join('#') + ' ';
        const matches = regex.exec(source);
        if (matches) {
            source = source.slice(matches[0].length);
        }
        return newHeader + source;
    }
    Private.setMarkdownHeader = setMarkdownHeader;
    /** Functionality related to collapsible headings */
    let Headings;
    (function (Headings) {
        /** Find the heading that is parent to cell.
         *
         * @param childCell - The cell that is child to the sought heading
         * @param notebook - The target notebook widget
         * @param includeChildCell [default=false] - if set to true and childCell is a heading itself, the childCell will be returned
         * @param returnIndex [default=false] - if set to true, the cell index is returned rather than the cell object.
         *
         * @returns the (index | Cell object) of the parent heading or (-1 | null) if there is no parent heading.
         */
        function findParentHeading(childCell, notebook, includeChildCell = false, returnIndex = false) {
            let cellIdx = notebook.widgets.indexOf(childCell) - (includeChildCell ? 1 : 0);
            while (cellIdx >= 0) {
                let headingInfo = NotebookActions.getHeadingInfo(notebook.widgets[cellIdx]);
                if (headingInfo.isHeading) {
                    return returnIndex ? cellIdx : notebook.widgets[cellIdx];
                }
                cellIdx--;
            }
            return returnIndex ? -1 : null;
        }
        Headings.findParentHeading = findParentHeading;
        /** Find heading above with leq level than baseCell heading level.
         *
         * @param baseCell - cell relative to which so search
         * @param notebook - target notebook widget
         * @param returnIndex [default=false] - if set to true, the cell index is returned rather than the cell object.
         *
         * @returns the (index | Cell object) of the found heading or (-1 | null) if no heading found.
         */
        function findLowerEqualLevelParentHeadingAbove(baseCell, notebook, returnIndex = false) {
            let baseHeadingLevel = Private.Headings.determineHeadingLevel(baseCell, notebook);
            if (baseHeadingLevel == -1) {
                baseHeadingLevel = 1; // if no heading level can be determined, assume we're on level 1
            }
            // find the heading above with heading level <= baseHeadingLevel and return its index
            let cellIdx = notebook.widgets.indexOf(baseCell) - 1;
            while (cellIdx >= 0) {
                let cell = notebook.widgets[cellIdx];
                let headingInfo = NotebookActions.getHeadingInfo(cell);
                if (headingInfo.isHeading &&
                    headingInfo.headingLevel <= baseHeadingLevel) {
                    return returnIndex ? cellIdx : cell;
                }
                cellIdx--;
            }
            return returnIndex ? -1 : null; // no heading found
        }
        Headings.findLowerEqualLevelParentHeadingAbove = findLowerEqualLevelParentHeadingAbove;
        /** Find next heading with equal or lower level.
         *
         * @param baseCell - cell relative to which so search
         * @param notebook - target notebook widget
         * @param returnIndex [default=false] - if set to true, the cell index is returned rather than the cell object.
         *
         * @returns the (index | Cell object) of the found heading or (-1 | null) if no heading found.
         */
        function findLowerEqualLevelHeadingBelow(baseCell, notebook, returnIndex = false) {
            let baseHeadingLevel = Private.Headings.determineHeadingLevel(baseCell, notebook);
            if (baseHeadingLevel == -1) {
                baseHeadingLevel = 1; // if no heading level can be determined, assume we're on level 1
            }
            let cellIdx = notebook.widgets.indexOf(baseCell) + 1;
            while (cellIdx < notebook.widgets.length) {
                let cell = notebook.widgets[cellIdx];
                let headingInfo = NotebookActions.getHeadingInfo(cell);
                if (headingInfo.isHeading &&
                    headingInfo.headingLevel <= baseHeadingLevel) {
                    return returnIndex ? cellIdx : cell;
                }
                cellIdx++;
            }
            return returnIndex ? -1 : null;
        }
        Headings.findLowerEqualLevelHeadingBelow = findLowerEqualLevelHeadingBelow;
        /** Find next heading.
         *
         * @param baseCell - cell relative to which so search
         * @param notebook - target notebook widget
         * @param returnIndex [default=false] - if set to true, the cell index is returned rather than the cell object.
         *
         * @returns the (index | Cell object) of the found heading or (-1 | null) if no heading found.
         */
        function findHeadingBelow(baseCell, notebook, returnIndex = false) {
            let cellIdx = notebook.widgets.indexOf(baseCell) + 1;
            while (cellIdx < notebook.widgets.length) {
                let cell = notebook.widgets[cellIdx];
                let headingInfo = NotebookActions.getHeadingInfo(cell);
                if (headingInfo.isHeading) {
                    return returnIndex ? cellIdx : cell;
                }
                cellIdx++;
            }
            return returnIndex ? -1 : null;
        }
        Headings.findHeadingBelow = findHeadingBelow;
        /** Determine the heading level of a cell.
         *
         * @param baseCell - The cell of which the heading level shall be determined
         * @param notebook - The target notebook widget
         *
         * @returns the heading level or -1 if there is no parent heading
         *
         * #### Notes
         * If the baseCell is a heading itself, the heading level of baseCell is returned.
         * If the baseCell is not a heading itself, the level of the parent heading is returned.
         * If there is no parent heading, -1 is returned.
         */
        function determineHeadingLevel(baseCell, notebook) {
            let headingInfoBaseCell = NotebookActions.getHeadingInfo(baseCell);
            // fill baseHeadingLevel or return null if there is no heading at or above baseCell
            if (headingInfoBaseCell.isHeading) {
                return headingInfoBaseCell.headingLevel;
            }
            else {
                let parentHeading = findParentHeading(baseCell, notebook, true);
                if (parentHeading == null) {
                    return -1;
                }
                return NotebookActions.getHeadingInfo(parentHeading).headingLevel;
            }
        }
        Headings.determineHeadingLevel = determineHeadingLevel;
        /** Insert a new heading cell at given position.
         *
         * @param cellIndex - where to insert
         * @param headingLevel - level of the new heading
         * @param notebook - target notebook
         *
         * #### Notes
         * Enters edit mode after insert.
         */
        async function insertHeadingAboveCellIndex(cellIndex, headingLevel, notebook) {
            var _a;
            headingLevel = Math.min(Math.max(headingLevel, 1), 6);
            const state = Private.getState(notebook);
            const model = notebook.model;
            const sharedModel = model.sharedModel;
            sharedModel.insertCell(cellIndex, {
                cell_type: 'markdown',
                source: '#'.repeat(headingLevel) + ' '
            });
            notebook.activeCellIndex = cellIndex;
            if (((_a = notebook.activeCell) === null || _a === void 0 ? void 0 : _a.inViewport) === false) {
                await signalToPromise(notebook.activeCell.inViewportChanged, 200).catch(() => {
                    // no-op
                });
            }
            notebook.deselectAll();
            void Private.handleState(notebook, state, true);
            notebook.mode = 'edit';
            notebook.widgets[cellIndex].setHidden(false);
        }
        Headings.insertHeadingAboveCellIndex = insertHeadingAboveCellIndex;
    })(Headings = Private.Headings || (Private.Headings = {}));
})(Private || (Private = {}));
//# sourceMappingURL=actions.js.map