@jupyterlab/notebook 4.6.0-rc.1 → 4.7.0-alpha.0
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/actions.d.ts +16 -0
- package/lib/actions.js +225 -46
- package/lib/actions.js.map +1 -1
- package/lib/panel.js +0 -3
- package/lib/panel.js.map +1 -1
- package/lib/searchprovider.js +0 -1
- package/lib/searchprovider.js.map +1 -1
- package/lib/widget.d.ts +10 -0
- package/lib/widget.js +43 -8
- package/lib/widget.js.map +1 -1
- package/lib/widgetfactory.d.ts +6 -0
- package/lib/widgetfactory.js +2 -0
- package/lib/widgetfactory.js.map +1 -1
- package/package.json +21 -20
- package/src/actions.tsx +286 -51
- package/src/panel.ts +1 -3
- package/src/searchprovider.ts +1 -3
- package/src/widget.ts +59 -10
- package/src/widgetfactory.ts +9 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
package/lib/actions.d.ts
CHANGED
|
@@ -159,6 +159,22 @@ export declare namespace NotebookActions {
|
|
|
159
159
|
* The new cell will be the active cell.
|
|
160
160
|
*/
|
|
161
161
|
function insertBelow(notebook: Notebook): void;
|
|
162
|
+
/**
|
|
163
|
+
* Move cells while preserving in-flight kernel futures.
|
|
164
|
+
*
|
|
165
|
+
* The underlying `jupyter-ydoc` `moveCells` implementation currently
|
|
166
|
+
* serializes cells to JSON and recreates them via a delete + insert
|
|
167
|
+
* transaction, which disposes any active kernel futures attached to the
|
|
168
|
+
* old widgets. This wrapper detaches futures before the move and
|
|
169
|
+
* reattaches them to the new widgets afterwards, and stores them in the
|
|
170
|
+
* undo stack so that undoing the move also restores execution state.
|
|
171
|
+
*
|
|
172
|
+
* @param notebook - The target notebook.
|
|
173
|
+
* @param from - Index of the first cell to move.
|
|
174
|
+
* @param to - Target index (as passed to `notebook.moveCell`).
|
|
175
|
+
* @param n - Number of cells to move.
|
|
176
|
+
*/
|
|
177
|
+
function moveCells(notebook: Notebook, from: number, to: number, n?: number): void;
|
|
162
178
|
/**
|
|
163
179
|
* Move the selected cell(s) down.
|
|
164
180
|
*
|
package/lib/actions.js
CHANGED
|
@@ -481,14 +481,62 @@ export class NotebookActions {
|
|
|
481
481
|
else {
|
|
482
482
|
lastIndex = notebook.model.cells.length;
|
|
483
483
|
}
|
|
484
|
-
|
|
485
|
-
|
|
484
|
+
const toIndex = shift > 0 ? lastIndex : firstIndex + shift;
|
|
485
|
+
moveCells(notebook, firstIndex, toIndex, lastIndex - firstIndex);
|
|
486
|
+
void Private.handleState(notebook, state, true);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Move cells while preserving in-flight kernel futures.
|
|
490
|
+
*
|
|
491
|
+
* The underlying `jupyter-ydoc` `moveCells` implementation currently
|
|
492
|
+
* serializes cells to JSON and recreates them via a delete + insert
|
|
493
|
+
* transaction, which disposes any active kernel futures attached to the
|
|
494
|
+
* old widgets. This wrapper detaches futures before the move and
|
|
495
|
+
* reattaches them to the new widgets afterwards, and stores them in the
|
|
496
|
+
* undo stack so that undoing the move also restores execution state.
|
|
497
|
+
*
|
|
498
|
+
* @param notebook - The target notebook.
|
|
499
|
+
* @param from - Index of the first cell to move.
|
|
500
|
+
* @param to - Target index (as passed to `notebook.moveCell`).
|
|
501
|
+
* @param n - Number of cells to move.
|
|
502
|
+
*/
|
|
503
|
+
function moveCells(notebook, from, to, n = 1) {
|
|
504
|
+
if (!notebook.model) {
|
|
505
|
+
return;
|
|
486
506
|
}
|
|
487
|
-
|
|
488
|
-
|
|
507
|
+
// Mirror `Notebook.moveCell`'s bounding/no-op logic so that we do not
|
|
508
|
+
// capture futures or touch the undo stack when the move won't happen.
|
|
509
|
+
// Otherwise we could attach execution metadata to an unrelated previous
|
|
510
|
+
// undo item, corrupting subsequent undo behavior.
|
|
511
|
+
const boundedTo = Math.min(notebook.model.cells.length - 1, Math.max(0, to));
|
|
512
|
+
if (boundedTo === from) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
// moveCells serializes cells to JSON and recreates widgets (delete+insert),
|
|
516
|
+
// which would dispose any in-flight futures. Capture them first.
|
|
517
|
+
const storedExecutions = [];
|
|
518
|
+
notebook.widgets.slice(from, from + n).forEach(child => {
|
|
519
|
+
if (!(child instanceof CodeCell)) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const stored = Private.captureExecution(child);
|
|
523
|
+
if (stored) {
|
|
524
|
+
storedExecutions.push(stored);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
notebook.moveCell(from, to, n);
|
|
528
|
+
// Immediately reconnect futures to the newly created widgets.
|
|
529
|
+
for (const stored of storedExecutions) {
|
|
530
|
+
Private.restoreExecution(notebook, stored);
|
|
531
|
+
}
|
|
532
|
+
// Store in the undo stack so that undoing the move can also restore state.
|
|
533
|
+
if (storedExecutions.length > 0) {
|
|
534
|
+
const undoManager = notebook.model.sharedModel.undoManager;
|
|
535
|
+
const lastItem = undoManager.undoStack[undoManager.undoStack.length - 1];
|
|
536
|
+
lastItem === null || lastItem === void 0 ? void 0 : lastItem.meta.set(Private.CELL_EXECUTION_META_KEY, storedExecutions);
|
|
489
537
|
}
|
|
490
|
-
void Private.handleState(notebook, state, true);
|
|
491
538
|
}
|
|
539
|
+
NotebookActions.moveCells = moveCells;
|
|
492
540
|
/**
|
|
493
541
|
* Move the selected cell(s) down.
|
|
494
542
|
*
|
|
@@ -1221,6 +1269,17 @@ export class NotebookActions {
|
|
|
1221
1269
|
switch (mode) {
|
|
1222
1270
|
case 'below':
|
|
1223
1271
|
index = notebook.activeCellIndex + 1;
|
|
1272
|
+
// If the active cell is a collapsed markdown heading with children,
|
|
1273
|
+
// insert after all the children, not just after the heading.
|
|
1274
|
+
{
|
|
1275
|
+
const activeCell = notebook.activeCell;
|
|
1276
|
+
if (activeCell instanceof MarkdownCell &&
|
|
1277
|
+
activeCell.headingCollapsed &&
|
|
1278
|
+
activeCell.numberChildNodes > 0) {
|
|
1279
|
+
index =
|
|
1280
|
+
notebook.activeCellIndex + activeCell.numberChildNodes + 1;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1224
1283
|
break;
|
|
1225
1284
|
case 'belowSelected':
|
|
1226
1285
|
notebook.widgets.forEach((child, childIndex) => {
|
|
@@ -1228,6 +1287,20 @@ export class NotebookActions {
|
|
|
1228
1287
|
index = childIndex + 1;
|
|
1229
1288
|
}
|
|
1230
1289
|
});
|
|
1290
|
+
// If the last selected cell is a collapsed markdown heading with children,
|
|
1291
|
+
// insert after all the children, not just after the heading.
|
|
1292
|
+
{
|
|
1293
|
+
const lastSelectedIndex = index - 1;
|
|
1294
|
+
if (lastSelectedIndex >= 0 &&
|
|
1295
|
+
lastSelectedIndex < notebook.widgets.length) {
|
|
1296
|
+
const widget = notebook.widgets[lastSelectedIndex];
|
|
1297
|
+
if (widget instanceof MarkdownCell &&
|
|
1298
|
+
widget.headingCollapsed &&
|
|
1299
|
+
widget.numberChildNodes > 0) {
|
|
1300
|
+
index = lastSelectedIndex + widget.numberChildNodes + 1;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1231
1304
|
break;
|
|
1232
1305
|
case 'above':
|
|
1233
1306
|
index = notebook.activeCellIndex;
|
|
@@ -1285,47 +1358,46 @@ export class NotebookActions {
|
|
|
1285
1358
|
}
|
|
1286
1359
|
const state = Private.getState(notebook);
|
|
1287
1360
|
notebook.mode = 'command';
|
|
1361
|
+
const undoManager = notebook.model.sharedModel.undoManager;
|
|
1362
|
+
// For cells that will be MOVED by the undo (i.e. they still exist in the
|
|
1363
|
+
// notebook at their current position), pre-capture their futures now before
|
|
1364
|
+
// sharedModel.undo() destroys those widgets. This prevents OutputArea.dispose()
|
|
1365
|
+
// from cancelling the future during the Y.js delete+insert that implements the move.
|
|
1366
|
+
const topItem = undoManager.undoStack[undoManager.undoStack.length - 1];
|
|
1367
|
+
const pendingExecutions = topItem === null || topItem === void 0 ? void 0 : topItem.meta.get(Private.CELL_EXECUTION_META_KEY);
|
|
1368
|
+
const preCaptured = new Map();
|
|
1369
|
+
pendingExecutions === null || pendingExecutions === void 0 ? void 0 : pendingExecutions.forEach(stored => {
|
|
1370
|
+
var _a;
|
|
1371
|
+
const cell = notebook.widgets.find(w => w.model.id === stored.cellId);
|
|
1372
|
+
if (!(cell instanceof CodeCell)) {
|
|
1373
|
+
return; // cell was deleted (not moved) — handled via stored future below
|
|
1374
|
+
}
|
|
1375
|
+
// Cell still present → move undo. Fresh capture protects the future.
|
|
1376
|
+
const fresh = (_a = Private.captureExecution(cell)) !== null && _a !== void 0 ? _a : {
|
|
1377
|
+
...stored,
|
|
1378
|
+
isDone: () => true,
|
|
1379
|
+
buffered: []
|
|
1380
|
+
};
|
|
1381
|
+
// The undo will roll the outputs back to their state at the time of
|
|
1382
|
+
// the move; snapshot the current outputs so anything received since
|
|
1383
|
+
// then can be re-applied after the undo.
|
|
1384
|
+
fresh.outputs = cell.model.outputs.toJSON();
|
|
1385
|
+
preCaptured.set(stored.cellId, fresh);
|
|
1386
|
+
});
|
|
1288
1387
|
// Capture execution context from the stack item being popped.
|
|
1289
1388
|
let storedExecutions;
|
|
1290
|
-
const undoManager = notebook.model.sharedModel.undoManager;
|
|
1291
1389
|
const onStackItemPopped = ({ stackItem }) => {
|
|
1292
1390
|
storedExecutions = stackItem.meta.get(Private.CELL_EXECUTION_META_KEY);
|
|
1293
1391
|
};
|
|
1294
1392
|
undoManager.on('stack-item-popped', onStackItemPopped);
|
|
1295
1393
|
notebook.model.sharedModel.undo();
|
|
1296
1394
|
undoManager.off('stack-item-popped', onStackItemPopped);
|
|
1297
|
-
// Restore execution state on resurrected cell widgets.
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (isDone()) {
|
|
1304
|
-
// Execution finished or was interrupted before undo - ensure state is idle.
|
|
1305
|
-
cell.model.executionState = 'idle';
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
// Still running - reconnect the future so the cell receives remaining
|
|
1309
|
-
// output and stdin requests (e.g. input()), and tracks the idle transition.
|
|
1310
|
-
cell.outputArea.reattachFuture(future);
|
|
1311
|
-
// Replay any IOPub messages that arrived while the future was detached.
|
|
1312
|
-
// After reattachFuture, future.onIOPub routes to the new output area.
|
|
1313
|
-
for (const msg of buffered) {
|
|
1314
|
-
void future.onIOPub(msg);
|
|
1315
|
-
}
|
|
1316
|
-
// The original execute() call targeted the old cell widget, so it will
|
|
1317
|
-
// not update executionCount/executionState on this resurrected cell.
|
|
1318
|
-
// Drive the idle transition here instead.
|
|
1319
|
-
const cellRef = cell;
|
|
1320
|
-
void future.done.then(reply => {
|
|
1321
|
-
if (!cellRef.isDisposed) {
|
|
1322
|
-
cellRef.model.executionCount = reply.content.execution_count;
|
|
1323
|
-
}
|
|
1324
|
-
}, () => {
|
|
1325
|
-
if (!cellRef.isDisposed) {
|
|
1326
|
-
cellRef.model.executionState = 'idle';
|
|
1327
|
-
}
|
|
1328
|
-
});
|
|
1395
|
+
// Restore execution state on resurrected/moved cell widgets.
|
|
1396
|
+
// For move-undo: use freshly pre-captured data (stored data is stale).
|
|
1397
|
+
// For delete-undo: stored data has the futures captured at deletion time.
|
|
1398
|
+
storedExecutions === null || storedExecutions === void 0 ? void 0 : storedExecutions.forEach(stored => {
|
|
1399
|
+
var _a;
|
|
1400
|
+
Private.restoreExecution(notebook, (_a = preCaptured.get(stored.cellId)) !== null && _a !== void 0 ? _a : stored);
|
|
1329
1401
|
});
|
|
1330
1402
|
notebook.deselectAll();
|
|
1331
1403
|
void Private.handleState(notebook, state);
|
|
@@ -2120,8 +2192,12 @@ var Private;
|
|
|
2120
2192
|
/** Key used to store cell execution state in Y.js undo stack item metadata. */
|
|
2121
2193
|
Private.CELL_EXECUTION_META_KEY = Symbol('cellExecutionState');
|
|
2122
2194
|
/**
|
|
2123
|
-
* Detach the kernel future from a code cell, buffering any
|
|
2124
|
-
*
|
|
2195
|
+
* Detach the kernel future from a code cell, buffering any messages that
|
|
2196
|
+
* arrive while it is detached so they can be replayed on reattach.
|
|
2197
|
+
*
|
|
2198
|
+
* `detachFuture` clears the IOPub, stdin and reply handlers, so all three
|
|
2199
|
+
* channels are buffered here; otherwise a message arriving during the
|
|
2200
|
+
* detached window (e.g. an `input()` request on stdin) would be dropped.
|
|
2125
2201
|
*
|
|
2126
2202
|
* Returns null if the cell has no active future.
|
|
2127
2203
|
*/
|
|
@@ -2136,11 +2212,81 @@ var Private;
|
|
|
2136
2212
|
});
|
|
2137
2213
|
const buffered = [];
|
|
2138
2214
|
future.onIOPub = msg => {
|
|
2139
|
-
buffered.push(msg);
|
|
2215
|
+
buffered.push({ channel: 'iopub', msg });
|
|
2216
|
+
};
|
|
2217
|
+
future.onStdin = msg => {
|
|
2218
|
+
buffered.push({ channel: 'stdin', msg });
|
|
2219
|
+
};
|
|
2220
|
+
future.onReply = msg => {
|
|
2221
|
+
buffered.push({ channel: 'reply', msg });
|
|
2140
2222
|
};
|
|
2141
2223
|
return { cellId: cell.model.id, future, isDone: () => done, buffered };
|
|
2142
2224
|
}
|
|
2143
2225
|
Private.captureExecution = captureExecution;
|
|
2226
|
+
/**
|
|
2227
|
+
* Reconnect a captured execution to the cell widget that now holds the model.
|
|
2228
|
+
*
|
|
2229
|
+
* Handles both the "still running" and "already finished" cases.
|
|
2230
|
+
*/
|
|
2231
|
+
function restoreExecution(notebook, { cellId, future, isDone, buffered, outputs }) {
|
|
2232
|
+
const cell = notebook.widgets.find(w => w.model.id === cellId);
|
|
2233
|
+
if (!(cell instanceof CodeCell)) {
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
if (outputs && !JSONExt.deepEqual(outputs, cell.model.outputs.toJSON())) {
|
|
2237
|
+
// Re-apply the output snapshot taken just before the undo: the Y.js
|
|
2238
|
+
// undo rolled the outputs back to their state at the time of the
|
|
2239
|
+
// undone action. Going through the output area model keeps the
|
|
2240
|
+
// in-memory model and the shared model in sync (output changes are
|
|
2241
|
+
// not tracked by the undo manager, so this does not pollute history).
|
|
2242
|
+
cell.model.outputs.fromJSON(outputs);
|
|
2243
|
+
}
|
|
2244
|
+
// Reattach the future (without clearing existing outputs) and replay any
|
|
2245
|
+
// messages buffered while it was detached, in arrival order. This is done
|
|
2246
|
+
// whether or not the execution has already finished, so that final outputs
|
|
2247
|
+
// (or a pending stdin request) that arrived while detached are not lost
|
|
2248
|
+
// (e.g. if the kernel completed in the brief window during the undo).
|
|
2249
|
+
cell.outputArea.reattachFuture(future);
|
|
2250
|
+
for (const buf of buffered) {
|
|
2251
|
+
switch (buf.channel) {
|
|
2252
|
+
case 'iopub':
|
|
2253
|
+
void future.onIOPub(buf.msg);
|
|
2254
|
+
break;
|
|
2255
|
+
case 'stdin':
|
|
2256
|
+
void future.onStdin(buf.msg);
|
|
2257
|
+
break;
|
|
2258
|
+
case 'reply':
|
|
2259
|
+
void future.onReply(buf.msg);
|
|
2260
|
+
break;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
if (isDone()) {
|
|
2264
|
+
// The execution already finished (e.g. it completed or was interrupted
|
|
2265
|
+
// during the undo). The resurrected cell may carry a stale 'running'
|
|
2266
|
+
// state from its restored snapshot, so reset it synchronously rather
|
|
2267
|
+
// than relying solely on the asynchronous `future.done` handler below.
|
|
2268
|
+
cell.model.executionState = 'idle';
|
|
2269
|
+
}
|
|
2270
|
+
else {
|
|
2271
|
+
// Restore the running state on the recreated cell widget.
|
|
2272
|
+
cell.model.executionState = 'running';
|
|
2273
|
+
}
|
|
2274
|
+
const cellRef = cell;
|
|
2275
|
+
void future.done.then(reply => {
|
|
2276
|
+
if (!cellRef.isDisposed) {
|
|
2277
|
+
// The future is authoritative for the prompt number; a snapshot
|
|
2278
|
+
// taken before completion would be stale (still null). Setting a
|
|
2279
|
+
// non-null execution count also flips the state back to 'idle'.
|
|
2280
|
+
cellRef.model.executionCount = reply.content.execution_count;
|
|
2281
|
+
cellRef.model.executionState = 'idle';
|
|
2282
|
+
}
|
|
2283
|
+
}, () => {
|
|
2284
|
+
if (!cellRef.isDisposed) {
|
|
2285
|
+
cellRef.model.executionState = 'idle';
|
|
2286
|
+
}
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
Private.restoreExecution = restoreExecution;
|
|
2144
2290
|
/**
|
|
2145
2291
|
* A signal that emits whenever a cell completes execution.
|
|
2146
2292
|
*/
|
|
@@ -2337,8 +2483,25 @@ var Private;
|
|
|
2337
2483
|
* @returns A list of 0 or more selected cells
|
|
2338
2484
|
*/
|
|
2339
2485
|
function selectedCells(notebook) {
|
|
2340
|
-
|
|
2341
|
-
|
|
2486
|
+
const cellsToInclude = new Set();
|
|
2487
|
+
// Collect all selected/active cells and expand collapsed sections
|
|
2488
|
+
for (let i = 0; i < notebook.widgets.length; i++) {
|
|
2489
|
+
const cell = notebook.widgets[i];
|
|
2490
|
+
if (notebook.isSelectedOrActive(cell)) {
|
|
2491
|
+
cellsToInclude.add(cell);
|
|
2492
|
+
// If this is a collapsed markdown cell, add all its children
|
|
2493
|
+
if (cell instanceof MarkdownCell &&
|
|
2494
|
+
cell.headingCollapsed &&
|
|
2495
|
+
cell.numberChildNodes > 0) {
|
|
2496
|
+
for (let j = i + 1; j <= i + cell.numberChildNodes; j++) {
|
|
2497
|
+
if (notebook.widgets[j]) {
|
|
2498
|
+
cellsToInclude.add(notebook.widgets[j]);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return Array.from(cellsToInclude)
|
|
2342
2505
|
.map(cell => cell.model.toJSON())
|
|
2343
2506
|
.map(cellJSON => {
|
|
2344
2507
|
if (cellJSON.metadata.deletable !== undefined) {
|
|
@@ -2522,16 +2685,32 @@ var Private;
|
|
|
2522
2685
|
const model = notebook.model;
|
|
2523
2686
|
const sharedModel = model.sharedModel;
|
|
2524
2687
|
const toDelete = [];
|
|
2688
|
+
const cellsToDeleteSet = new Set();
|
|
2525
2689
|
notebook.mode = 'command';
|
|
2526
|
-
// Find the cells to delete.
|
|
2690
|
+
// Find the cells to delete, expanding collapsed sections.
|
|
2527
2691
|
notebook.widgets.forEach((child, index) => {
|
|
2528
|
-
var _a;
|
|
2692
|
+
var _a, _b;
|
|
2529
2693
|
const deletable = child.model.getMetadata('deletable') !== false;
|
|
2530
2694
|
if (notebook.isSelectedOrActive(child) && deletable) {
|
|
2531
|
-
|
|
2695
|
+
cellsToDeleteSet.add(index);
|
|
2532
2696
|
(_a = notebook.model) === null || _a === void 0 ? void 0 : _a.deletedCells.push(child.model.id);
|
|
2697
|
+
// If this is a collapsed markdown cell, mark all its children for deletion
|
|
2698
|
+
if (child instanceof MarkdownCell &&
|
|
2699
|
+
child.headingCollapsed &&
|
|
2700
|
+
child.numberChildNodes > 0) {
|
|
2701
|
+
for (let j = index + 1; j <= index + child.numberChildNodes; j++) {
|
|
2702
|
+
if (notebook.widgets[j]) {
|
|
2703
|
+
const childDeletable = notebook.widgets[j].model.getMetadata('deletable') !== false;
|
|
2704
|
+
if (childDeletable) {
|
|
2705
|
+
cellsToDeleteSet.add(j);
|
|
2706
|
+
(_b = notebook.model) === null || _b === void 0 ? void 0 : _b.deletedCells.push(notebook.widgets[j].model.id);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2533
2711
|
}
|
|
2534
2712
|
});
|
|
2713
|
+
toDelete.push(...Array.from(cellsToDeleteSet).sort((a, b) => a - b));
|
|
2535
2714
|
// If cells are not deletable, we may not have anything to delete.
|
|
2536
2715
|
if (toDelete.length > 0) {
|
|
2537
2716
|
// Detach futures before the transaction so dispose() does not cancel them.
|