jupyterlab-ipyflow 0.0.173 → 0.0.174

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/index.js +147 -137
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -2,7 +2,6 @@ import { ICommandPalette } from '@jupyterlab/apputils';
2
2
  import { CodeCell } from '@jupyterlab/cells';
3
3
  import { INotebookTracker } from '@jupyterlab/notebook';
4
4
  import _ from 'lodash';
5
- const IPYFLOW_KERNEL_NAME = 'ipyflow';
6
5
  const waitingClass = 'waiting-cell';
7
6
  const readyClass = 'ready-cell';
8
7
  const readyMakingClass = 'ready-making-cell';
@@ -10,44 +9,32 @@ const readyMakingInputClass = 'ready-making-input-cell';
10
9
  const linkedWaitingClass = 'linked-waiting';
11
10
  const linkedReadyMakerClass = 'linked-ready-maker';
12
11
  const cleanup = new Event('cleanup');
13
- // ipyflow frontend state
14
- let dirtyCells = new Set();
15
- let waitingCells = new Set();
16
- let readyCells = new Set();
17
- let waiterLinks = {};
18
- let readyMakerLinks = {};
19
- let activeCell = null;
20
- let activeCellId = null;
21
- let cellsById = {};
22
- let cellModelsById = {};
23
- let orderIdxById = {};
24
- let cellPendingExecution = null;
25
- let lastExecutionMode = null;
26
- let isReactivelyExecuting = false;
27
- let isAltModeExecuting = false;
28
- let lastExecutionHighlights = null;
29
- let executedReactiveReadyCells = new Set();
30
- let newReadyCells = new Set();
31
- let forcedReactiveCells = new Set();
32
- function resetState() {
33
- dirtyCells = new Set();
34
- waitingCells = new Set();
35
- readyCells = new Set();
36
- waiterLinks = {};
37
- readyMakerLinks = {};
38
- activeCell = null;
39
- activeCellId = null;
40
- cellsById = {};
41
- cellModelsById = {};
42
- orderIdxById = {};
43
- cellPendingExecution = null;
44
- lastExecutionMode = null;
45
- isReactivelyExecuting = false;
46
- isAltModeExecuting = false;
47
- lastExecutionHighlights = null;
48
- executedReactiveReadyCells = new Set();
49
- newReadyCells = new Set();
50
- forcedReactiveCells = new Set();
12
+ const ipyflowState = {};
13
+ function initSessionState(session_id) {
14
+ ipyflowState[session_id] = {
15
+ isIpyflowCommConnected: false,
16
+ dirtyCells: new Set(),
17
+ waitingCells: new Set(),
18
+ readyCells: new Set(),
19
+ waiterLinks: {},
20
+ readyMakerLinks: {},
21
+ activeCell: null,
22
+ activeCellId: null,
23
+ cellsById: {},
24
+ cellModelsById: {},
25
+ orderIdxById: {},
26
+ cellPendingExecution: null,
27
+ lastExecutionMode: null,
28
+ isReactivelyExecuting: false,
29
+ isAltModeExecuting: false,
30
+ lastExecutionHighlights: null,
31
+ executedReactiveReadyCells: new Set(),
32
+ newReadyCells: new Set(),
33
+ forcedReactiveCells: new Set(),
34
+ };
35
+ }
36
+ function resetSessionState(session_id) {
37
+ delete ipyflowState[session_id];
51
38
  }
52
39
  /**
53
40
  * Initialization data for the jupyterlab-ipyflow extension.
@@ -63,17 +50,19 @@ const extension = {
63
50
  isVisible: () => true,
64
51
  isToggled: () => false,
65
52
  execute: () => {
53
+ var _a, _b;
66
54
  const session = notebooks.currentWidget.sessionContext;
67
55
  if (!session.isReady) {
68
56
  return;
69
57
  }
70
- if (session.session.kernel.name !== IPYFLOW_KERNEL_NAME) {
58
+ const state = ((_a = ipyflowState[session.session.id]) !== null && _a !== void 0 ? _a : {});
59
+ if (!((_b = state.isIpyflowCommConnected) !== null && _b !== void 0 ? _b : false)) {
71
60
  app.commands.execute('notebook:enter-command-mode');
72
61
  CodeCell.execute(notebooks.activeCell, session);
73
62
  }
74
63
  else if (notebooks.activeCell.model.type === 'code') {
75
64
  app.commands.execute('notebook:enter-command-mode');
76
- if (isAltModeExecuting) {
65
+ if (state.isAltModeExecuting) {
77
66
  // run the toggle twice to get to the same exec mode, but with updated timestamp
78
67
  session.session.kernel
79
68
  .requestExecute({
@@ -94,7 +83,7 @@ const extension = {
94
83
  });
95
84
  }
96
85
  else {
97
- isAltModeExecuting = true;
86
+ state.isAltModeExecuting = true;
98
87
  session.session.kernel
99
88
  .requestExecute({
100
89
  code: '%flow toggle-reactivity-until-next-reset',
@@ -143,27 +132,44 @@ const extension = {
143
132
  });
144
133
  notebooks.widgetAdded.connect((sender, nbPanel) => {
145
134
  const session = nbPanel.sessionContext;
135
+ let commDisconnectHandler = () => resetSessionState(session.session.id);
136
+ const registerCommTarget = () => {
137
+ session.session.kernel.registerCommTarget('ipyflow-client', (comm, _open_msg) => {
138
+ comm.onMsg = (msg) => {
139
+ var _a;
140
+ const payload = msg.content.data;
141
+ if (!((_a = payload.success) !== null && _a !== void 0 ? _a : true)) {
142
+ return;
143
+ }
144
+ if (payload.type === 'unestablish') {
145
+ commDisconnectHandler();
146
+ }
147
+ else if (payload.type === 'establish') {
148
+ commDisconnectHandler();
149
+ commDisconnectHandler = connectToComm(session, nbPanel.content);
150
+ }
151
+ };
152
+ commDisconnectHandler();
153
+ commDisconnectHandler = connectToComm(session, nbPanel.content);
154
+ });
155
+ };
146
156
  session.ready.then(() => {
147
157
  clearCellState(nbPanel.content);
148
- let commDisconnectHandler = resetState;
149
- if (session.session.kernel.name === IPYFLOW_KERNEL_NAME) {
150
- session.ready.then(() => {
151
- commDisconnectHandler = connectToComm(session, nbPanel.content);
152
- });
153
- }
158
+ registerCommTarget();
159
+ commDisconnectHandler();
160
+ commDisconnectHandler = connectToComm(session, nbPanel.content);
154
161
  session.kernelChanged.connect((_, args) => {
155
162
  if (args.newValue == null) {
156
163
  return;
157
164
  }
158
- resetState();
159
165
  clearCellState(nbPanel.content);
160
166
  commDisconnectHandler();
161
- commDisconnectHandler = resetState;
162
- if (args.newValue.name === IPYFLOW_KERNEL_NAME) {
163
- session.ready.then(() => {
164
- commDisconnectHandler = connectToComm(session, nbPanel.content);
165
- });
166
- }
167
+ resetSessionState(session.session.id);
168
+ commDisconnectHandler = () => resetSessionState(session.session.id);
169
+ session.ready.then(() => {
170
+ registerCommTarget();
171
+ commDisconnectHandler = connectToComm(session, nbPanel.content);
172
+ });
167
173
  });
168
174
  });
169
175
  });
@@ -212,16 +218,6 @@ const addWaitingOutputInteractions = (elem, linkedInputClass) => {
212
218
  addWaitingOutputInteraction(getJpOutputCollapser(elem), getJpInputCollapser(elem), 'mouseover', 'add', linkedInputClass);
213
219
  addWaitingOutputInteraction(getJpOutputCollapser(elem), getJpInputCollapser(elem), 'mouseout', 'remove', linkedInputClass);
214
220
  };
215
- const refreshNodeMapping = (notebook) => {
216
- cellsById = {};
217
- cellModelsById = {};
218
- orderIdxById = {};
219
- notebook.widgets.forEach((cell, idx) => {
220
- cellsById[cell.model.id] = cell.node;
221
- cellModelsById[cell.model.id] = cell.model;
222
- orderIdxById[cell.model.id] = idx;
223
- });
224
- };
225
221
  const clearCellState = (notebook) => {
226
222
  notebook.widgets.forEach((cell) => {
227
223
  cell.node.classList.remove(waitingClass);
@@ -264,9 +260,10 @@ const addUnsafeCellInteraction = (elem, linkedElems, cellsById, collapserFun, ev
264
260
  attachCleanupListener(elem, evt, listener);
265
261
  };
266
262
  const connectToComm = (session, notebook) => {
267
- resetState();
268
- activeCell = notebook.activeCell;
269
- activeCellId = activeCell.model.id;
263
+ initSessionState(session.session.id);
264
+ const state = ipyflowState[session.session.id];
265
+ state.activeCell = notebook.activeCell;
266
+ state.activeCellId = state.activeCell.model.id;
270
267
  const comm = session.session.kernel.createComm('ipyflow');
271
268
  let disconnected = false;
272
269
  const gatherCellMetadataAndContent = () => {
@@ -297,7 +294,7 @@ const connectToComm = (session, notebook) => {
297
294
  type: 'compute_exec_schedule',
298
295
  executed_cell_id: cell === null || cell === void 0 ? void 0 : cell.id,
299
296
  cell_metadata_by_id,
300
- is_reactively_executing: isReactivelyExecuting,
297
+ is_reactively_executing: state.isReactivelyExecuting,
301
298
  });
302
299
  };
303
300
  const onExecution = (cell, args) => {
@@ -330,6 +327,16 @@ const connectToComm = (session, notebook) => {
330
327
  };
331
328
  comm.send(payload);
332
329
  };
330
+ const refreshNodeMapping = (notebook) => {
331
+ state.cellsById = {};
332
+ state.cellModelsById = {};
333
+ state.orderIdxById = {};
334
+ notebook.widgets.forEach((cell, idx) => {
335
+ state.cellsById[cell.model.id] = cell.node;
336
+ state.cellModelsById[cell.model.id] = cell.model;
337
+ state.orderIdxById[cell.model.id] = idx;
338
+ });
339
+ };
333
340
  const onActiveCellChange = (nb, cell) => {
334
341
  if (notebook !== nb) {
335
342
  return;
@@ -339,34 +346,33 @@ const connectToComm = (session, notebook) => {
339
346
  return;
340
347
  }
341
348
  notifyActiveCell(cell.model);
342
- if (activeCell !== null && activeCell.model !== null) {
343
- if (activeCell.model.isDirty) {
344
- dirtyCells.add(activeCellId);
349
+ if (state.activeCell !== null && state.activeCell.model !== null) {
350
+ if (state.activeCell.model.isDirty) {
351
+ state.dirtyCells.add(state.activeCellId);
345
352
  }
346
353
  else {
347
- dirtyCells.delete(activeCellId);
354
+ state.dirtyCells.delete(state.activeCellId);
348
355
  }
349
- activeCell.model.stateChanged.disconnect(onExecution, activeCell.model.stateChanged);
356
+ state.activeCell.model.stateChanged.disconnect(onExecution, state.activeCell.model.stateChanged);
350
357
  }
351
- activeCell = cell;
352
- activeCellId = cell.model.id;
353
- if (activeCell === null ||
354
- activeCell.model === null ||
355
- activeCell.model.type !== 'code') {
358
+ state.activeCell = cell;
359
+ state.activeCellId = cell.model.id;
360
+ if (state.activeCell === null ||
361
+ state.activeCell.model === null ||
362
+ state.activeCell.model.type !== 'code') {
356
363
  return;
357
364
  }
358
- activeCell.model.stateChanged.connect(onExecution);
359
- notifyActiveCell(activeCell.model);
360
- if (dirtyCells.has(activeCellId)) {
361
- const activeCellModel = activeCell.model;
365
+ state.activeCell.model.stateChanged.connect(onExecution);
366
+ notifyActiveCell(state.activeCell.model);
367
+ if (state.dirtyCells.has(state.activeCellId)) {
368
+ const activeCellModel = state.activeCell.model;
362
369
  if (activeCellModel._setDirty !== undefined) {
363
370
  activeCellModel._setDirty(true);
364
371
  }
365
372
  }
366
373
  refreshNodeMapping(notebook);
367
- updateOneCellUI(activeCellId);
374
+ updateOneCellUI(state.activeCellId);
368
375
  };
369
- notebook.activeCellChanged.connect(onActiveCellChange);
370
376
  const actionUpdatePairs = [
371
377
  {
372
378
  action: 'mouseover',
@@ -378,7 +384,7 @@ const connectToComm = (session, notebook) => {
378
384
  },
379
385
  ];
380
386
  const updateOneCellUI = (id) => {
381
- const model = cellModelsById[id];
387
+ const model = state.cellModelsById[id];
382
388
  if (model.type !== 'code') {
383
389
  return;
384
390
  }
@@ -386,88 +392,90 @@ const connectToComm = (session, notebook) => {
386
392
  if (codeModel.executionCount == null) {
387
393
  return;
388
394
  }
389
- const elem = cellsById[id];
390
- if (waitingCells.has(id)) {
395
+ const elem = state.cellsById[id];
396
+ if (state.waitingCells.has(id)) {
391
397
  elem.classList.add(waitingClass);
392
398
  elem.classList.add(readyClass);
393
399
  elem.classList.remove(readyMakingInputClass);
394
400
  addWaitingOutputInteractions(elem, linkedWaitingClass);
395
401
  }
396
- else if (readyCells.has(id)) {
402
+ else if (state.readyCells.has(id)) {
397
403
  elem.classList.add(readyMakingInputClass);
398
404
  elem.classList.add(readyClass);
399
405
  addWaitingOutputInteractions(elem, linkedReadyMakerClass);
400
406
  }
401
- if (lastExecutionMode === 'reactive') {
407
+ if (state.lastExecutionMode === 'reactive') {
402
408
  return;
403
409
  }
404
- if (Object.prototype.hasOwnProperty.call(waiterLinks, id)) {
410
+ if (Object.prototype.hasOwnProperty.call(state.waiterLinks, id)) {
405
411
  actionUpdatePairs.forEach(({ action, update }) => {
406
- addUnsafeCellInteraction(getJpInputCollapser(elem), waiterLinks[id], cellsById, getJpInputCollapser, action, update, waitingCells);
407
- addUnsafeCellInteraction(getJpOutputCollapser(elem), waiterLinks[id], cellsById, getJpInputCollapser, action, update, waitingCells);
412
+ addUnsafeCellInteraction(getJpInputCollapser(elem), state.waiterLinks[id], state.cellsById, getJpInputCollapser, action, update, state.waitingCells);
413
+ addUnsafeCellInteraction(getJpOutputCollapser(elem), state.waiterLinks[id], state.cellsById, getJpInputCollapser, action, update, state.waitingCells);
408
414
  });
409
415
  }
410
- if (Object.prototype.hasOwnProperty.call(readyMakerLinks, id)) {
411
- if (!waitingCells.has(id)) {
416
+ if (Object.prototype.hasOwnProperty.call(state.readyMakerLinks, id)) {
417
+ if (!state.waitingCells.has(id)) {
412
418
  elem.classList.add(readyMakingClass);
413
419
  elem.classList.add(readyClass);
414
420
  }
415
421
  actionUpdatePairs.forEach(({ action, update }) => {
416
- addUnsafeCellInteraction(getJpInputCollapser(elem), readyMakerLinks[id], cellsById, getJpInputCollapser, action, update, waitingCells);
417
- addUnsafeCellInteraction(getJpInputCollapser(elem), readyMakerLinks[id], cellsById, getJpOutputCollapser, action, update, waitingCells);
422
+ addUnsafeCellInteraction(getJpInputCollapser(elem), state.readyMakerLinks[id], state.cellsById, getJpInputCollapser, action, update, state.waitingCells);
423
+ addUnsafeCellInteraction(getJpInputCollapser(elem), state.readyMakerLinks[id], state.cellsById, getJpOutputCollapser, action, update, state.waitingCells);
418
424
  });
419
425
  }
420
426
  };
421
427
  const updateUI = (notebook) => {
422
428
  clearCellState(notebook);
423
- if (lastExecutionHighlights === 'none') {
429
+ if (state.lastExecutionHighlights === 'none') {
424
430
  return;
425
431
  }
426
432
  refreshNodeMapping(notebook);
427
- for (const [id] of Object.entries(cellsById)) {
433
+ for (const [id] of Object.entries(state.cellsById)) {
428
434
  updateOneCellUI(id);
429
435
  }
430
436
  };
431
437
  comm.onMsg = (msg) => {
432
438
  var _a, _b;
433
439
  const payload = msg.content.data;
434
- if (disconnected || !((_a = payload.success) !== null && _a !== void 0 ? _a : false)) {
440
+ if (disconnected || !((_a = payload.success) !== null && _a !== void 0 ? _a : true)) {
435
441
  return;
436
442
  }
437
443
  if (payload.type === 'establish') {
444
+ state.isIpyflowCommConnected = true;
445
+ notebook.activeCellChanged.connect(onActiveCellChange);
438
446
  notebook.activeCell.model.stateChanged.connect(onExecution);
439
447
  notifyActiveCell(notebook.activeCell.model);
440
448
  notebook.model.contentChanged.connect(onContentChanged);
441
449
  requestComputeExecSchedule();
442
450
  }
443
451
  else if (payload.type === 'set_exec_mode') {
444
- isAltModeExecuting = false;
445
- lastExecutionMode = payload.exec_mode;
452
+ state.isAltModeExecuting = false;
453
+ state.lastExecutionMode = payload.exec_mode;
446
454
  }
447
455
  else if (payload.type === 'compute_exec_schedule') {
448
- waitingCells = new Set(payload.waiting_cells);
449
- readyCells = new Set(payload.ready_cells);
450
- newReadyCells = new Set([
451
- ...newReadyCells,
456
+ state.waitingCells = new Set(payload.waiting_cells);
457
+ state.readyCells = new Set(payload.ready_cells);
458
+ state.newReadyCells = new Set([
459
+ ...state.newReadyCells,
452
460
  ...payload.new_ready_cells,
453
461
  ]);
454
- forcedReactiveCells = new Set([
455
- ...forcedReactiveCells,
462
+ state.forcedReactiveCells = new Set([
463
+ ...state.forcedReactiveCells,
456
464
  ...payload.forced_reactive_cells,
457
465
  ]);
458
- waiterLinks = payload.waiter_links;
459
- readyMakerLinks = payload.ready_maker_links;
460
- cellPendingExecution = null;
466
+ state.waiterLinks = payload.waiter_links;
467
+ state.readyMakerLinks = payload.ready_maker_links;
468
+ state.cellPendingExecution = null;
461
469
  const exec_mode = payload.exec_mode;
462
- isReactivelyExecuting =
463
- isReactivelyExecuting ||
470
+ state.isReactivelyExecuting =
471
+ state.isReactivelyExecuting ||
464
472
  ((_b = payload === null || payload === void 0 ? void 0 : payload.is_reactively_executing) !== null && _b !== void 0 ? _b : false);
465
473
  const flow_order = payload.flow_order;
466
474
  const exec_schedule = payload.exec_schedule;
467
- lastExecutionMode = exec_mode;
468
- lastExecutionHighlights = payload.highlights;
475
+ state.lastExecutionMode = exec_mode;
476
+ state.lastExecutionHighlights = payload.highlights;
469
477
  const lastExecutedCellId = payload.last_executed_cell_id;
470
- executedReactiveReadyCells.add(lastExecutedCellId);
478
+ state.executedReactiveReadyCells.add(lastExecutedCellId);
471
479
  const last_execution_was_error = payload.last_execution_was_error;
472
480
  if (!last_execution_was_error) {
473
481
  let lastExecutedCellIdSeen = false;
@@ -479,19 +487,19 @@ const connectToComm = (session, notebook) => {
479
487
  }
480
488
  }
481
489
  if (cell.model.type !== 'code' ||
482
- executedReactiveReadyCells.has(cell.model.id)) {
490
+ state.executedReactiveReadyCells.has(cell.model.id)) {
483
491
  continue;
484
492
  }
485
- if (!newReadyCells.has(cell.model.id)) {
493
+ if (!state.newReadyCells.has(cell.model.id)) {
486
494
  continue;
487
495
  }
488
- if (!forcedReactiveCells.has(cell.model.id) &&
496
+ if (!state.forcedReactiveCells.has(cell.model.id) &&
489
497
  exec_mode !== 'reactive') {
490
498
  continue;
491
499
  }
492
500
  const codeCell = cell;
493
- if (cellPendingExecution === null) {
494
- cellPendingExecution = codeCell;
501
+ if (state.cellPendingExecution === null) {
502
+ state.cellPendingExecution = codeCell;
495
503
  // break early if using one of the order-based semantics
496
504
  if (flow_order === 'in_order' || exec_schedule === 'strict') {
497
505
  break;
@@ -501,34 +509,34 @@ const connectToComm = (session, notebook) => {
501
509
  // pass
502
510
  }
503
511
  else if (codeCell.model.executionCount <
504
- cellPendingExecution.model.executionCount) {
512
+ state.cellPendingExecution.model.executionCount) {
505
513
  // otherwise, execute in order of earliest execution counter
506
- cellPendingExecution = codeCell;
514
+ state.cellPendingExecution = codeCell;
507
515
  }
508
516
  }
509
517
  }
510
- if (cellPendingExecution === null) {
511
- if (isReactivelyExecuting) {
512
- if (lastExecutionHighlights === 'reactive') {
513
- readyCells = executedReactiveReadyCells;
518
+ if (state.cellPendingExecution === null) {
519
+ if (state.isReactivelyExecuting) {
520
+ if (state.lastExecutionHighlights === 'reactive') {
521
+ state.readyCells = state.executedReactiveReadyCells;
514
522
  }
515
523
  comm.send({
516
524
  type: 'reactivity_cleanup',
517
525
  });
518
526
  }
519
- forcedReactiveCells = new Set();
520
- newReadyCells = new Set();
521
- executedReactiveReadyCells = new Set();
527
+ state.forcedReactiveCells = new Set();
528
+ state.newReadyCells = new Set();
529
+ state.executedReactiveReadyCells = new Set();
522
530
  updateUI(notebook);
523
- isReactivelyExecuting = false;
524
- if (lastExecutionMode === 'reactive') {
525
- isAltModeExecuting = false;
531
+ state.isReactivelyExecuting = false;
532
+ if (state.lastExecutionMode === 'reactive') {
533
+ state.isAltModeExecuting = false;
526
534
  }
527
535
  }
528
536
  else {
529
- isReactivelyExecuting = true;
530
- onActiveCellChange(notebook, cellPendingExecution);
531
- CodeCell.execute(cellPendingExecution, session);
537
+ state.isReactivelyExecuting = true;
538
+ onActiveCellChange(notebook, state.cellPendingExecution);
539
+ CodeCell.execute(state.cellPendingExecution, session);
532
540
  }
533
541
  }
534
542
  };
@@ -537,8 +545,10 @@ const connectToComm = (session, notebook) => {
537
545
  });
538
546
  // return a disconnection handle
539
547
  return () => {
548
+ comm.dispose();
540
549
  disconnected = true;
541
- resetState();
550
+ state.isIpyflowCommConnected = false;
551
+ resetSessionState(session.session.id);
542
552
  };
543
553
  };
544
554
  export default extension;
package/package.json CHANGED
@@ -67,5 +67,5 @@
67
67
  "extension": true,
68
68
  "outputDir": "../../core/ipyflow/resources/labextension/"
69
69
  },
70
- "version": "0.0.173"
70
+ "version": "0.0.174"
71
71
  }