1 |
|
2 |
|
3 |
|
4 | import {
|
5 | CellModel,
|
6 | CodeCellModel,
|
7 | ICellModel,
|
8 | MarkdownCellModel,
|
9 | RawCellModel
|
10 | } from '@jupyterlab/cells';
|
11 | import { IObservableList } from '@jupyterlab/observables';
|
12 | import {
|
13 | ISharedCell,
|
14 | ISharedCodeCell,
|
15 | ISharedMarkdownCell,
|
16 | ISharedNotebook,
|
17 | ISharedRawCell,
|
18 | NotebookChange
|
19 | } from '@jupyter/ydoc';
|
20 | import { ISignal, Signal } from '@lumino/signaling';
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | export class CellList {
|
26 | |
27 |
|
28 |
|
29 | constructor(protected model: ISharedNotebook) {
|
30 | this._insertCells(0, this.model.cells);
|
31 |
|
32 | this.model.changed.connect(this._onSharedModelChanged, this);
|
33 | }
|
34 |
|
35 | |
36 |
|
37 |
|
38 | get changed(): ISignal<this, IObservableList.IChangedArgs<ICellModel>> {
|
39 | return this._changed;
|
40 | }
|
41 |
|
42 | |
43 |
|
44 |
|
45 | get isDisposed(): boolean {
|
46 | return this._isDisposed;
|
47 | }
|
48 |
|
49 | |
50 |
|
51 |
|
52 |
|
53 |
|
54 | get length(): number {
|
55 | return this.model.cells.length;
|
56 | }
|
57 |
|
58 | |
59 |
|
60 |
|
61 |
|
62 |
|
63 | *[Symbol.iterator](): IterableIterator<ICellModel> {
|
64 | for (const cell of this.model.cells) {
|
65 | yield this._cellMap.get(cell)!;
|
66 | }
|
67 | }
|
68 |
|
69 | |
70 |
|
71 |
|
72 | dispose(): void {
|
73 | if (this._isDisposed) {
|
74 | return;
|
75 | }
|
76 | this._isDisposed = true;
|
77 |
|
78 |
|
79 | for (const cell of this.model.cells) {
|
80 | this._cellMap.get(cell)?.dispose();
|
81 | }
|
82 | Signal.clearData(this);
|
83 | }
|
84 |
|
85 | |
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | get(index: number): ICellModel {
|
93 | return this._cellMap.get(this.model.cells[index])!;
|
94 | }
|
95 |
|
96 | private _insertCells(index: number, cells: Array<ISharedCell>) {
|
97 | cells.forEach(sharedModel => {
|
98 | let cellModel: CellModel;
|
99 | switch (sharedModel.cell_type) {
|
100 | case 'code': {
|
101 | cellModel = new CodeCellModel({
|
102 | sharedModel: sharedModel as ISharedCodeCell
|
103 | });
|
104 | break;
|
105 | }
|
106 | case 'markdown': {
|
107 | cellModel = new MarkdownCellModel({
|
108 | sharedModel: sharedModel as ISharedMarkdownCell
|
109 | });
|
110 | break;
|
111 | }
|
112 | default: {
|
113 | cellModel = new RawCellModel({
|
114 | sharedModel: sharedModel as ISharedRawCell
|
115 | });
|
116 | }
|
117 | }
|
118 |
|
119 | this._cellMap.set(sharedModel, cellModel);
|
120 | sharedModel.disposed.connect(() => {
|
121 | cellModel.dispose();
|
122 | this._cellMap.delete(sharedModel);
|
123 | });
|
124 | });
|
125 |
|
126 | return this.length;
|
127 | }
|
128 |
|
129 | private _onSharedModelChanged(self: ISharedNotebook, change: NotebookChange) {
|
130 | let currpos = 0;
|
131 |
|
132 | const events = new Array<IObservableList.IChangedArgs<ICellModel>>();
|
133 | change.cellsChange?.forEach(delta => {
|
134 | if (delta.insert != null) {
|
135 | this._insertCells(currpos, delta.insert);
|
136 | events.push({
|
137 | type: 'add',
|
138 | newIndex: currpos,
|
139 | newValues: delta.insert.map(c => this._cellMap.get(c)!),
|
140 | oldIndex: -2,
|
141 | oldValues: []
|
142 | });
|
143 |
|
144 | currpos += delta.insert.length;
|
145 | } else if (delta.delete != null) {
|
146 | events.push({
|
147 | type: 'remove',
|
148 | newIndex: -1,
|
149 | newValues: [],
|
150 | oldIndex: currpos,
|
151 |
|
152 | oldValues: new Array(delta.delete).fill(undefined)
|
153 | });
|
154 | } else if (delta.retain != null) {
|
155 | currpos += delta.retain;
|
156 | }
|
157 | });
|
158 |
|
159 | events.forEach(msg => this._changed.emit(msg));
|
160 | }
|
161 |
|
162 | private _cellMap = new WeakMap<ISharedCell, ICellModel>();
|
163 | private _changed = new Signal<this, IObservableList.IChangedArgs<ICellModel>>(
|
164 | this
|
165 | );
|
166 | private _isDisposed = false;
|
167 | }
|