-
Notifications
You must be signed in to change notification settings - Fork 950
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
Add support for widgets in JupyterLab code consoles #3004
Add support for widgets in JupyterLab code consoles #3004
Conversation
Awesome! Just curious, if a notebook and console share a kernel, do the widgets stay in sync? |
It should be possible to make it work by changing the way widget managers are stored here:
ipywidgets/packages/jupyterlab-manager/src/plugin.ts Lines 323 to 330 in 5132234
So that instead of a |
Nice! And it still works between two views of a notebook, or two views of an output? |
In that case yes, as all the views share the same
But using the path is a bit too brittle. Maybe there should be a |
Agreed. I haven't checked, but is there any object that the two KernelConnections share? Some reference to the underlying kernel representation, perhaps? |
I'd rather avoid having the console know too much about the notebook, and vice versa, as hard-coding these checks doesn't scale to other extensions as well. |
dd26cff
to
9d2e0b2
Compare
🥜 gallery here: I am very excited about the PR and wanted exactly this with ipympl figures today. |
Great PR, looking forward to this feature as well!! Kind regards |
9d2e0b2
to
e7f6389
Compare
e7f6389
to
5193ee4
Compare
Just rebased one onto the latest
Here is a diff to use the session context path: diff --git a/jupyterlab_widgets/package.json b/jupyterlab_widgets/package.json
index 9f22f8cb..e4095418 100644
--- a/jupyterlab_widgets/package.json
+++ b/jupyterlab_widgets/package.json
@@ -68,7 +68,6 @@
"@lumino/coreutils": "^1.3.0",
"@lumino/disposable": "^1.1.1",
"@lumino/messaging": "^1.2.1",
- "@lumino/properties": "^1.1.0",
"@lumino/signaling": "^1.2.0",
"@lumino/widgets": "^1.3.0",
"@types/backbone": "1.4.1",
diff --git a/jupyterlab_widgets/src/plugin.ts b/jupyterlab_widgets/src/plugin.ts
index 10fafc8d..fe5bb93b 100644
--- a/jupyterlab_widgets/src/plugin.ts
+++ b/jupyterlab_widgets/src/plugin.ts
@@ -33,8 +33,6 @@ import { toArray, filter } from '@lumino/algorithm';
import { DisposableDelegate } from '@lumino/disposable';
-import { AttachedProperty } from '@lumino/properties';
-
import { WidgetRenderer } from './renderer';
import {
@@ -89,8 +87,7 @@ function* consoleWidgetRenderers(
): 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 codecell of (cell as unknown as CodeCell).outputArea.widgets) {
for (const output of toArray(codecell.children())) {
if (output instanceof WidgetRenderer) {
yield output;
@@ -136,11 +133,11 @@ export function registerWidgetManager(
rendermime: IRenderMimeRegistry,
renderers: IterableIterator<WidgetRenderer>
): DisposableDelegate {
- let wManager = Private.widgetManagerProperty.get(context);
+ let wManager = Private.widgetManagerProperty.get(context.sessionContext.path);
if (!wManager) {
wManager = new WidgetManager(context, rendermime, SETTINGS);
WIDGET_REGISTRY.forEach((data) => wManager!.register(data));
- Private.widgetManagerProperty.set(context, wManager);
+ Private.widgetManagerProperty.set(context.sessionContext.path, wManager);
}
for (const r of renderers) {
@@ -172,14 +169,14 @@ export function registerConsoleWidgetManager(
rendermime: IRenderMimeRegistry,
renderers: IterableIterator<WidgetRenderer>
): DisposableDelegate {
- let wManager = Private.widgetManagerProperty.get(console);
+ let wManager = Private.widgetManagerProperty.get(console.sessionContext.path);
if (!wManager) {
wManager = new KernelWidgetManager(
console.sessionContext.session?.kernel!,
rendermime
);
WIDGET_REGISTRY.forEach((data) => wManager!.register(data));
- Private.widgetManagerProperty.set(console, wManager);
+ Private.widgetManagerProperty.set(console.sessionContext.path, wManager);
}
for (const r of renderers) {
@@ -249,7 +246,9 @@ function activateWidgetExtension(
return;
}
- const wManager = Private.widgetManagerProperty.get(nb.context);
+ const wManager = Private.widgetManagerProperty.get(
+ nb.context.sessionContext.path
+ );
if (wManager) {
wManager.onUnhandledIOPubMessage.connect(
(
@@ -414,12 +413,8 @@ namespace Private {
/**
* A private attached property for a widget manager.
*/
- export const widgetManagerProperty = new AttachedProperty<
- DocumentRegistry.Context | CodeConsole,
+ export const widgetManagerProperty = new Map<
+ string,
WidgetManager | KernelWidgetManager | undefined
- >({
- name: 'widgetManager',
- create: (owner: DocumentRegistry.Context | CodeConsole): undefined =>
- undefined,
- });
+ >();
}
diff --git a/yarn.lock b/yarn.lock
index bae0d729..49a69346 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1551,7 +1551,7 @@
"@lumino/disposable" "^1.7.0"
"@lumino/signaling" "^1.7.0"
-"@lumino/properties@^1.1.0", "@lumino/properties@^1.2.3", "@lumino/properties@^1.5.0":
+"@lumino/properties@^1.2.3", "@lumino/properties@^1.5.0":
version "1.5.0"
resolved "https://registry.npmjs.org/@lumino/properties/-/properties-1.5.0.tgz#7e8638e84c51bb110c5a69f91ca8b0e40b2c3fca"
integrity sha512-YqpJE6/1Wkjrie0E+ypu+yzd55B5RlvKYMnQs3Ox+SrJsnNBhA6Oj44EhVf8SUTuHgn1t/mm+LvbswKN5RM4+g==
cc @trungleduc who was interested in this |
Thank @jtpio, i'm trying to use kernel id from session context as map keys with handler for |
Thanks @trungleduc for looking into it.
Sounds like this could work? How does it behave with kernel restarts? |
It works pretty well with kernel restart and kernel change events. I stored current id of kernel so inside the 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;
}
}); |
NIce, thanks @trungleduc for sharing. Maybe you would like to open a PR with your additional commits, so it's easier to check the diff and try it on Binder? |
PR opened on jtpio#2 |
Thanks @trungleduc! I just pushed a change to fix the integrity check. |
For those who would like to test this PR on Binder: https://mybinder.org/v2/gh/jtpio/ipywidgets/code-consoles-widgets?urlpath=lab widgets-code-consoles.mp4Marking as ready for review, so other folks can start testing the feature and reviewing the code. Thanks! |
jupyterlab_widgets/src/plugin.ts
Outdated
sessionContext: ISessionContext | ||
): Promise<Private.IWidgetManagerOwner> { | ||
await sessionContext.ready; | ||
return sessionContext.session!.kernel!.id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering whether we should use the non-null assertion operator here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A SessionContext
can exist without having a kernel yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering whether we should use the non-null assertion operator here?
I think it makes more sense to abort if we don't have a kernel. When we eventually get a kernel in the session context, the kernel changed signal is triggered which calls this again.
Update: Never mind. I've thought more about it, and put some thoughts below.
Adding to the 8.0 milestone for consideration, since this is changing a couple of interfaces. |
This is great! Many thanks to @jtpio and @trungleduc! I can't wait to see this land in the next 8.0 pre-release. |
Looks good! This is really exciting, especially in combination with your recent work on replite @jasongrout this PR is already a good improvement, maybe we could iterate later (in separate PRs) on making this more generic? I don't want to be asking too much, but it would be amazing if this could be included in 8.0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
This seems to need a rebase though |
Thanks @martinRenou. Starting the rebase now. |
3b09ef1
to
016560f
Compare
For reference this means widgets also show up in the RetroLab (future Notebook v7) code consoles: |
Temporary - it will properly work on future ipywidgets release see jupyter-widgets/ipywidgets#3004)
I wanted to give this PR a try to see if I can help with testing, rebasing or anything. However when I do
in a fresh virtualenv I still get the "Loading widget..." when creating eg an |
This PR is probably a big improvement for lots of reasons, but I have the feeling that consoles should not be a different beast than notebooks anyway. They should be a "restricted notebook", where one can only append new cells and not be able to change previous cells. If we based consoles on notebooks, then they would support widgets out of the box, right? |
016560f
to
e642a78
Compare
I just squashed to make the rebase easier (so that I don't need to fix the conflicts for all commits) |
e642a78
to
802fce2
Compare
This is changing exported APIs, we should do some exported functions renaming in order to stay backward compatible (if we don't want to wait for 9.x) |
Docs build failure should be fixed by #3918 |
Co-authored-by: Duc Trung LE <[email protected]>
573dc82
to
a66d362
Compare
Fixes #2370
This mimics the same approach as for the notebooks for now, but it might be possible to have both the consoles and notebooks share more logic.