Skip to content

Commit 802fce2

Browse files
jtpiotrungleduc
authored andcommitted
Widgets support in JupyterLab console
Co-authored-by: Duc Trung LE <leductrungxf@gmail.com>
1 parent b78de43 commit 802fce2

File tree

4 files changed

+3731
-2840
lines changed

4 files changed

+3731
-2840
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,5 @@ ui-tests/playwright-report
4949
**/lite/.cache
5050
**/*.doit.*
5151
**/docs/typedoc/
52+
53+
.yarn

python/jupyterlab_widgets/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
"@jupyter-widgets/controls": "^5.0.8",
5252
"@jupyter-widgets/output": "^6.0.7",
5353
"@jupyterlab/application": "^3.0.0 || ^4.0.0",
54+
"@jupyterlab/apputils": "^3.0.0 || ^4.0.0",
55+
"@jupyterlab/console": "^3.0.0 || ^4.0.0",
5456
"@jupyterlab/docregistry": "^3.0.0 || ^4.0.0",
5557
"@jupyterlab/logconsole": "^3.0.0 || ^4.0.0",
5658
"@jupyterlab/mainmenu": "^3.0.0 || ^4.0.0",
@@ -65,7 +67,6 @@
6567
"@lumino/algorithm": "^1.11.1 || ^2.0.0",
6668
"@lumino/coreutils": "^1.11.1 || ^2.1",
6769
"@lumino/disposable": "^1.10.1 || ^2.1",
68-
"@lumino/properties": "^1.8.1 || ^2.1",
6970
"@lumino/signaling": "^1.10.1 || ^2.1",
7071
"@lumino/widgets": "^1.30.0 || ^2.1",
7172
"@types/backbone": "1.4.14",

python/jupyterlab_widgets/src/plugin.ts

Lines changed: 181 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
// Distributed under the terms of the Modified BSD License.
33

44
import { ISettingRegistry } from '@jupyterlab/settingregistry';
5+
56
import * as nbformat from '@jupyterlab/nbformat';
67

7-
import { DocumentRegistry } from '@jupyterlab/docregistry';
8+
import {
9+
IConsoleTracker,
10+
CodeConsole,
11+
ConsolePanel,
12+
} from '@jupyterlab/console';
813

914
import {
10-
INotebookModel,
1115
INotebookTracker,
1216
Notebook,
1317
NotebookPanel,
@@ -30,11 +34,13 @@ import { filter } from '@lumino/algorithm';
3034

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

33-
import { AttachedProperty } from '@lumino/properties';
34-
3537
import { WidgetRenderer } from './renderer';
3638

37-
import { WidgetManager, WIDGET_VIEW_MIMETYPE } from './manager';
39+
import {
40+
WidgetManager,
41+
WIDGET_VIEW_MIMETYPE,
42+
KernelWidgetManager,
43+
} from './manager';
3844

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

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

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

@@ -59,7 +66,7 @@ const SETTINGS: WidgetManager.Settings = { saveState: false };
5966
/**
6067
* Iterate through all widget renderers in a notebook.
6168
*/
62-
function* widgetRenderers(
69+
function* notebookWidgetRenderers(
6370
nb: Notebook
6471
): Generator<WidgetRenderer, void, unknown> {
6572
for (const cell of nb.widgets) {
@@ -77,6 +84,25 @@ function* widgetRenderers(
7784
}
7885
}
7986

87+
/**
88+
* Iterate through all widget renderers in a console.
89+
*/
90+
function* consoleWidgetRenderers(
91+
console: CodeConsole
92+
): Generator<WidgetRenderer, void, unknown> {
93+
for (const cell of toArray(console.cells)) {
94+
if (cell.model.type === 'code') {
95+
for (const codecell of (cell as unknown as CodeCell).outputArea.widgets) {
96+
for (const output of toArray(codecell.children())) {
97+
if (output instanceof WidgetRenderer) {
98+
yield output;
99+
}
100+
}
101+
}
102+
}
103+
}
104+
}
105+
80106
/**
81107
* Iterate through all matching linked output views
82108
*/
@@ -109,16 +135,69 @@ function* chain<T>(
109135
}
110136
}
111137

112-
export function registerWidgetManager(
113-
context: DocumentRegistry.IContext<INotebookModel>,
138+
/**
139+
* Get the kernel id of current notebook or console panel, this value
140+
* is used as key for `Private.widgetManagerProperty` to store the widget
141+
* manager of current notebook or console panel.
142+
*
143+
* @param {ISessionContext} sessionContext The session context of notebook or
144+
* console panel.
145+
*/
146+
async function getWidgetManagerOwner(
147+
sessionContext: ISessionContext
148+
): Promise<Private.IWidgetManagerOwner> {
149+
await sessionContext.ready;
150+
return sessionContext.session!.kernel!.id;
151+
}
152+
153+
/**
154+
* Common handler for registering both notebook and console
155+
* `WidgetManager`
156+
*
157+
* @param {(Notebook | CodeConsole)} content Context of panel.
158+
* @param {ISessionContext} sessionContext Session context of panel.
159+
* @param {IRenderMimeRegistry} rendermime Rendermime of panel.
160+
* @param {IterableIterator<WidgetRenderer>} renderers Iterator of
161+
* `WidgetRenderer` inside panel
162+
* @param {(() => WidgetManager | KernelWidgetManager)} widgetManagerFactory
163+
* function to create widget manager.
164+
*/
165+
async function registerWidgetHandler(
166+
content: Notebook | CodeConsole,
167+
sessionContext: ISessionContext,
114168
rendermime: IRenderMimeRegistry,
115-
renderers: IterableIterator<WidgetRenderer>
116-
): DisposableDelegate {
117-
let wManager = Private.widgetManagerProperty.get(context);
169+
renderers: IterableIterator<WidgetRenderer>,
170+
widgetManagerFactory: () => WidgetManager | KernelWidgetManager
171+
): Promise<DisposableDelegate> {
172+
const wManagerOwner = await getWidgetManagerOwner(sessionContext);
173+
let wManager = Private.widgetManagerProperty.get(wManagerOwner);
174+
let currentOwner: string;
175+
118176
if (!wManager) {
119-
wManager = new WidgetManager(context, rendermime, SETTINGS);
177+
wManager = widgetManagerFactory();
120178
WIDGET_REGISTRY.forEach((data) => wManager!.register(data));
121-
Private.widgetManagerProperty.set(context, wManager);
179+
Private.widgetManagerProperty.set(wManagerOwner, wManager);
180+
currentOwner = wManagerOwner;
181+
content.disposed.connect((_) => {
182+
const currentwManager = Private.widgetManagerProperty.get(currentOwner);
183+
if (currentwManager) {
184+
Private.widgetManagerProperty.delete(currentOwner);
185+
}
186+
});
187+
188+
sessionContext.kernelChanged.connect((_, args) => {
189+
const { newValue } = args;
190+
if (newValue) {
191+
const newKernelId = newValue.id;
192+
const oldwManager = Private.widgetManagerProperty.get(currentOwner);
193+
194+
if (oldwManager) {
195+
Private.widgetManagerProperty.delete(currentOwner);
196+
Private.widgetManagerProperty.set(newKernelId, oldwManager);
197+
}
198+
currentOwner = newKernelId;
199+
}
200+
});
122201
}
123202

124203
for (const r of renderers) {
@@ -145,6 +224,45 @@ export function registerWidgetManager(
145224
});
146225
}
147226

227+
export async function registerWidgetManager(
228+
panel: NotebookPanel,
229+
renderers: IterableIterator<WidgetRenderer>
230+
): Promise<DisposableDelegate> {
231+
const content = panel.content;
232+
const context = panel.context;
233+
const sessionContext = context.sessionContext;
234+
const rendermime = content.rendermime;
235+
const widgetManagerFactory = () =>
236+
new WidgetManager(context, rendermime, SETTINGS);
237+
238+
return registerWidgetHandler(
239+
content,
240+
sessionContext,
241+
rendermime,
242+
renderers,
243+
widgetManagerFactory
244+
);
245+
}
246+
247+
export async function registerConsoleWidgetManager(
248+
panel: ConsolePanel,
249+
renderers: IterableIterator<WidgetRenderer>
250+
): Promise<DisposableDelegate> {
251+
const content = panel.console;
252+
const sessionContext = content.sessionContext;
253+
const rendermime = content.rendermime;
254+
const widgetManagerFactory = () =>
255+
new KernelWidgetManager(sessionContext.session!.kernel!, rendermime);
256+
257+
return registerWidgetHandler(
258+
content,
259+
sessionContext,
260+
rendermime,
261+
renderers,
262+
widgetManagerFactory
263+
);
264+
}
265+
148266
/**
149267
* The widget manager provider.
150268
*/
@@ -154,6 +272,7 @@ export const managerPlugin: JupyterFrontEndPlugin<base.IJupyterWidgetRegistry> =
154272
requires: [IRenderMimeRegistry],
155273
optional: [
156274
INotebookTracker,
275+
IConsoleTracker,
157276
ISettingRegistry,
158277
IMainMenu,
159278
ILoggerRegistry,
@@ -175,6 +294,7 @@ function activateWidgetExtension(
175294
app: JupyterFrontEnd,
176295
rendermime: IRenderMimeRegistry,
177296
tracker: INotebookTracker | null,
297+
consoleTracker: IConsoleTracker | null,
178298
settingRegistry: ISettingRegistry | null,
179299
menu: IMainMenu | null,
180300
loggerRegistry: ILoggerRegistry | null,
@@ -183,15 +303,23 @@ function activateWidgetExtension(
183303
const { commands } = app;
184304
const trans = (translator ?? nullTranslator).load('jupyterlab_widgets');
185305

186-
const bindUnhandledIOPubMessageSignal = (nb: NotebookPanel): void => {
306+
const bindUnhandledIOPubMessageSignal = async (
307+
nb: NotebookPanel
308+
): Promise<void> => {
187309
if (!loggerRegistry) {
188310
return;
189311
}
312+
const wManagerOwner = await getWidgetManagerOwner(
313+
nb.context.sessionContext
314+
);
315+
const wManager = Private.widgetManagerProperty.get(wManagerOwner);
190316

191-
const wManager = Private.widgetManagerProperty.get(nb.context);
192317
if (wManager) {
193318
wManager.onUnhandledIOPubMessage.connect(
194-
(sender: WidgetManager, msg: KernelMessage.IIOPubMessage) => {
319+
(
320+
sender: WidgetManager | KernelWidgetManager,
321+
msg: KernelMessage.IIOPubMessage
322+
) => {
195323
const logger = loggerRegistry.getLogger(nb.context.path);
196324
let level: LogLevel = 'warning';
197325
if (
@@ -233,32 +361,32 @@ function activateWidgetExtension(
233361
);
234362

235363
if (tracker !== null) {
236-
tracker.forEach((panel) => {
237-
registerWidgetManager(
238-
panel.context,
239-
panel.content.rendermime,
240-
chain(
241-
widgetRenderers(panel.content),
242-
outputViews(app, panel.context.path)
243-
)
364+
const rendererIterator = (panel: NotebookPanel) =>
365+
chain(
366+
notebookWidgetRenderers(panel.content),
367+
outputViews(app, panel.context.path)
244368
);
245-
369+
tracker.forEach(async (panel) => {
370+
await registerWidgetManager(panel, rendererIterator(panel));
246371
bindUnhandledIOPubMessageSignal(panel);
247372
});
248-
tracker.widgetAdded.connect((sender, panel) => {
249-
registerWidgetManager(
250-
panel.context,
251-
panel.content.rendermime,
252-
chain(
253-
widgetRenderers(panel.content),
254-
outputViews(app, panel.context.path)
255-
)
256-
);
257-
373+
tracker.widgetAdded.connect(async (sender, panel) => {
374+
await registerWidgetManager(panel, rendererIterator(panel));
258375
bindUnhandledIOPubMessageSignal(panel);
259376
});
260377
}
261378

379+
if (consoleTracker !== null) {
380+
const rendererIterator = (panel: ConsolePanel) =>
381+
chain(consoleWidgetRenderers(panel.console));
382+
383+
consoleTracker.forEach(async (panel) => {
384+
await registerConsoleWidgetManager(panel, rendererIterator(panel));
385+
});
386+
consoleTracker.widgetAdded.connect(async (sender, panel) => {
387+
await registerConsoleWidgetManager(panel, rendererIterator(panel));
388+
});
389+
}
262390
if (settingRegistry !== null) {
263391
// Add a command for automatically saving (jupyter-)widget state.
264392
commands.addCommand('@jupyter-widgets/jupyterlab-manager:saveWidgetState', {
@@ -378,13 +506,23 @@ export default [
378506
];
379507
namespace Private {
380508
/**
381-
* A private attached property for a widget manager.
509+
* A type alias for keys of `widgetManagerProperty` .
382510
*/
383-
export const widgetManagerProperty = new AttachedProperty<
384-
DocumentRegistry.Context,
385-
WidgetManager | undefined
386-
>({
387-
name: 'widgetManager',
388-
create: (owner: DocumentRegistry.Context): undefined => undefined,
389-
});
511+
export type IWidgetManagerOwner = string;
512+
513+
/**
514+
* A type alias for values of `widgetManagerProperty` .
515+
*/
516+
export type IWidgetManagerValue =
517+
| WidgetManager
518+
| KernelWidgetManager
519+
| undefined;
520+
521+
/**
522+
* A private map for a widget manager.
523+
*/
524+
export const widgetManagerProperty = new Map<
525+
IWidgetManagerOwner,
526+
IWidgetManagerValue
527+
>();
390528
}

0 commit comments

Comments
 (0)