1// Copyright (c) Jupyter Development Team. 2// Distributed under the terms of the Modified BSD License. 3'use strict'; 4 5import * as nbformat from '@jupyterlab/nbformat'; 6 7import { 8 IDiffEntry, IDiffArrayEntry 9} from '../diffentries'; 10 11import { 12 getSubDiffByKey 13} from '../util'; 14 15import { 16 IStringDiffModel, createPatchStringDiffModel 17} from './string'; 18 19import { 20 CellDiffModel, 21 createUnchangedCellDiffModel, createAddedCellDiffModel, 22 createDeletedCellDiffModel, createPatchedCellDiffModel 23} from './cell'; 24 25 26/** 27 * Diff model for a Jupyter Notebook 28 */ 29export class NotebookDiffModel { 30 31 /** 32 * Create a new NotebookDiffModel from a base notebook and a list of diffs. 33 * 34 * The base as well as the diff entries are normally supplied by the nbdime 35 * server. 36 */ 37 constructor(base: nbformat.INotebookContent, diff: IDiffEntry[]) { 38 // Process global notebook metadata field 39 let metaDiff = getSubDiffByKey(diff, 'metadata'); 40 if (base.metadata && metaDiff) { 41 this.metadata = createPatchStringDiffModel(base.metadata, metaDiff); 42 } else { 43 this.metadata = null; 44 } 45 if (this.metadata) { 46 this.metadata.collapsible = true; 47 this.metadata.collapsibleHeader = 'Notebook metadata changed'; 48 this.metadata.startCollapsed = true; 49 } 50 // The notebook metadata MIME type is used for determining the MIME type 51 // of source cells, so store it easily accessible: 52 let mimetype: string | undefined; 53 try { 54 mimetype = base.metadata.language_info!.mimetype; 55 } catch (e) { 56 // missing metadata (probably old notebook) 57 } 58 this.mimetype = mimetype || 'text/python'; 59 60 // Build cell diff models. Follows similar logic to patching code: 61 this.cells = []; 62 this.chunkedCells = []; 63 let take = 0; 64 let skip = 0; 65 let previousChunkIndex = -1; 66 let currentChunk: CellDiffModel[] = []; 67 for (let e of getSubDiffByKey(diff, 'cells') as IDiffArrayEntry[] || []) { 68 let index = e.key; 69 70 // diff is sorted on index, so take any preceding cells as unchanged: 71 for (let i=take; i < index; i++) { 72 let cell = createUnchangedCellDiffModel(base.cells[i], this.mimetype); 73 this.cells.push(cell); 74 this.chunkedCells.push([cell]); 75 } 76 77 if (index !== previousChunkIndex) { 78 currentChunk = []; 79 this.chunkedCells.push(currentChunk); 80 previousChunkIndex = index; 81 } 82 83 // Process according to diff type: 84 if (e.op === 'addrange') { 85 // One or more inserted/added cells: 86 for (let ei of e.valuelist) { 87 let cell = createAddedCellDiffModel(ei as nbformat.ICell, this.mimetype); 88 this.cells.push(cell); 89 currentChunk.push(cell); 90 } 91 skip = 0; 92 } else if (e.op === 'removerange') { 93 // One or more removed/deleted cells: 94 skip = e.length; 95 for (let i=index; i < index + skip; i++) { 96 let cell = createDeletedCellDiffModel(base.cells[i], this.mimetype); 97 this.cells.push(cell); 98 currentChunk.push(cell); 99 } 100 } else if (e.op === 'patch') { 101 // Ensure patches gets their own chunk, even if they share index: 102 if (currentChunk.length > 0) { 103 currentChunk = []; 104 this.chunkedCells.push(currentChunk); 105 } 106 // A cell has changed: 107 let cell = createPatchedCellDiffModel(base.cells[index], e.diff, this.mimetype); 108 this.cells.push(cell); 109 currentChunk.push(cell); 110 skip = 1; 111 } 112 113 // Skip the specified number of elements, but never decrement take. 114 // Note that take can pass index in diffs with repeated +/- on the 115 // same index, i.e. [op_remove(index), op_add(index, value)] 116 take = Math.max(take, index + skip); 117 } 118 // Take unchanged values at end 119 for (let i=take; i < base.cells.length; i++) { 120 let cell = createUnchangedCellDiffModel(base.cells[i], this.mimetype); 121 this.cells.push(cell); 122 this.chunkedCells.push([cell]); 123 } 124 } 125 126 /** 127 * Diff model of the notebook's root metadata field 128 */ 129 metadata: IStringDiffModel | null; 130 131 /** 132 * The default MIME type according to the notebook's root metadata 133 */ 134 mimetype: string; 135 136 /** 137 * List of all cell diff models, including unchanged, added/removed and 138 * changed cells, in order. 139 */ 140 cells: CellDiffModel[]; 141 142 /** 143 * List of chunks of cells, e.g. so that any changes that occur in the same 144 * location optionally can be shown side by side. 145 */ 146 chunkedCells: CellDiffModel[][]; 147} 148