Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define cells to run independent of selection #65

Closed
wants to merge 9 commits into from
49 changes: 34 additions & 15 deletions packages/notebook-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2409,31 +2409,50 @@ function addCommands(
});
commands.addCommand(CommandIDs.restartAndRunToSelected, {
label: trans.__('Restart Kernel and Run up to Selected Cell…'),
execute: async () => {
const restarted: boolean = await commands.execute(CommandIDs.restart, {
activate: false
});
execute: async args => {
const current = getCurrent(tracker, shell, { activate: false, ...args });
if (!current) {
return;
}
const { context, content } = current;

const cells = content.widgets.slice(0, content.activeCellIndex + 1);
const restarted = await sessionDialogs.restart(current.sessionContext);

if (restarted) {
const executed: boolean = await commands.execute(
CommandIDs.runAllAbove,
{ activate: false }
return NotebookActions.runCells(
content,
cells,
context.sessionContext,
sessionDialogs,
translator
);
if (executed) {
return commands.execute(CommandIDs.run);
}
}
},
isEnabled: isEnabledAndSingleSelected
});
commands.addCommand(CommandIDs.restartRunAll, {
label: trans.__('Restart Kernel and Run All Cells…'),
caption: trans.__('Restart the kernel and run all cells'),
execute: async () => {
const restarted: boolean = await commands.execute(CommandIDs.restart, {
activate: false
});
execute: async args => {
const current = getCurrent(tracker, shell, { activate: false, ...args });

if (!current) {
return;
}
const { context, content } = current;

const cells = content.widgets;
const restarted = await sessionDialogs.restart(current.sessionContext);

if (restarted) {
await commands.execute(CommandIDs.runAll);
return NotebookActions.runCells(
content,
cells,
context.sessionContext,
sessionDialogs,
translator
);
}
},
isEnabled: args => (args.toolbar ? true : isEnabled()),
Expand Down
141 changes: 102 additions & 39 deletions packages/notebook/src/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,44 @@ export namespace NotebookActions {
return promise;
}

/**
* 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.
*/
export function runCells(
notebook: Notebook,
cells: readonly Cell[],
sessionContext?: ISessionContext,
sessionDialogs?: ISessionContextDialogs,
translator?: ITranslator
): Promise<boolean> {
if (!notebook.model) {
return Promise.resolve(false);
}

const state = Private.getState(notebook);
const promise = Private.runCells(
notebook,
cells,
sessionContext,
sessionDialogs,
translator
);
Private.handleRunState(notebook, state, false);
return promise;
}

/**
* Run the selected cell(s) and advance to the next cell.
*
Expand Down Expand Up @@ -692,18 +730,19 @@ export namespace NotebookActions {
}

const state = Private.getState(notebook);
const lastIndex = notebook.widgets.length;

notebook.widgets.forEach(child => {
notebook.select(child);
});

const promise = Private.runSelected(
const promise = Private.runCells(
notebook,
notebook.widgets,
sessionContext,
sessionDialogs,
translator
);

notebook.activeCellIndex = lastIndex;
notebook.deselectAll();

Private.handleRunState(notebook, state, true);
return promise;
}
Expand Down Expand Up @@ -759,20 +798,16 @@ export namespace NotebookActions {

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(
const promise = Private.runCells(
notebook,
notebook.widgets.slice(0, notebook.activeCellIndex),
sessionContext,
sessionDialogs,
translator
);

notebook.activeCellIndex++;
notebook.deselectAll();

Private.handleRunState(notebook, state, true);
return promise;
}
Expand Down Expand Up @@ -802,19 +837,19 @@ export namespace NotebookActions {
}

const state = Private.getState(notebook);
const lastIndex = notebook.widgets.length;

notebook.deselectAll();
for (let i = notebook.activeCellIndex; i < notebook.widgets.length; ++i) {
notebook.select(notebook.widgets[i]);
}

const promise = Private.runSelected(
const promise = Private.runCells(
notebook,
notebook.widgets.slice(notebook.activeCellIndex),
sessionContext,
sessionDialogs,
translator
);

notebook.activeCellIndex = lastIndex;
notebook.deselectAll();

Private.handleRunState(notebook, state, true);
return promise;
}
Expand Down Expand Up @@ -2232,35 +2267,24 @@ namespace Private {
* 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
*/
export function runSelected(
export function runCells(
notebook: Notebook,
cells: readonly Cell[],
sessionContext?: ISessionContext,
sessionDialogs?: ISessionContextDialogs,
translator?: ITranslator
): Promise<boolean> {
const lastCell = cells[-1];
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, sessionContext, sessionDialogs, translator)
cells.map(cell =>
runCell(notebook, cell, sessionContext, sessionDialogs, translator)
)
)
.then(results => {
Expand All @@ -2269,7 +2293,7 @@ namespace Private {
}
selectionExecuted.emit({
notebook,
lastCell: notebook.widgets[lastIndex]
lastCell
});
// Post an update request.
notebook.update();
Expand All @@ -2278,7 +2302,7 @@ namespace Private {
})
.catch(reason => {
if (reason.message.startsWith('KernelReplyNotOK')) {
selected.map(cell => {
cells.map(cell => {
// Remove '*' prompt from cells that didn't execute
if (
cell.model.type === 'code' &&
Expand All @@ -2293,7 +2317,7 @@ namespace Private {

selectionExecuted.emit({
notebook,
lastCell: notebook.widgets[lastIndex]
lastCell
});

notebook.update();
Expand All @@ -2302,6 +2326,45 @@ namespace Private {
});
}

/**
* Run the selected cells.
*
* @param notebook Notebook
* @param sessionContext Notebook session context
* @param sessionDialogs Session dialogs
* @param translator Application translator
*/
export function runSelected(
notebook: Notebook,
sessionContext?: ISessionContext,
sessionDialogs?: ISessionContextDialogs,
translator?: ITranslator
): Promise<boolean> {
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
);
}

/**
* Run a cell.
*/
Expand Down
51 changes: 51 additions & 0 deletions packages/notebook/test/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,36 @@ describe('@jupyterlab/notebook', () => {
});
});

describe('#runCells()', () => {
beforeEach(() => {
// Make sure all cells have valid code.
widget.widgets[2].model.sharedModel.setSource('a = 1');
});

it('should change to command mode', async () => {
widget.mode = 'edit';
const result = await NotebookActions.runCells(
widget,
[widget.widgets[2]],
sessionContext
);
expect(result).toBe(true);
expect(widget.mode).toBe('command');
});

it('should preserve the existing selection', async () => {
const next = widget.widgets[2];
widget.select(next);
const result = await NotebookActions.runCells(
widget,
[widget.widgets[1]],
sessionContext
);
expect(result).toBe(true);
expect(widget.isSelected(widget.widgets[2])).toBe(true);
});
});

describe('#runAll()', () => {
beforeEach(() => {
// Make sure all cells have valid code.
Expand Down Expand Up @@ -1070,6 +1100,27 @@ describe('@jupyterlab/notebook', () => {
});
});

describe('#runAllBelow()', () => {
it('should run all selected cell and all below', async () => {
const next = widget.widgets[1] as MarkdownCell;
const cell = widget.activeCell as CodeCell;
cell.model.outputs.clear();
next.rendered = false;
const result = await NotebookActions.runAllBelow(
widget,
sessionContext
);
expect(result).toBe(true);
expect(cell.model.outputs.length).toBeGreaterThan(0);
expect(next.rendered).toBe(true);
});

it('should activate the last cell', async () => {
await NotebookActions.runAllBelow(widget, sessionContext);
expect(widget.activeCellIndex).toBe(widget.widgets.length - 1);
});
});

describe('#selectAbove()', () => {
it('should select the cell above the active cell', () => {
widget.activeCellIndex = 1;
Expand Down
Loading