sophon-notebook-notebook
Version:
JupyterLab - Notebook
1,395 lines • 57.5 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { Clipboard, Dialog, showDialog } from 'sophon-notebook-apputils';
import { CodeCell, MarkdownCell } from 'sophon-notebook-cells';
import { ArrayExt, each, toArray } from '@phosphor/algorithm';
import { ElementExt } from '@phosphor/domutils';
import { Signal } from '@phosphor/signaling';
import * as React from 'react';
// The message to display to the user when prompting to trust the notebook.
const TRUST_MESSAGE = (React.createElement("p", null,
"A trusted Jupyter notebook may execute hidden malicious code when you open it.",
React.createElement("br", null),
"Selecting trust will re-render this notebook in a trusted state.",
React.createElement("br", null),
"For more information, see the",
React.createElement("a", { href: "https://jupyter-notebook.readthedocs.io/en/stable/security.html" }, "Jupyter security documentation")));
/**
* The mimetype used for Jupyter cell data.
*/
const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells';
/**
* 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 is run.
*/
static get executed() {
return Private.executed;
}
/**
* 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() { }
}
/**
* A namespace for `NotebookActions` static methods.
*/
(function (NotebookActions) {
/**
* Split the active cell into two cells.
*
* @param widget - The target notebook widget.
*
* #### Notes
* It will preserve the existing mode.
* The second cell will be activated.
* The existing selection will be cleared.
* 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.
*/
function splitCell(notebook) {
if (!notebook.model || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
notebook.deselectAll();
const nbModel = notebook.model;
const index = notebook.activeCellIndex;
const child = notebook.widgets[index];
const editor = child.editor;
const position = editor.getCursorPosition();
const offset = editor.getOffsetAt(position);
const orig = child.model.value.text;
// Create new models to preserve history.
const clone0 = Private.cloneCell(nbModel, child.model);
const clone1 = Private.cloneCell(nbModel, child.model);
if (clone0.type === 'code') {
clone0.outputs.clear();
}
clone0.value.text = orig
.slice(0, offset)
.replace(/^\n+/, '')
.replace(/\n+$/, '');
clone1.value.text = orig
.slice(offset)
.replace(/^\n+/, '')
.replace(/\n+$/, '');
// Make the changes while preserving history.
const cells = nbModel.cells;
cells.beginCompoundOperation();
cells.set(index, clone0);
cells.insert(index + 1, clone1);
cells.endCompoundOperation();
notebook.activeCellIndex++;
Private.handleState(notebook, state);
}
NotebookActions.splitCell = splitCell;
/**
* Merge the selected cells.
*
* @param notebook - The target notebook widget.
*
* #### Notes
* The widget mode will be preserved.
* If only one cell is selected, the next 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) {
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;
// Get the cells to merge.
notebook.widgets.forEach((child, index) => {
if (notebook.isSelectedOrActive(child)) {
toMerge.push(child.model.value.text);
if (index !== active) {
toDelete.push(child.model);
}
}
});
// Check for only a single cell selected.
if (toMerge.length === 1) {
// Bail if it is the last cell.
if (active === cells.length - 1) {
return;
}
// Otherwise merge with the next cell.
const cellModel = cells.get(active + 1);
toMerge.push(cellModel.value.text);
toDelete.push(cellModel);
}
notebook.deselectAll();
// Create a new cell for the source to preserve history.
const newModel = Private.cloneCell(model, primary.model);
newModel.value.text = toMerge.join('\n\n');
if (newModel.type === 'code') {
newModel.outputs.clear();
}
// Make the changes while preserving history.
cells.beginCompoundOperation();
cells.set(active, newModel);
toDelete.forEach(cell => {
cells.removeValue(cell);
});
cells.endCompoundOperation();
// If the original cell is a markdown cell, make sure
// the new cell is unrendered.
if (primary instanceof MarkdownCell) {
notebook.activeCell.rendered = false;
}
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);
Private.handleState(notebook, state);
}
NotebookActions.deleteCells = deleteCells;
/**
* Insert a new code cell above the active cell.
*
* @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 || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
const model = notebook.model;
const cell = model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {});
const active = notebook.activeCellIndex;
model.cells.insert(active, cell);
// Make the newly inserted cell active.
notebook.activeCellIndex = active;
notebook.deselectAll();
Private.handleState(notebook, state, true);
}
NotebookActions.insertAbove = insertAbove;
/**
* Insert a new code cell below the active cell.
*
* @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 || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
const model = notebook.model;
const cell = model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {});
model.cells.insert(notebook.activeCellIndex + 1, cell);
// Make the newly inserted cell active.
notebook.activeCellIndex++;
notebook.deselectAll();
Private.handleState(notebook, state, true);
}
NotebookActions.insertBelow = insertBelow;
/**
* Move the selected cell(s) down.
*
* @param notebook = The target notebook widget.
*/
function moveDown(notebook) {
if (!notebook.model || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
const cells = notebook.model.cells;
const widgets = notebook.widgets;
cells.beginCompoundOperation();
for (let i = cells.length - 2; i > -1; i--) {
if (notebook.isSelectedOrActive(widgets[i])) {
if (!notebook.isSelectedOrActive(widgets[i + 1])) {
cells.move(i, i + 1);
if (notebook.activeCellIndex === i) {
notebook.activeCellIndex++;
}
notebook.select(widgets[i + 1]);
notebook.deselect(widgets[i]);
}
}
}
cells.endCompoundOperation();
Private.handleState(notebook, state, true);
}
NotebookActions.moveDown = moveDown;
/**
* Move the selected cell(s) up.
*
* @param widget - The target notebook widget.
*/
function moveUp(notebook) {
if (!notebook.model || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
const cells = notebook.model.cells;
const widgets = notebook.widgets;
cells.beginCompoundOperation();
for (let i = 1; i < cells.length; i++) {
if (notebook.isSelectedOrActive(widgets[i])) {
if (!notebook.isSelectedOrActive(widgets[i - 1])) {
cells.move(i, i - 1);
if (notebook.activeCellIndex === i) {
notebook.activeCellIndex--;
}
notebook.select(widgets[i - 1]);
notebook.deselect(widgets[i]);
}
}
}
cells.endCompoundOperation();
Private.handleState(notebook, state, true);
}
NotebookActions.moveUp = moveUp;
/**
* 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) {
if (!notebook.model || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
Private.changeCellType(notebook, value);
Private.handleState(notebook, state);
}
NotebookActions.changeCellType = changeCellType;
/**
* Run the selected cell(s).
*
* @param notebook - The target notebook widget.
*
* @param session - The optional client session object.
*
* #### 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, session) {
if (!notebook.model || !notebook.activeCell) {
return Promise.resolve(false);
}
const state = Private.getState(notebook);
const promise = Private.runSelected(notebook, session);
Private.handleRunState(notebook, state, false);
return promise;
}
NotebookActions.run = run;
/**
* Run the selected cell(s) and advance to the next cell.
*
* @param notebook - The target notebook widget.
*
* @param session - The optional client session object.
*
* #### 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.
*/
function runAndAdvance(notebook, session) {
if (!notebook.model || !notebook.activeCell) {
return Promise.resolve(false);
}
const state = Private.getState(notebook);
const promise = Private.runSelected(notebook, session);
const model = notebook.model;
if (notebook.activeCellIndex === notebook.widgets.length - 1) {
const cell = model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {});
model.cells.push(cell);
notebook.activeCellIndex++;
notebook.mode = 'edit';
}
else {
notebook.activeCellIndex++;
}
Private.handleRunState(notebook, state, true);
return promise;
}
NotebookActions.runAndAdvance = runAndAdvance;
/**
* Run the selected cell(s) and insert a new code cell.
*
* @param notebook - The target notebook widget.
*
* @param session - The optional client session object.
*
* #### 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.
*/
function runAndInsert(notebook, session) {
if (!notebook.model || !notebook.activeCell) {
return Promise.resolve(false);
}
const state = Private.getState(notebook);
const promise = Private.runSelected(notebook, session);
const model = notebook.model;
const cell = model.contentFactory.createCell(notebook.notebookConfig.defaultCell, {});
model.cells.insert(notebook.activeCellIndex + 1, cell);
notebook.activeCellIndex++;
notebook.mode = 'edit';
Private.handleRunState(notebook, state, true);
return promise;
}
NotebookActions.runAndInsert = runAndInsert;
/**
* Run all of the cells in the notebook.
*
* @param notebook - The target notebook widget.
*
* @param session - The optional client session object.
*
* #### 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, session) {
if (!notebook.model || !notebook.activeCell) {
return Promise.resolve(false);
}
const state = Private.getState(notebook);
notebook.widgets.forEach(child => {
notebook.select(child);
});
const promise = Private.runSelected(notebook, session);
Private.handleRunState(notebook, state, true);
return promise;
}
NotebookActions.runAll = runAll;
function renderAllMarkdown(notebook, session) {
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, session);
notebook.activeCellIndex = previousIndex;
Private.handleRunState(notebook, state, true);
return promise;
}
NotebookActions.renderAllMarkdown = renderAllMarkdown;
/**
* Run all of the cells before the currently active cell (exclusive).
*
* @param notebook - The target notebook widget.
*
* @param session - The optional client session object.
*
* #### 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, session) {
const { activeCell, activeCellIndex, model } = notebook;
if (!model || !activeCell || activeCellIndex < 1) {
return Promise.resolve(false);
}
const state = Private.getState(notebook);
notebook.activeCellIndex--;
notebook.deselectAll();
for (let i = 0; i < notebook.activeCellIndex; ++i) {
notebook.select(notebook.widgets[i]);
}
const promise = Private.runSelected(notebook, session);
notebook.activeCellIndex++;
Private.handleRunState(notebook, state, true);
return promise;
}
NotebookActions.runAllAbove = runAllAbove;
/**
* Run all of the cells after the currently active cell (inclusive).
*
* @param notebook - The target notebook widget.
*
* @param session - The optional client session object.
*
* #### 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, session) {
if (!notebook.model || !notebook.activeCell) {
return Promise.resolve(false);
}
const state = Private.getState(notebook);
notebook.deselectAll();
for (let i = notebook.activeCellIndex; i < notebook.widgets.length; ++i) {
notebook.select(notebook.widgets[i]);
}
const promise = Private.runSelected(notebook, session);
Private.handleRunState(notebook, state, true);
return promise;
}
NotebookActions.runAllBelow = runAllBelow;
/**
* 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;
}
if (notebook.activeCellIndex === 0) {
return;
}
let possibleNextCell = notebook.activeCellIndex - 1;
// find first non hidden cell above current cell
if (notebook.mode === 'edit') {
while (notebook.widgets[possibleNextCell].inputHidden) {
// If we are at the top cell, we cannot change selection.
if (possibleNextCell === 0) {
return;
}
possibleNextCell -= 1;
}
}
const state = Private.getState(notebook);
notebook.activeCellIndex = possibleNextCell;
notebook.deselectAll();
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;
}
const maxCellIndex = notebook.widgets.length - 1;
if (notebook.activeCellIndex === maxCellIndex) {
return;
}
let possibleNextCell = notebook.activeCellIndex + 1;
// find first non hidden cell below current cell
if (notebook.mode === 'edit') {
while (notebook.widgets[possibleNextCell].inputHidden) {
// If we are at the bottom cell, we cannot change selection.
if (possibleNextCell === maxCellIndex) {
return;
}
possibleNextCell += 1;
}
}
const state = Private.getState(notebook);
notebook.activeCellIndex = possibleNextCell;
notebook.deselectAll();
Private.handleState(notebook, state, true);
}
NotebookActions.selectBelow = selectBelow;
/**
* Extend the selection to the cell above.
*
* @param notebook - The target notebook widget.
*
* #### Notes
* This is a no-op if the first cell is the active cell.
* The new cell will be activated.
*/
function extendSelectionAbove(notebook) {
if (!notebook.model || !notebook.activeCell) {
return;
}
// Do not wrap around.
if (notebook.activeCellIndex === 0) {
return;
}
const state = Private.getState(notebook);
notebook.mode = 'command';
notebook.extendContiguousSelectionTo(notebook.activeCellIndex - 1);
Private.handleState(notebook, state, true);
}
NotebookActions.extendSelectionAbove = extendSelectionAbove;
/**
* Extend the selection to the cell below.
*
* @param notebook - The target notebook widget.
*
* #### Notes
* This is a no-op if the last cell is the active cell.
* The new cell will be activated.
*/
function extendSelectionBelow(notebook) {
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';
notebook.extendContiguousSelectionTo(notebook.activeCellIndex + 1);
Private.handleState(notebook, state, true);
}
NotebookActions.extendSelectionBelow = extendSelectionBelow;
/**
* insert getDataset code into current cell after cursor
*/
function executeGetData(dataInfo, notebook, session) {
if (!notebook.model || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
notebook.deselectAll();
const cellIndex = notebook.activeCellIndex;
const cellTarget = notebook.widgets[cellIndex];
const cellEditor = cellTarget.editor;
const position = cellEditor.getCursorPosition();
const endLine = cellEditor.getLine(position.line).length;
position.column = endLine;
const offset = cellEditor.getOffsetAt(position);
const originTxt = cellTarget.model.value.text;
const preTxt = originTxt.slice(0, offset);
const sufTxt = originTxt.slice(offset);
const newLine = offset === 0 ? '' : '\n';
switch (session.kernel.name) {
case 'pyspark3kernel':
cellEditor.model.value.text =
preTxt +
newLine +
'input_' +
dataInfo.name +
' = entry.get_df_by_id(spark, "' +
dataInfo.pid +
'", "' +
dataInfo.id +
'")' +
sufTxt;
break;
case 'sparkrkernel':
cellEditor.model.value.text =
preTxt +
newLine +
'input_' +
dataInfo.name +
' <- sophonR.getDataByID(spark, "' +
dataInfo.pid +
'", "' +
dataInfo.id +
'")' +
sufTxt;
break;
case 'sparkkernel':
cellEditor.model.value.text =
preTxt +
newLine +
'val input_' +
dataInfo.name +
' = SophonNotebook.getDataFrameByID(spark, "' +
dataInfo.pid +
'", "' +
dataInfo.id +
'")' +
sufTxt;
break;
default:
alert('This function only suit for spark kernel !!');
break;
}
Private.handleState(notebook, state);
}
NotebookActions.executeGetData = executeGetData;
/**
* insert getTextData code into current ater cursor
*/
function executeGetTextData(dataInfo, notebook, session) {
if (!notebook.model || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
notebook.deselectAll();
const cellIndex = notebook.activeCellIndex;
const cellTarget = notebook.widgets[cellIndex];
const cellEditor = cellTarget.editor;
const position = cellEditor.getCursorPosition();
const offset = cellEditor.getOffsetAt(position);
const originTxt = cellTarget.model.value.text;
const preTxt = originTxt.slice(0, offset);
const sufTxt = originTxt.slice(offset);
const newLine = offset === 0 ? '' : '\n';
switch (session.kernel.name) {
case 'pyspark3kernel':
cellTarget.model.value.text =
preTxt +
newLine +
'inputstream_' +
dataInfo.name +
' = entry.get_hdfs_file_input_stream(spark, "' +
dataInfo.pid +
'", "' +
dataInfo.id +
'")' +
sufTxt;
break;
case 'sparkrkernel':
cellTarget.model.value.text =
preTxt +
newLine +
'inputstream_' +
dataInfo.name +
' <- sophonR.getHdfsFileInputStream(spark, "' +
dataInfo.pid +
'", "' +
dataInfo.id +
'")' +
sufTxt;
break;
case 'sparkkernel':
cellTarget.model.value.text =
preTxt +
newLine +
'val inputstream_' +
dataInfo.name +
' = SophonNotebook.getHdfsFileInputStream(spark, "' +
dataInfo.pid +
'", "' +
dataInfo.id +
'")' +
sufTxt;
break;
default:
alert('This function only suit for spark kernel !!');
break;
}
Private.handleState(notebook, state);
}
NotebookActions.executeGetTextData = executeGetTextData;
/**
* 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 targe notebook widget.
*/
function deselectAll(notebook) {
if (!notebook.model || !notebook.activeCell) {
return;
}
notebook.deselectAll();
}
NotebookActions.deselectAll = deselectAll;
/**
* Copy the selected cell data to a clipboard.
*
* @param notebook - The target notebook widget.
*/
function copy(notebook) {
Private.copyOrCut(notebook, false);
}
NotebookActions.copy = copy;
/**
* 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;
/**
* Paste cells from the application clipboard.
*
* @param notebook - The target notebook widget.
*
* @param mode - the mode of the paste operation: 'below' pastes cells
* below the active cell, 'above' pastes cells above the active cell,
* and 'replace' removes the currently selected cells and pastes 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 paste(notebook, mode = 'below') {
if (!notebook.model || !notebook.activeCell) {
return;
}
const clipboard = Clipboard.getInstance();
if (!clipboard.hasData(JUPYTER_CELL_MIME)) {
return;
}
const state = Private.getState(notebook);
const values = clipboard.getData(JUPYTER_CELL_MIME);
const model = notebook.model;
notebook.mode = 'command';
const newCells = values.map(cell => {
switch (cell.cell_type) {
case 'code':
return model.contentFactory.createCodeCell({ cell });
case 'markdown':
return model.contentFactory.createMarkdownCell({ cell });
default:
return model.contentFactory.createRawCell({ cell });
}
});
const cells = notebook.model.cells;
let index;
cells.beginCompoundOperation();
// Set the starting index of the paste operation depending upon the mode.
switch (mode) {
case 'below':
index = notebook.activeCellIndex;
break;
case 'above':
index = notebook.activeCellIndex - 1;
break;
case 'replace':
// Find the cells to delete.
const toDelete = [];
notebook.widgets.forEach((child, index) => {
const deletable = child.model.metadata.get('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 => {
cells.remove(i);
});
}
index = toDelete[0];
break;
default:
break;
}
newCells.forEach(cell => {
cells.insert(++index, cell);
});
cells.endCompoundOperation();
notebook.activeCellIndex += newCells.length;
notebook.deselectAll();
Private.handleState(notebook, state);
}
NotebookActions.paste = paste;
/**
* Undo a cell action.
*
* @param notebook - The target notebook widget.
*
* #### Notes
* This is a no-op if if there are no cell actions to undo.
*/
function undo(notebook) {
if (!notebook.model || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
notebook.mode = 'command';
notebook.model.cells.undo();
notebook.deselectAll();
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.cells.redo();
notebook.deselectAll();
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: Object.assign({}, config.code, { lineNumbers }),
markdown: Object.assign({}, config.markdown, { lineNumbers }),
raw: Object.assign({}, config.raw, { lineNumbers })
};
notebook.editorConfig = newConfig;
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);
each(notebook.model.cells, (cell, index) => {
const child = notebook.widgets[index];
if (notebook.isSelectedOrActive(child) && cell.type === 'code') {
cell.outputs.clear();
child.outputHidden = false;
cell.executionCount = null;
}
});
Private.handleState(notebook, state);
}
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);
each(notebook.model.cells, (cell, index) => {
const child = notebook.widgets[index];
if (cell.type === 'code') {
cell.outputs.clear();
cell.executionCount = null;
child.outputHidden = false;
}
});
Private.handleState(notebook, state);
}
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;
}
});
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;
}
});
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;
}
});
Private.handleState(notebook, state);
}
NotebookActions.hideAllCode = hideAllCode;
/**
* Show the code on all code cells.
*
* @param widget - 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;
}
});
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;
}
});
Private.handleState(notebook, state);
}
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;
}
});
Private.handleState(notebook, state);
}
NotebookActions.showOutput = showOutput;
/**
* 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;
}
});
Private.handleState(notebook, state);
}
NotebookActions.hideAllOutputs = hideAllOutputs;
/**
* 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;
}
});
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;
}
});
Private.handleState(notebook, state);
}
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;
}
});
Private.handleState(notebook, state);
}
NotebookActions.disableOutputScrolling = disableOutputScrolling;
/**
* Set the markdown header level.
*
* @param notebook - The target notebook widget.
*
* @param level - The header level.
*
* #### 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) {
if (!notebook.model || !notebook.activeCell) {
return;
}
const state = Private.getState(notebook);
const cells = notebook.model.cells;
level = Math.min(Math.max(level, 1), 6);
notebook.widgets.forEach((child, index) => {
if (notebook.isSelectedOrActive(child)) {
Private.setMarkdownHeader(cells.get(index), level);
}
});
Private.changeCellType(notebook, 'markdown');
Private.handleState(notebook, state);
}
NotebookActions.setMarkdownHeader = setMarkdownHeader;
/**
* Trust the notebook after prompting the user.
*
* @param notebook - The target notebook widget.
*
* @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) {
if (!notebook.model) {
return Promise.resolve();
}
// Do nothing if already trusted.
const cells = toArray(notebook.model.cells);
const trusted = cells.every(cell => cell.trusted);
if (trusted) {
return showDialog({
body: 'Notebook is already trusted',
buttons: [Dialog.okButton()]
}).then(() => undefined);
}
return showDialog({
body: TRUST_MESSAGE,
title: 'Trust this notebook?',
buttons: [Dialog.cancelButton(), Dialog.warnButton()]
}).then(result => {
if (result.button.accept) {
cells.forEach(cell => {
cell.trusted = true;
});
}
});
}
NotebookActions.trust = trust;
})(NotebookActions || (NotebookActions = {}));
/**
* A namespace for private data.
*/
var Private;
(function (Private) {
/**
* A signal that emits whenever a cell is run.
*/
Private.executed = new Signal({});
/**
* Get the state of a widget before running an action.
*/
function getState(notebook) {
return {
wasFocused: notebook.node.contains(document.activeElement),
activeCell: notebook.activeCell
};
}
Private.getState = getState;
/**
* Handle the state of a widget after running an action.
*/
function handleState(notebook, state, scrollIfNeeded = false) {
const { activeCell, node } = notebook;
if (state.wasFocused || notebook.mode === 'edit') {
notebook.activate();
}
if (scrollIfNeeded) {
ElementExt.scrollIntoViewIfNeeded(node, activeCell.node);
}
}
Private.handleState = handleState;
/**
* Handle the state of a widget after running a run action.
*/
function handleRunState(notebook, state, scroll = false) {
if (state.wasFocused || notebook.mode === 'edit') {
notebook.activate();
}
if (scroll) {
// Scroll to the top of the previous active cell output.
const rect = state.activeCell.inputArea.node.getBoundingClientRect();
notebook.scrollToPosition(rect.bottom, 45);
}
}
Private.handleRunState = handleRunState;
/**
* Clone a cell model.
*/
function cloneCell(model, cell) {
switch (cell.type) {
case 'code':
// TODO why isn't modeldb or id passed here?
return model.contentFactory.createCodeCell({ cell: cell.toJSON() });
case 'markdown':
// TODO why isn't modeldb or id passed here?
return model.contentFactory.createMarkdownCell({ cell: cell.toJSON() });
default:
// TODO why isn't modeldb or id passed here?
return model.contentFactory.createRawCell({ cell: cell.toJSON() });
}
}
Private.cloneCell = cloneCell;
/**
* Run the selected cells.
*/
function runSelected(notebook, session) {
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 Promise.all(selected.map(child => runCell(notebook, child, session)))
.then(results => {
if (notebook.isDisposed) {
return false;
}
// Post an update request.
notebook.update();
return results.every(result => result);
})
.catch(reason => {
if (reason.message === 'KernelReplyNotOK') {
selected.map(cell => {
// Remove '*' prompt from cells that didn't execute
if (cell.model.type === 'code' &&
cell.model.executionCount == null) {
cell.setPrompt('');
}
});
}
else {
throw reason;
}
notebook.update();
return false;
});
}
Private.runSelected = runSelected;
/**
* Run a cell.
*/
function runCell(notebook, cell, session) {
switch (cell.model.type) {
case 'markdown':
cell.rendered = true;
cell.inputHidden = false;
Private.executed.emit({ notebook, cell });
break;
case 'code':
if (session) {
return CodeCell.execute(cell, session, {
deletedCells: notebook.model.deletedCells
})
.then(reply => {
notebook.model.deletedCells.splice(0, notebook.model.deletedCells.length);
if (cell.isDisposed) {
return false;
}
if (!reply) {
return true;
}
if (reply.content.status === 'ok') {
const content = reply.content;
if (content.payload && content.payload.length) {
handlePayload(content, notebook, cell);
}
return true;
}
else {
throw new Error('KernelReplyNotOK');
}
})
.catch(reason => {
if (reason.message !== 'Canceled') {
throw reason;
}
return false;
})
.then(ran => {
if (ran) {
Private.execute