Skip to content

Commit 7848cee

Browse files
committed
widgets: setup comm for all widgets that get created
- this is needed, e.g., to handle on_click for buttons, and makes it so they now work - the target_name is critical to set or the comm messages get silently ignored, though they used to be seen in ipywidgets 7.x.
1 parent 9e0ca51 commit 7848cee

File tree

7 files changed

+119
-52
lines changed

7 files changed

+119
-52
lines changed

src/packages/frontend/jupyter/browser-actions.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,13 +470,19 @@ export class JupyterActions extends JupyterActions0 {
470470
this.deprecated("command", name);
471471
};
472472

473-
send_comm_message_to_kernel = async (
474-
comm_id: string,
475-
data: any,
476-
): Promise<string> => {
473+
send_comm_message_to_kernel = async ({
474+
comm_id,
475+
target_name,
476+
data,
477+
}: {
478+
comm_id: string;
479+
target_name: string;
480+
data: any;
481+
}): Promise<string> => {
477482
const msg_id = uuid();
478-
await this.api_call("comm", [msg_id, comm_id, data]);
479-
console.log("send_comm_message_to_kernel", "sent", [msg_id, comm_id, data]);
483+
const msg = { msg_id, target_name, comm_id, data };
484+
await this.api_call("comm", msg);
485+
// console.log("send_comm_message_to_kernel", "sent", msg);
480486
return msg_id;
481487
};
482488

src/packages/frontend/jupyter/output-messages/widget.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,11 @@ export const Widget: React.FC<WidgetProps> = React.memo(
259259
view.current.send = (content) => {
260260
if (!is_mounted.current || actions == null) return;
261261
const data = { method: "custom", content };
262-
actions.send_comm_message_to_kernel(model_id, data);
262+
actions.send_comm_message_to_kernel({
263+
comm_id: model_id,
264+
data,
265+
target_name: "jupyter.widget",
266+
});
263267
};
264268
}
265269

src/packages/frontend/jupyter/widgets/manager2.ts

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ export class WidgetManager {
135135
return;
136136
}
137137
const state = await this.deserializeState(model, changed);
138+
if (state.hasOwnProperty("outputs") && state["outputs"] == null) {
139+
// It can definitely be 'undefined' but set, e.g., the 'out.clear_output()' example at
140+
// https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html
141+
// causes this, which then totally breaks rendering (due to how the
142+
// upstream widget manager works). This works around that.
143+
state["outputs"] = [];
144+
}
145+
138146
log("set_state", state);
139147
model.set_state(state);
140148
};
@@ -265,10 +273,23 @@ export class WidgetManager {
265273
if (this.watching.has(model_id)) {
266274
return;
267275
}
268-
const model = await this.manager.get_model(model_id);
269-
model.on("change", this.handleModelChange);
276+
// do this before anything else async, or we could end up watching it more
277+
// than once at a time.
270278
this.watching.add(model_id);
279+
const model = await this.manager.get_model(model_id);
271280
this.last_changed[model_id] = { last_changed: 0 };
281+
model.on("change", this.handleModelChange);
282+
283+
// Also, setup comm channel.
284+
const comm = await this.openCommChannel({
285+
comm_id: model_id,
286+
target_name: "jupyter.widget",
287+
});
288+
model.comm = comm;
289+
model.comm_live = true;
290+
// TODO: we need to setup handling comm messages from
291+
// the kernel, which should call model._handle_comm_msg.
292+
// See ipywidgets/packages/base/src/widget.ts
272293
};
273294

274295
// handleModelChange is called when an ipywidgets model changes.
@@ -430,6 +451,49 @@ export class WidgetManager {
430451
// log("dereferenceModelLinks", "AFTER (success)", state);
431452
return true;
432453
};
454+
455+
openCommChannel = async ({
456+
comm_id,
457+
target_name,
458+
data,
459+
buffers,
460+
}: {
461+
comm_id: string;
462+
target_name: string;
463+
data?: unknown;
464+
buffers?: ArrayBuffer[];
465+
}): Promise<Comm> => {
466+
log("openCommChannel", { comm_id, target_name, data, buffers });
467+
const { send_comm_message_to_kernel } = this.actions;
468+
const comm = {
469+
async send(data: unknown, opts?: { buffers?: ArrayBuffer[] }) {
470+
// TODO: buffers! These need to get sent somehow.
471+
log("comm.send", data, opts);
472+
await send_comm_message_to_kernel({ comm_id, target_name, data });
473+
},
474+
475+
close() {
476+
// nothing to actually do (?)
477+
log("Connection closed");
478+
},
479+
480+
get messages() {
481+
// TODO:
482+
log("comm.message: Not Implemented");
483+
// upstream widgets handles these via
484+
// comm.on_msg(this._handle_comm_msg.bind(this));
485+
// see ipywidgets/packages/base/src/widget.ts
486+
return {
487+
[Symbol.asyncIterator]: async function* () {},
488+
};
489+
},
490+
};
491+
492+
if (data != null || buffers != null) {
493+
await comm.send(data, { buffers });
494+
}
495+
return comm;
496+
};
433497
}
434498

435499
class Environment implements WidgetEnvironment {
@@ -451,6 +515,13 @@ class Environment implements WidgetEnvironment {
451515
state = this.manager.ipywidgets_state.get_model_state(model_id);
452516
}
453517
}
518+
if (state.hasOwnProperty("outputs") && state["outputs"] == null) {
519+
// It can definitely be 'undefined' but set, e.g., the 'out.clear_output()' example at
520+
// https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html
521+
// causes this, which then totally breaks rendering (due to how the
522+
// upstream widget manager works). This works around that.
523+
state["outputs"] = [];
524+
}
454525
setTimeout(() => this.manager.watchModel(model_id), 1);
455526
return {
456527
modelName: state._model_name,
@@ -461,37 +532,12 @@ class Environment implements WidgetEnvironment {
461532
}
462533

463534
async openCommChannel(
464-
targetName: string,
535+
target_name: string,
465536
data?: unknown,
466537
buffers?: ArrayBuffer[],
467538
): Promise<Comm> {
468-
log("openCommChannel", { targetName, data, buffers });
469-
const { send_comm_message_to_kernel } = this.manager.actions;
470-
const comm = {
471-
async send(data: unknown, opts?: { buffers?: ArrayBuffer[] }) {
472-
// TODO: buffers! These need to get encoded separately (?).
473-
log("comm.send -- (todo: buffers)", data, opts);
474-
await send_comm_message_to_kernel(targetName, data);
475-
},
476-
477-
close() {
478-
log("Connection closed");
479-
},
480-
481-
get messages() {
482-
// const message = {
483-
// data: "Hello",
484-
// buffers: [new ArrayBuffer(8)],
485-
// };
486-
return {
487-
[Symbol.asyncIterator]: async function* () {
488-
//yield message;
489-
},
490-
};
491-
},
492-
};
493-
await comm.send(data, { buffers });
494-
return comm;
539+
log("Environment: openCommChannel", { target_name, data, buffers });
540+
throw Error("Not implemented!");
495541
}
496542

497543
async renderOutput(outputItem: any, destination: Element): Promise<void> {

src/packages/jupyter/kernel/kernel.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -990,18 +990,24 @@ class JupyterKernel extends EventEmitter implements JupyterKernelInterface {
990990
);
991991
}
992992

993-
public send_comm_message_to_kernel(
994-
msg_id: string,
995-
comm_id: string,
996-
data: any,
997-
): void {
993+
public send_comm_message_to_kernel({
994+
msg_id,
995+
comm_id,
996+
target_name,
997+
data,
998+
}: {
999+
msg_id: string;
1000+
comm_id: string;
1001+
target_name: string;
1002+
data: any;
1003+
}): void {
9981004
const dbg = this.dbg("send_comm_message_to_kernel");
9991005

10001006
const message = {
10011007
parent_header: {},
10021008
metadata: {},
10031009
channel: "shell",
1004-
content: { comm_id, data },
1010+
content: { comm_id, target_name, data },
10051011
header: {
10061012
msg_id,
10071013
username: "user",

src/packages/jupyter/kernel/websocket-api.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,7 @@ export async function handleApiRequest(
7878
return {};
7979
}
8080
case "comm":
81-
const [msg_id, comm_id, data] = query;
82-
return kernel.send_comm_message_to_kernel(msg_id, comm_id, data);
81+
return kernel.send_comm_message_to_kernel(query);
8382
default:
8483
throw Error(`unknown endpoint "${endpoint}"`);
8584
}

src/packages/jupyter/redux/project-actions.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,11 +1336,12 @@ export class JupyterActions extends JupyterActions0 {
13361336
if (type === "value") {
13371337
const state = this.syncdb.ipywidgets_state.get_model_value(model_id);
13381338
data = { method: "update", state };
1339-
this.jupyter_kernel.send_comm_message_to_kernel(
1340-
misc.uuid(),
1341-
model_id,
1339+
this.jupyter_kernel.send_comm_message_to_kernel({
1340+
msg_id: misc.uuid(),
1341+
target_name: "jupyter.widget",
1342+
comm_id: model_id,
13421343
data,
1343-
);
1344+
});
13441345
} else if (type === "buffers") {
13451346
// TODO: we will implement this soon. A good example where this is required
13461347
// is by the file upload widget:

src/packages/jupyter/types/project-interface.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export interface Message {
6060
// which case value not in doc and sent via different channel).
6161
export type StdinFunction = (
6262
prompt: string,
63-
password: boolean
63+
password: boolean,
6464
) => Promise<string>;
6565

6666
export interface ExecOpts {
@@ -141,12 +141,17 @@ export interface JupyterKernelInterface extends EventEmitterInterface {
141141
nbconvert(args: string[], timeout?: number): Promise<void>;
142142
load_attachment(path: string): Promise<string>;
143143
process_attachment(base64, mime): string | undefined;
144-
send_comm_message_to_kernel(msg_id: string, comm_id: string, data: any): void;
144+
send_comm_message_to_kernel(msg: {
145+
msg_id: string;
146+
comm_id: string;
147+
target_name: string;
148+
data: any;
149+
}): void;
145150
get_connection_file(): string | undefined;
146151

147152
_execute_code_queue: CodeExecutionEmitterInterface[];
148153
clear_execute_code_queue(): void;
149-
_process_execute_code_queue(): Promise<void>
154+
_process_execute_code_queue(): Promise<void>;
150155

151156
chdir(path: string): Promise<void>;
152157
ensure_running(): Promise<void>;

0 commit comments

Comments
 (0)