jupyterlab-ipyflow 0.0.217 → 0.0.228

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.
@@ -0,0 +1,22 @@
1
+ declare const _default: {
2
+ readonly ipyflowClassicColors: string;
3
+ readonly ipyflowSlice: string;
4
+ readonly ipyflowSliceExecute: string;
5
+ readonly jpCell: string;
6
+ readonly jpCollapser: string;
7
+ readonly jpCollapserChild: string;
8
+ readonly jpInputCollapser: string;
9
+ readonly jpInputPrompt: string;
10
+ readonly jpModDirty: string;
11
+ readonly jpModSelected: string;
12
+ readonly jpNotebook: string;
13
+ readonly jpOutputCollapser: string;
14
+ readonly jpWindowedPanelScrollbarContent: string;
15
+ readonly linkedReadyMaker: string;
16
+ readonly linkedWaiting: string;
17
+ readonly readyCell: string;
18
+ readonly readyMakingCell: string;
19
+ readonly readyMakingInputCell: string;
20
+ readonly waitingCell: string;
21
+ };
22
+ export default _default;
package/lib/classes.js ADDED
@@ -0,0 +1,13 @@
1
+ import styles from '../style/index.module.css';
2
+ function hyphenToCamel(s) {
3
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
4
+ }
5
+ function makeRawClassMap(styles) {
6
+ const out = {};
7
+ for (const key of Object.keys(styles)) {
8
+ const camel = hyphenToCamel(key);
9
+ out[camel] = key; // value = original hyphenated class name
10
+ }
11
+ return out;
12
+ }
13
+ export default makeRawClassMap(styles);
@@ -0,0 +1,16 @@
1
+ import { ISessionContext } from '@jupyterlab/apputils';
2
+ import { IDocumentManager } from '@jupyterlab/docmanager';
3
+ import { INotebookTracker, Notebook } from '@jupyterlab/notebook';
4
+ /**
5
+ * Establish the ipyflow comm for a notebook session: create the store, build
6
+ * the connection context, wire the notebook event handlers and message
7
+ * dispatcher, and open the comm. Returns a disconnect handler that tears the
8
+ * connection down.
9
+ */
10
+ export declare function connectToComm(session: ISessionContext, notebooks: INotebookTracker, notebook: Notebook, docManager: IDocumentManager): () => void;
11
+ /**
12
+ * Activate-time wiring: track the foreground notebook for the debug hook and
13
+ * register the `ipyflow-client` comm target (plus kernel-restart handling) for
14
+ * each notebook as it is added.
15
+ */
16
+ export declare function setupComm(notebooks: INotebookTracker, docManager: IDocumentManager): void;
@@ -0,0 +1,138 @@
1
+ import { clearDebugStore, getStore, initStore, resetStore, setDebugStore, } from '../state/registry';
2
+ import { updateUI } from '../ui/decorations';
3
+ import { clearCellState } from '../ui/dom';
4
+ import { createMessageHandler } from './messageHandlers';
5
+ import { createNotebookEventHandlers } from './notebookEvents';
6
+ /**
7
+ * Establish the ipyflow comm for a notebook session: create the store, build
8
+ * the connection context, wire the notebook event handlers and message
9
+ * dispatcher, and open the comm. Returns a disconnect handler that tears the
10
+ * connection down.
11
+ */
12
+ export function connectToComm(session, notebooks, notebook, docManager) {
13
+ const store = initStore(session.session.id);
14
+ store.activeCell = notebook.activeCell;
15
+ store.comm = session.session.kernel.createComm('ipyflow', 'ipyflow');
16
+ store.notebook = notebook;
17
+ store.session = session;
18
+ const ipyflowMetadata = notebook.model.getMetadata?.('ipyflow') ?? {};
19
+ const ctx = {
20
+ store,
21
+ session,
22
+ notebooks,
23
+ notebook,
24
+ docManager,
25
+ disconnected: false,
26
+ onEstablishPayload: null,
27
+ ipyflowMetadata,
28
+ safeSend: null, // assigned just below
29
+ };
30
+ const commDisconnectHandler = () => {
31
+ if (!store.comm.isDisposed) {
32
+ store.comm.dispose();
33
+ }
34
+ ctx.disconnected = true;
35
+ store.isIpyflowCommConnected = false;
36
+ resetStore(session.session.id);
37
+ };
38
+ const safeSend = (data) => {
39
+ if (ctx.disconnected) {
40
+ return;
41
+ }
42
+ else if (store.comm.isDisposed) {
43
+ ctx.onEstablishPayload = data;
44
+ const oldComm = store.comm;
45
+ store.comm = session.session.kernel.createComm('ipyflow', 'ipyflow');
46
+ store.comm.onMsg = oldComm.onMsg;
47
+ store.comm.open({
48
+ interface: 'jupyterlab',
49
+ cell_metadata_by_id: store.gatherCellMetadataAndContent(),
50
+ cell_parents: ctx.ipyflowMetadata?.cell_parents ?? {},
51
+ cell_children: ctx.ipyflowMetadata?.cell_children ?? {},
52
+ });
53
+ }
54
+ else {
55
+ store.comm.send(data);
56
+ }
57
+ };
58
+ ctx.safeSend = safeSend;
59
+ store.safeSend = safeSend;
60
+ const handlers = createNotebookEventHandlers(ctx);
61
+ // Wire the listeners that must be live before `establish`.
62
+ for (const cell of notebook.widgets) {
63
+ cell.model.stateChanged.connect(handlers.onExecution);
64
+ }
65
+ notebook.model.cells.changed.connect(handlers.onCellsAdded);
66
+ notebooks.selectionChanged.connect(handlers.onSelectionChanged);
67
+ // Re-render decorations whenever the store signals a change.
68
+ store.changed.connect(() => updateUI(store, notebooks));
69
+ store.comm.onMsg = createMessageHandler(ctx, handlers);
70
+ store.comm.open({
71
+ interface: 'jupyterlab',
72
+ cell_metadata_by_id: store.gatherCellMetadataAndContent(),
73
+ cell_parents: ctx.ipyflowMetadata?.cell_parents ?? {},
74
+ cell_children: ctx.ipyflowMetadata?.cell_children ?? {},
75
+ });
76
+ return commDisconnectHandler;
77
+ }
78
+ /**
79
+ * Activate-time wiring: track the foreground notebook for the debug hook and
80
+ * register the `ipyflow-client` comm target (plus kernel-restart handling) for
81
+ * each notebook as it is added.
82
+ */
83
+ export function setupComm(notebooks, docManager) {
84
+ notebooks.currentChanged.connect((_, nbPanel) => {
85
+ const session = nbPanel.sessionContext;
86
+ if (session?.session == null) {
87
+ clearDebugStore();
88
+ return;
89
+ }
90
+ const store = getStore(session.session.id);
91
+ setDebugStore(store ?? null);
92
+ if (store?.isIpyflowCommConnected ?? false) {
93
+ store.requestComputeExecSchedule();
94
+ }
95
+ });
96
+ notebooks.widgetAdded.connect((_sender, nbPanel) => {
97
+ const session = nbPanel.sessionContext;
98
+ let commDisconnectHandler = () => resetStore(session.session.id);
99
+ const registerCommTarget = () => {
100
+ session.session.kernel.registerCommTarget('ipyflow-client', (comm, _open_msg) => {
101
+ comm.onMsg = (msg) => {
102
+ const payload = msg.content.data;
103
+ if (!(payload.success ?? true)) {
104
+ return;
105
+ }
106
+ if (payload.type === 'unestablish') {
107
+ commDisconnectHandler();
108
+ }
109
+ else if (payload.type === 'establish') {
110
+ commDisconnectHandler();
111
+ commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager);
112
+ }
113
+ };
114
+ commDisconnectHandler();
115
+ commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager);
116
+ });
117
+ };
118
+ session.ready.then(() => {
119
+ clearCellState(nbPanel.content);
120
+ registerCommTarget();
121
+ commDisconnectHandler();
122
+ commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager);
123
+ session.kernelChanged.connect((_, args) => {
124
+ if (args.newValue == null) {
125
+ return;
126
+ }
127
+ clearCellState(nbPanel.content);
128
+ commDisconnectHandler();
129
+ resetStore(session.session.id);
130
+ commDisconnectHandler = () => resetStore(session.session.id);
131
+ session.ready.then(() => {
132
+ registerCommTarget();
133
+ commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager);
134
+ });
135
+ });
136
+ });
137
+ });
138
+ }
@@ -0,0 +1,27 @@
1
+ import { ISessionContext } from '@jupyterlab/apputils';
2
+ import { IDocumentManager } from '@jupyterlab/docmanager';
3
+ import { INotebookTracker, Notebook } from '@jupyterlab/notebook';
4
+ import { JSONValue } from '@lumino/coreutils';
5
+ import { IpyflowSessionStore } from '../state/SessionStore';
6
+ /**
7
+ * Mutable per-connection context shared by the comm lifecycle, the notebook
8
+ * event handlers, the message dispatcher, and the schedule handler. Bundling
9
+ * the closure state that `connectToComm` used to capture lets those concerns
10
+ * live in separate modules while still sharing the same `disconnected` flag,
11
+ * deferred establish payload, etc.
12
+ */
13
+ export interface IConnectionContext {
14
+ store: IpyflowSessionStore;
15
+ session: ISessionContext;
16
+ notebooks: INotebookTracker;
17
+ notebook: Notebook;
18
+ docManager: IDocumentManager;
19
+ /** Set true by the disconnect handler; read by every handler to bail out. */
20
+ disconnected: boolean;
21
+ /** Payload buffered while the comm is re-created, replayed on establish. */
22
+ onEstablishPayload: JSONValue | null;
23
+ /** ipyflow metadata read off the notebook model at connect time. */
24
+ ipyflowMetadata: any;
25
+ /** Send that transparently re-creates a disposed comm. */
26
+ safeSend: (data: JSONValue) => void;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { KernelMessage } from '@jupyterlab/services';
2
+ import { IConnectionContext } from './context';
3
+ import { INotebookEventHandlers } from './notebookEvents';
4
+ /**
5
+ * Build the `comm.onMsg` dispatcher. On `establish` it wires up the
6
+ * active-cell/content/execution listeners and flushes any buffered payload; on
7
+ * `set_exec_mode` it records the mode; `compute_exec_schedule` is delegated to
8
+ * the schedule handler.
9
+ */
10
+ export declare function createMessageHandler(ctx: IConnectionContext, handlers: INotebookEventHandlers): (msg: KernelMessage.ICommMsgMsg) => void;
@@ -0,0 +1,40 @@
1
+ import { refreshNodeMapping } from '../ui/decorations';
2
+ import { handleComputeExecSchedule } from './schedule';
3
+ /**
4
+ * Build the `comm.onMsg` dispatcher. On `establish` it wires up the
5
+ * active-cell/content/execution listeners and flushes any buffered payload; on
6
+ * `set_exec_mode` it records the mode; `compute_exec_schedule` is delegated to
7
+ * the schedule handler.
8
+ */
9
+ export function createMessageHandler(ctx, handlers) {
10
+ const { store, notebook } = ctx;
11
+ return (msg) => {
12
+ const payload = msg.content.data;
13
+ if (ctx.disconnected || !(payload.success ?? true)) {
14
+ return;
15
+ }
16
+ if (payload.type === 'establish') {
17
+ notebook.scrollbar = true;
18
+ store.isIpyflowCommConnected = true;
19
+ refreshNodeMapping(store, notebook);
20
+ notebook.activeCellChanged.connect(handlers.onActiveCellChange);
21
+ notebook.activeCell.model.stateChanged.connect(handlers.onExecution);
22
+ handlers.onActiveCellChange(notebook, notebook.activeCell);
23
+ notebook.model.contentChanged.connect(handlers.onContentChanged);
24
+ notebook.model.cells.changed.connect(handlers.onContentChanged);
25
+ if (ctx.onEstablishPayload !== null) {
26
+ const toSend = ctx.onEstablishPayload;
27
+ ctx.onEstablishPayload = null;
28
+ ctx.safeSend(toSend);
29
+ }
30
+ store.requestComputeExecSchedule();
31
+ }
32
+ else if (payload.type === 'set_exec_mode') {
33
+ store.numAltModeExecutes = 0;
34
+ store.settings.exec_mode = payload.exec_mode;
35
+ }
36
+ else if (payload.type === 'compute_exec_schedule') {
37
+ handleComputeExecSchedule(ctx, handlers.debouncedSave, payload);
38
+ }
39
+ };
40
+ }
@@ -0,0 +1,22 @@
1
+ import { Cell, ICellModel } from '@jupyterlab/cells';
2
+ import type { IChangedArgs } from '@jupyterlab/coreutils/lib/interfaces';
3
+ import { CellList, Notebook } from '@jupyterlab/notebook';
4
+ import type { IObservableList } from '@jupyterlab/observables';
5
+ import { IConnectionContext } from './context';
6
+ export interface INotebookEventHandlers {
7
+ syncDirtiness: (cell: Cell<ICellModel>) => void;
8
+ onContentChanged: () => void;
9
+ onExecution: (cell: ICellModel, args: IChangedArgs<any>) => void;
10
+ onCellsAdded: (cells: CellList, change: IObservableList.IChangedArgs<ICellModel>) => void;
11
+ notifyActiveCell: (newActiveCell: ICellModel) => void;
12
+ onActiveCellChange: (nb: Notebook, cell: Cell<ICellModel>) => void;
13
+ onSelectionChanged: () => void;
14
+ debouncedSave: () => void;
15
+ }
16
+ /**
17
+ * Build the set of notebook/selection event handlers bound to a connection
18
+ * context. They are wired up at different points (connect time vs. establish),
19
+ * so they are created once here and shared. UI updates are signalled via
20
+ * `store.emitChanged()` rather than calling the renderer directly.
21
+ */
22
+ export declare function createNotebookEventHandlers(ctx: IConnectionContext): INotebookEventHandlers;
@@ -0,0 +1,166 @@
1
+ import { JSONExt } from '@lumino/coreutils';
2
+ import classes from '../classes';
3
+ import { getStore } from '../state/registry';
4
+ import { debounce } from '../utils';
5
+ /**
6
+ * Build the set of notebook/selection event handlers bound to a connection
7
+ * context. They are wired up at different points (connect time vs. establish),
8
+ * so they are created once here and shared. UI updates are signalled via
9
+ * `store.emitChanged()` rather than calling the renderer directly.
10
+ */
11
+ export function createNotebookEventHandlers(ctx) {
12
+ const { store, notebook, notebooks, docManager } = ctx;
13
+ const syncDirtiness = (cell) => {
14
+ if (cell !== null && cell.model !== null) {
15
+ if (cell.model.isDirty) {
16
+ store.dirtyCells.add(cell.model.id);
17
+ }
18
+ else {
19
+ store.dirtyCells.delete(cell.model.id);
20
+ }
21
+ }
22
+ };
23
+ const onContentChanged = debounce(() => {
24
+ if (ctx.disconnected) {
25
+ notebook.model.contentChanged.disconnect(onContentChanged);
26
+ notebook.model.cells.changed.disconnect(onContentChanged);
27
+ return;
28
+ }
29
+ const cell_metadata_by_id = store.gatherCellMetadataAndContent();
30
+ if (JSONExt.deepEqual(cell_metadata_by_id, store.lastCellMetadataMap)) {
31
+ // fixes https://github.com/ipyflow/ipyflow/issues/145
32
+ return;
33
+ }
34
+ store.lastCellMetadataMap = cell_metadata_by_id;
35
+ notebook.widgets.forEach(syncDirtiness);
36
+ ctx.safeSend({
37
+ type: 'notify_content_changed',
38
+ cell_metadata_by_id,
39
+ });
40
+ }, 500);
41
+ const onExecution = (cell, args) => {
42
+ if (ctx.disconnected) {
43
+ cell.stateChanged.disconnect(onExecution);
44
+ return;
45
+ }
46
+ if (args.name !== 'executionCount' || args.newValue === null) {
47
+ return;
48
+ }
49
+ store.executedCells.add(cell.id);
50
+ store.dirtyCells.delete(cell.id);
51
+ notebook.widgets.forEach((itercell) => {
52
+ if (itercell.model.id === cell.id) {
53
+ itercell.node.classList.remove(classes.readyCell);
54
+ itercell.node.classList.remove(classes.readyMakingInputCell);
55
+ }
56
+ });
57
+ };
58
+ const onCellsAdded = (_cells, change) => {
59
+ if (ctx.disconnected) {
60
+ notebook.model.cells.changed.disconnect(onCellsAdded);
61
+ return;
62
+ }
63
+ if (change.type === 'add') {
64
+ for (const cell of change.newValues) {
65
+ cell?.stateChanged.connect(onExecution);
66
+ }
67
+ }
68
+ else if (change.type === 'remove') {
69
+ for (const cell of change.oldValues) {
70
+ cell?.stateChanged.disconnect(onExecution);
71
+ }
72
+ }
73
+ };
74
+ const notifyActiveCell = (newActiveCell) => {
75
+ if (newActiveCell.id == null) {
76
+ return;
77
+ }
78
+ let newActiveCellOrderIdx = -1;
79
+ notebook.widgets.forEach((itercell, idx) => {
80
+ if (itercell.model.id === newActiveCell.id) {
81
+ newActiveCellOrderIdx = idx;
82
+ }
83
+ });
84
+ ctx.safeSend({
85
+ type: 'change_active_cell',
86
+ active_cell_id: newActiveCell.id,
87
+ active_cell_order_idx: newActiveCellOrderIdx,
88
+ });
89
+ };
90
+ const onActiveCellChange = (nb, cell) => {
91
+ if (notebook !== nb) {
92
+ return;
93
+ }
94
+ if (ctx.disconnected) {
95
+ notebook.activeCellChanged.disconnect(onActiveCellChange);
96
+ return;
97
+ }
98
+ notifyActiveCell(cell.model);
99
+ store.prevActiveCell = store.activeCell;
100
+ store.activeCell = cell;
101
+ if (store.activeCell === null ||
102
+ store.activeCell.model === null ||
103
+ store.activeCell.model.type !== 'code') {
104
+ return;
105
+ }
106
+ if (store.dirtyCells.has(store.activeCell.model.id)) {
107
+ store.activeCell.model._setDirty?.(true);
108
+ }
109
+ store.emitChanged();
110
+ };
111
+ const onSelectionChanged = () => {
112
+ if (ctx.disconnected) {
113
+ notebooks.selectionChanged.disconnect(onSelectionChanged);
114
+ }
115
+ const nbPanel = notebooks?.currentWidget;
116
+ const session = nbPanel?.sessionContext;
117
+ if (!(session?.isReady ?? false)) {
118
+ return;
119
+ }
120
+ const selectionStore = getStore(session.session.id);
121
+ if (!(selectionStore?.isIpyflowCommConnected ?? false)) {
122
+ return;
123
+ }
124
+ const selectionNotebook = nbPanel.content;
125
+ selectionStore.selectedCells = selectionNotebook.widgets
126
+ .filter((cell) => cell.model.type === 'code' && selectionNotebook.isSelected(cell))
127
+ .map((cell) => cell.model.id);
128
+ selectionStore.emitChanged();
129
+ };
130
+ const debouncedSave = debounce(() => {
131
+ if (ctx.disconnected) {
132
+ return;
133
+ }
134
+ // Persist ipyflow metadata by saving *this connection's* notebook (not
135
+ // whatever happens to be focused), and only if it is still open. The save
136
+ // is debounced, so by the time it fires the notebook may have been closed
137
+ // (context disposed) or its file removed; calling context.save() then makes
138
+ // JupyterLab surface a "File Save Error" dialog itself, so guard first.
139
+ const nbPanel = notebooks.find((panel) => panel.content === notebook);
140
+ if (nbPanel == null ||
141
+ nbPanel.isDisposed ||
142
+ nbPanel.context == null ||
143
+ nbPanel.context.isDisposed) {
144
+ return;
145
+ }
146
+ if (nbPanel.context.model?.collaborative ?? false) {
147
+ return;
148
+ }
149
+ else if (docManager.autosave && docManager.autosaveInterval <= 5) {
150
+ return;
151
+ }
152
+ // Still possible for the file to vanish between this check and the write;
153
+ // swallow that transient rejection rather than leaving it unhandled.
154
+ void nbPanel.context.save().catch(() => undefined);
155
+ }, 200);
156
+ return {
157
+ syncDirtiness,
158
+ onContentChanged,
159
+ onExecution,
160
+ onCellsAdded,
161
+ notifyActiveCell,
162
+ onActiveCellChange,
163
+ onSelectionChanged,
164
+ debouncedSave,
165
+ };
166
+ }
@@ -0,0 +1,10 @@
1
+ import { IConnectionContext } from './context';
2
+ /**
3
+ * Handle a `compute_exec_schedule` message: absorb the kernel's view of the
4
+ * dependency graph and reactive state, drive the next batch/incremental
5
+ * reactive execution step, and emit a UI refresh once the reactive cascade for
6
+ * this round has settled. Ported verbatim from the original monolithic
7
+ * `comm.onMsg` handler, with `state` -> `ctx.store` and the direct `updateUI`
8
+ * call replaced by `store.emitChanged()`.
9
+ */
10
+ export declare function handleComputeExecSchedule(ctx: IConnectionContext, debouncedSave: () => void, payload: any): void;
@@ -0,0 +1,165 @@
1
+ import { drainDeferredCells, hasDeferredCells } from '../state/deferred';
2
+ import { mergeMaps } from '../utils';
3
+ /**
4
+ * Handle a `compute_exec_schedule` message: absorb the kernel's view of the
5
+ * dependency graph and reactive state, drive the next batch/incremental
6
+ * reactive execution step, and emit a UI refresh once the reactive cascade for
7
+ * this round has settled. Ported verbatim from the original monolithic
8
+ * `comm.onMsg` handler, with `state` -> `ctx.store` and the direct `updateUI`
9
+ * call replaced by `store.emitChanged()`.
10
+ */
11
+ export function handleComputeExecSchedule(ctx, debouncedSave, payload) {
12
+ const { store, notebook } = ctx;
13
+ store.settings = payload.settings;
14
+ const ipyflow_metadata = notebook.model.getMetadata?.('ipyflow') ?? {};
15
+ const parentsFromMetadata = ipyflow_metadata?.cell_parents ?? {};
16
+ const childrenFromMetadata = ipyflow_metadata?.cell_children ?? {};
17
+ store.cellParents = mergeMaps(payload.cell_parents, parentsFromMetadata);
18
+ store.cellChildren = mergeMaps(payload.cell_children, childrenFromMetadata);
19
+ store.executedCells = new Set(payload.executed_cells);
20
+ notebook.model.setMetadata?.('ipyflow', {
21
+ cell_parents: store.cellParents,
22
+ cell_children: store.cellChildren,
23
+ });
24
+ debouncedSave();
25
+ store.waitingCells = new Set(payload.waiting_cells);
26
+ store.readyCells = new Set(payload.ready_cells);
27
+ store.forcedReactiveCells = new Set([
28
+ ...store.forcedReactiveCells,
29
+ ...payload.forced_reactive_cells,
30
+ ]);
31
+ store.waiterLinks = payload.waiter_links;
32
+ store.readyMakerLinks = payload.ready_maker_links;
33
+ store.staleParents = payload.stale_parents;
34
+ store.staleParentsByExecutedCellByChild =
35
+ payload.stale_parents_by_executed_cell_by_child;
36
+ store.staleParentsByChildByExecutedCell =
37
+ payload.stale_parents_by_child_by_executed_cell;
38
+ store.cellPendingExecution = null;
39
+ const exec_mode = payload.exec_mode;
40
+ store.isReactivelyExecuting =
41
+ store.isReactivelyExecuting ||
42
+ (payload?.is_reactively_executing ?? false) ||
43
+ exec_mode === 'reactive';
44
+ if (exec_mode === 'reactive') {
45
+ store.newReadyCells = new Set([
46
+ ...store.newReadyCells,
47
+ ...payload.new_ready_cells,
48
+ ]);
49
+ }
50
+ else {
51
+ store.newReadyCells = new Set();
52
+ }
53
+ const flow_order = payload.flow_order;
54
+ const exec_schedule = payload.exec_schedule;
55
+ store.lastExecutionHighlights = payload.highlights;
56
+ const lastExecutedCellId = payload.last_executed_cell_id;
57
+ store.executedReactiveReadyCells.add(lastExecutedCellId);
58
+ if (hasDeferredCells()) {
59
+ const cells = drainDeferredCells();
60
+ if (store.isBatchReactive()) {
61
+ store.executeClosure(cells);
62
+ }
63
+ else {
64
+ store.executeCells(cells);
65
+ }
66
+ return;
67
+ }
68
+ const last_execution_was_error = payload.last_execution_was_error;
69
+ let doneReactivelyExecuting = false;
70
+ if (last_execution_was_error) {
71
+ doneReactivelyExecuting = true;
72
+ }
73
+ else if (store.settings.reactivity_mode === 'batch') {
74
+ let reactiveCells;
75
+ if (exec_mode === 'reactive') {
76
+ reactiveCells = store
77
+ .computeTransitiveClosure([...store.newReadyCells, ...store.forcedReactiveCells].filter((id) => !store.executedReactiveReadyCells.has(id)))
78
+ .filter((cell) => !store.executedReactiveReadyCells.has(cell.model.id));
79
+ }
80
+ else {
81
+ reactiveCells = [...store.forcedReactiveCells]
82
+ .filter((id) => !store.executedReactiveReadyCells.has(id) &&
83
+ store.cellsById[id] !== undefined &&
84
+ store.orderIdxById[id] !== undefined)
85
+ .sort((a, b) => store.orderIdxById[a] - store.orderIdxById[b])
86
+ .map((id) => store.cellsById[id]);
87
+ }
88
+ if (reactiveCells.length === 0) {
89
+ doneReactivelyExecuting = true;
90
+ }
91
+ else {
92
+ store.isReactivelyExecuting = true;
93
+ store.executedReactiveReadyCells = new Set([
94
+ ...store.executedReactiveReadyCells,
95
+ ...reactiveCells.map((cell) => cell.model.id),
96
+ ]);
97
+ store.executeCells(reactiveCells);
98
+ }
99
+ }
100
+ else if (store.settings.reactivity_mode === 'incremental') {
101
+ let lastExecutedCellIdSeen = false;
102
+ for (const cell of notebook.widgets) {
103
+ if (!lastExecutedCellIdSeen) {
104
+ lastExecutedCellIdSeen = cell.model.id === lastExecutedCellId;
105
+ if (flow_order === 'in_order' || exec_schedule === 'strict') {
106
+ continue;
107
+ }
108
+ }
109
+ if (cell.model.type !== 'code' ||
110
+ store.executedReactiveReadyCells.has(cell.model.id)) {
111
+ continue;
112
+ }
113
+ if (!store.forcedReactiveCells.has(cell.model.id)) {
114
+ if (!store.newReadyCells.has(cell.model.id) ||
115
+ exec_mode !== 'reactive') {
116
+ continue;
117
+ }
118
+ }
119
+ const codeCell = cell;
120
+ if (store.cellPendingExecution === null) {
121
+ store.cellPendingExecution = codeCell;
122
+ // break early if using one of the order-based semantics
123
+ if (flow_order === 'in_order' || exec_schedule === 'strict') {
124
+ break;
125
+ }
126
+ }
127
+ else if (codeCell.model.executionCount == null) {
128
+ // pass
129
+ }
130
+ else if (codeCell.model.executionCount <
131
+ store.cellPendingExecution.model.executionCount) {
132
+ // otherwise, execute in order of earliest execution counter
133
+ store.cellPendingExecution = codeCell;
134
+ }
135
+ }
136
+ if (store.cellPendingExecution === null) {
137
+ doneReactivelyExecuting = true;
138
+ }
139
+ else {
140
+ store.isReactivelyExecuting = true;
141
+ store.executedCells.add(store.cellPendingExecution.model.id);
142
+ store.executeCells([store.cellPendingExecution]);
143
+ }
144
+ }
145
+ if (doneReactivelyExecuting) {
146
+ if (store.isReactivelyExecuting) {
147
+ if (store.lastExecutionHighlights === 'reactive') {
148
+ store.readyCells = store.executedReactiveReadyCells;
149
+ }
150
+ ctx.safeSend({
151
+ type: 'reactivity_cleanup',
152
+ });
153
+ }
154
+ if (store.numAltModeExecutes > 0 &&
155
+ --store.numAltModeExecutes === 0 &&
156
+ store.settings.reactivity_mode === 'incremental') {
157
+ store.toggleReactivity();
158
+ }
159
+ store.forcedReactiveCells = new Set();
160
+ store.newReadyCells = new Set();
161
+ store.executedReactiveReadyCells = new Set();
162
+ store.isReactivelyExecuting = false;
163
+ store.emitChanged();
164
+ }
165
+ }
@@ -0,0 +1,9 @@
1
+ import { JupyterFrontEnd } from '@jupyterlab/application';
2
+ import { ICommandPalette } from '@jupyterlab/apputils';
3
+ import { INotebookTracker } from '@jupyterlab/notebook';
4
+ /**
5
+ * Register the ipyflow execution commands (and their keybindings / palette
6
+ * entries): run-ready-cells, alt-mode execute, and forward/backward slice
7
+ * execution.
8
+ */
9
+ export declare function registerCommands(app: JupyterFrontEnd, notebooks: INotebookTracker, palette: ICommandPalette): void;