Skip to content

Commit

Permalink
Widgets support in JupyterLab console
Browse files Browse the repository at this point in the history
Co-authored-by: Duc Trung LE <[email protected]>
  • Loading branch information
2 people authored and martinRenou committed May 23, 2024
1 parent 11ade00 commit e642a78
Show file tree
Hide file tree
Showing 5 changed files with 1,053 additions and 1,917 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
}
},
"devDependencies": {
"@jupyterlab/buildutils": "^3.2.5",
"@jupyterlab/buildutils": "^3.0.7",
"@typescript-eslint/eslint-plugin": "^5.8.0",
"@typescript-eslint/parser": "^5.8.0",
"eslint": "^8.5.0",
Expand Down
3 changes: 2 additions & 1 deletion python/jupyterlab_widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
"@jupyter-widgets/controls": "^4.0.0-beta.1",
"@jupyter-widgets/output": "^5.0.0-beta.1",
"@jupyterlab/application": "^3.0.0",
"@jupyterlab/apputils": "^3.0.0",
"@jupyterlab/console": "^3.0.0",
"@jupyterlab/docregistry": "^3.0.0",
"@jupyterlab/logconsole": "^3.0.0",
"@jupyterlab/mainmenu": "^3.0.0",
Expand All @@ -68,7 +70,6 @@
"@lumino/coreutils": "^1.11.1",
"@lumino/disposable": "^1.10.1",
"@lumino/messaging": "^1.10.1",
"@lumino/properties": "^1.8.1",
"@lumino/signaling": "^1.10.1",
"@lumino/widgets": "^1.30.0",
"@types/backbone": "1.4.14",
Expand Down
224 changes: 181 additions & 43 deletions python/jupyterlab_widgets/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
// Distributed under the terms of the Modified BSD License.

import { ISettingRegistry } from '@jupyterlab/settingregistry';

import * as nbformat from '@jupyterlab/nbformat';

import { DocumentRegistry } from '@jupyterlab/docregistry';
import {
IConsoleTracker,
CodeConsole,
ConsolePanel,
} from '@jupyterlab/console';

import {
INotebookModel,
INotebookTracker,
Notebook,
NotebookPanel,
Expand All @@ -30,11 +34,13 @@ import { toArray, filter } from '@lumino/algorithm';

import { DisposableDelegate } from '@lumino/disposable';

import { AttachedProperty } from '@lumino/properties';

import { WidgetRenderer } from './renderer';

import { WidgetManager, WIDGET_VIEW_MIMETYPE } from './manager';
import {
WidgetManager,
WIDGET_VIEW_MIMETYPE,
KernelWidgetManager,
} from './manager';

import { OutputModel, OutputView, OUTPUT_WIDGET_VERSION } from './output';

Expand All @@ -48,6 +54,7 @@ import '@jupyter-widgets/base/css/index.css';
import '@jupyter-widgets/controls/css/widgets-base.css';
import { KernelMessage } from '@jupyterlab/services';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import { ISessionContext } from '@jupyterlab/apputils';

const WIDGET_REGISTRY: base.IWidgetRegistryData[] = [];

Expand All @@ -59,7 +66,7 @@ const SETTINGS: WidgetManager.Settings = { saveState: false };
/**
* Iterate through all widget renderers in a notebook.
*/
function* widgetRenderers(
function* notebookWidgetRenderers(
nb: Notebook
): Generator<WidgetRenderer, void, unknown> {
for (const cell of nb.widgets) {
Expand All @@ -75,6 +82,25 @@ function* widgetRenderers(
}
}

/**
* Iterate through all widget renderers in a console.
*/
function* consoleWidgetRenderers(
console: CodeConsole
): Generator<WidgetRenderer, void, unknown> {
for (const cell of toArray(console.cells)) {
if (cell.model.type === 'code') {
for (const codecell of (cell as unknown as CodeCell).outputArea.widgets) {
for (const output of toArray(codecell.children())) {
if (output instanceof WidgetRenderer) {
yield output;
}
}
}
}
}
}

/**
* Iterate through all matching linked output views
*/
Expand Down Expand Up @@ -105,16 +131,69 @@ function* chain<T>(
}
}

export function registerWidgetManager(
context: DocumentRegistry.IContext<INotebookModel>,
/**
* Get the kernel id of current notebook or console panel, this value
* is used as key for `Private.widgetManagerProperty` to store the widget
* manager of current notebook or console panel.
*
* @param {ISessionContext} sessionContext The session context of notebook or
* console panel.
*/
async function getWidgetManagerOwner(
sessionContext: ISessionContext
): Promise<Private.IWidgetManagerOwner> {
await sessionContext.ready;
return sessionContext.session!.kernel!.id;
}

/**
* Common handler for registering both notebook and console
* `WidgetManager`
*
* @param {(Notebook | CodeConsole)} content Context of panel.
* @param {ISessionContext} sessionContext Session context of panel.
* @param {IRenderMimeRegistry} rendermime Rendermime of panel.
* @param {IterableIterator<WidgetRenderer>} renderers Iterator of
* `WidgetRenderer` inside panel
* @param {(() => WidgetManager | KernelWidgetManager)} widgetManagerFactory
* function to create widget manager.
*/
async function registerWidgetHandler(
content: Notebook | CodeConsole,
sessionContext: ISessionContext,
rendermime: IRenderMimeRegistry,
renderers: IterableIterator<WidgetRenderer>
): DisposableDelegate {
let wManager = Private.widgetManagerProperty.get(context);
renderers: IterableIterator<WidgetRenderer>,
widgetManagerFactory: () => WidgetManager | KernelWidgetManager
): Promise<DisposableDelegate> {
const wManagerOwner = await getWidgetManagerOwner(sessionContext);
let wManager = Private.widgetManagerProperty.get(wManagerOwner);
let currentOwner: string;

if (!wManager) {
wManager = new WidgetManager(context, rendermime, SETTINGS);
wManager = widgetManagerFactory();
WIDGET_REGISTRY.forEach((data) => wManager!.register(data));
Private.widgetManagerProperty.set(context, wManager);
Private.widgetManagerProperty.set(wManagerOwner, wManager);
currentOwner = wManagerOwner;
content.disposed.connect((_) => {
const currentwManager = Private.widgetManagerProperty.get(currentOwner);
if (currentwManager) {
Private.widgetManagerProperty.delete(currentOwner);
}
});

sessionContext.kernelChanged.connect((_, args) => {
const { newValue } = args;
if (newValue) {
const newKernelId = newValue.id;
const oldwManager = Private.widgetManagerProperty.get(currentOwner);

if (oldwManager) {
Private.widgetManagerProperty.delete(currentOwner);
Private.widgetManagerProperty.set(newKernelId, oldwManager);
}
currentOwner = newKernelId;
}
});
}

for (const r of renderers) {
Expand All @@ -141,6 +220,45 @@ export function registerWidgetManager(
});
}

export async function registerWidgetManager(
panel: NotebookPanel,
renderers: IterableIterator<WidgetRenderer>
): Promise<DisposableDelegate> {
const content = panel.content;
const context = panel.context;
const sessionContext = context.sessionContext;
const rendermime = content.rendermime;
const widgetManagerFactory = () =>
new WidgetManager(context, rendermime, SETTINGS);

return registerWidgetHandler(
content,
sessionContext,
rendermime,
renderers,
widgetManagerFactory
);
}

export async function registerConsoleWidgetManager(
panel: ConsolePanel,
renderers: IterableIterator<WidgetRenderer>
): Promise<DisposableDelegate> {
const content = panel.console;
const sessionContext = content.sessionContext;
const rendermime = content.rendermime;
const widgetManagerFactory = () =>
new KernelWidgetManager(sessionContext.session!.kernel!, rendermime);

return registerWidgetHandler(
content,
sessionContext,
rendermime,
renderers,
widgetManagerFactory
);
}

/**
* The widget manager provider.
*/
Expand All @@ -149,6 +267,7 @@ const plugin: JupyterFrontEndPlugin<base.IJupyterWidgetRegistry> = {
requires: [IRenderMimeRegistry],
optional: [
INotebookTracker,
IConsoleTracker,
ISettingRegistry,
IMainMenu,
ILoggerRegistry,
Expand All @@ -172,6 +291,7 @@ function activateWidgetExtension(
app: JupyterFrontEnd,
rendermime: IRenderMimeRegistry,
tracker: INotebookTracker | null,
consoleTracker: IConsoleTracker | null,
settingRegistry: ISettingRegistry | null,
menu: IMainMenu | null,
loggerRegistry: ILoggerRegistry | null,
Expand All @@ -180,15 +300,23 @@ function activateWidgetExtension(
const { commands } = app;
const trans = (translator ?? nullTranslator).load('jupyterlab_widgets');

const bindUnhandledIOPubMessageSignal = (nb: NotebookPanel): void => {
const bindUnhandledIOPubMessageSignal = async (
nb: NotebookPanel
): Promise<void> => {
if (!loggerRegistry) {
return;
}
const wManagerOwner = await getWidgetManagerOwner(
nb.context.sessionContext
);
const wManager = Private.widgetManagerProperty.get(wManagerOwner);

const wManager = Private.widgetManagerProperty.get(nb.context);
if (wManager) {
wManager.onUnhandledIOPubMessage.connect(
(sender: WidgetManager, msg: KernelMessage.IIOPubMessage) => {
(
sender: WidgetManager | KernelWidgetManager,
msg: KernelMessage.IIOPubMessage
) => {
const logger = loggerRegistry.getLogger(nb.context.path);
let level: LogLevel = 'warning';
if (
Expand Down Expand Up @@ -230,32 +358,32 @@ function activateWidgetExtension(
);

if (tracker !== null) {
tracker.forEach((panel) => {
registerWidgetManager(
panel.context,
panel.content.rendermime,
chain(
widgetRenderers(panel.content),
outputViews(app, panel.context.path)
)
const rendererIterator = (panel: NotebookPanel) =>
chain(
notebookWidgetRenderers(panel.content),
outputViews(app, panel.context.path)
);

tracker.forEach(async (panel) => {
await registerWidgetManager(panel, rendererIterator(panel));
bindUnhandledIOPubMessageSignal(panel);
});
tracker.widgetAdded.connect((sender, panel) => {
registerWidgetManager(
panel.context,
panel.content.rendermime,
chain(
widgetRenderers(panel.content),
outputViews(app, panel.context.path)
)
);

tracker.widgetAdded.connect(async (sender, panel) => {
await registerWidgetManager(panel, rendererIterator(panel));
bindUnhandledIOPubMessageSignal(panel);
});
}

if (consoleTracker !== null) {
const rendererIterator = (panel: ConsolePanel) =>
chain(consoleWidgetRenderers(panel.console));

consoleTracker.forEach(async (panel) => {
await registerConsoleWidgetManager(panel, rendererIterator(panel));
});
consoleTracker.widgetAdded.connect(async (sender, panel) => {
await registerConsoleWidgetManager(panel, rendererIterator(panel));
});
}
if (settingRegistry !== null) {
// Add a command for automatically saving (jupyter-)widget state.
commands.addCommand('@jupyter-widgets/jupyterlab-manager:saveWidgetState', {
Expand Down Expand Up @@ -328,13 +456,23 @@ function activateWidgetExtension(

namespace Private {
/**
* A private attached property for a widget manager.
* A type alias for keys of `widgetManagerProperty` .
*/
export const widgetManagerProperty = new AttachedProperty<
DocumentRegistry.Context,
WidgetManager | undefined
>({
name: 'widgetManager',
create: (owner: DocumentRegistry.Context): undefined => undefined,
});
export type IWidgetManagerOwner = string;

/**
* A type alias for values of `widgetManagerProperty` .
*/
export type IWidgetManagerValue =
| WidgetManager
| KernelWidgetManager
| undefined;

/**
* A private map for a widget manager.
*/
export const widgetManagerProperty = new Map<
IWidgetManagerOwner,
IWidgetManagerValue
>();
}
11 changes: 7 additions & 4 deletions python/jupyterlab_widgets/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Panel, Widget as LuminoWidget } from '@lumino/widgets';

import { IRenderMime } from '@jupyterlab/rendermime-interfaces';

import { WidgetManager } from './manager';
import { LabWidgetManager } from './manager';
import { DOMWidgetModel } from '@jupyter-widgets/base';

/**
Expand All @@ -19,7 +19,10 @@ export class WidgetRenderer
extends Panel
implements IRenderMime.IRenderer, IDisposable
{
constructor(options: IRenderMime.IRendererOptions, manager?: WidgetManager) {
constructor(
options: IRenderMime.IRendererOptions,
manager?: LabWidgetManager
) {
super();
this.mimeType = options.mimeType;
if (manager) {
Expand All @@ -30,7 +33,7 @@ export class WidgetRenderer
/**
* The widget manager.
*/
set manager(value: WidgetManager) {
set manager(value: LabWidgetManager) {
value.restored.connect(this._rerender, this);
this._manager.resolve(value);
}
Expand Down Expand Up @@ -117,6 +120,6 @@ export class WidgetRenderer
* The mimetype being rendered.
*/
readonly mimeType: string;
private _manager = new PromiseDelegate<WidgetManager>();
private _manager = new PromiseDelegate<LabWidgetManager>();
private _rerenderMimeModel: IRenderMime.IMimeModel | null = null;
}
Loading

0 comments on commit e642a78

Please sign in to comment.