Skip to content

Commit

Permalink
Define cells to run as independent of selection (#14996)
Browse files Browse the repository at this point in the history
* Define cells to run as independent of selection

* Restore side effects of `runAll()`

* Skip optional `slice` argument

Co-authored-by: Frédéric Collonval <fcollonval@users.noreply.github.com>

* Restore previous behaviour of `runAllBelow()`

by making last cell active.

---------

Co-authored-by: Frédéric Collonval <fcollonval@users.noreply.github.com>
  • Loading branch information
krassowski and fcollonval committed Nov 10, 2023
1 parent 06ebbae commit ea6dd41
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 54 deletions.
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 @@ -552,6 +552,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 @@ -699,18 +737,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 @@ -766,20 +805,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 @@ -809,19 +844,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 @@ -2245,35 +2280,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 @@ -2282,7 +2306,7 @@ namespace Private {
}
selectionExecuted.emit({
notebook,
lastCell: notebook.widgets[lastIndex]
lastCell
});
// Post an update request.
notebook.update();
Expand All @@ -2291,7 +2315,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 @@ -2306,7 +2330,7 @@ namespace Private {

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

notebook.update();
Expand All @@ -2315,6 +2339,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

0 comments on commit ea6dd41

Please sign in to comment.