@jupyterlab/notebook 4.6.0 → 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/src/actions.tsx
CHANGED
|
@@ -603,15 +603,77 @@ export namespace NotebookActions {
|
|
|
603
603
|
lastIndex = notebook.model.cells.length;
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
} else {
|
|
609
|
-
notebook.moveCell(firstIndex, firstIndex + shift, lastIndex - firstIndex);
|
|
610
|
-
}
|
|
606
|
+
const toIndex = shift > 0 ? lastIndex : firstIndex + shift;
|
|
607
|
+
moveCells(notebook, firstIndex, toIndex, lastIndex - firstIndex);
|
|
611
608
|
|
|
612
609
|
void Private.handleState(notebook, state, true);
|
|
613
610
|
}
|
|
614
611
|
|
|
612
|
+
/**
|
|
613
|
+
* Move cells while preserving in-flight kernel futures.
|
|
614
|
+
*
|
|
615
|
+
* The underlying `jupyter-ydoc` `moveCells` implementation currently
|
|
616
|
+
* serializes cells to JSON and recreates them via a delete + insert
|
|
617
|
+
* transaction, which disposes any active kernel futures attached to the
|
|
618
|
+
* old widgets. This wrapper detaches futures before the move and
|
|
619
|
+
* reattaches them to the new widgets afterwards, and stores them in the
|
|
620
|
+
* undo stack so that undoing the move also restores execution state.
|
|
621
|
+
*
|
|
622
|
+
* @param notebook - The target notebook.
|
|
623
|
+
* @param from - Index of the first cell to move.
|
|
624
|
+
* @param to - Target index (as passed to `notebook.moveCell`).
|
|
625
|
+
* @param n - Number of cells to move.
|
|
626
|
+
*/
|
|
627
|
+
export function moveCells(
|
|
628
|
+
notebook: Notebook,
|
|
629
|
+
from: number,
|
|
630
|
+
to: number,
|
|
631
|
+
n = 1
|
|
632
|
+
): void {
|
|
633
|
+
if (!notebook.model) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Mirror `Notebook.moveCell`'s bounding/no-op logic so that we do not
|
|
638
|
+
// capture futures or touch the undo stack when the move won't happen.
|
|
639
|
+
// Otherwise we could attach execution metadata to an unrelated previous
|
|
640
|
+
// undo item, corrupting subsequent undo behavior.
|
|
641
|
+
const boundedTo = Math.min(
|
|
642
|
+
notebook.model.cells.length - 1,
|
|
643
|
+
Math.max(0, to)
|
|
644
|
+
);
|
|
645
|
+
if (boundedTo === from) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// moveCells serializes cells to JSON and recreates widgets (delete+insert),
|
|
650
|
+
// which would dispose any in-flight futures. Capture them first.
|
|
651
|
+
const storedExecutions: Private.IStoredCellExecution[] = [];
|
|
652
|
+
notebook.widgets.slice(from, from + n).forEach(child => {
|
|
653
|
+
if (!(child instanceof CodeCell)) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const stored = Private.captureExecution(child);
|
|
657
|
+
if (stored) {
|
|
658
|
+
storedExecutions.push(stored);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
notebook.moveCell(from, to, n);
|
|
663
|
+
|
|
664
|
+
// Immediately reconnect futures to the newly created widgets.
|
|
665
|
+
for (const stored of storedExecutions) {
|
|
666
|
+
Private.restoreExecution(notebook, stored);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Store in the undo stack so that undoing the move can also restore state.
|
|
670
|
+
if (storedExecutions.length > 0) {
|
|
671
|
+
const undoManager = (notebook.model.sharedModel as YNotebook).undoManager;
|
|
672
|
+
const lastItem = undoManager.undoStack[undoManager.undoStack.length - 1];
|
|
673
|
+
lastItem?.meta.set(Private.CELL_EXECUTION_META_KEY, storedExecutions);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
615
677
|
/**
|
|
616
678
|
* Move the selected cell(s) down.
|
|
617
679
|
*
|
|
@@ -1530,6 +1592,19 @@ export namespace NotebookActions {
|
|
|
1530
1592
|
switch (mode) {
|
|
1531
1593
|
case 'below':
|
|
1532
1594
|
index = notebook.activeCellIndex + 1;
|
|
1595
|
+
// If the active cell is a collapsed markdown heading with children,
|
|
1596
|
+
// insert after all the children, not just after the heading.
|
|
1597
|
+
{
|
|
1598
|
+
const activeCell = notebook.activeCell;
|
|
1599
|
+
if (
|
|
1600
|
+
activeCell instanceof MarkdownCell &&
|
|
1601
|
+
activeCell.headingCollapsed &&
|
|
1602
|
+
activeCell.numberChildNodes > 0
|
|
1603
|
+
) {
|
|
1604
|
+
index =
|
|
1605
|
+
notebook.activeCellIndex + activeCell.numberChildNodes + 1;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1533
1608
|
break;
|
|
1534
1609
|
case 'belowSelected':
|
|
1535
1610
|
notebook.widgets.forEach((child, childIndex) => {
|
|
@@ -1537,7 +1612,24 @@ export namespace NotebookActions {
|
|
|
1537
1612
|
index = childIndex + 1;
|
|
1538
1613
|
}
|
|
1539
1614
|
});
|
|
1540
|
-
|
|
1615
|
+
// If the last selected cell is a collapsed markdown heading with children,
|
|
1616
|
+
// insert after all the children, not just after the heading.
|
|
1617
|
+
{
|
|
1618
|
+
const lastSelectedIndex = index - 1;
|
|
1619
|
+
if (
|
|
1620
|
+
lastSelectedIndex >= 0 &&
|
|
1621
|
+
lastSelectedIndex < notebook.widgets.length
|
|
1622
|
+
) {
|
|
1623
|
+
const widget = notebook.widgets[lastSelectedIndex];
|
|
1624
|
+
if (
|
|
1625
|
+
widget instanceof MarkdownCell &&
|
|
1626
|
+
widget.headingCollapsed &&
|
|
1627
|
+
widget.numberChildNodes > 0
|
|
1628
|
+
) {
|
|
1629
|
+
index = lastSelectedIndex + widget.numberChildNodes + 1;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1541
1633
|
break;
|
|
1542
1634
|
case 'above':
|
|
1543
1635
|
index = notebook.activeCellIndex;
|
|
@@ -1609,9 +1701,37 @@ export namespace NotebookActions {
|
|
|
1609
1701
|
const state = Private.getState(notebook);
|
|
1610
1702
|
notebook.mode = 'command';
|
|
1611
1703
|
|
|
1704
|
+
const undoManager = (notebook.model.sharedModel as YNotebook).undoManager;
|
|
1705
|
+
|
|
1706
|
+
// For cells that will be MOVED by the undo (i.e. they still exist in the
|
|
1707
|
+
// notebook at their current position), pre-capture their futures now before
|
|
1708
|
+
// sharedModel.undo() destroys those widgets. This prevents OutputArea.dispose()
|
|
1709
|
+
// from cancelling the future during the Y.js delete+insert that implements the move.
|
|
1710
|
+
const topItem = undoManager.undoStack[undoManager.undoStack.length - 1];
|
|
1711
|
+
const pendingExecutions = topItem?.meta.get(
|
|
1712
|
+
Private.CELL_EXECUTION_META_KEY
|
|
1713
|
+
) as Private.IStoredCellExecution[] | undefined;
|
|
1714
|
+
const preCaptured = new Map<string, Private.IStoredCellExecution>();
|
|
1715
|
+
pendingExecutions?.forEach(stored => {
|
|
1716
|
+
const cell = notebook.widgets.find(w => w.model.id === stored.cellId);
|
|
1717
|
+
if (!(cell instanceof CodeCell)) {
|
|
1718
|
+
return; // cell was deleted (not moved) — handled via stored future below
|
|
1719
|
+
}
|
|
1720
|
+
// Cell still present → move undo. Fresh capture protects the future.
|
|
1721
|
+
const fresh = Private.captureExecution(cell) ?? {
|
|
1722
|
+
...stored,
|
|
1723
|
+
isDone: () => true,
|
|
1724
|
+
buffered: []
|
|
1725
|
+
};
|
|
1726
|
+
// The undo will roll the outputs back to their state at the time of
|
|
1727
|
+
// the move; snapshot the current outputs so anything received since
|
|
1728
|
+
// then can be re-applied after the undo.
|
|
1729
|
+
fresh.outputs = cell.model.outputs.toJSON();
|
|
1730
|
+
preCaptured.set(stored.cellId, fresh);
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1612
1733
|
// Capture execution context from the stack item being popped.
|
|
1613
1734
|
let storedExecutions: Private.IStoredCellExecution[] | undefined;
|
|
1614
|
-
const undoManager = (notebook.model.sharedModel as YNotebook).undoManager;
|
|
1615
1735
|
const onStackItemPopped = ({
|
|
1616
1736
|
stackItem
|
|
1617
1737
|
}: {
|
|
@@ -1625,40 +1745,13 @@ export namespace NotebookActions {
|
|
|
1625
1745
|
notebook.model.sharedModel.undo();
|
|
1626
1746
|
undoManager.off('stack-item-popped', onStackItemPopped);
|
|
1627
1747
|
|
|
1628
|
-
// Restore execution state on resurrected cell widgets.
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
// Execution finished or was interrupted before undo - ensure state is idle.
|
|
1636
|
-
cell.model.executionState = 'idle';
|
|
1637
|
-
return;
|
|
1638
|
-
}
|
|
1639
|
-
// Still running - reconnect the future so the cell receives remaining
|
|
1640
|
-
// output and stdin requests (e.g. input()), and tracks the idle transition.
|
|
1641
|
-
cell.outputArea.reattachFuture(future);
|
|
1642
|
-
// Replay any IOPub messages that arrived while the future was detached.
|
|
1643
|
-
// After reattachFuture, future.onIOPub routes to the new output area.
|
|
1644
|
-
for (const msg of buffered) {
|
|
1645
|
-
void future.onIOPub(msg);
|
|
1646
|
-
}
|
|
1647
|
-
// The original execute() call targeted the old cell widget, so it will
|
|
1648
|
-
// not update executionCount/executionState on this resurrected cell.
|
|
1649
|
-
// Drive the idle transition here instead.
|
|
1650
|
-
const cellRef = cell;
|
|
1651
|
-
void future.done.then(
|
|
1652
|
-
reply => {
|
|
1653
|
-
if (!cellRef.isDisposed) {
|
|
1654
|
-
cellRef.model.executionCount = reply.content.execution_count;
|
|
1655
|
-
}
|
|
1656
|
-
},
|
|
1657
|
-
() => {
|
|
1658
|
-
if (!cellRef.isDisposed) {
|
|
1659
|
-
cellRef.model.executionState = 'idle';
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1748
|
+
// Restore execution state on resurrected/moved cell widgets.
|
|
1749
|
+
// For move-undo: use freshly pre-captured data (stored data is stale).
|
|
1750
|
+
// For delete-undo: stored data has the futures captured at deletion time.
|
|
1751
|
+
storedExecutions?.forEach(stored => {
|
|
1752
|
+
Private.restoreExecution(
|
|
1753
|
+
notebook,
|
|
1754
|
+
preCaptured.get(stored.cellId) ?? stored
|
|
1662
1755
|
);
|
|
1663
1756
|
});
|
|
1664
1757
|
|
|
@@ -2568,6 +2661,15 @@ namespace Private {
|
|
|
2568
2661
|
/** Key used to store cell execution state in Y.js undo stack item metadata. */
|
|
2569
2662
|
export const CELL_EXECUTION_META_KEY = Symbol('cellExecutionState');
|
|
2570
2663
|
|
|
2664
|
+
/**
|
|
2665
|
+
* A kernel message that arrived while the future was detached, tagged with
|
|
2666
|
+
* its channel so it can be dispatched to the right handler on replay.
|
|
2667
|
+
*/
|
|
2668
|
+
export type IBufferedMessage =
|
|
2669
|
+
| { channel: 'iopub'; msg: KernelMessage.IIOPubMessage }
|
|
2670
|
+
| { channel: 'stdin'; msg: KernelMessage.IStdinMessage }
|
|
2671
|
+
| { channel: 'reply'; msg: KernelMessage.IExecuteReplyMsg };
|
|
2672
|
+
|
|
2571
2673
|
export interface IStoredCellExecution {
|
|
2572
2674
|
cellId: string;
|
|
2573
2675
|
future: Kernel.IShellFuture<
|
|
@@ -2575,13 +2677,28 @@ namespace Private {
|
|
|
2575
2677
|
KernelMessage.IExecuteReplyMsg
|
|
2576
2678
|
>;
|
|
2577
2679
|
isDone: () => boolean;
|
|
2578
|
-
/**
|
|
2579
|
-
|
|
2680
|
+
/**
|
|
2681
|
+
* Kernel messages (IOPub, stdin and reply) that arrived while the future
|
|
2682
|
+
* was detached, in arrival order, so they can be replayed on reattach.
|
|
2683
|
+
*/
|
|
2684
|
+
buffered: IBufferedMessage[];
|
|
2685
|
+
/**
|
|
2686
|
+
* Snapshot of the cell outputs to restore after an undo.
|
|
2687
|
+
*
|
|
2688
|
+
* The Y.js undo of a move (a delete + insert transaction) resurrects the
|
|
2689
|
+
* cell as it was when the move happened, rolling back any output received
|
|
2690
|
+
* since. Re-applying this snapshot after the undo prevents that loss.
|
|
2691
|
+
*/
|
|
2692
|
+
outputs?: nbformat.IOutput[];
|
|
2580
2693
|
}
|
|
2581
2694
|
|
|
2582
2695
|
/**
|
|
2583
|
-
* Detach the kernel future from a code cell, buffering any
|
|
2584
|
-
*
|
|
2696
|
+
* Detach the kernel future from a code cell, buffering any messages that
|
|
2697
|
+
* arrive while it is detached so they can be replayed on reattach.
|
|
2698
|
+
*
|
|
2699
|
+
* `detachFuture` clears the IOPub, stdin and reply handlers, so all three
|
|
2700
|
+
* channels are buffered here; otherwise a message arriving during the
|
|
2701
|
+
* detached window (e.g. an `input()` request on stdin) would be dropped.
|
|
2585
2702
|
*
|
|
2586
2703
|
* Returns null if the cell has no active future.
|
|
2587
2704
|
*/
|
|
@@ -2596,13 +2713,88 @@ namespace Private {
|
|
|
2596
2713
|
void future.done.finally(() => {
|
|
2597
2714
|
done = true;
|
|
2598
2715
|
});
|
|
2599
|
-
const buffered:
|
|
2716
|
+
const buffered: IBufferedMessage[] = [];
|
|
2600
2717
|
future.onIOPub = msg => {
|
|
2601
|
-
buffered.push(msg);
|
|
2718
|
+
buffered.push({ channel: 'iopub', msg });
|
|
2719
|
+
};
|
|
2720
|
+
future.onStdin = msg => {
|
|
2721
|
+
buffered.push({ channel: 'stdin', msg });
|
|
2722
|
+
};
|
|
2723
|
+
future.onReply = msg => {
|
|
2724
|
+
buffered.push({ channel: 'reply', msg });
|
|
2602
2725
|
};
|
|
2603
2726
|
return { cellId: cell.model.id, future, isDone: () => done, buffered };
|
|
2604
2727
|
}
|
|
2605
2728
|
|
|
2729
|
+
/**
|
|
2730
|
+
* Reconnect a captured execution to the cell widget that now holds the model.
|
|
2731
|
+
*
|
|
2732
|
+
* Handles both the "still running" and "already finished" cases.
|
|
2733
|
+
*/
|
|
2734
|
+
export function restoreExecution(
|
|
2735
|
+
notebook: Notebook,
|
|
2736
|
+
{ cellId, future, isDone, buffered, outputs }: IStoredCellExecution
|
|
2737
|
+
): void {
|
|
2738
|
+
const cell = notebook.widgets.find(w => w.model.id === cellId);
|
|
2739
|
+
if (!(cell instanceof CodeCell)) {
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
if (outputs && !JSONExt.deepEqual(outputs, cell.model.outputs.toJSON())) {
|
|
2743
|
+
// Re-apply the output snapshot taken just before the undo: the Y.js
|
|
2744
|
+
// undo rolled the outputs back to their state at the time of the
|
|
2745
|
+
// undone action. Going through the output area model keeps the
|
|
2746
|
+
// in-memory model and the shared model in sync (output changes are
|
|
2747
|
+
// not tracked by the undo manager, so this does not pollute history).
|
|
2748
|
+
cell.model.outputs.fromJSON(outputs);
|
|
2749
|
+
}
|
|
2750
|
+
// Reattach the future (without clearing existing outputs) and replay any
|
|
2751
|
+
// messages buffered while it was detached, in arrival order. This is done
|
|
2752
|
+
// whether or not the execution has already finished, so that final outputs
|
|
2753
|
+
// (or a pending stdin request) that arrived while detached are not lost
|
|
2754
|
+
// (e.g. if the kernel completed in the brief window during the undo).
|
|
2755
|
+
cell.outputArea.reattachFuture(future);
|
|
2756
|
+
for (const buf of buffered) {
|
|
2757
|
+
switch (buf.channel) {
|
|
2758
|
+
case 'iopub':
|
|
2759
|
+
void future.onIOPub(buf.msg);
|
|
2760
|
+
break;
|
|
2761
|
+
case 'stdin':
|
|
2762
|
+
void future.onStdin(buf.msg);
|
|
2763
|
+
break;
|
|
2764
|
+
case 'reply':
|
|
2765
|
+
void future.onReply(buf.msg);
|
|
2766
|
+
break;
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
if (isDone()) {
|
|
2770
|
+
// The execution already finished (e.g. it completed or was interrupted
|
|
2771
|
+
// during the undo). The resurrected cell may carry a stale 'running'
|
|
2772
|
+
// state from its restored snapshot, so reset it synchronously rather
|
|
2773
|
+
// than relying solely on the asynchronous `future.done` handler below.
|
|
2774
|
+
cell.model.executionState = 'idle';
|
|
2775
|
+
} else {
|
|
2776
|
+
// Restore the running state on the recreated cell widget.
|
|
2777
|
+
cell.model.executionState = 'running';
|
|
2778
|
+
}
|
|
2779
|
+
const cellRef = cell;
|
|
2780
|
+
void future.done.then(
|
|
2781
|
+
reply => {
|
|
2782
|
+
if (!cellRef.isDisposed) {
|
|
2783
|
+
// The future is authoritative for the prompt number; a snapshot
|
|
2784
|
+
// taken before completion would be stale (still null). Setting a
|
|
2785
|
+
// non-null execution count also flips the state back to 'idle'.
|
|
2786
|
+
cellRef.model.executionCount = reply.content.execution_count;
|
|
2787
|
+
cellRef.model.executionState = 'idle';
|
|
2788
|
+
}
|
|
2789
|
+
},
|
|
2790
|
+
() => {
|
|
2791
|
+
if (!cellRef.isDisposed) {
|
|
2792
|
+
cellRef.model.executionState = 'idle';
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2606
2798
|
/**
|
|
2607
2799
|
* Notebook cell executor
|
|
2608
2800
|
*/
|
|
@@ -2909,8 +3101,30 @@ namespace Private {
|
|
|
2909
3101
|
* @returns A list of 0 or more selected cells
|
|
2910
3102
|
*/
|
|
2911
3103
|
export function selectedCells(notebook: Notebook): nbformat.ICell[] {
|
|
2912
|
-
|
|
2913
|
-
|
|
3104
|
+
const cellsToInclude = new Set<Cell>();
|
|
3105
|
+
|
|
3106
|
+
// Collect all selected/active cells and expand collapsed sections
|
|
3107
|
+
for (let i = 0; i < notebook.widgets.length; i++) {
|
|
3108
|
+
const cell = notebook.widgets[i];
|
|
3109
|
+
if (notebook.isSelectedOrActive(cell)) {
|
|
3110
|
+
cellsToInclude.add(cell);
|
|
3111
|
+
|
|
3112
|
+
// If this is a collapsed markdown cell, add all its children
|
|
3113
|
+
if (
|
|
3114
|
+
cell instanceof MarkdownCell &&
|
|
3115
|
+
cell.headingCollapsed &&
|
|
3116
|
+
cell.numberChildNodes > 0
|
|
3117
|
+
) {
|
|
3118
|
+
for (let j = i + 1; j <= i + cell.numberChildNodes; j++) {
|
|
3119
|
+
if (notebook.widgets[j]) {
|
|
3120
|
+
cellsToInclude.add(notebook.widgets[j]);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
return Array.from(cellsToInclude)
|
|
2914
3128
|
.map(cell => cell.model.toJSON())
|
|
2915
3129
|
.map(cellJSON => {
|
|
2916
3130
|
if ((cellJSON.metadata as JSONObject).deletable !== undefined) {
|
|
@@ -3117,19 +3331,40 @@ namespace Private {
|
|
|
3117
3331
|
const model = notebook.model!;
|
|
3118
3332
|
const sharedModel = model.sharedModel;
|
|
3119
3333
|
const toDelete: number[] = [];
|
|
3334
|
+
const cellsToDeleteSet = new Set<number>();
|
|
3120
3335
|
|
|
3121
3336
|
notebook.mode = 'command';
|
|
3122
3337
|
|
|
3123
|
-
// Find the cells to delete.
|
|
3338
|
+
// Find the cells to delete, expanding collapsed sections.
|
|
3124
3339
|
notebook.widgets.forEach((child, index) => {
|
|
3125
3340
|
const deletable = child.model.getMetadata('deletable') !== false;
|
|
3126
3341
|
|
|
3127
3342
|
if (notebook.isSelectedOrActive(child) && deletable) {
|
|
3128
|
-
|
|
3343
|
+
cellsToDeleteSet.add(index);
|
|
3129
3344
|
notebook.model?.deletedCells.push(child.model.id);
|
|
3345
|
+
|
|
3346
|
+
// If this is a collapsed markdown cell, mark all its children for deletion
|
|
3347
|
+
if (
|
|
3348
|
+
child instanceof MarkdownCell &&
|
|
3349
|
+
child.headingCollapsed &&
|
|
3350
|
+
child.numberChildNodes > 0
|
|
3351
|
+
) {
|
|
3352
|
+
for (let j = index + 1; j <= index + child.numberChildNodes; j++) {
|
|
3353
|
+
if (notebook.widgets[j]) {
|
|
3354
|
+
const childDeletable =
|
|
3355
|
+
notebook.widgets[j].model.getMetadata('deletable') !== false;
|
|
3356
|
+
if (childDeletable) {
|
|
3357
|
+
cellsToDeleteSet.add(j);
|
|
3358
|
+
notebook.model?.deletedCells.push(notebook.widgets[j].model.id);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3130
3363
|
}
|
|
3131
3364
|
});
|
|
3132
3365
|
|
|
3366
|
+
toDelete.push(...Array.from(cellsToDeleteSet).sort((a, b) => a - b));
|
|
3367
|
+
|
|
3133
3368
|
// If cells are not deletable, we may not have anything to delete.
|
|
3134
3369
|
if (toDelete.length > 0) {
|
|
3135
3370
|
// Detach futures before the transaction so dispose() does not cancel them.
|
package/src/panel.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
-
|
|
5
3
|
import type { ISessionContext } from '@jupyterlab/apputils';
|
|
6
4
|
import { Dialog, Printing, showDialog } from '@jupyterlab/apputils';
|
|
7
5
|
import { isMarkdownCellModel } from '@jupyterlab/cells';
|
|
@@ -191,7 +189,7 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
|
|
|
191
189
|
* Handle a change in the kernel by updating the document metadata.
|
|
192
190
|
*/
|
|
193
191
|
private _onKernelChanged(
|
|
194
|
-
sender:
|
|
192
|
+
sender: ISessionContext,
|
|
195
193
|
args: Session.ISessionConnection.IKernelChangedArgs
|
|
196
194
|
): void {
|
|
197
195
|
if (!this.model || !args.newValue) {
|
package/src/searchprovider.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
-
|
|
5
3
|
import { Dialog, showDialog } from '@jupyterlab/apputils';
|
|
6
4
|
import type {
|
|
7
5
|
CellSearchProvider,
|
|
@@ -66,7 +64,7 @@ export class NotebookSearchProvider extends SearchProvider<NotebookPanel> {
|
|
|
66
64
|
this._filtersChanged.connect(this._setEnginesSelectionSearchMode, this);
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
private _onNotebookStateChanged(_: Notebook, args: IChangedArgs<
|
|
67
|
+
private _onNotebookStateChanged(_: Notebook, args: IChangedArgs<unknown>) {
|
|
70
68
|
if (args.name === 'mode') {
|
|
71
69
|
// Delay the update to ensure that `document.activeElement` settled.
|
|
72
70
|
window.setTimeout(() => {
|
package/src/widget.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { IEditorMimeTypeService } from '@jupyterlab/codeeditor';
|
|
|
15
15
|
import type { IChangedArgs } from '@jupyterlab/coreutils';
|
|
16
16
|
import type * as nbformat from '@jupyterlab/nbformat';
|
|
17
17
|
import type { IObservableList } from '@jupyterlab/observables';
|
|
18
|
+
import type { IPageHandler } from '@jupyterlab/outputarea';
|
|
18
19
|
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
19
20
|
import type { IMapChange } from '@jupyter/ydoc';
|
|
20
21
|
import { TableOfContentsUtils } from '@jupyterlab/toc';
|
|
@@ -245,6 +246,7 @@ export class StaticNotebook extends WindowedList<NotebookViewModel> {
|
|
|
245
246
|
this.notebookConfig =
|
|
246
247
|
options.notebookConfig || StaticNotebook.defaultNotebookConfig;
|
|
247
248
|
this._updateNotebookConfig();
|
|
249
|
+
this._pageHandler = options.pageHandler;
|
|
248
250
|
this._mimetypeService = options.mimeTypeService;
|
|
249
251
|
this.renderingLayout = options.notebookConfig?.renderingLayout;
|
|
250
252
|
this.kernelHistory = options.kernelHistory;
|
|
@@ -729,6 +731,7 @@ export class StaticNotebook extends WindowedList<NotebookViewModel> {
|
|
|
729
731
|
maxNumberOutputs: this.notebookConfig.maxNumberOutputs,
|
|
730
732
|
model,
|
|
731
733
|
placeholder: this._notebookConfig.windowingMode !== 'none',
|
|
734
|
+
pageHandler: this._pageHandler,
|
|
732
735
|
rendermime,
|
|
733
736
|
translator: this.translator
|
|
734
737
|
};
|
|
@@ -1222,6 +1225,7 @@ export class StaticNotebook extends WindowedList<NotebookViewModel> {
|
|
|
1222
1225
|
private _renderingLayout: RenderingLayout | undefined;
|
|
1223
1226
|
private _renderingLayoutChanged = new Signal<this, RenderingLayout>(this);
|
|
1224
1227
|
private _contentVisibilityObserver: IntersectionObserver | null = null;
|
|
1228
|
+
private _pageHandler: IPageHandler | undefined;
|
|
1225
1229
|
}
|
|
1226
1230
|
|
|
1227
1231
|
/**
|
|
@@ -1276,6 +1280,11 @@ export namespace StaticNotebook {
|
|
|
1276
1280
|
* The renderer used by the underlying windowed list.
|
|
1277
1281
|
*/
|
|
1278
1282
|
renderer?: WindowedList.IRenderer;
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Optional handler for pager payloads (`source: page`).
|
|
1286
|
+
*/
|
|
1287
|
+
pageHandler?: IPageHandler;
|
|
1279
1288
|
}
|
|
1280
1289
|
|
|
1281
1290
|
/**
|
|
@@ -1991,6 +2000,7 @@ export class Notebook extends StaticNotebook {
|
|
|
1991
2000
|
}
|
|
1992
2001
|
|
|
1993
2002
|
this._ensureFocus();
|
|
2003
|
+
|
|
1994
2004
|
if (newValue === oldValue) {
|
|
1995
2005
|
return;
|
|
1996
2006
|
}
|
|
@@ -2085,16 +2095,25 @@ export class Notebook extends StaticNotebook {
|
|
|
2085
2095
|
if (newActiveCellIndex >= 0) {
|
|
2086
2096
|
this.activeCellIndex = newActiveCellIndex;
|
|
2087
2097
|
}
|
|
2098
|
+
|
|
2099
|
+
// Deselect all cells first to clear any stale selection state.
|
|
2100
|
+
this.deselectAll();
|
|
2088
2101
|
if (from > to) {
|
|
2089
2102
|
isSelected.forEach((selected, idx) => {
|
|
2090
2103
|
if (selected) {
|
|
2091
|
-
this.
|
|
2104
|
+
const widget = this.widgets[to + idx];
|
|
2105
|
+
if (widget) {
|
|
2106
|
+
this.select(widget);
|
|
2107
|
+
}
|
|
2092
2108
|
}
|
|
2093
2109
|
});
|
|
2094
2110
|
} else {
|
|
2095
2111
|
isSelected.forEach((selected, idx) => {
|
|
2096
2112
|
if (selected) {
|
|
2097
|
-
this.
|
|
2113
|
+
const widget = this.widgets[to - n + 1 + idx];
|
|
2114
|
+
if (widget) {
|
|
2115
|
+
this.select(widget);
|
|
2116
|
+
}
|
|
2098
2117
|
}
|
|
2099
2118
|
});
|
|
2100
2119
|
}
|
|
@@ -2112,6 +2131,7 @@ export class Notebook extends StaticNotebook {
|
|
|
2112
2131
|
return;
|
|
2113
2132
|
}
|
|
2114
2133
|
Private.selectedProperty.set(widget, true);
|
|
2134
|
+
this._selectCollapsedSection(widget);
|
|
2115
2135
|
this._selectionChanged.emit(void 0);
|
|
2116
2136
|
this.update();
|
|
2117
2137
|
}
|
|
@@ -2127,6 +2147,19 @@ export class Notebook extends StaticNotebook {
|
|
|
2127
2147
|
if (!Private.selectedProperty.get(widget)) {
|
|
2128
2148
|
return;
|
|
2129
2149
|
}
|
|
2150
|
+
// Deselect all children if widget is a collapsed heading
|
|
2151
|
+
if (
|
|
2152
|
+
widget instanceof MarkdownCell &&
|
|
2153
|
+
widget.headingCollapsed &&
|
|
2154
|
+
widget.numberChildNodes > 0
|
|
2155
|
+
) {
|
|
2156
|
+
const idx = this.widgets.indexOf(widget);
|
|
2157
|
+
for (let i = idx + 1; i <= idx + widget.numberChildNodes; i++) {
|
|
2158
|
+
if (this.widgets[i]) {
|
|
2159
|
+
Private.selectedProperty.set(this.widgets[i], false);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2130
2163
|
Private.selectedProperty.set(widget, false);
|
|
2131
2164
|
this._selectionChanged.emit(void 0);
|
|
2132
2165
|
this.update();
|
|
@@ -2162,11 +2195,8 @@ export class Notebook extends StaticNotebook {
|
|
|
2162
2195
|
}
|
|
2163
2196
|
if (changed) {
|
|
2164
2197
|
this._selectionChanged.emit(void 0);
|
|
2198
|
+
this.update();
|
|
2165
2199
|
}
|
|
2166
|
-
// Make sure we have a valid active cell.
|
|
2167
|
-
// eslint-disable-next-line no-self-assign
|
|
2168
|
-
this.activeCellIndex = this.activeCellIndex;
|
|
2169
|
-
this.update();
|
|
2170
2200
|
}
|
|
2171
2201
|
|
|
2172
2202
|
/**
|
|
@@ -2264,6 +2294,25 @@ export class Notebook extends StaticNotebook {
|
|
|
2264
2294
|
}
|
|
2265
2295
|
}
|
|
2266
2296
|
|
|
2297
|
+
/**
|
|
2298
|
+
* Select all child cells of a collapsed heading, if applicable.
|
|
2299
|
+
*/
|
|
2300
|
+
private _selectCollapsedSection(cell: Cell | null): void {
|
|
2301
|
+
if (
|
|
2302
|
+
cell instanceof MarkdownCell &&
|
|
2303
|
+
cell.headingCollapsed &&
|
|
2304
|
+
cell.numberChildNodes > 0
|
|
2305
|
+
) {
|
|
2306
|
+
const idx = this.widgets.indexOf(cell);
|
|
2307
|
+
for (let i = idx; i <= idx + cell.numberChildNodes; i++) {
|
|
2308
|
+
if (this.widgets[i]) {
|
|
2309
|
+
Private.selectedProperty.set(this.widgets[i], true);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
this._selectionChanged.emit(void 0);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2267
2316
|
/**
|
|
2268
2317
|
* Get the head and anchor of a contiguous cell selection.
|
|
2269
2318
|
*
|
|
@@ -2958,9 +3007,9 @@ export class Notebook extends StaticNotebook {
|
|
|
2958
3007
|
(this.rendermime.sanitizer.allowNamedProperties ?? false)
|
|
2959
3008
|
? 'id'
|
|
2960
3009
|
: 'data-jupyter-id';
|
|
2961
|
-
const element = this.node.querySelector(
|
|
3010
|
+
const element = this.node.querySelector<HTMLElement>(
|
|
2962
3011
|
`h${heading.level}[${attribute}="${CSS.escape(id)}"]`
|
|
2963
|
-
)
|
|
3012
|
+
)!;
|
|
2964
3013
|
|
|
2965
3014
|
return {
|
|
2966
3015
|
cell,
|
|
@@ -3374,8 +3423,8 @@ export class Notebook extends StaticNotebook {
|
|
|
3374
3423
|
return;
|
|
3375
3424
|
}
|
|
3376
3425
|
|
|
3377
|
-
// Move the cells
|
|
3378
|
-
|
|
3426
|
+
// Move the selected block of cells, preserving in-flight executions.
|
|
3427
|
+
NotebookActions.moveCells(this, fromIndex, toIndex, toMove.length);
|
|
3379
3428
|
} else {
|
|
3380
3429
|
// Handle the case where we are copying cells between
|
|
3381
3430
|
// notebooks.
|
package/src/widgetfactory.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { IEditorMimeTypeService } from '@jupyterlab/codeeditor';
|
|
6
6
|
import type { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
7
7
|
import { ABCWidgetFactory } from '@jupyterlab/docregistry';
|
|
8
|
+
import type { IPageHandler } from '@jupyterlab/outputarea';
|
|
8
9
|
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
9
10
|
import type { ITranslator } from '@jupyterlab/translation';
|
|
10
11
|
import type { INotebookModel } from './model';
|
|
@@ -33,6 +34,7 @@ export class NotebookWidgetFactory extends ABCWidgetFactory<
|
|
|
33
34
|
options.editorConfig || StaticNotebook.defaultEditorConfig;
|
|
34
35
|
this._notebookConfig =
|
|
35
36
|
options.notebookConfig || StaticNotebook.defaultNotebookConfig;
|
|
37
|
+
this._pageHandler = options.pageHandler;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/*
|
|
@@ -95,6 +97,7 @@ export class NotebookWidgetFactory extends ABCWidgetFactory<
|
|
|
95
97
|
notebookConfig: source
|
|
96
98
|
? source.content.notebookConfig
|
|
97
99
|
: this._notebookConfig,
|
|
100
|
+
pageHandler: this._pageHandler,
|
|
98
101
|
translator,
|
|
99
102
|
kernelHistory
|
|
100
103
|
};
|
|
@@ -105,6 +108,7 @@ export class NotebookWidgetFactory extends ABCWidgetFactory<
|
|
|
105
108
|
|
|
106
109
|
private _editorConfig: StaticNotebook.IEditorConfig;
|
|
107
110
|
private _notebookConfig: StaticNotebook.INotebookConfig;
|
|
111
|
+
private _pageHandler: IPageHandler | undefined;
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
/**
|
|
@@ -142,6 +146,11 @@ export namespace NotebookWidgetFactory {
|
|
|
142
146
|
*/
|
|
143
147
|
notebookConfig?: StaticNotebook.INotebookConfig;
|
|
144
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Optional handler for pager payloads (`source: page`).
|
|
151
|
+
*/
|
|
152
|
+
pageHandler?: IPageHandler;
|
|
153
|
+
|
|
145
154
|
/**
|
|
146
155
|
* The application language translator.
|
|
147
156
|
*/
|
package/style/index.css
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
@import url('~@jupyterlab/documentsearch/style/index.css');
|
|
15
15
|
@import url('~@jupyterlab/codemirror/style/index.css');
|
|
16
16
|
@import url('~@jupyterlab/docregistry/style/index.css');
|
|
17
|
+
@import url('~@jupyterlab/outputarea/style/index.css');
|
|
17
18
|
@import url('~@jupyterlab/toc/style/index.css');
|
|
18
19
|
@import url('~@jupyterlab/cells/style/index.css');
|
|
19
20
|
@import url('~@jupyterlab/lsp/style/index.css');
|
package/style/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import '@jupyterlab/codeeditor/style/index.js';
|
|
|
14
14
|
import '@jupyterlab/documentsearch/style/index.js';
|
|
15
15
|
import '@jupyterlab/codemirror/style/index.js';
|
|
16
16
|
import '@jupyterlab/docregistry/style/index.js';
|
|
17
|
+
import '@jupyterlab/outputarea/style/index.js';
|
|
17
18
|
import '@jupyterlab/toc/style/index.js';
|
|
18
19
|
import '@jupyterlab/cells/style/index.js';
|
|
19
20
|
import '@jupyterlab/lsp/style/index.js';
|