UNPKG

16.4 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { ObservableMap } from '@jupyterlab/observables';
4import * as models from '@jupyterlab/shared-models';
5import { ArrayExt, ArrayIterator, each, toArray } from '@lumino/algorithm';
6import { Signal } from '@lumino/signaling';
7/**
8 * A cell list object that supports undo/redo.
9 */
10export class CellList {
11 /**
12 * Construct the cell list.
13 */
14 constructor(modelDB, factory, model) {
15 /**
16 * Prevents that the modeldb event handler is executed when the shared-model event handler is executed and vice-versa.
17 */
18 this._mutex = models.createMutex();
19 this._isDisposed = false;
20 this._changed = new Signal(this);
21 this._factory = factory;
22 this._cellOrder = modelDB.createList('cellOrder');
23 this._cellMap = new ObservableMap();
24 this._cellOrder.changed.connect(this._onOrderChanged, this);
25 this.nbmodel = model;
26 this.nbmodel.changed.connect(this.onSharedModelChanged, this);
27 this.changed.connect(this.onModelDBChanged, this);
28 }
29 onModelDBChanged(self, change) {
30 this._mutex(() => {
31 const nbmodel = this.nbmodel;
32 nbmodel.transact(() => {
33 if (change.type === 'set' || change.type === 'remove') {
34 nbmodel.deleteCellRange(change.oldIndex, change.oldIndex + change.oldValues.length);
35 }
36 if (change.type === 'set' ||
37 change.type === 'add' ||
38 change.type === 'move') {
39 const cells = change.newValues.map(cell => {
40 return cell.sharedModel.clone();
41 });
42 let insertLocation = change.newIndex;
43 if (change.type === 'move' && insertLocation > change.oldIndex) {
44 insertLocation += change.oldValues.length;
45 }
46 nbmodel.insertCells(insertLocation, cells);
47 change.newValues.forEach((cell, index) => {
48 cell.switchSharedModel(cells[index], false);
49 });
50 }
51 if (change.type === 'move') {
52 let from = change.oldIndex;
53 if (from >= change.newIndex) {
54 from += change.oldValues.length;
55 }
56 nbmodel.deleteCellRange(from, from + change.oldValues.length);
57 }
58 });
59 });
60 }
61 onSharedModelChanged(self, change) {
62 this._mutex(() => {
63 var _a;
64 let currpos = 0;
65 (_a = change.cellsChange) === null || _a === void 0 ? void 0 : _a.forEach(delta => {
66 if (delta.insert != null) {
67 const cells = delta.insert.map(nbcell => {
68 const cell = this._factory.createCell(nbcell.cell_type, {});
69 cell.switchSharedModel(nbcell, true);
70 return cell;
71 });
72 this.insertAll(currpos, cells);
73 currpos += delta.insert.length;
74 }
75 else if (delta.delete != null) {
76 this.removeRange(currpos, currpos + delta.delete);
77 }
78 else if (delta.retain != null) {
79 currpos += delta.retain;
80 }
81 });
82 });
83 }
84 /**
85 * A signal emitted when the cell list has changed.
86 */
87 get changed() {
88 return this._changed;
89 }
90 /**
91 * Test whether the cell list has been disposed.
92 */
93 get isDisposed() {
94 return this._isDisposed;
95 }
96 /**
97 * Test whether the list is empty.
98 *
99 * @returns `true` if the cell list is empty, `false` otherwise.
100 *
101 * #### Notes
102 * This is a read-only property.
103 *
104 * #### Complexity
105 * Constant.
106 *
107 * #### Iterator Validity
108 * No changes.
109 */
110 get isEmpty() {
111 return this._cellOrder.length === 0;
112 }
113 /**
114 * Get the length of the cell list.
115 *
116 * @return The number of cells in the cell list.
117 *
118 * #### Notes
119 * This is a read-only property.
120 *
121 * #### Complexity
122 * Constant.
123 *
124 * #### Iterator Validity
125 * No changes.
126 */
127 get length() {
128 return this._cellOrder.length;
129 }
130 /**
131 * Create an iterator over the cells in the cell list.
132 *
133 * @returns A new iterator starting at the front of the cell list.
134 *
135 * #### Complexity
136 * Constant.
137 *
138 * #### Iterator Validity
139 * No changes.
140 */
141 iter() {
142 const arr = [];
143 for (const id of toArray(this._cellOrder)) {
144 arr.push(this._cellMap.get(id));
145 }
146 return new ArrayIterator(arr);
147 }
148 /**
149 * Dispose of the resources held by the cell list.
150 */
151 dispose() {
152 if (this._isDisposed) {
153 return;
154 }
155 this._isDisposed = true;
156 Signal.clearData(this);
157 // Clean up the cell map and cell order objects.
158 for (const cell of this._cellMap.values()) {
159 cell.dispose();
160 }
161 this._cellMap.dispose();
162 this._cellOrder.dispose();
163 }
164 /**
165 * Get the cell at the specified index.
166 *
167 * @param index - The positive integer index of interest.
168 *
169 * @returns The cell at the specified index.
170 *
171 * #### Complexity
172 * Constant.
173 *
174 * #### Iterator Validity
175 * No changes.
176 *
177 * #### Undefined Behavior
178 * An `index` which is non-integral or out of range.
179 */
180 get(index) {
181 return this._cellMap.get(this._cellOrder.get(index));
182 }
183 /**
184 * Set the cell at the specified index.
185 *
186 * @param index - The positive integer index of interest.
187 *
188 * @param cell - The cell to set at the specified index.
189 *
190 * #### Complexity
191 * Constant.
192 *
193 * #### Iterator Validity
194 * No changes.
195 *
196 * #### Undefined Behavior
197 * An `index` which is non-integral or out of range.
198 *
199 * #### Notes
200 * This should be considered to transfer ownership of the
201 * cell to the `CellList`. As such, `cell.dispose()` should
202 * not be called by other actors.
203 */
204 set(index, cell) {
205 // Set the internal data structures.
206 this._cellMap.set(cell.id, cell);
207 this._cellOrder.set(index, cell.id);
208 }
209 /**
210 * Add a cell to the back of the cell list.
211 *
212 * @param cell - The cell to add to the back of the cell list.
213 *
214 * @returns The new length of the cell list.
215 *
216 * #### Complexity
217 * Constant.
218 *
219 * #### Iterator Validity
220 * No changes.
221 *
222 * #### Notes
223 * This should be considered to transfer ownership of the
224 * cell to the `CellList`. As such, `cell.dispose()` should
225 * not be called by other actors.
226 */
227 push(cell) {
228 // Set the internal data structures.
229 this._cellMap.set(cell.id, cell);
230 const num = this._cellOrder.push(cell.id);
231 return num;
232 }
233 /**
234 * Insert a cell into the cell list at a specific index.
235 *
236 * @param index - The index at which to insert the cell.
237 *
238 * @param cell - The cell to set at the specified index.
239 *
240 * @returns The new length of the cell list.
241 *
242 * #### Complexity
243 * Linear.
244 *
245 * #### Iterator Validity
246 * No changes.
247 *
248 * #### Notes
249 * The `index` will be clamped to the bounds of the cell list.
250 *
251 * #### Undefined Behavior
252 * An `index` which is non-integral.
253 *
254 * #### Notes
255 * This should be considered to transfer ownership of the
256 * cell to the `CellList`. As such, `cell.dispose()` should
257 * not be called by other actors.
258 */
259 insert(index, cell) {
260 // Set the internal data structures.
261 this._cellMap.set(cell.id, cell);
262 this._cellOrder.insert(index, cell.id);
263 }
264 /**
265 * Remove the first occurrence of a cell from the cell list.
266 *
267 * @param cell - The cell of interest.
268 *
269 * @returns The index of the removed cell, or `-1` if the cell
270 * is not contained in the cell list.
271 *
272 * #### Complexity
273 * Linear.
274 *
275 * #### Iterator Validity
276 * Iterators pointing at the removed cell and beyond are invalidated.
277 */
278 removeValue(cell) {
279 const index = ArrayExt.findFirstIndex(toArray(this._cellOrder), id => this._cellMap.get(id) === cell);
280 this.remove(index);
281 return index;
282 }
283 /**
284 * Remove and return the cell at a specific index.
285 *
286 * @param index - The index of the cell of interest.
287 *
288 * @returns The cell at the specified index, or `undefined` if the
289 * index is out of range.
290 *
291 * #### Complexity
292 * Constant.
293 *
294 * #### Iterator Validity
295 * Iterators pointing at the removed cell and beyond are invalidated.
296 *
297 * #### Undefined Behavior
298 * An `index` which is non-integral.
299 */
300 remove(index) {
301 const id = this._cellOrder.get(index);
302 this._cellOrder.remove(index);
303 const cell = this._cellMap.get(id);
304 return cell;
305 }
306 /**
307 * Remove all cells from the cell list.
308 *
309 * #### Complexity
310 * Linear.
311 *
312 * #### Iterator Validity
313 * All current iterators are invalidated.
314 */
315 clear() {
316 this._cellOrder.clear();
317 }
318 /**
319 * Move a cell from one index to another.
320 *
321 * @parm fromIndex - The index of the element to move.
322 *
323 * @param toIndex - The index to move the element to.
324 *
325 * #### Complexity
326 * Constant.
327 *
328 * #### Iterator Validity
329 * Iterators pointing at the lesser of the `fromIndex` and the `toIndex`
330 * and beyond are invalidated.
331 *
332 * #### Undefined Behavior
333 * A `fromIndex` or a `toIndex` which is non-integral.
334 */
335 move(fromIndex, toIndex) {
336 this._cellOrder.move(fromIndex, toIndex);
337 }
338 /**
339 * Push a set of cells to the back of the cell list.
340 *
341 * @param cells - An iterable or array-like set of cells to add.
342 *
343 * @returns The new length of the cell list.
344 *
345 * #### Complexity
346 * Linear.
347 *
348 * #### Iterator Validity
349 * No changes.
350 *
351 * #### Notes
352 * This should be considered to transfer ownership of the
353 * cells to the `CellList`. As such, `cell.dispose()` should
354 * not be called by other actors.
355 */
356 pushAll(cells) {
357 const newValues = toArray(cells);
358 each(newValues, cell => {
359 // Set the internal data structures.
360 this._cellMap.set(cell.id, cell);
361 this._cellOrder.push(cell.id);
362 });
363 return this.length;
364 }
365 /**
366 * Insert a set of items into the cell list at the specified index.
367 *
368 * @param index - The index at which to insert the cells.
369 *
370 * @param cells - The cells to insert at the specified index.
371 *
372 * @returns The new length of the cell list.
373 *
374 * #### Complexity.
375 * Linear.
376 *
377 * #### Iterator Validity
378 * No changes.
379 *
380 * #### Notes
381 * The `index` will be clamped to the bounds of the cell list.
382 *
383 * #### Undefined Behavior.
384 * An `index` which is non-integral.
385 *
386 * #### Notes
387 * This should be considered to transfer ownership of the
388 * cells to the `CellList`. As such, `cell.dispose()` should
389 * not be called by other actors.
390 */
391 insertAll(index, cells) {
392 const newValues = toArray(cells);
393 each(newValues, cell => {
394 this._cellMap.set(cell.id, cell);
395 // @todo it looks like this compound operation shoult start before the `each` loop.
396 this._cellOrder.beginCompoundOperation();
397 this._cellOrder.insert(index++, cell.id);
398 this._cellOrder.endCompoundOperation();
399 });
400 return this.length;
401 }
402 /**
403 * Remove a range of items from the cell list.
404 *
405 * @param startIndex - The start index of the range to remove (inclusive).
406 *
407 * @param endIndex - The end index of the range to remove (exclusive).
408 *
409 * @returns The new length of the cell list.
410 *
411 * #### Complexity
412 * Linear.
413 *
414 * #### Iterator Validity
415 * Iterators pointing to the first removed cell and beyond are invalid.
416 *
417 * #### Undefined Behavior
418 * A `startIndex` or `endIndex` which is non-integral.
419 */
420 removeRange(startIndex, endIndex) {
421 this._cellOrder.removeRange(startIndex, endIndex);
422 return this.length;
423 }
424 /**
425 * Whether the object can redo changes.
426 */
427 get canRedo() {
428 return this.nbmodel.canRedo();
429 }
430 /**
431 * Whether the object can undo changes.
432 */
433 get canUndo() {
434 return this.nbmodel.canUndo();
435 }
436 /**
437 * Begin a compound operation.
438 *
439 * @param isUndoAble - Whether the operation is undoable.
440 * The default is `true`.
441 */
442 beginCompoundOperation(isUndoAble) {
443 this._cellOrder.beginCompoundOperation(isUndoAble);
444 }
445 /**
446 * End a compound operation.
447 */
448 endCompoundOperation() {
449 this._cellOrder.endCompoundOperation();
450 }
451 /**
452 * Undo an operation.
453 */
454 undo() {
455 this.nbmodel.undo();
456 }
457 /**
458 * Redo an operation.
459 */
460 redo() {
461 this.nbmodel.redo();
462 }
463 /**
464 * Clear the change stack.
465 */
466 clearUndo() {
467 this.nbmodel.clearUndoHistory();
468 }
469 _onOrderChanged(order, change) {
470 if (change.type === 'add' || change.type === 'set') {
471 each(change.newValues, id => {
472 const existingCell = this._cellMap.get(id);
473 if (existingCell == null) {
474 const cellDB = this._factory.modelDB;
475 const cellType = cellDB.createValue(id + '.type');
476 let cell;
477 switch (cellType.get()) {
478 case 'code':
479 cell = this._factory.createCodeCell({ id: id });
480 break;
481 case 'markdown':
482 cell = this._factory.createMarkdownCell({ id: id });
483 break;
484 default:
485 cell = this._factory.createRawCell({ id: id });
486 break;
487 }
488 this._cellMap.set(id, cell);
489 }
490 else if (!existingCell.sharedModel.isStandalone) {
491 this._mutex(() => {
492 // it does already exist, probably because it was deleted previously and we introduced it
493 // copy it to a fresh codecell instance
494 const cell = existingCell.toJSON();
495 let freshCell = null;
496 switch (cell.cell_type) {
497 case 'code':
498 freshCell = this._factory.createCodeCell({ cell });
499 break;
500 case 'markdown':
501 freshCell = this._factory.createMarkdownCell({ cell });
502 break;
503 default:
504 freshCell = this._factory.createRawCell({ cell });
505 break;
506 }
507 this._cellMap.set(id, freshCell);
508 });
509 }
510 });
511 }
512 const newValues = [];
513 const oldValues = [];
514 each(change.newValues, id => {
515 newValues.push(this._cellMap.get(id));
516 });
517 each(change.oldValues, id => {
518 oldValues.push(this._cellMap.get(id));
519 });
520 this._changed.emit({
521 type: change.type,
522 oldIndex: change.oldIndex,
523 newIndex: change.newIndex,
524 oldValues,
525 newValues
526 });
527 }
528}
529//# sourceMappingURL=celllist.js.map
\No newline at end of file