jupyterlab-ipyflow 0.0.176 → 0.0.178

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.
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ICommandPalette } from '@jupyterlab/apputils';
2
2
  import { CodeCell } from '@jupyterlab/cells';
3
- import { INotebookTracker } from '@jupyterlab/notebook';
3
+ import { INotebookTracker, NotebookActions, } from '@jupyterlab/notebook';
4
4
  import _ from 'lodash';
5
5
  const waitingClass = 'waiting-cell';
6
6
  const readyClass = 'ready-cell';
@@ -8,34 +8,148 @@ const readyMakingClass = 'ready-making-cell';
8
8
  const readyMakingInputClass = 'ready-making-input-cell';
9
9
  const linkedWaitingClass = 'linked-waiting';
10
10
  const linkedReadyMakerClass = 'linked-ready-maker';
11
+ const selfSliceClass = 'ipyflow-slice-self';
12
+ const directSliceClass = 'ipyflow-slice-direct';
13
+ const sliceClass = 'ipyflow-slice';
11
14
  const cleanup = new Event('cleanup');
15
+ // ipyflow frontend state
16
+ class IpyflowSessionState {
17
+ constructor() {
18
+ this.comm = null;
19
+ this.notebook = null;
20
+ this.session = null;
21
+ this.isIpyflowCommConnected = false;
22
+ this.executionScheduledCells = [];
23
+ this.selectedCells = [];
24
+ this.dirtyCells = new Set();
25
+ this.waitingCells = new Set();
26
+ this.readyCells = new Set();
27
+ this.waiterLinks = {};
28
+ this.readyMakerLinks = {};
29
+ this.prevActiveCell = null;
30
+ this.activeCell = null;
31
+ this.cellsById = {};
32
+ this.orderIdxById = {};
33
+ this.cellPendingExecution = null;
34
+ this.isReactivelyExecuting = false;
35
+ this.numAltModeExecutes = 0;
36
+ this.altModeExecuteCells = null;
37
+ this.lastExecutionHighlights = null;
38
+ this.executedReactiveReadyCells = new Set();
39
+ this.newReadyCells = new Set();
40
+ this.forcedReactiveCells = new Set();
41
+ this.forcedCascadingReactiveCells = new Set();
42
+ this.numPendingForcedReactiveCounterBumps = 0;
43
+ this.cellParents = {};
44
+ this.cellChildren = {};
45
+ this.settings = {};
46
+ }
47
+ gatherCellMetadataAndContent() {
48
+ const cell_metadata_by_id = {};
49
+ this.notebook.widgets.forEach((itercell, idx) => {
50
+ const model = itercell.model;
51
+ cell_metadata_by_id[model.id] = {
52
+ index: idx,
53
+ content: model.sharedModel.getSource(),
54
+ type: model.type,
55
+ };
56
+ });
57
+ return cell_metadata_by_id;
58
+ }
59
+ requestComputeExecSchedule() {
60
+ this.comm.send({
61
+ type: 'compute_exec_schedule',
62
+ cell_metadata_by_id: this.gatherCellMetadataAndContent(),
63
+ is_reactively_executing: this.isReactivelyExecuting,
64
+ });
65
+ }
66
+ executeCells(cells) {
67
+ if (cells.length === 0) {
68
+ return;
69
+ }
70
+ let numFinished = 0;
71
+ for (const cell of cells) {
72
+ // if any of them fail, change the [*] to [ ] on subsequent cells
73
+ CodeCell.execute(cell, this.session).then((msg) => {
74
+ var _a, _b, _c;
75
+ if (((_b = (_a = msg) === null || _a === void 0 ? void 0 : _a.content) === null || _b === void 0 ? void 0 : _b.status) === 'error') {
76
+ numFinished = cells.length;
77
+ for (const cell of cells) {
78
+ if ((_c = cell.promptNode.textContent) === null || _c === void 0 ? void 0 : _c.includes('[*]')) {
79
+ cell.setPrompt('');
80
+ }
81
+ }
82
+ }
83
+ else {
84
+ numFinished++;
85
+ }
86
+ if (numFinished === cells.length) {
87
+ this.requestComputeExecSchedule();
88
+ }
89
+ });
90
+ }
91
+ }
92
+ toggleReactivity() {
93
+ if (this.settings.exec_mode === 'reactive') {
94
+ this.settings.exec_mode = 'normal';
95
+ }
96
+ else if (this.settings.exec_mode === 'normal') {
97
+ this.settings.exec_mode = 'reactive';
98
+ }
99
+ return this.session.session.kernel.requestExecute({
100
+ code: '%flow toggle-reactivity',
101
+ silent: true,
102
+ store_history: false,
103
+ });
104
+ }
105
+ bumpForcedReactiveCounter() {
106
+ this.numPendingForcedReactiveCounterBumps--;
107
+ return this.session.session.kernel.requestExecute({
108
+ code: '%flow bump-min-forced-reactive-counter',
109
+ silent: true,
110
+ store_history: false,
111
+ });
112
+ }
113
+ computeTransitiveClosure(cellIds, inclusive = true, parents = false) {
114
+ const closure = new Set();
115
+ for (const cellId of cellIds) {
116
+ if (parents) {
117
+ computeTransitiveClosureHelper(closure, cellId, this.cellParents);
118
+ }
119
+ else {
120
+ computeTransitiveClosureHelper(closure, cellId, this.cellChildren);
121
+ }
122
+ }
123
+ if (!inclusive) {
124
+ for (const cellId of cellIds) {
125
+ closure.delete(cellId);
126
+ }
127
+ }
128
+ return Array.from(closure)
129
+ .filter((id) => this.cellsById[id] !== undefined)
130
+ .filter((id) => this.orderIdxById[id] !== undefined)
131
+ .sort((a, b) => this.orderIdxById[a] - this.orderIdxById[b])
132
+ .map((id) => this.cellsById[id]);
133
+ }
134
+ }
12
135
  const ipyflowState = {};
13
136
  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
- };
137
+ ipyflowState[session_id] = new IpyflowSessionState();
35
138
  }
36
139
  function resetSessionState(session_id) {
37
140
  delete ipyflowState[session_id];
38
141
  }
142
+ function computeTransitiveClosureHelper(closure, cellId, edges) {
143
+ if (closure.has(cellId)) {
144
+ return;
145
+ }
146
+ closure.add(cellId);
147
+ const children = edges[cellId];
148
+ if (children === undefined) {
149
+ return;
150
+ }
151
+ children.forEach((child) => computeTransitiveClosureHelper(closure, child, edges));
152
+ }
39
153
  /**
40
154
  * Initialization data for the jupyterlab-ipyflow extension.
41
155
  */
@@ -55,46 +169,52 @@ const extension = {
55
169
  if (!session.isReady) {
56
170
  return;
57
171
  }
172
+ app.commands.execute('notebook:enter-command-mode');
58
173
  const state = ((_a = ipyflowState[session.session.id]) !== null && _a !== void 0 ? _a : {});
174
+ const altModeExecuteCells = state.altModeExecuteCells;
175
+ state.altModeExecuteCells = null;
59
176
  if (!((_b = state.isIpyflowCommConnected) !== null && _b !== void 0 ? _b : false)) {
60
- app.commands.execute('notebook:enter-command-mode');
61
177
  CodeCell.execute(notebooks.activeCell, session);
178
+ return;
179
+ }
180
+ if (state.settings.reactivity_mode !== 'batch' &&
181
+ altModeExecuteCells !== null) {
182
+ return;
62
183
  }
63
- else if (notebooks.activeCell.model.type === 'code') {
64
- app.commands.execute('notebook:enter-command-mode');
65
- if (state.isAltModeExecuting) {
66
- // run the toggle twice to get to the same exec mode, but with updated timestamp
67
- session.session.kernel
68
- .requestExecute({
69
- code: '%flow toggle-reactivity-until-next-reset',
70
- silent: true,
71
- store_history: false,
72
- })
73
- .done.then(() => {
74
- session.session.kernel
75
- .requestExecute({
76
- code: '%flow toggle-reactivity-until-next-reset',
77
- silent: true,
78
- store_history: false,
79
- })
80
- .done.then(() => {
81
- CodeCell.execute(notebooks.activeCell, session);
82
- });
83
- });
184
+ if (notebooks.activeCell.model.type !== 'code') {
185
+ return;
186
+ }
187
+ state.numAltModeExecutes++;
188
+ if (state.settings.reactivity_mode === 'incremental') {
189
+ if (state.numAltModeExecutes === 1) {
190
+ state
191
+ .toggleReactivity()
192
+ .done.then(() => CodeCell.execute(notebooks.activeCell, session));
84
193
  }
85
194
  else {
86
- state.isAltModeExecuting = true;
87
- session.session.kernel
88
- .requestExecute({
89
- code: '%flow toggle-reactivity-until-next-reset',
90
- silent: true,
91
- store_history: false,
92
- })
93
- .done.then(() => {
94
- CodeCell.execute(notebooks.activeCell, session);
95
- });
195
+ CodeCell.execute(notebooks.activeCell, session);
96
196
  }
97
197
  }
198
+ else if (state.settings.reactivity_mode === 'batch') {
199
+ let closure = altModeExecuteCells !== null && altModeExecuteCells !== void 0 ? altModeExecuteCells : [notebooks.activeCell];
200
+ if (state.settings.exec_mode === 'normal' &&
201
+ altModeExecuteCells === null) {
202
+ closure = state.computeTransitiveClosure([
203
+ notebooks.activeCell.model.id,
204
+ ]);
205
+ }
206
+ if (state.numAltModeExecutes === 1) {
207
+ state
208
+ .toggleReactivity()
209
+ .done.then(() => state.executeCells(closure));
210
+ }
211
+ else {
212
+ state.executeCells(closure);
213
+ }
214
+ }
215
+ else {
216
+ console.error(`Unknown reactivity mode: ${state.settings.reactivity_mode}`);
217
+ }
98
218
  },
99
219
  });
100
220
  app.commands.addKeyBinding({
@@ -112,24 +232,148 @@ const extension = {
112
232
  category: 'execution',
113
233
  args: {},
114
234
  });
115
- app.commands.addCommand('alt-execute', {
116
- label: 'Alt Execute',
235
+ const executeSlice = (isBackward) => {
236
+ var _a, _b;
237
+ const session = notebooks.currentWidget.sessionContext;
238
+ if (!session.isReady) {
239
+ return;
240
+ }
241
+ const state = ((_a = ipyflowState[session.session.id]) !== null && _a !== void 0 ? _a : {});
242
+ if (!((_b = state.isIpyflowCommConnected) !== null && _b !== void 0 ? _b : false)) {
243
+ return;
244
+ }
245
+ app.commands.execute('notebook:enter-command-mode');
246
+ const closure = state.computeTransitiveClosure([state.activeCell.model.id], true, isBackward);
247
+ state.numPendingForcedReactiveCounterBumps++;
248
+ if (state.settings.exec_mode === 'normal') {
249
+ state.executeCells(closure);
250
+ }
251
+ else {
252
+ state.altModeExecuteCells = closure;
253
+ app.commands.execute('alt-mode-execute');
254
+ }
255
+ };
256
+ app.commands.addCommand('execute-forward-slice', {
257
+ label: 'Execute Forward Slice',
117
258
  isEnabled: () => true,
118
259
  isVisible: () => true,
119
260
  isToggled: () => false,
120
- execute: () => {
121
- const session = notebooks.currentWidget.sessionContext;
122
- if (session.isReady) {
123
- app.commands.execute('notebook:enter-command-mode');
124
- CodeCell.execute(notebooks.activeCell, session);
125
- }
126
- },
261
+ execute: () => executeSlice(false),
262
+ });
263
+ app.commands.addKeyBinding({
264
+ command: 'execute-forward-slice',
265
+ keys: ['Accel J'],
266
+ selector: '.jp-Notebook',
127
267
  });
128
268
  app.commands.addKeyBinding({
129
- command: 'alt-execute',
130
- keys: ['Accel Enter'],
269
+ command: 'execute-forward-slice',
270
+ keys: ['Accel ArrowDown'],
131
271
  selector: '.jp-Notebook',
132
272
  });
273
+ app.commands.addCommand('execute-backward-slice', {
274
+ label: 'Execute Backward Slice',
275
+ isEnabled: () => true,
276
+ isVisible: () => true,
277
+ isToggled: () => false,
278
+ execute: () => executeSlice(true),
279
+ });
280
+ app.commands.addKeyBinding({
281
+ command: 'execute-backward-slice',
282
+ keys: ['Accel K'],
283
+ selector: '.jp-Notebook',
284
+ });
285
+ app.commands.addKeyBinding({
286
+ command: 'execute-backward-slice',
287
+ keys: ['Accel ArrowUp'],
288
+ selector: '.jp-Notebook',
289
+ });
290
+ const executeRemaining = () => {
291
+ var _a, _b;
292
+ const session = notebooks.currentWidget.sessionContext;
293
+ if (!session.isReady) {
294
+ return;
295
+ }
296
+ const state = ((_a = ipyflowState[session.session.id]) !== null && _a !== void 0 ? _a : {});
297
+ if ((_b = state.isIpyflowCommConnected) !== null && _b !== void 0 ? _b : false) {
298
+ let closureCellIds = state.executionScheduledCells;
299
+ state.executionScheduledCells = [];
300
+ if (closureCellIds.length === 0) {
301
+ closureCellIds = [state.activeCell.model.id];
302
+ }
303
+ const closure = state.computeTransitiveClosure(closureCellIds, false);
304
+ if (closure.length > 0) {
305
+ state.executeCells(closure);
306
+ }
307
+ else {
308
+ state.requestComputeExecSchedule();
309
+ }
310
+ }
311
+ };
312
+ NotebookActions.executionScheduled.connect((_, args) => {
313
+ var _a, _b, _c;
314
+ const notebook = notebooks === null || notebooks === void 0 ? void 0 : notebooks.currentWidget;
315
+ if ((notebook === null || notebook === void 0 ? void 0 : notebook.content) !== args.notebook) {
316
+ return;
317
+ }
318
+ const session = notebook === null || notebook === void 0 ? void 0 : notebook.sessionContext;
319
+ if (!((_a = session === null || session === void 0 ? void 0 : session.isReady) !== null && _a !== void 0 ? _a : false)) {
320
+ return;
321
+ }
322
+ const state = ((_b = ipyflowState[session.session.id]) !== null && _b !== void 0 ? _b : {});
323
+ if (!((_c = state.isIpyflowCommConnected) !== null && _c !== void 0 ? _c : false)) {
324
+ return;
325
+ }
326
+ const settings = state.settings;
327
+ const isBatch = (settings === null || settings === void 0 ? void 0 : settings.reactivity_mode) === 'batch';
328
+ const isReactive = (settings === null || settings === void 0 ? void 0 : settings.exec_mode) === 'reactive';
329
+ if (!isBatch || !isReactive) {
330
+ return;
331
+ }
332
+ if (args.cell.model.type === 'code') {
333
+ state.executionScheduledCells.push(args.cell.model.id);
334
+ }
335
+ });
336
+ app.commands.commandExecuted.connect((_, args) => {
337
+ var _a, _b, _c;
338
+ const notebook = notebooks === null || notebooks === void 0 ? void 0 : notebooks.currentWidget;
339
+ const session = notebook === null || notebook === void 0 ? void 0 : notebook.sessionContext;
340
+ if (!((_a = session === null || session === void 0 ? void 0 : session.isReady) !== null && _a !== void 0 ? _a : false)) {
341
+ return;
342
+ }
343
+ const state = ((_b = ipyflowState[session.session.id]) !== null && _b !== void 0 ? _b : {});
344
+ if (!((_c = state.isIpyflowCommConnected) !== null && _c !== void 0 ? _c : false)) {
345
+ return;
346
+ }
347
+ const settings = state.settings;
348
+ const isBatch = (settings === null || settings === void 0 ? void 0 : settings.reactivity_mode) === 'batch';
349
+ const isReactive = (settings === null || settings === void 0 ? void 0 : settings.exec_mode) === 'reactive';
350
+ if (!isBatch) {
351
+ return;
352
+ }
353
+ if (args.id === 'notebook:run-cell') {
354
+ if (isReactive) {
355
+ executeRemaining();
356
+ }
357
+ else {
358
+ state.requestComputeExecSchedule();
359
+ }
360
+ }
361
+ else if (args.id === 'notebook:run-cell-and-select-next') {
362
+ const origActiveCell = state.activeCell;
363
+ try {
364
+ state.activeCell = state.prevActiveCell;
365
+ if (isReactive) {
366
+ executeRemaining();
367
+ }
368
+ else {
369
+ state.requestComputeExecSchedule();
370
+ }
371
+ }
372
+ finally {
373
+ state.activeCell = origActiveCell;
374
+ }
375
+ }
376
+ });
133
377
  notebooks.widgetAdded.connect((sender, nbPanel) => {
134
378
  const session = nbPanel.sessionContext;
135
379
  let commDisconnectHandler = () => resetSessionState(session.session.id);
@@ -146,18 +390,18 @@ const extension = {
146
390
  }
147
391
  else if (payload.type === 'establish') {
148
392
  commDisconnectHandler();
149
- commDisconnectHandler = connectToComm(session, nbPanel.content);
393
+ commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content);
150
394
  }
151
395
  };
152
396
  commDisconnectHandler();
153
- commDisconnectHandler = connectToComm(session, nbPanel.content);
397
+ commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content);
154
398
  });
155
399
  };
156
400
  session.ready.then(() => {
157
401
  clearCellState(nbPanel.content);
158
402
  registerCommTarget();
159
403
  commDisconnectHandler();
160
- commDisconnectHandler = connectToComm(session, nbPanel.content);
404
+ commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content);
161
405
  session.kernelChanged.connect((_, args) => {
162
406
  if (args.newValue == null) {
163
407
  return;
@@ -168,7 +412,7 @@ const extension = {
168
412
  commDisconnectHandler = () => resetSessionState(session.session.id);
169
413
  session.ready.then(() => {
170
414
  registerCommTarget();
171
- commDisconnectHandler = connectToComm(session, nbPanel.content);
415
+ commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content);
172
416
  });
173
417
  });
174
418
  });
@@ -224,6 +468,9 @@ const clearCellState = (notebook) => {
224
468
  cell.node.classList.remove(readyMakingClass);
225
469
  cell.node.classList.remove(readyClass);
226
470
  cell.node.classList.remove(readyMakingInputClass);
471
+ cell.node.classList.remove(selfSliceClass);
472
+ cell.node.classList.remove(directSliceClass);
473
+ cell.node.classList.remove(sliceClass);
227
474
  // clear any old event listeners
228
475
  const inputCollapser = getJpInputCollapser(cell.node);
229
476
  if (inputCollapser !== null) {
@@ -249,7 +496,7 @@ const addUnsafeCellInteraction = (elem, linkedElems, cellsById, collapserFun, ev
249
496
  if (waitingCells.has(linkedId)) {
250
497
  css = linkedWaitingClass;
251
498
  }
252
- const collapser = collapserFun(cellsById[linkedId]);
499
+ const collapser = collapserFun(cellsById[linkedId].node);
253
500
  if (collapser === null || collapser.firstElementChild === null) {
254
501
  return;
255
502
  }
@@ -259,44 +506,36 @@ const addUnsafeCellInteraction = (elem, linkedElems, cellsById, collapserFun, ev
259
506
  elem.addEventListener(evt, listener);
260
507
  attachCleanupListener(elem, evt, listener);
261
508
  };
262
- const connectToComm = (session, notebook) => {
509
+ const connectToComm = (session, notebooks, notebook) => {
263
510
  initSessionState(session.session.id);
264
511
  const state = ipyflowState[session.session.id];
265
512
  state.activeCell = notebook.activeCell;
266
- state.activeCellId = state.activeCell.model.id;
267
- const comm = session.session.kernel.createComm('ipyflow');
513
+ const comm = session.session.kernel.createComm('ipyflow', 'ipyflow');
514
+ state.comm = comm;
515
+ state.notebook = notebook;
516
+ state.session = session;
268
517
  let disconnected = false;
269
- const gatherCellMetadataAndContent = () => {
270
- const cell_metadata_by_id = {};
271
- notebook.widgets.forEach((itercell, idx) => {
272
- const model = itercell.model;
273
- cell_metadata_by_id[model.id] = {
274
- index: idx,
275
- content: model.sharedModel.getSource(),
276
- type: model.type,
277
- };
278
- });
279
- return cell_metadata_by_id;
518
+ const syncDirtiness = (cell) => {
519
+ if (cell !== null && cell.model !== null) {
520
+ if (cell.model.isDirty) {
521
+ state.dirtyCells.add(cell.model.id);
522
+ }
523
+ else {
524
+ state.dirtyCells.delete(cell.model.id);
525
+ }
526
+ }
280
527
  };
281
528
  const onContentChanged = _.debounce(() => {
282
529
  if (disconnected) {
283
530
  notebook.model.contentChanged.disconnect(onContentChanged);
284
531
  return;
285
532
  }
533
+ notebook.widgets.forEach(syncDirtiness);
286
534
  comm.send({
287
535
  type: 'notify_content_changed',
288
- cell_metadata_by_id: gatherCellMetadataAndContent(),
536
+ cell_metadata_by_id: state.gatherCellMetadataAndContent(),
289
537
  });
290
538
  }, 500);
291
- const requestComputeExecSchedule = (cell) => {
292
- const cell_metadata_by_id = gatherCellMetadataAndContent();
293
- comm.send({
294
- type: 'compute_exec_schedule',
295
- executed_cell_id: cell === null || cell === void 0 ? void 0 : cell.id,
296
- cell_metadata_by_id,
297
- is_reactively_executing: state.isReactivelyExecuting,
298
- });
299
- };
300
539
  const onExecution = (cell, args) => {
301
540
  if (disconnected) {
302
541
  cell.stateChanged.disconnect(onExecution);
@@ -305,14 +544,37 @@ const connectToComm = (session, notebook) => {
305
544
  if (args.name !== 'executionCount' || args.newValue === null) {
306
545
  return;
307
546
  }
547
+ state.dirtyCells.delete(cell.id);
308
548
  notebook.widgets.forEach((itercell) => {
309
549
  if (itercell.model.id === cell.id) {
310
550
  itercell.node.classList.remove(readyClass);
311
551
  itercell.node.classList.remove(readyMakingInputClass);
312
552
  }
313
553
  });
314
- requestComputeExecSchedule(cell);
554
+ if (state.settings.reactivity_mode === 'incremental') {
555
+ state.requestComputeExecSchedule();
556
+ }
557
+ };
558
+ for (const cell of notebook.widgets) {
559
+ cell.model.stateChanged.connect(onExecution);
560
+ }
561
+ const onCellsAdded = (_, change) => {
562
+ if (disconnected) {
563
+ notebook.model.cells.changed.disconnect(onCellsAdded);
564
+ return;
565
+ }
566
+ if (change.type === 'add') {
567
+ for (const cell of change.newValues) {
568
+ cell === null || cell === void 0 ? void 0 : cell.stateChanged.connect(onExecution);
569
+ }
570
+ }
571
+ else if (change.type === 'remove') {
572
+ for (const cell of change.oldValues) {
573
+ cell === null || cell === void 0 ? void 0 : cell.stateChanged.disconnect(onExecution);
574
+ }
575
+ }
315
576
  };
577
+ notebook.model.cells.changed.connect(onCellsAdded);
316
578
  const notifyActiveCell = (newActiveCell) => {
317
579
  let newActiveCellOrderIdx = -1;
318
580
  notebook.widgets.forEach((itercell, idx) => {
@@ -329,15 +591,14 @@ const connectToComm = (session, notebook) => {
329
591
  };
330
592
  const refreshNodeMapping = (notebook) => {
331
593
  state.cellsById = {};
332
- state.cellModelsById = {};
333
594
  state.orderIdxById = {};
334
595
  notebook.widgets.forEach((cell, idx) => {
335
- state.cellsById[cell.model.id] = cell.node;
336
- state.cellModelsById[cell.model.id] = cell.model;
596
+ state.cellsById[cell.model.id] = cell;
337
597
  state.orderIdxById[cell.model.id] = idx;
338
598
  });
339
599
  };
340
600
  const onActiveCellChange = (nb, cell) => {
601
+ var _a, _b;
341
602
  if (notebook !== nb) {
342
603
  return;
343
604
  }
@@ -346,32 +607,18 @@ const connectToComm = (session, notebook) => {
346
607
  return;
347
608
  }
348
609
  notifyActiveCell(cell.model);
349
- if (state.activeCell !== null && state.activeCell.model !== null) {
350
- if (state.activeCell.model.isDirty) {
351
- state.dirtyCells.add(state.activeCellId);
352
- }
353
- else {
354
- state.dirtyCells.delete(state.activeCellId);
355
- }
356
- state.activeCell.model.stateChanged.disconnect(onExecution, state.activeCell.model.stateChanged);
357
- }
610
+ state.prevActiveCell = state.activeCell;
358
611
  state.activeCell = cell;
359
- state.activeCellId = cell.model.id;
360
612
  if (state.activeCell === null ||
361
613
  state.activeCell.model === null ||
362
614
  state.activeCell.model.type !== 'code') {
363
615
  return;
364
616
  }
365
- state.activeCell.model.stateChanged.connect(onExecution);
366
617
  notifyActiveCell(state.activeCell.model);
367
- if (state.dirtyCells.has(state.activeCellId)) {
368
- const activeCellModel = state.activeCell.model;
369
- if (activeCellModel._setDirty !== undefined) {
370
- activeCellModel._setDirty(true);
371
- }
618
+ if (state.dirtyCells.has(state.activeCell.model.id)) {
619
+ (_b = (_a = state.activeCell.model)._setDirty) === null || _b === void 0 ? void 0 : _b.call(_a, true);
372
620
  }
373
- refreshNodeMapping(notebook);
374
- updateOneCellUI(state.activeCellId);
621
+ updateUI(notebook);
375
622
  };
376
623
  const actionUpdatePairs = [
377
624
  {
@@ -383,8 +630,8 @@ const connectToComm = (session, notebook) => {
383
630
  update: 'remove',
384
631
  },
385
632
  ];
386
- const updateOneCellUI = (id) => {
387
- const model = state.cellModelsById[id];
633
+ const updateOneCellUI = (id, isSelf, inDirectSlice, inSlice, showCollapserHighlights) => {
634
+ const model = state.cellsById[id].model;
388
635
  if (model.type !== 'code') {
389
636
  return;
390
637
  }
@@ -392,7 +639,28 @@ const connectToComm = (session, notebook) => {
392
639
  if (codeModel.executionCount == null) {
393
640
  return;
394
641
  }
395
- const elem = state.cellsById[id];
642
+ const elem = state.cellsById[id].node;
643
+ if (isSelf) {
644
+ elem.classList.add(selfSliceClass);
645
+ }
646
+ else {
647
+ elem.classList.remove(selfSliceClass);
648
+ }
649
+ if (inDirectSlice && !isSelf) {
650
+ elem.classList.add(directSliceClass);
651
+ }
652
+ else {
653
+ elem.classList.remove(directSliceClass);
654
+ }
655
+ if (inSlice && !inDirectSlice && !isSelf) {
656
+ elem.classList.add(sliceClass);
657
+ }
658
+ else {
659
+ elem.classList.remove(sliceClass);
660
+ }
661
+ if (!showCollapserHighlights) {
662
+ return;
663
+ }
396
664
  if (state.waitingCells.has(id)) {
397
665
  elem.classList.add(waitingClass);
398
666
  elem.classList.add(readyClass);
@@ -404,16 +672,16 @@ const connectToComm = (session, notebook) => {
404
672
  elem.classList.add(readyClass);
405
673
  addWaitingOutputInteractions(elem, linkedReadyMakerClass);
406
674
  }
407
- if (state.lastExecutionMode === 'reactive') {
675
+ if (state.settings.exec_mode === 'reactive') {
408
676
  return;
409
677
  }
410
- if (Object.prototype.hasOwnProperty.call(state.waiterLinks, id)) {
678
+ if (state.waiterLinks[id] !== undefined) {
411
679
  actionUpdatePairs.forEach(({ action, update }) => {
412
680
  addUnsafeCellInteraction(getJpInputCollapser(elem), state.waiterLinks[id], state.cellsById, getJpInputCollapser, action, update, state.waitingCells);
413
681
  addUnsafeCellInteraction(getJpOutputCollapser(elem), state.waiterLinks[id], state.cellsById, getJpInputCollapser, action, update, state.waitingCells);
414
682
  });
415
683
  }
416
- if (Object.prototype.hasOwnProperty.call(state.readyMakerLinks, id)) {
684
+ if (state.readyMakerLinks[id] !== undefined) {
417
685
  if (!state.waitingCells.has(id)) {
418
686
  elem.classList.add(readyMakingClass);
419
687
  elem.classList.add(readyClass);
@@ -426,14 +694,52 @@ const connectToComm = (session, notebook) => {
426
694
  };
427
695
  const updateUI = (notebook) => {
428
696
  clearCellState(notebook);
429
- if (state.lastExecutionHighlights === 'none') {
430
- return;
431
- }
432
697
  refreshNodeMapping(notebook);
698
+ const slice = new Set();
699
+ let directSlice = new Set();
700
+ let closureCellIds = state.selectedCells;
701
+ if (closureCellIds.length === 0) {
702
+ closureCellIds = [state.activeCell.model.id];
703
+ }
704
+ for (const cellId of closureCellIds) {
705
+ if (state.cellChildren[cellId] !== undefined ||
706
+ state.cellParents[cellId] !== undefined) {
707
+ directSlice = new Set([
708
+ cellId,
709
+ ...directSlice,
710
+ ...state.cellChildren[cellId],
711
+ ...state.cellParents[cellId],
712
+ ]);
713
+ computeTransitiveClosureHelper(slice, cellId, state.cellChildren);
714
+ slice.delete(cellId);
715
+ computeTransitiveClosureHelper(slice, cellId, state.cellParents);
716
+ }
717
+ }
433
718
  for (const [id] of Object.entries(state.cellsById)) {
434
- updateOneCellUI(id);
719
+ updateOneCellUI(id, id === state.activeCell.model.id && directSlice.has(id), directSlice.has(id), slice.has(id), state.lastExecutionHighlights !== 'none');
720
+ }
721
+ };
722
+ const onSelectionChanged = () => {
723
+ var _a, _b, _c;
724
+ if (disconnected) {
725
+ notebooks.selectionChanged.disconnect(onSelectionChanged);
726
+ }
727
+ const nbPanel = notebooks === null || notebooks === void 0 ? void 0 : notebooks.currentWidget;
728
+ const session = nbPanel === null || nbPanel === void 0 ? void 0 : nbPanel.sessionContext;
729
+ if (!((_a = session === null || session === void 0 ? void 0 : session.isReady) !== null && _a !== void 0 ? _a : false)) {
730
+ return;
435
731
  }
732
+ const state = ((_b = ipyflowState[session.session.id]) !== null && _b !== void 0 ? _b : {});
733
+ if (!((_c = state.isIpyflowCommConnected) !== null && _c !== void 0 ? _c : false)) {
734
+ return;
735
+ }
736
+ const notebook = nbPanel.content;
737
+ state.selectedCells = notebook.widgets
738
+ .filter((cell) => cell.model.type === 'code' && notebook.isSelected(cell))
739
+ .map((cell) => cell.model.id);
740
+ updateUI(notebook);
436
741
  };
742
+ notebooks.selectionChanged.connect(onSelectionChanged);
437
743
  comm.onMsg = (msg) => {
438
744
  var _a, _b;
439
745
  const payload = msg.content.data;
@@ -446,38 +752,97 @@ const connectToComm = (session, notebook) => {
446
752
  notebook.activeCell.model.stateChanged.connect(onExecution);
447
753
  notifyActiveCell(notebook.activeCell.model);
448
754
  notebook.model.contentChanged.connect(onContentChanged);
449
- requestComputeExecSchedule();
755
+ state.requestComputeExecSchedule();
450
756
  }
451
757
  else if (payload.type === 'set_exec_mode') {
452
- state.isAltModeExecuting = false;
453
- state.lastExecutionMode = payload.exec_mode;
758
+ state.numAltModeExecutes = 0;
759
+ state.settings.exec_mode = payload.exec_mode;
454
760
  }
455
761
  else if (payload.type === 'compute_exec_schedule') {
762
+ state.settings = payload.settings;
763
+ state.cellParents = payload.cell_parents;
764
+ state.cellChildren = payload.cell_children;
456
765
  state.waitingCells = new Set(payload.waiting_cells);
457
766
  state.readyCells = new Set(payload.ready_cells);
458
- state.newReadyCells = new Set([
459
- ...state.newReadyCells,
460
- ...payload.new_ready_cells,
461
- ]);
462
- state.forcedReactiveCells = new Set([
463
- ...state.forcedReactiveCells,
464
- ...payload.forced_reactive_cells,
465
- ]);
767
+ if (state.numPendingForcedReactiveCounterBumps === 0) {
768
+ state.forcedReactiveCells = new Set([
769
+ ...state.forcedReactiveCells,
770
+ ...payload.forced_reactive_cells,
771
+ ]);
772
+ state.forcedCascadingReactiveCells = new Set([
773
+ ...state.forcedCascadingReactiveCells,
774
+ ...payload.forced_cascading_reactive_cells,
775
+ ]);
776
+ }
777
+ else {
778
+ state.forcedReactiveCells = new Set();
779
+ state.forcedCascadingReactiveCells = new Set();
780
+ }
466
781
  state.waiterLinks = payload.waiter_links;
467
782
  state.readyMakerLinks = payload.ready_maker_links;
468
783
  state.cellPendingExecution = null;
469
784
  const exec_mode = payload.exec_mode;
470
785
  state.isReactivelyExecuting =
471
786
  state.isReactivelyExecuting ||
472
- ((_b = payload === null || payload === void 0 ? void 0 : payload.is_reactively_executing) !== null && _b !== void 0 ? _b : false);
787
+ ((_b = payload === null || payload === void 0 ? void 0 : payload.is_reactively_executing) !== null && _b !== void 0 ? _b : false) ||
788
+ exec_mode === 'reactive';
789
+ if (exec_mode === 'reactive') {
790
+ state.newReadyCells = new Set([
791
+ ...state.newReadyCells,
792
+ ...payload.new_ready_cells,
793
+ ]);
794
+ }
795
+ else {
796
+ state.newReadyCells = new Set();
797
+ }
473
798
  const flow_order = payload.flow_order;
474
799
  const exec_schedule = payload.exec_schedule;
475
- state.lastExecutionMode = exec_mode;
476
800
  state.lastExecutionHighlights = payload.highlights;
477
801
  const lastExecutedCellId = payload.last_executed_cell_id;
478
802
  state.executedReactiveReadyCells.add(lastExecutedCellId);
479
803
  const last_execution_was_error = payload.last_execution_was_error;
480
- if (!last_execution_was_error) {
804
+ let doneReactivelyExecuting = false;
805
+ if (last_execution_was_error) {
806
+ doneReactivelyExecuting = true;
807
+ }
808
+ else if (state.settings.reactivity_mode === 'batch') {
809
+ const cascadingReactiveCellIds = state
810
+ .computeTransitiveClosure(Array.from(state.forcedCascadingReactiveCells).filter((id) => !state.executedReactiveReadyCells.has(id)))
811
+ .map((cell) => cell.model.id);
812
+ let reactiveCells;
813
+ if (exec_mode === 'reactive') {
814
+ reactiveCells = state
815
+ .computeTransitiveClosure([
816
+ ...state.newReadyCells,
817
+ ...state.forcedReactiveCells,
818
+ ...cascadingReactiveCellIds,
819
+ ].filter((id) => !state.executedReactiveReadyCells.has(id)))
820
+ .filter((cell) => !state.executedReactiveReadyCells.has(cell.model.id));
821
+ }
822
+ else {
823
+ reactiveCells = [
824
+ ...state.forcedReactiveCells,
825
+ ...cascadingReactiveCellIds,
826
+ ]
827
+ .filter((id) => !state.executedReactiveReadyCells.has(id) &&
828
+ state.cellsById[id] !== undefined &&
829
+ state.orderIdxById[id] !== undefined)
830
+ .sort((a, b) => state.orderIdxById[a] - state.orderIdxById[b])
831
+ .map((id) => state.cellsById[id]);
832
+ }
833
+ if (reactiveCells.length === 0) {
834
+ doneReactivelyExecuting = true;
835
+ }
836
+ else {
837
+ state.isReactivelyExecuting = true;
838
+ state.executedReactiveReadyCells = new Set([
839
+ ...state.executedReactiveReadyCells,
840
+ ...reactiveCells.map((cell) => cell.model.id),
841
+ ]);
842
+ state.executeCells(reactiveCells);
843
+ }
844
+ }
845
+ else if (state.settings.reactivity_mode === 'incremental') {
481
846
  let lastExecutedCellIdSeen = false;
482
847
  for (const cell of notebook.widgets) {
483
848
  if (!lastExecutedCellIdSeen) {
@@ -490,12 +855,11 @@ const connectToComm = (session, notebook) => {
490
855
  state.executedReactiveReadyCells.has(cell.model.id)) {
491
856
  continue;
492
857
  }
493
- if (!state.newReadyCells.has(cell.model.id)) {
494
- continue;
495
- }
496
- if (!state.forcedReactiveCells.has(cell.model.id) &&
497
- exec_mode !== 'reactive') {
498
- continue;
858
+ if (!state.forcedReactiveCells.has(cell.model.id)) {
859
+ if (!state.newReadyCells.has(cell.model.id) ||
860
+ exec_mode !== 'reactive') {
861
+ continue;
862
+ }
499
863
  }
500
864
  const codeCell = cell;
501
865
  if (state.cellPendingExecution === null) {
@@ -514,8 +878,15 @@ const connectToComm = (session, notebook) => {
514
878
  state.cellPendingExecution = codeCell;
515
879
  }
516
880
  }
881
+ if (state.cellPendingExecution === null) {
882
+ doneReactivelyExecuting = true;
883
+ }
884
+ else {
885
+ state.isReactivelyExecuting = true;
886
+ CodeCell.execute(state.cellPendingExecution, session);
887
+ }
517
888
  }
518
- if (state.cellPendingExecution === null) {
889
+ if (doneReactivelyExecuting) {
519
890
  if (state.isReactivelyExecuting) {
520
891
  if (state.lastExecutionHighlights === 'reactive') {
521
892
  state.readyCells = state.executedReactiveReadyCells;
@@ -524,19 +895,18 @@ const connectToComm = (session, notebook) => {
524
895
  type: 'reactivity_cleanup',
525
896
  });
526
897
  }
898
+ if (state.numAltModeExecutes > 0 && --state.numAltModeExecutes === 0) {
899
+ state.toggleReactivity();
900
+ }
901
+ if (state.numPendingForcedReactiveCounterBumps > 0) {
902
+ state.bumpForcedReactiveCounter();
903
+ }
527
904
  state.forcedReactiveCells = new Set();
905
+ state.forcedCascadingReactiveCells = new Set();
528
906
  state.newReadyCells = new Set();
529
907
  state.executedReactiveReadyCells = new Set();
530
- updateUI(notebook);
531
908
  state.isReactivelyExecuting = false;
532
- if (state.lastExecutionMode === 'reactive') {
533
- state.isAltModeExecuting = false;
534
- }
535
- }
536
- else {
537
- state.isReactivelyExecuting = true;
538
- onActiveCellChange(notebook, state.cellPendingExecution);
539
- CodeCell.execute(state.cellPendingExecution, session);
909
+ updateUI(notebook);
540
910
  }
541
911
  }
542
912
  };
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.176"
70
+ "version": "0.0.178"
71
71
  }
package/style/index.css CHANGED
@@ -62,3 +62,26 @@
62
62
  .ready-making-input-cell .jp-InputCollapser:hover > .jp-Collapser-child {
63
63
  background-color: var(--ready-making-color);
64
64
  }
65
+
66
+ /* get rid of the '.' in the dirty indicator since we have appropriated it */
67
+ .jp-Notebook .jp-Cell.jp-mod-dirty .jp-InputPrompt::before {
68
+ color: var(--jp-warn-color1);
69
+ content: '';
70
+ }
71
+
72
+ .jp-Notebook .jp-Cell.ipyflow-slice-self .jp-InputPrompt::before {
73
+ color: var(--jp-brand-color1);
74
+ content: '•';
75
+ }
76
+
77
+ .jp-Notebook .jp-Cell.ipyflow-slice-direct .jp-InputPrompt::before {
78
+ color: var(--ready-making-color);
79
+ content: '•';
80
+ }
81
+
82
+ .jp-Notebook .jp-Cell.ipyflow-slice .jp-InputPrompt::before {
83
+ /* color: var(--waiting-color); */
84
+ /* just use the same color as direct for now */
85
+ color: var(--ready-making-color);
86
+ content: '•';
87
+ }