Extension to add a new command to execute cells below selected cells

Hi there,

I would like to add a new command to JupyterLab. I would like to execute cells below selected cell. There is a command in JupyterLab that execute selected cell and all below, I would like to change this behavior slightly, I dont want to execute the current cell. I would like to call the new command with ipylab package.

Extension code:

import {
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';

import { INotebookTracker } from '@jupyterlab/notebook';
/**
 * Initialization data for the myextension extension.
 */
const plugin: JupyterFrontEndPlugin<void> = {
  id: 'myextension:plugin',
  description: 'A JupyterLab extension.',
  autoStart: true,
  requires: [INotebookTracker],
  activate: (app: JupyterFrontEnd, notebookTracker: INotebookTracker) => {
    console.log('Extension is activated!');
    const commandID = 'execute-only-below';
    app.commands.addCommand(commandID, {
      label: 'Execute only below cells',
      execute: () => {
        console.log(`Execute ${commandID}`);
        const nb = notebookTracker.currentWidget;
        // how to execute only below cells
        // without changing command mode
        // without changing selection
        if (nb) {
          const m = nb.model;
          if (m) {
            // execute cells below cell that calls command ???
          }
        }
      }
    });
  }
};

export default plugin;

Code to call the command:

from ipylab import JupyterFrontEnd
app = JupyterFrontEnd()
app.commands.execute("execute-only-below")

I would like to use this extension to write a custom ipwyidgets handler that will re-execute cells after widget update. Something like:

t = ipywidgets.Text("hello")

def handle_slider_change(change):
    app.commands.execute("execute-only-below")

t.observe(handle_slider_change, names='value')

Maybe there is a better way to automatically re-execute cells after widget update?

This topic is related to Execute all cells after widget interaction · Issue #3877 · jupyter-widgets/ipywidgets · GitHub

1 Like

without changing selection

This should be straightforward in JupyterLab 4.1, basically following:

but removing notebook.activeCellIndex = lastIndex; notebook.deselectAll(); and instead of Private.runCells using the public NotebookActions.runCells() method.

Note that prior to Define cells to run as independent of selection by krassowski · Pull Request #14996 · jupyterlab/jupyterlab · GitHub (which will be released in 4.1) the execution was tracked by changing selection of cells thus selection would always be modified; since that PR however the selection is not changed internally, and when using NotebookActions.runCells you can decide if the current cell should stay selected or not.

without changing command mode

this is harder because Private.runCells still modifies the mode internally. I am not sure if it has to modify the mode but for certain we could make it optional. If this is desirable I would suggest opening an issue to discuss :slight_smile:

1 Like

Thank you for prompt response!

I’ve updated dependencies:

    "dependencies": {
        "@jupyterlab/application": "4.1.0-beta.0",
        "@jupyterlab/apputils": "4.2.0-beta.0",
        "@jupyterlab/notebook": "4.1.0-beta.0"
    },

removed node_modules and installed all packages with new versions, and have the following extension code:

import {
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ISessionContext, ISessionContextDialogs } from '@jupyterlab/apputils';

import {
  INotebookTracker,
  Notebook,
  NotebookActions
} from '@jupyterlab/notebook';

import { ITranslator } from '@jupyterlab/translation';

function runAllBelow(
  notebook: Notebook,
  sessionContext?: ISessionContext,
  sessionDialogs?: ISessionContextDialogs,
  translator?: ITranslator
): Promise<boolean> {
  if (!notebook.model || !notebook.activeCell) {
    return Promise.resolve(false);
  }
  const promise = NotebookActions.runCells(
    notebook,
    notebook.widgets.slice(notebook.activeCellIndex),
    sessionContext,
    sessionDialogs,
    translator
  );

  return promise;
}

/**
 * Initialization data for the myextension extension.
 */
const plugin: JupyterFrontEndPlugin<void> = {
  id: 'myextension:plugin',
  description: 'A JupyterLab extension.',
  autoStart: true,
  requires: [INotebookTracker],
  activate: (app: JupyterFrontEnd, notebookTracker: INotebookTracker) => {
    console.log('Extension is activated!');
    const commandID = 'execute-only-below';
    app.commands.addCommand(commandID, {
      label: 'Execute only below cells',
      execute: () => {
        console.log(`Execute ${commandID}`);
        const nb = notebookTracker.currentWidget;
        // how to execute only below cells
        // without changing command mode
        // without changing selection
        if (nb) {
          runAllBelow(nb.content, nb.context.sessionContext);
        }
      }
    });
  }
};

export default plugin;

When calling a new command I got error in js console:

index.ts:24 Uncaught (in promise) TypeError: _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_0__.NotebookActions.runCells is not a function
    at runAllBelow (index.ts:24:35)
    at execute (index.ts:55:11)
    at g.execute (jlab_core.a61821d8a2a9c35d7996.js?v=a61821d8a2a9c35d7996:1:1615626)
    at m._execute (856.9dc62dfa1fc2e1667e5a.js?v=9dc62dfa1fc2e1667e5a:1:1425)
    at m._onMessage (856.9dc62dfa1fc2e1667e5a.js?v=9dc62dfa1fc2e1667e5a:1:1016)
    at v (644.558670f1aa9ae5791769.js?v=558670f1aa9ae5791769:1:3083)
    at d (644.558670f1aa9ae5791769.js?v=558670f1aa9ae5791769:1:2817)
    at c (644.558670f1aa9ae5791769.js?v=558670f1aa9ae5791769:1:740)
    at a.trigger (644.558670f1aa9ae5791769.js?v=558670f1aa9ae5791769:1:2710)
    at m._handle_comm_msg (595.74686e2543ce21f10975.js?v=74686e2543ce21f10975:1:8842)

What am I missing?

Did you also install the latest jupyterlab pre-release (e.g. using pip install -U --pre 'jupyterlab>=4.1.0b0')?

1 Like

Thank you! I was missing Python jupytarlab update. Here is my full solution:

import {
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ISessionContext, ISessionContextDialogs } from '@jupyterlab/apputils';

import {
  INotebookTracker,
  Notebook,
  NotebookActions
} from '@jupyterlab/notebook';

import { ITranslator } from '@jupyterlab/translation';

function runAllBelow(
  notebook: Notebook,
  sessionContext?: ISessionContext,
  sessionDialogs?: ISessionContextDialogs,
  translator?: ITranslator
): Promise<boolean> {
  if (!notebook.model || !notebook.activeCell) {
    return Promise.resolve(false);
  }
  const promise = NotebookActions.runCells(
    notebook,
    notebook.widgets.slice(notebook.activeCellIndex + 1),
    sessionContext,
    sessionDialogs,
    translator
  );

  return promise;
}

/**
 * Initialization data for the myextension extension.
 */
const plugin: JupyterFrontEndPlugin<void> = {
  id: 'myextension:plugin',
  description: 'A JupyterLab extension.',
  autoStart: true,
  requires: [INotebookTracker],
  activate: (app: JupyterFrontEnd, notebookTracker: INotebookTracker) => {
    console.log('Extension is activated!');
    const commandID = 'execute-only-below';
    app.commands.addCommand(commandID, {
      label: 'Execute only below cells',
      execute: () => {
        const nb = notebookTracker.currentWidget;
        if (nb) {
          runAllBelow(nb.content, nb.context.sessionContext);
        }
      }
    });
  }
};

export default plugin;
2 Likes