notebookViewModel.ts 25.0 KB
Newer Older
R
rebornix 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

R
rebornix 已提交
6
import { CancellationTokenSource } from 'vs/base/common/cancellation';
R
rebornix 已提交
7
import { onUnexpectedError } from 'vs/base/common/errors';
R
rebornix 已提交
8
import { Emitter, Event } from 'vs/base/common/event';
R
rebornix 已提交
9
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
R
rebornix 已提交
10
import * as strings from 'vs/base/common/strings';
R
rebornix 已提交
11
import { URI } from 'vs/base/common/uri';
R
rebornix 已提交
12 13
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { Range } from 'vs/editor/common/core/range';
R
rebornix 已提交
14
import * as editorCommon from 'vs/editor/common/editorCommon';
R
rebornix 已提交
15 16 17
import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
import { IntervalNode, IntervalTree } from 'vs/editor/common/model/intervalTree';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
R
rebornix 已提交
18
import { WorkspaceTextEdit } from 'vs/editor/common/modes';
R
rebornix 已提交
19
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
R
rebornix 已提交
20
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
R
rebornix 已提交
21
import { CellEditState, CellFindMatch, ICellRange, ICellViewModel, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
R
rebornix 已提交
22 23 24
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { DeleteCellEdit, InsertCellEdit, MoveCellEdit } from 'vs/workbench/contrib/notebook/browser/viewModel/cellEdit';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
R
rebornix 已提交
25
import { NotebookEventDispatcher, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
R
rebornix 已提交
26 27
import { CellFoldingState, FoldingModel, FoldingRegionDelegate } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
R
rebornix 已提交
28
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
R
rebornix 已提交
29
import { CellKind, ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon';
R
rebornix 已提交
30 31 32

export interface INotebookEditorViewState {
	editingCells: { [key: number]: boolean };
R
rebornix 已提交
33
	editorViewStates: { [key: number]: editorCommon.ICodeEditorViewState | null };
34
	cellTotalHeights?: { [key: number]: number };
R
rebornix 已提交
35
	scrollPosition?: { left: number; top: number; };
R
rebornix 已提交
36 37
}

R
rebornix 已提交
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
export interface ICellModelDecorations {
	ownerId: number;
	decorations: string[];
}

export interface ICellModelDeltaDecorations {
	ownerId: number;
	decorations: IModelDeltaDecoration[];
}

export interface IModelDecorationsChangeAccessor {
	deltaDecorations(oldDecorations: ICellModelDecorations[], newDecorations: ICellModelDeltaDecorations[]): ICellModelDecorations[];
}

const invalidFunc = () => { throw new Error(`Invalid change accessor`); };


R
rebornix 已提交
55 56 57 58 59 60 61 62 63 64 65
export type NotebookViewCellsSplice = [
	number /* start */,
	number /* delete count */,
	CellViewModel[]
];

export interface INotebookViewCellsUpdateEvent {
	synchronous: boolean;
	splices: NotebookViewCellsSplice[];
}

R
rebornix 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

class DecorationsTree {
	private readonly _decorationsTree: IntervalTree;

	constructor() {
		this._decorationsTree = new IntervalTree();
	}

	public intervalSearch(start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, cachedVersionId: number): IntervalNode[] {
		const r1 = this._decorationsTree.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId);
		return r1;
	}

	public search(filterOwnerId: number, filterOutValidation: boolean, overviewRulerOnly: boolean, cachedVersionId: number): IntervalNode[] {
		return this._decorationsTree.search(filterOwnerId, filterOutValidation, cachedVersionId);

	}

	public collectNodesFromOwner(ownerId: number): IntervalNode[] {
		const r1 = this._decorationsTree.collectNodesFromOwner(ownerId);
		return r1;
	}

	public collectNodesPostOrder(): IntervalNode[] {
		const r1 = this._decorationsTree.collectNodesPostOrder();
		return r1;
	}

	public insert(node: IntervalNode): void {
		this._decorationsTree.insert(node);
	}

	public delete(node: IntervalNode): void {
		this._decorationsTree.delete(node);
	}

	public resolveNode(node: IntervalNode, cachedVersionId: number): void {
		this._decorationsTree.resolveNode(node, cachedVersionId);
	}

	public acceptReplace(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void {
		this._decorationsTree.acceptReplace(offset, length, textLength, forceMoveMarkers);
	}
}

const TRACKED_RANGE_OPTIONS = [
	ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }),
	ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }),
	ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.GrowsOnlyWhenTypingBefore }),
	ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.GrowsOnlyWhenTypingAfter }),
];

function _normalizeOptions(options: IModelDecorationOptions): ModelDecorationOptions {
	if (options instanceof ModelDecorationOptions) {
		return options;
	}
	return ModelDecorationOptions.createDynamic(options);
}

let MODEL_ID = 0;


R
rebornix 已提交
128
export class NotebookViewModel extends Disposable implements FoldingRegionDelegate {
R
rebornix 已提交
129 130 131
	private _localStore: DisposableStore = this._register(new DisposableStore());
	private _viewCells: CellViewModel[] = [];

132 133 134 135 136 137 138 139 140 141
	private _currentTokenSource: CancellationTokenSource | undefined;

	get currentTokenSource(): CancellationTokenSource | undefined {
		return this._currentTokenSource;
	}

	set currentTokenSource(v: CancellationTokenSource | undefined) {
		this._currentTokenSource = v;
	}

R
rebornix 已提交
142
	get viewCells(): ICellViewModel[] {
R
rebornix 已提交
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
		return this._viewCells;
	}

	get notebookDocument() {
		return this._model.notebook;
	}

	get renderers() {
		return this._model.notebook!.renderers;
	}

	get handle() {
		return this._model.notebook.handle;
	}

	get languages() {
		return this._model.notebook.languages;
	}

	get uri() {
		return this._model.notebook.uri;
	}

R
rebornix 已提交
166 167 168 169
	get metadata() {
		return this._model.notebook.metadata;
	}

R
rebornix 已提交
170 171
	private readonly _onDidChangeViewCells = new Emitter<INotebookViewCellsUpdateEvent>();
	get onDidChangeViewCells(): Event<INotebookViewCellsUpdateEvent> { return this._onDidChangeViewCells.event; }
R
rebornix 已提交
172

R
rebornix 已提交
173 174 175 176 177 178 179 180 181
	private _lastNotebookEditResource: URI[] = [];

	get lastNotebookEditResource(): URI | null {
		if (this._lastNotebookEditResource.length) {
			return this._lastNotebookEditResource[this._lastNotebookEditResource.length - 1];
		}
		return null;
	}

182 183 184 185
	get layoutInfo(): NotebookLayoutInfo | null {
		return this._layoutInfo;
	}

R
rebornix 已提交
186 187 188 189 190 191
	private _decorationsTree = new DecorationsTree();
	private _decorations: { [decorationId: string]: IntervalNode; } = Object.create(null);
	private _lastDecorationId: number = 0;
	private readonly _instanceId: string;
	public readonly id: string;

R
rebornix 已提交
192 193 194 195 196
	private _foldingModel: FoldingModel;
	private _onDidFoldingRegionChanges = new Emitter<void>();
	onDidFoldingRegionChanged: Event<void> = this._onDidFoldingRegionChanges.event;
	private _hiddenRanges: ICellRange[] = [];

R
rebornix 已提交
197 198 199
	constructor(
		public viewType: string,
		private _model: NotebookEditorModel,
R
rebornix 已提交
200
		readonly eventDispatcher: NotebookEventDispatcher,
201
		private _layoutInfo: NotebookLayoutInfo | null,
R
rebornix 已提交
202
		@IInstantiationService private readonly instantiationService: IInstantiationService,
R
rebornix 已提交
203 204
		@IBulkEditService private readonly bulkEditService: IBulkEditService,
		@IUndoRedoService private readonly undoService: IUndoRedoService
R
rebornix 已提交
205 206 207
	) {
		super();

R
rebornix 已提交
208 209 210 211
		MODEL_ID++;
		this.id = '$notebookViewModel' + MODEL_ID;
		this._instanceId = strings.singleLetterHash(MODEL_ID);

R
rebornix 已提交
212 213 214 215 216 217 218 219 220 221
		// this._register(this._model.onDidChangeCells(e => {
		// 	this._onDidChangeViewCells.fire({
		// 		synchronous: true,
		// 		splices: e.map(splice => {
		// 			return [splice[0], splice[1], splice[2].map(cell => {
		// 				return createCellViewModel(this.instantiationService, this, cell as NotebookCellTextModel);
		// 			})];
		// 		})
		// 	});
		// }));
R
rebornix 已提交
222

R
rebornix 已提交
223 224 225 226
		this._register(this._model.notebook.onDidChangeMetadata(e => {
			this.eventDispatcher.emit([new NotebookMetadataChangedEvent(e)]);
		}));

227 228
		this._register(this.eventDispatcher.onDidChangeLayout((e) => {
			this._layoutInfo = e.value;
R
rebornix 已提交
229 230 231 232 233 234 235 236 237 238 239 240

			this._viewCells.forEach(cell => {
				if (cell.cellKind === CellKind.Markdown) {
					if (e.source.width || e.source.fontInfo) {
						cell.layoutChange({ outerWidth: e.value.width, font: e.value.fontInfo });
					}
				} else {
					if (e.source.width !== undefined) {
						cell.layoutChange({ outerWidth: e.value.width, font: e.value.fontInfo });
					}
				}
			});
241 242
		}));

R
rebornix 已提交
243
		this._viewCells = this._model!.notebook!.cells.map(cell => {
R
rebornix 已提交
244
			return createCellViewModel(this.instantiationService, this, cell);
R
rebornix 已提交
245
		});
R
rebornix 已提交
246 247 248

		this._foldingModel = new FoldingModel();
		this._foldingModel.attachViewModel(this);
249 250 251 252

		this._register(this._foldingModel.onDidFoldingRegionChanged(() => {
			this._updateFoldingRanges();
		}));
R
rebornix 已提交
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
	}
	getFoldingStartIndex(cell: CellViewModel): number {
		const modelIndex = this.viewCells.indexOf(cell);
		if (modelIndex < 0) {
			return -1;
		}

		const range = this._foldingModel.regions.findRange(modelIndex + 1);
		const startIndex = this._foldingModel.regions.getStartLineNumber(range) - 1;
		return startIndex;
	}

	getFoldingState(cell: CellViewModel): CellFoldingState {
		const modelIndex = this.viewCells.indexOf(cell);
		if (modelIndex < 0) {
			return -1;
		}

		const range = this._foldingModel.regions.findRange(modelIndex + 1);
		const startIndex = this._foldingModel.regions.getStartLineNumber(range) - 1;

		if (startIndex !== modelIndex) {
			return CellFoldingState.None;
		}

		return this._foldingModel.regions.isCollapsed(range) ? CellFoldingState.Collapsed : CellFoldingState.Expanded;
	}

	setFoldingState(cell: CellViewModel, state: CellFoldingState): void {
		const modelIndex = this.viewCells.indexOf(cell);
		if (modelIndex < 0) {
			return;
		}

		const range = this._foldingModel.regions.findRange(modelIndex + 1);
		const startIndex = this._foldingModel.regions.getStartLineNumber(range) - 1;

		if (startIndex !== modelIndex) {
			return;
		}

294
		this._foldingModel.setCollapsed(range, state === CellFoldingState.Collapsed);
R
rebornix 已提交
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
		this._updateFoldingRanges();
	}

	private _updateFoldingRanges() {
		let updateHiddenAreas = false;
		let newHiddenAreas: ICellRange[] = [];

		let ranges = this._foldingModel.regions;
		let i = 0; // index into hidden
		let k = 0;

		let lastCollapsedStart = Number.MAX_VALUE;
		let lastCollapsedEnd = -1;

		for (; i < ranges.length; i++) {
			if (!ranges.isCollapsed(i)) {
				continue;
			}

			let startLineNumber = ranges.getStartLineNumber(i) + 1; // the first line is not hidden
			let endLineNumber = ranges.getEndLineNumber(i);
			if (lastCollapsedStart <= startLineNumber && endLineNumber <= lastCollapsedEnd) {
				// ignore ranges contained in collapsed regions
				continue;
			}

			if (!updateHiddenAreas && k < this._hiddenRanges.length && this._hiddenRanges[k].start + 1 === startLineNumber && (this._hiddenRanges[k].start + this._hiddenRanges[k].length) === endLineNumber) {
				// reuse the old ranges
				newHiddenAreas.push(this._hiddenRanges[k]);
				k++;
			} else {
				updateHiddenAreas = true;
				newHiddenAreas.push({ start: startLineNumber - 1, length: endLineNumber - startLineNumber + 1 });
			}
			lastCollapsedStart = startLineNumber;
			lastCollapsedEnd = endLineNumber;
		}

		if (updateHiddenAreas || k < this._hiddenRanges.length) {
			this._hiddenRanges = newHiddenAreas;
			this._onDidFoldingRegionChanges.fire();
R
rebornix 已提交
336 337 338 339 340
			this._viewCells.forEach(cell => {
				if (cell.cellKind === CellKind.Markdown) {
					cell.triggerfoldingStateChange();
				}
			});
R
rebornix 已提交
341 342 343 344 345
		}
	}

	getHiddenRanges() {
		return this._hiddenRanges;
R
rebornix 已提交
346 347 348 349 350 351 352
	}

	isDirty() {
		return this._model.isDirty();
	}

	hide() {
R
rebornix 已提交
353
		this._viewCells.forEach(cell => {
R
rebornix 已提交
354
			if (cell.getText() !== '') {
355
				cell.editState = CellEditState.Preview;
R
rebornix 已提交
356 357 358 359
			}
		});
	}

R
rebornix 已提交
360
	getCellIndex(cell: ICellViewModel) {
R
rebornix 已提交
361
		return this._viewCells.indexOf(cell as CellViewModel);
R
rebornix 已提交
362 363
	}

R
rebornix 已提交
364 365 366 367 368
	getVersionId() {
		return this._model.notebook.versionId;
	}

	getTrackedRange(id: string): ICellRange | null {
369
		return this._getDecorationRange(id);
R
rebornix 已提交
370 371
	}

372
	private _getDecorationRange(decorationId: string): ICellRange | null {
R
rebornix 已提交
373 374 375 376 377 378 379 380 381
		const node = this._decorations[decorationId];
		if (!node) {
			return null;
		}
		const versionId = this.getVersionId();
		if (node.cachedVersionId !== versionId) {
			this._decorationsTree.resolveNode(node, versionId);
		}
		if (node.range === null) {
R
rebornix 已提交
382
			return { start: node.cachedAbsoluteStart - 1, length: node.cachedAbsoluteEnd - node.cachedAbsoluteStart + 1 };
R
rebornix 已提交
383 384
		}

R
rebornix 已提交
385
		return { start: node.range.startLineNumber - 1, length: node.range.endLineNumber - node.range.startLineNumber + 1 };
R
rebornix 已提交
386 387 388 389 390 391 392 393 394 395
	}

	setTrackedRange(id: string | null, newRange: ICellRange | null, newStickiness: TrackedRangeStickiness): string | null {
		const node = (id ? this._decorations[id] : null);

		if (!node) {
			if (!newRange) {
				return null;
			}

R
rebornix 已提交
396
			return this._deltaCellDecorationsImpl(0, [], [{ range: new Range(newRange.start + 1, 1, newRange.start + newRange.length, 1), options: TRACKED_RANGE_OPTIONS[newStickiness] }])[0];
R
rebornix 已提交
397 398 399 400 401 402 403 404 405 406
		}

		if (!newRange) {
			// node exists, the request is to delete => delete node
			this._decorationsTree.delete(node);
			delete this._decorations[node.id];
			return null;
		}

		this._decorationsTree.delete(node);
R
rebornix 已提交
407
		node.reset(this.getVersionId(), newRange.start, newRange.start + newRange.length, new Range(newRange.start + 1, 1, newRange.start + newRange.length, 1));
R
rebornix 已提交
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
		node.setOptions(TRACKED_RANGE_OPTIONS[newStickiness]);
		this._decorationsTree.insert(node);
		return node.id;
	}

	private _deltaCellDecorationsImpl(ownerId: number, oldDecorationsIds: string[], newDecorations: IModelDeltaDecoration[]): string[] {
		const versionId = this.getVersionId();

		const oldDecorationsLen = oldDecorationsIds.length;
		let oldDecorationIndex = 0;

		const newDecorationsLen = newDecorations.length;
		let newDecorationIndex = 0;

		let result = new Array<string>(newDecorationsLen);
		while (oldDecorationIndex < oldDecorationsLen || newDecorationIndex < newDecorationsLen) {

			let node: IntervalNode | null = null;

			if (oldDecorationIndex < oldDecorationsLen) {
				// (1) get ourselves an old node
				do {
					node = this._decorations[oldDecorationsIds[oldDecorationIndex++]];
				} while (!node && oldDecorationIndex < oldDecorationsLen);

				// (2) remove the node from the tree (if it exists)
				if (node) {
					this._decorationsTree.delete(node);
					// this._onDidChangeDecorations.checkAffectedAndFire(node.options);
				}
			}

			if (newDecorationIndex < newDecorationsLen) {
				// (3) create a new node if necessary
				if (!node) {
					const internalDecorationId = (++this._lastDecorationId);
					const decorationId = `${this._instanceId};${internalDecorationId}`;
					node = new IntervalNode(decorationId, 0, 0);
					this._decorations[decorationId] = node;
				}

				// (4) initialize node
				const newDecoration = newDecorations[newDecorationIndex];
				// const range = this._validateRangeRelaxedNoAllocations(newDecoration.range);
				const range = newDecoration.range;
				const options = _normalizeOptions(newDecoration.options);
				// const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);
				// const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);

				node.ownerId = ownerId;
				node.reset(versionId, range.startLineNumber, range.endLineNumber, Range.lift(range));
				node.setOptions(options);
				// this._onDidChangeDecorations.checkAffectedAndFire(options);

				this._decorationsTree.insert(node);

				result[newDecorationIndex] = node.id;

				newDecorationIndex++;
			} else {
				if (node) {
					delete this._decorations[node.id];
				}
			}
		}

		return result;
	}

477 478
	private _insertCellDelegate(insertIndex: number, insertCell: CellViewModel) {
		this._viewCells!.splice(insertIndex, 0, insertCell);
R
rebornix 已提交
479
		this._model.insertCell(insertCell.model, insertIndex);
480 481 482 483 484 485
		this._localStore.add(insertCell);
		this._onDidChangeViewCells.fire({ synchronous: true, splices: [[insertIndex, 0, [insertCell]]] });
	}

	private _deleteCellDelegate(deleteIndex: number, cell: ICell) {
		this._viewCells.splice(deleteIndex, 1);
486
		this._model.deleteCell(deleteIndex);
487 488 489
		this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] });
	}

R
rebornix 已提交
490 491 492 493 494 495 496 497 498 499 500
	createCell(index: number, source: string[], language: string, type: CellKind, synchronous: boolean) {
		const cell = this._model.notebook.createCellTextModel(source, language, type, [], undefined);
		let newCell: CellViewModel = createCellViewModel(this.instantiationService, this, cell);
		this._viewCells!.splice(index, 0, newCell);
		this._model.insertCell(cell, index);
		this._localStore.add(newCell);
		this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, {
			insertCell: this._insertCellDelegate.bind(this),
			deleteCell: this._deleteCellDelegate.bind(this)
		}));

R
rebornix 已提交
501
		this._decorationsTree.acceptReplace(index, 0, 1, true);
R
rebornix 已提交
502 503 504 505
		this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 0, [newCell]]] });
		return newCell;
	}

R
rebornix 已提交
506
	insertCell(index: number, cell: NotebookCellTextModel, synchronous: boolean): CellViewModel {
R
rebornix 已提交
507
		let newCell: CellViewModel = createCellViewModel(this.instantiationService, this, cell);
R
rebornix 已提交
508
		this._viewCells!.splice(index, 0, newCell);
R
rebornix 已提交
509
		this._model.insertCell(newCell.model, index);
510
		this._localStore.add(newCell);
R
rebornix 已提交
511
		this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, {
512 513
			insertCell: this._insertCellDelegate.bind(this),
			deleteCell: this._deleteCellDelegate.bind(this)
R
rebornix 已提交
514 515
		}));

R
rebornix 已提交
516
		this._decorationsTree.acceptReplace(index, 0, 1, true);
R
rebornix 已提交
517
		this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 0, [newCell]]] });
518
		return newCell;
R
rebornix 已提交
519 520
	}

R
rebornix 已提交
521
	deleteCell(index: number, synchronous: boolean) {
R
rebornix 已提交
522 523
		let viewCell = this._viewCells[index];
		this._viewCells.splice(index, 1);
524
		this._model.deleteCell(index);
R
rebornix 已提交
525 526

		this.undoService.pushElement(new DeleteCellEdit(this.uri, index, viewCell, {
527
			insertCell: this._insertCellDelegate.bind(this),
R
rebornix 已提交
528
			deleteCell: this._deleteCellDelegate.bind(this),
R
rebornix 已提交
529
			createCellViewModel: (cell: NotebookCellTextModel) => {
R
rebornix 已提交
530 531 532
				return createCellViewModel(this.instantiationService, this, cell);
			}
		}));
R
rebornix 已提交
533

R
rebornix 已提交
534 535 536

		this._decorationsTree.acceptReplace(index, 1, 0, true);

R
rebornix 已提交
537
		this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] });
538
		viewCell.dispose();
R
rebornix 已提交
539 540
	}

R
rebornix 已提交
541
	moveCellToIdx(index: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean = true): boolean {
542
		// TODO roblou - works differently if index > or < newIdx, fix, write tests
R
Rob Lourens 已提交
543

R
rebornix 已提交
544
		const viewCell = this.viewCells[index] as CellViewModel;
545 546 547 548 549
		if (!viewCell) {
			return false;
		}

		this.viewCells.splice(index, 1);
550
		this._model.deleteCell(index);
551 552

		this.viewCells!.splice(newIdx, 0, viewCell);
R
rebornix 已提交
553
		this._model.insertCell(viewCell.model, newIdx);
554

R
rebornix 已提交
555 556 557 558 559 560 561 562 563 564 565
		if (pushedToUndoStack) {
			this.undoService.pushElement(new MoveCellEdit(this.uri, index, newIdx, {
				moveCell: (fromIndex: number, toIndex: number) => {
					this.moveCellToIdx(fromIndex, toIndex, true, false);
				}
			}));
		}

		this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] });
		this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[newIdx, 0, [viewCell]]] });

566 567 568
		return true;
	}

R
rebornix 已提交
569 570
	saveEditorViewState(): INotebookEditorViewState {
		const state: { [key: number]: boolean } = {};
R
rebornix 已提交
571
		this._viewCells.filter(cell => cell.editState === CellEditState.Editing).forEach(cell => state[cell.model.handle] = true);
R
rebornix 已提交
572
		const editorViewStates: { [key: number]: editorCommon.ICodeEditorViewState } = {};
R
rebornix 已提交
573
		this._viewCells.map(cell => ({ handle: cell.model.handle, state: cell.saveEditorViewState() })).forEach(viewState => {
R
rebornix 已提交
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
			if (viewState.state) {
				editorViewStates[viewState.handle] = viewState.state;
			}
		});

		return {
			editingCells: state,
			editorViewStates: editorViewStates
		};
	}

	restoreEditorViewState(viewState: INotebookEditorViewState | undefined): void {
		if (!viewState) {
			return;
		}

590
		this._viewCells.forEach((cell, index) => {
R
rebornix 已提交
591 592 593
			const isEditing = viewState.editingCells && viewState.editingCells[cell.handle];
			const editorViewState = viewState.editorViewStates && viewState.editorViewStates[cell.handle];

594
			cell.editState = isEditing ? CellEditState.Editing : CellEditState.Preview;
595 596
			const cellHeight = viewState.cellTotalHeights ? viewState.cellTotalHeights[index] : undefined;
			cell.restoreEditorViewState(editorViewState, cellHeight);
R
rebornix 已提交
597 598 599
		});
	}

R
rebornix 已提交
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
	/**
	 * Editor decorations across cells. For example, find decorations for multiple code cells
	 * The reason that we can't completely delegate this to CodeEditorWidget is most of the time, the editors for cells are not created yet but we already have decorations for them.
	 */
	changeDecorations<T>(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null {
		const changeAccessor: IModelDecorationsChangeAccessor = {
			deltaDecorations: (oldDecorations: ICellModelDecorations[], newDecorations: ICellModelDeltaDecorations[]): ICellModelDecorations[] => {
				return this.deltaDecorationsImpl(oldDecorations, newDecorations);
			}
		};

		let result: T | null = null;
		try {
			result = callback(changeAccessor);
		} catch (e) {
			onUnexpectedError(e);
		}

		changeAccessor.deltaDecorations = invalidFunc;

		return result;
	}

	deltaDecorationsImpl(oldDecorations: ICellModelDecorations[], newDecorations: ICellModelDeltaDecorations[]): ICellModelDecorations[] {

R
rebornix 已提交
625
		const mapping = new Map<number, { cell: CellViewModel; oldDecorations: string[]; newDecorations: IModelDeltaDecoration[] }>();
R
rebornix 已提交
626 627 628 629
		oldDecorations.forEach(oldDecoration => {
			const ownerId = oldDecoration.ownerId;

			if (!mapping.has(ownerId)) {
R
rebornix 已提交
630
				const cell = this._viewCells.find(cell => cell.handle === ownerId);
631 632 633
				if (cell) {
					mapping.set(ownerId, { cell: cell, oldDecorations: [], newDecorations: [] });
				}
R
rebornix 已提交
634 635 636
			}

			const data = mapping.get(ownerId)!;
R
rebornix 已提交
637 638 639
			if (data) {
				data.oldDecorations = oldDecoration.decorations;
			}
R
rebornix 已提交
640 641 642 643 644 645
		});

		newDecorations.forEach(newDecoration => {
			const ownerId = newDecoration.ownerId;

			if (!mapping.has(ownerId)) {
R
rebornix 已提交
646
				const cell = this._viewCells.find(cell => cell.handle === ownerId);
647 648 649 650

				if (cell) {
					mapping.set(ownerId, { cell: cell, oldDecorations: [], newDecorations: [] });
				}
R
rebornix 已提交
651 652 653
			}

			const data = mapping.get(ownerId)!;
R
rebornix 已提交
654 655 656
			if (data) {
				data.newDecorations = newDecoration.decorations;
			}
R
rebornix 已提交
657 658 659 660 661 662 663 664 665 666 667 668 669 670
		});

		const ret: ICellModelDecorations[] = [];
		mapping.forEach((value, ownerId) => {
			const cellRet = value.cell.deltaDecorations(value.oldDecorations, value.newDecorations);
			ret.push({
				ownerId: ownerId,
				decorations: cellRet
			});
		});

		return ret;
	}

R
rebornix 已提交
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721

	/**
	 * Search in notebook text model
	 * @param value
	 */
	find(value: string): CellFindMatch[] {
		const matches: CellFindMatch[] = [];
		this._viewCells.forEach(cell => {
			const cellMatches = cell.startFind(value);
			if (cellMatches) {
				matches.push(cellMatches);
			}
		});

		return matches;
	}

	replaceOne(cell: ICellViewModel, range: Range, text: string): Promise<void> {
		const viewCell = cell as CellViewModel;
		this._lastNotebookEditResource.push(viewCell.uri);
		return viewCell.resolveTextModel().then(() => {
			this.bulkEditService.apply({ edits: [{ edit: { range: range, text: text }, resource: cell.uri }] }, { quotableLabel: 'Notebook Replace' });
		});
	}

	async replaceAll(matches: CellFindMatch[], text: string): Promise<void> {
		if (!matches.length) {
			return;
		}

		let textEdits: WorkspaceTextEdit[] = [];
		this._lastNotebookEditResource.push(matches[0].cell.uri);

		matches.forEach(match => {
			match.matches.forEach(singleMatch => {
				textEdits.push({
					edit: { range: singleMatch.range, text: text },
					resource: match.cell.uri
				});
			});
		});

		return Promise.all(matches.map(match => {
			return match.cell.resolveTextModel();
		})).then(async () => {
			this.bulkEditService.apply({ edits: textEdits }, { quotableLabel: 'Notebook Replace All' });
			return;
		});
	}

	canUndo(): boolean {
R
rebornix 已提交
722
		return this.undoService.canUndo(this.uri);
R
rebornix 已提交
723 724 725
	}

	undo() {
R
rebornix 已提交
726 727
		this.undoService.undo(this.uri);
	}
R
rebornix 已提交
728

R
rebornix 已提交
729 730
	redo() {
		this.undoService.redo(this.uri);
R
rebornix 已提交
731 732
	}

R
rebornix 已提交
733 734 735 736 737 738 739 740
	equal(model: NotebookEditorModel) {
		return this._model === model;
	}

	dispose() {
		this._localStore.clear();
		this._viewCells.forEach(cell => {
			cell.save();
R
rebornix 已提交
741
			cell.dispose();
R
rebornix 已提交
742 743 744 745 746
		});

		super.dispose();
	}
}
R
rebornix 已提交
747 748 749

export type CellViewModel = CodeCellViewModel | MarkdownCellViewModel;

R
rebornix 已提交
750
export function createCellViewModel(instantiationService: IInstantiationService, notebookViewModel: NotebookViewModel, cell: NotebookCellTextModel) {
R
rebornix 已提交
751
	if (cell.cellKind === CellKind.Code) {
R
rebornix 已提交
752
		return instantiationService.createInstance(CodeCellViewModel, notebookViewModel.viewType, notebookViewModel.handle, cell, notebookViewModel.layoutInfo);
R
rebornix 已提交
753
	} else {
R
rebornix 已提交
754
		return instantiationService.createInstance(MarkdownCellViewModel, notebookViewModel.viewType, notebookViewModel.handle, cell, notebookViewModel.layoutInfo, notebookViewModel);
R
rebornix 已提交
755 756
	}
}