notebookCellList.ts 31.7 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 * as DOM from 'vs/base/browser/dom';
R
rebornix 已提交
7
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
R
rebornix 已提交
8
import { IListRenderer, IListVirtualDelegate, ListError } from 'vs/base/browser/ui/list/list';
9
import { IListStyles, IStyleController, MouseController, IListOptions } from 'vs/base/browser/ui/list/listWidget';
R
rebornix 已提交
10 11 12
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
R
rebornix 已提交
13
import { ScrollEvent } from 'vs/base/common/scrollable';
R
rebornix 已提交
14 15 16
import { Range } from 'vs/editor/common/core/range';
import { TrackedRangeStickiness } from 'vs/editor/common/model';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
R
rebornix 已提交
17 18 19 20 21
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/listService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
22
import { CellRevealPosition, CellRevealType, CursorAtBoundary, getVisibleCells, ICellRange, ICellViewModel, INotebookCellList, reduceCellRanges, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
R
rebornix 已提交
23
import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
24
import { diff, IOutput, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
R
rebornix 已提交
25

R
rebornix 已提交
26
export class NotebookCellList extends WorkbenchList<CellViewModel> implements IDisposable, IStyleController, INotebookCellList {
R
rebornix 已提交
27 28 29
	get onWillScroll(): Event<ScrollEvent> { return this.view.onWillScroll; }

	get rowsContainer(): HTMLElement {
R
rebornix 已提交
30
		return this.view.containerDomNode;
R
rebornix 已提交
31
	}
32
	private _previousFocusedElements: CellViewModel[] = [];
R
rebornix 已提交
33
	private _localDisposableStore = new DisposableStore();
R
rebornix 已提交
34
	private _viewModelStore = new DisposableStore();
35
	private styleElement?: HTMLStyleElement;
R
rebornix 已提交
36 37 38

	private readonly _onDidRemoveOutput = new Emitter<IOutput>();
	readonly onDidRemoveOutput: Event<IOutput> = this._onDidRemoveOutput.event;
39 40
	private readonly _onDidHideOutput = new Emitter<IOutput>();
	readonly onDidHideOutput: Event<IOutput> = this._onDidHideOutput.event;
R
rebornix 已提交
41 42

	private _viewModel: NotebookViewModel | null = null;
R
rebornix 已提交
43
	private _hiddenRangeIds: string[] = [];
R
rebornix 已提交
44
	private hiddenRangesPrefixSum: PrefixSumComputer | null = null;
R
rebornix 已提交
45

R
rebornix 已提交
46 47 48
	constructor(
		private listUser: string,
		container: HTMLElement,
R
rebornix 已提交
49 50
		delegate: IListVirtualDelegate<CellViewModel>,
		renderers: IListRenderer<CellViewModel, any>[],
R
rebornix 已提交
51
		contextKeyService: IContextKeyService,
R
rebornix 已提交
52
		options: IWorkbenchListOptions<CellViewModel>,
R
rebornix 已提交
53 54 55 56 57 58 59
		@IListService listService: IListService,
		@IThemeService themeService: IThemeService,
		@IConfigurationService configurationService: IConfigurationService,
		@IKeybindingService keybindingService: IKeybindingService

	) {
		super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService);
60 61 62
		this._previousFocusedElements = this.getFocusedElements();
		this._localDisposableStore.add(this.onDidChangeFocus((e) => {
			this._previousFocusedElements.forEach(element => {
R
rebornix 已提交
63 64 65 66
				if (e.elements.indexOf(element) < 0) {
					element.onDeselect();
				}
			});
67
			this._previousFocusedElements = e.elements;
68 69

			// Force focus out of webview if focus is in webview and I press an arrow key to focus the next cell
70 71 72
			if (document.activeElement && document.activeElement.tagName.toLowerCase() === 'webview') {
				this.focusView();
			}
R
rebornix 已提交
73
		}));
R
rebornix 已提交
74 75 76 77

		const notebookEditorCursorAtBoundaryContext = NOTEBOOK_EDITOR_CURSOR_BOUNDARY.bindTo(contextKeyService);
		notebookEditorCursorAtBoundaryContext.set('none');

78 79
		let cursorSelectionListener: IDisposable | null = null;
		let textEditorAttachListener: IDisposable | null = null;
R
rebornix 已提交
80

81
		const recomputeContext = (element: CellViewModel) => {
R
rebornix 已提交
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
			switch (element.cursorAtBoundary()) {
				case CursorAtBoundary.Both:
					notebookEditorCursorAtBoundaryContext.set('both');
					break;
				case CursorAtBoundary.Top:
					notebookEditorCursorAtBoundaryContext.set('top');
					break;
				case CursorAtBoundary.Bottom:
					notebookEditorCursorAtBoundaryContext.set('bottom');
					break;
				default:
					notebookEditorCursorAtBoundaryContext.set('none');
					break;
			}
			return;
		};

		// Cursor Boundary context
100
		this._localDisposableStore.add(this.onDidChangeFocus((e) => {
R
rebornix 已提交
101
			if (e.elements.length) {
102 103
				cursorSelectionListener?.dispose();
				textEditorAttachListener?.dispose();
R
rebornix 已提交
104 105 106
				// we only validate the first focused element
				const focusedElement = e.elements[0];

107 108 109 110
				cursorSelectionListener = focusedElement.onDidChangeState((e) => {
					if (e.selectionChanged) {
						recomputeContext(focusedElement);
					}
R
rebornix 已提交
111
				});
112 113 114 115 116 117 118

				textEditorAttachListener = focusedElement.onDidChangeEditorAttachState(() => {
					if (focusedElement.editorAttached) {
						recomputeContext(focusedElement);
					}
				});

R
rebornix 已提交
119 120 121 122 123 124 125 126
				recomputeContext(focusedElement);
				return;
			}

			// reset context
			notebookEditorCursorAtBoundaryContext.set('none');
		}));

R
rebornix 已提交
127 128
	}

129 130 131 132 133 134 135 136 137 138 139 140 141 142
	elementAt(position: number): ICellViewModel {
		return this.element(this.view.indexAt(position));
	}

	elementHeight(element: ICellViewModel): number {
		let index = this._getViewIndexUpperBound(element);
		if (index === undefined || index < 0 || index >= this.length) {
			this._getViewIndexUpperBound(element);
			throw new ListError(this.listUser, `Invalid index ${index}`);
		}

		return this.view.elementHeight(index);
	}

143
	protected createMouseController(_options: IListOptions<CellViewModel>): MouseController<CellViewModel> {
144 145 146
		return new NotebookMouseController(this);
	}

R
rebornix 已提交
147 148 149
	detachViewModel() {
		this._viewModelStore.clear();
		this._viewModel = null;
150
		this.hiddenRangesPrefixSum = null;
R
rebornix 已提交
151 152
	}

R
rebornix 已提交
153 154 155
	attachViewModel(model: NotebookViewModel) {
		this._viewModel = model;
		this._viewModelStore.add(model.onDidChangeViewCells((e) => {
R
rebornix 已提交
156 157 158 159 160 161 162 163 164 165 166 167 168 169
			const currentRanges = this._hiddenRangeIds.map(id => this._viewModel!.getTrackedRange(id)).filter(range => range !== null) as ICellRange[];
			const newVisibleViewCells: CellViewModel[] = getVisibleCells(this._viewModel!.viewCells as CellViewModel[], currentRanges);

			const oldVisibleViewCells: CellViewModel[] = [];
			const oldViewCellMapping = new Set<string>();
			for (let i = 0; i < this.length; i++) {
				oldVisibleViewCells.push(this.element(i));
				oldViewCellMapping.add(this.element(i).uri.toString());
			}

			const viewDiffs = diff<CellViewModel>(oldVisibleViewCells, newVisibleViewCells, a => {
				return oldViewCellMapping.has(a.uri.toString());
			});

R
rebornix 已提交
170
			if (e.synchronous) {
R
rebornix 已提交
171
				viewDiffs.reverse().forEach((diff) => {
R
rebornix 已提交
172
					// remove output in the webview
173 174 175
					const hideOutputs: IOutput[] = [];
					const deletedOutputs: IOutput[] = [];

R
rebornix 已提交
176
					for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
R
rebornix 已提交
177
						const cell = this.element(i);
178
						if (this._viewModel!.hasCell(cell.handle)) {
179 180 181 182
							hideOutputs.push(...cell?.model.outputs);
						} else {
							deletedOutputs.push(...cell?.model.outputs);
						}
R
rebornix 已提交
183 184
					}

R
rebornix 已提交
185
					this.splice2(diff.start, diff.deleteCount, diff.toInsert);
186 187 188

					hideOutputs.forEach(output => this._onDidHideOutput.fire(output));
					deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output));
R
rebornix 已提交
189 190 191
				});
			} else {
				DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
192
					viewDiffs.reverse().forEach((diff) => {
193 194 195
						const hideOutputs: IOutput[] = [];
						const deletedOutputs: IOutput[] = [];

R
rebornix 已提交
196
						for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
R
rebornix 已提交
197
							const cell = this.element(i);
198
							if (this._viewModel!.hasCell(cell.handle)) {
199 200 201 202
								hideOutputs.push(...cell?.model.outputs);
							} else {
								deletedOutputs.push(...cell?.model.outputs);
							}
R
rebornix 已提交
203 204
						}

R
rebornix 已提交
205
						this.splice2(diff.start, diff.deleteCount, diff.toInsert);
206 207 208

						hideOutputs.forEach(output => this._onDidHideOutput.fire(output));
						deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output));
R
rebornix 已提交
209 210 211 212 213
					});
				});
			}
		}));

R
rebornix 已提交
214 215 216 217 218 219 220 221
		this._viewModelStore.add(model.onDidChangeSelection(() => {
			// convert model selections to view selections
			const viewSelections = model.selectionHandles.map(handle => {
				return model.getCellByHandle(handle);
			}).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!));
			this.setFocus(viewSelections);
		}));

R
rebornix 已提交
222 223 224 225 226 227 228 229 230
		const hiddenRanges = model.getHiddenRanges();
		this.setHiddenAreas(hiddenRanges, false);
		const newRanges = reduceCellRanges(hiddenRanges);
		const viewCells = model.viewCells.slice(0) as CellViewModel[];
		newRanges.reverse().forEach(range => {
			viewCells.splice(range.start, range.end - range.start + 1);
		});

		this.splice2(0, 0, viewCells);
R
rebornix 已提交
231 232
	}

R
rebornix 已提交
233
	clear() {
R
rebornix 已提交
234 235 236
		super.splice(0, this.length);
	}

R
rebornix 已提交
237 238 239 240 241
	setHiddenAreas(_ranges: ICellRange[], triggerViewUpdate: boolean): boolean {
		if (!this._viewModel) {
			return false;
		}

R
rebornix 已提交
242 243 244 245 246 247
		const newRanges = reduceCellRanges(_ranges);
		// delete old tracking ranges
		const oldRanges = this._hiddenRangeIds.map(id => this._viewModel!.getTrackedRange(id)).filter(range => range !== null) as ICellRange[];
		if (newRanges.length === oldRanges.length) {
			let hasDifference = false;
			for (let i = 0; i < newRanges.length; i++) {
R
rebornix 已提交
248
				if (!(newRanges[i].start === oldRanges[i].start && newRanges[i].end === oldRanges[i].end)) {
R
rebornix 已提交
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
					hasDifference = true;
					break;
				}
			}

			if (!hasDifference) {
				return false;
			}
		}

		this._hiddenRangeIds.forEach(id => this._viewModel!.setTrackedRange(id, null, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter));
		const hiddenAreaIds = newRanges.map(range => this._viewModel!.setTrackedRange(null, range, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter)).filter(id => id !== null) as string[];

		this._hiddenRangeIds = hiddenAreaIds;

R
rebornix 已提交
264 265 266 267 268 269 270 271 272 273
		// set hidden ranges prefix sum
		let start = 0;
		let index = 0;
		let ret: number[] = [];

		while (index < newRanges.length) {
			for (let j = start; j < newRanges[index].start - 1; j++) {
				ret.push(1);
			}

R
rebornix 已提交
274 275
			ret.push(newRanges[index].end - newRanges[index].start + 1 + 1);
			start = newRanges[index].end + 1;
R
rebornix 已提交
276 277 278
			index++;
		}

R
rebornix 已提交
279
		for (let i = start; i < this._viewModel!.length; i++) {
R
rebornix 已提交
280 281 282 283 284 285 286 287 288
			ret.push(1);
		}

		const values = new Uint32Array(ret.length);
		for (let i = 0; i < ret.length; i++) {
			values[i] = ret[i];
		}

		this.hiddenRangesPrefixSum = new PrefixSumComputer(values);
R
rebornix 已提交
289 290 291 292

		if (triggerViewUpdate) {
			this.updateHiddenAreasInView(oldRanges, newRanges);
		}
R
rebornix 已提交
293

R
rebornix 已提交
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
		return true;
	}

	/**
	 * oldRanges and newRanges are all reduced and sorted.
	 */
	updateHiddenAreasInView(oldRanges: ICellRange[], newRanges: ICellRange[]) {
		const oldViewCellEntries: CellViewModel[] = getVisibleCells(this._viewModel!.viewCells as CellViewModel[], oldRanges);
		const oldViewCellMapping = new Set<string>();
		oldViewCellEntries.forEach(cell => {
			oldViewCellMapping.add(cell.uri.toString());
		});

		const newViewCellEntries: CellViewModel[] = getVisibleCells(this._viewModel!.viewCells as CellViewModel[], newRanges);

		const viewDiffs = diff<CellViewModel>(oldViewCellEntries, newViewCellEntries, a => {
			return oldViewCellMapping.has(a.uri.toString());
		});

		viewDiffs.reverse().forEach((diff) => {
			// remove output in the webview
315 316 317
			const hideOutputs: IOutput[] = [];
			const deletedOutputs: IOutput[] = [];

R
rebornix 已提交
318 319
			for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
				const cell = this.element(i);
320
				if (this._viewModel!.hasCell(cell.handle)) {
321 322 323 324
					hideOutputs.push(...cell?.model.outputs);
				} else {
					deletedOutputs.push(...cell?.model.outputs);
				}
R
rebornix 已提交
325 326 327
			}

			this.splice2(diff.start, diff.deleteCount, diff.toInsert);
328 329 330

			hideOutputs.forEach(output => this._onDidHideOutput.fire(output));
			deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output));
R
rebornix 已提交
331 332 333 334 335 336
		});
	}

	splice2(start: number, deleteCount: number, elements: CellViewModel[] = []): void {
		// we need to convert start and delete count based on hidden ranges
		super.splice(start, deleteCount, elements);
337 338 339 340 341 342 343 344 345 346 347 348

		const selectionsLeft = [];
		this._viewModel!.selectionHandles.forEach(handle => {
			if (this._viewModel!.hasCell(handle)) {
				selectionsLeft.push(handle);
			}
		});

		if (!selectionsLeft.length && this._viewModel!.viewCells) {
			// after splice, the selected cells are deleted
			this._viewModel!.selectionHandles = [this._viewModel!.viewCells[0].handle];
		}
R
rebornix 已提交
349 350
	}

351
	getViewIndex(cell: ICellViewModel) {
R
rebornix 已提交
352 353 354 355 356
		const modelIndex = this._viewModel!.getCellIndex(cell);
		if (!this.hiddenRangesPrefixSum) {
			return modelIndex;
		}

357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
		const viewIndexInfo = this.hiddenRangesPrefixSum.getIndexOf(modelIndex);

		if (viewIndexInfo.remainder !== 0) {
			return undefined;
		} else {
			return viewIndexInfo.index;
		}
	}


	private _getViewIndexUpperBound(cell: ICellViewModel) {
		const modelIndex = this._viewModel!.getCellIndex(cell);
		if (!this.hiddenRangesPrefixSum) {
			return modelIndex;
		}

		const viewIndexInfo = this.hiddenRangesPrefixSum.getIndexOf(modelIndex);

		return viewIndexInfo.index;
R
rebornix 已提交
376 377
	}

R
rebornix 已提交
378
	focusElement(cell: ICellViewModel) {
379
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
380 381 382

		if (index !== undefined) {
			this.setFocus([index]);
R
rebornix 已提交
383
		}
R
rebornix 已提交
384
	}
R
rebornix 已提交
385

R
rebornix 已提交
386
	selectElement(cell: ICellViewModel) {
R
rebornix 已提交
387 388
		if (this._viewModel) {
			this._viewModel.selectionHandles = [cell.handle];
R
rebornix 已提交
389
		}
R
rebornix 已提交
390 391 392 393 394 395

		const index = this._getViewIndexUpperBound(cell);
		if (index !== undefined) {
			this.setSelection([index]);
			this.setFocus([index]);
		}
R
rebornix 已提交
396 397
	}

R
rebornix 已提交
398
	setFocus(indexes: number[], browserEvent?: UIEvent): void {
399 400 401 402
		if (!indexes.length) {
			return;
		}

R
rebornix 已提交
403
		if (this._viewModel) {
R
rebornix 已提交
404
			this._viewModel.selectionHandles = indexes.map(index => this.element(index)).map(cell => cell.handle);
R
rebornix 已提交
405 406 407 408 409
		}

		super.setFocus(indexes, browserEvent);
	}

R
rebornix 已提交
410
	revealElementInView(cell: ICellViewModel) {
411
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
412 413 414 415 416 417 418

		if (index !== undefined) {
			this._revealInView(index);
		}
	}

	revealElementInCenterIfOutsideViewport(cell: ICellViewModel) {
419
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
420 421 422 423 424 425 426

		if (index !== undefined) {
			this._revealInCenterIfOutsideViewport(index);
		}
	}

	revealElementInCenter(cell: ICellViewModel) {
427
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
428 429 430 431

		if (index !== undefined) {
			this._revealInCenter(index);
		}
R
rebornix 已提交
432
	}
R
rebornix 已提交
433

R
rebornix 已提交
434
	revealElementLineInView(cell: ICellViewModel, line: number): void {
435
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
436 437 438 439 440 441 442

		if (index !== undefined) {
			this._revealLineInView(index, line);
		}
	}

	revealElementLineInCenter(cell: ICellViewModel, line: number) {
443
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
444 445 446 447 448 449 450

		if (index !== undefined) {
			this._revealLineInCenter(index, line);
		}
	}

	revealElementLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number) {
451
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
452 453 454 455 456 457 458

		if (index !== undefined) {
			this._revealLineInCenterIfOutsideViewport(index, line);
		}
	}

	revealElementRangeInView(cell: ICellViewModel, range: Range): void {
459
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
460 461 462 463 464 465 466

		if (index !== undefined) {
			this._revealRangeInView(index, range);
		}
	}

	revealElementRangeInCenter(cell: ICellViewModel, range: Range): void {
467
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
468 469 470 471 472 473 474

		if (index !== undefined) {
			this._revealRangeInCenter(index, range);
		}
	}

	revealElementRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void {
475
		const index = this._getViewIndexUpperBound(cell);
R
rebornix 已提交
476 477 478 479 480 481 482

		if (index !== undefined) {
			this._revealRangeInCenterIfOutsideViewport(index, range);
		}
	}

	domElementOfElement(element: ICellViewModel): HTMLElement | null {
483
		const index = this._getViewIndexUpperBound(element);
R
rebornix 已提交
484 485 486 487 488
		if (index !== undefined) {
			return this.view.domElement(index);
		}

		return null;
R
rebornix 已提交
489 490 491 492 493 494 495
	}

	focusView() {
		this.view.domNode.focus();
	}

	getAbsoluteTopOfElement(element: ICellViewModel): number {
496
		let index = this._getViewIndexUpperBound(element);
R
rebornix 已提交
497
		if (index === undefined || index < 0 || index >= this.length) {
498
			this._getViewIndexUpperBound(element);
499 500 501
			throw new ListError(this.listUser, `Invalid index ${index}`);
		}

R
rebornix 已提交
502
		return this.view.elementTop(index);
503 504
	}

R
rebornix 已提交
505 506 507 508
	triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
		this.view.triggerScrollFromMouseWheelEvent(browserEvent);
	}

R
rebornix 已提交
509 510

	updateElementHeight2(element: ICellViewModel, size: number): void {
511
		const index = this._getViewIndexUpperBound(element);
R
rebornix 已提交
512 513 514 515
		if (index === undefined) {
			return;
		}

516
		const focused = this.getFocus();
517
		this.view.updateElementHeight(index, size, focused.length ? focused[0] : null);
R
rebornix 已提交
518 519
	}

520 521 522 523 524 525 526
	// override
	domFocus() {
		if (document.activeElement && this.view.domNode.contains(document.activeElement)) {
			// for example, when focus goes into monaco editor, if we refocus the list view, the editor will lose focus.
			return;
		}

R
rebornix 已提交
527 528 529 530
		if (!isMacintosh && document.activeElement && isContextMenuFocused()) {
			return;
		}

531 532
		super.domFocus();
	}
R
rebornix 已提交
533

534 535
	private _revealRange(viewIndex: number, range: Range, revealType: CellRevealType, newlyCreated: boolean, alignToBottom: boolean) {
		const element = this.view.element(viewIndex);
R
rebornix 已提交
536 537 538 539
		const scrollTop = this.view.getScrollTop();
		const wrapperBottom = scrollTop + this.view.renderHeight;
		const startLineNumber = range.startLineNumber;
		const lineOffset = element.getLineScrollTopOffset(startLineNumber);
540
		const elementTop = this.view.elementTop(viewIndex);
541
		const lineTop = elementTop + lineOffset;
R
rebornix 已提交
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559

		// TODO@rebornix 30 ---> line height * 1.5
		if (lineTop < scrollTop) {
			this.view.setScrollTop(lineTop - 30);
		} else if (lineTop > wrapperBottom) {
			this.view.setScrollTop(scrollTop + lineTop - wrapperBottom + 30);
		} else if (newlyCreated) {
			// newly scrolled into view
			if (alignToBottom) {
				// align to the bottom
				this.view.setScrollTop(scrollTop + lineTop - wrapperBottom + 30);
			} else {
				// align to to top
				this.view.setScrollTop(lineTop - 30);
			}
		}

		if (revealType === CellRevealType.Range) {
560
			element.revealRangeInCenter(range);
R
rebornix 已提交
561 562 563
		}
	}

R
rebornix 已提交
564 565 566
	// List items have real dynamic heights, which means after we set `scrollTop` based on the `elementTop(index)`, the element at `index` might still be removed from the view once all relayouting tasks are done.
	// For example, we scroll item 10 into the view upwards, in the first round, items 7, 8, 9, 10 are all in the viewport. Then item 7 and 8 resize themselves to be larger and finally item 10 is removed from the view.
	// To ensure that item 10 is always there, we need to scroll item 10 to the top edge of the viewport.
567
	private _revealRangeInternal(viewIndex: number, range: Range, revealType: CellRevealType) {
R
rebornix 已提交
568 569
		const scrollTop = this.view.getScrollTop();
		const wrapperBottom = scrollTop + this.view.renderHeight;
570 571
		const elementTop = this.view.elementTop(viewIndex);
		const element = this.view.element(viewIndex);
R
rebornix 已提交
572 573

		if (element.editorAttached) {
574
			this._revealRange(viewIndex, range, revealType, false, false);
R
rebornix 已提交
575
		} else {
576
			const elementHeight = this.view.elementHeight(viewIndex);
R
rebornix 已提交
577 578 579 580 581 582 583 584 585 586 587 588 589
			let upwards = false;

			if (elementTop + elementHeight < scrollTop) {
				// scroll downwards
				this.view.setScrollTop(elementTop);
				upwards = false;
			} else if (elementTop > wrapperBottom) {
				// scroll upwards
				this.view.setScrollTop(elementTop - this.view.renderHeight / 2);
				upwards = true;
			}

			const editorAttachedPromise = new Promise((resolve, reject) => {
590 591 592
				element.onDidChangeEditorAttachState(() => {
					element.editorAttached ? resolve() : reject();
				});
R
rebornix 已提交
593 594 595
			});

			editorAttachedPromise.then(() => {
596
				this._revealRange(viewIndex, range, revealType, true, upwards);
R
rebornix 已提交
597 598 599 600
			});
		}
	}

601 602
	private _revealLineInView(viewIndex: number, line: number) {
		this._revealRangeInternal(viewIndex, new Range(line, 1, line, 1), CellRevealType.Line);
R
rebornix 已提交
603
	}
R
rebornix 已提交
604

605 606
	private _revealRangeInView(viewIndex: number, range: Range): void {
		this._revealRangeInternal(viewIndex, range, CellRevealType.Range);
R
rebornix 已提交
607
	}
R
rebornix 已提交
608

609 610 611
	private _revealRangeInCenterInternal(viewIndex: number, range: Range, revealType: CellRevealType) {
		const reveal = (viewIndex: number, range: Range, revealType: CellRevealType) => {
			const element = this.view.element(viewIndex);
R
rebornix 已提交
612
			let lineOffset = element.getLineScrollTopOffset(range.startLineNumber);
613
			let lineOffsetInView = this.view.elementTop(viewIndex) + lineOffset;
R
rebornix 已提交
614
			this.view.setScrollTop(lineOffsetInView - this.view.renderHeight / 2);
R
rebornix 已提交
615 616

			if (revealType === CellRevealType.Range) {
617
				element.revealRangeInCenter(range);
R
rebornix 已提交
618
			}
R
rebornix 已提交
619 620
		};

621
		const elementTop = this.view.elementTop(viewIndex);
R
rebornix 已提交
622 623
		const viewItemOffset = elementTop;
		this.view.setScrollTop(viewItemOffset - this.view.renderHeight / 2);
624
		const element = this.view.element(viewIndex);
R
rebornix 已提交
625

R
rebornix 已提交
626
		if (!element.editorAttached) {
627
			getEditorAttachedPromise(element).then(() => reveal(viewIndex, range, revealType));
R
rebornix 已提交
628
		} else {
629
			reveal(viewIndex, range, revealType);
R
rebornix 已提交
630 631 632
		}
	}

633 634
	private _revealLineInCenter(viewIndex: number, line: number) {
		this._revealRangeInCenterInternal(viewIndex, new Range(line, 1, line, 1), CellRevealType.Line);
R
rebornix 已提交
635 636
	}

637 638
	private _revealRangeInCenter(viewIndex: number, range: Range): void {
		this._revealRangeInCenterInternal(viewIndex, range, CellRevealType.Range);
R
rebornix 已提交
639 640
	}

641 642 643
	private _revealRangeInCenterIfOutsideViewportInternal(viewIndex: number, range: Range, revealType: CellRevealType) {
		const reveal = (viewIndex: number, range: Range, revealType: CellRevealType) => {
			const element = this.view.element(viewIndex);
R
rebornix 已提交
644
			let lineOffset = element.getLineScrollTopOffset(range.startLineNumber);
645
			let lineOffsetInView = this.view.elementTop(viewIndex) + lineOffset;
R
rebornix 已提交
646 647 648
			this.view.setScrollTop(lineOffsetInView - this.view.renderHeight / 2);

			if (revealType === CellRevealType.Range) {
649
				element.revealRangeInCenter(range);
R
rebornix 已提交
650 651 652
			}
		};

R
rebornix 已提交
653 654
		const scrollTop = this.view.getScrollTop();
		const wrapperBottom = scrollTop + this.view.renderHeight;
655
		const elementTop = this.view.elementTop(viewIndex);
R
rebornix 已提交
656
		const viewItemOffset = elementTop;
657
		const element = this.view.element(viewIndex);
R
rebornix 已提交
658 659

		if (viewItemOffset < scrollTop || viewItemOffset > wrapperBottom) {
R
rebornix 已提交
660
			// let it render
R
rebornix 已提交
661
			this.view.setScrollTop(viewItemOffset - this.view.renderHeight / 2);
R
rebornix 已提交
662 663

			// after rendering, it might be pushed down due to markdown cell dynamic height
664
			const elementTop = this.view.elementTop(viewIndex);
R
rebornix 已提交
665 666 667
			this.view.setScrollTop(elementTop - this.view.renderHeight / 2);

			// reveal editor
R
rebornix 已提交
668
			if (!element.editorAttached) {
669
				getEditorAttachedPromise(element).then(() => reveal(viewIndex, range, revealType));
R
rebornix 已提交
670
			} else {
R
rebornix 已提交
671
				// for example markdown
R
rebornix 已提交
672
			}
673
		} else {
R
rebornix 已提交
674 675 676 677
			if (element.editorAttached) {
				element.revealRangeInCenter(range);
			} else {
				// for example, markdown cell in preview mode
678
				getEditorAttachedPromise(element).then(() => reveal(viewIndex, range, revealType));
R
rebornix 已提交
679
			}
R
rebornix 已提交
680 681 682
		}
	}

683 684
	private _revealLineInCenterIfOutsideViewport(viewIndex: number, line: number) {
		this._revealRangeInCenterIfOutsideViewportInternal(viewIndex, new Range(line, 1, line, 1), CellRevealType.Line);
R
rebornix 已提交
685
	}
R
rebornix 已提交
686

687 688
	private _revealRangeInCenterIfOutsideViewport(viewIndex: number, range: Range): void {
		this._revealRangeInCenterIfOutsideViewportInternal(viewIndex, range, CellRevealType.Range);
R
rebornix 已提交
689 690
	}

691 692
	private _revealInternal(viewIndex: number, ignoreIfInsideViewport: boolean, revealPosition: CellRevealPosition) {
		if (viewIndex >= this.view.length) {
693 694 695
			return;
		}

R
rebornix 已提交
696 697
		const scrollTop = this.view.getScrollTop();
		const wrapperBottom = scrollTop + this.view.renderHeight;
698
		const elementTop = this.view.elementTop(viewIndex);
R
rebornix 已提交
699

700
		if (ignoreIfInsideViewport && elementTop >= scrollTop && elementTop < wrapperBottom) {
R
rebornix 已提交
701 702 703 704
			// inside the viewport
			return;
		}

R
rebornix 已提交
705 706
		// first render
		const viewItemOffset = revealPosition === CellRevealPosition.Top ? elementTop : (elementTop - this.view.renderHeight / 2);
R
rebornix 已提交
707
		this.view.setScrollTop(viewItemOffset);
R
rebornix 已提交
708 709

		// second scroll as markdown cell is dynamic
710
		const newElementTop = this.view.elementTop(viewIndex);
R
rebornix 已提交
711 712
		const newViewItemOffset = revealPosition === CellRevealPosition.Top ? newElementTop : (newElementTop - this.view.renderHeight / 2);
		this.view.setScrollTop(newViewItemOffset);
R
rebornix 已提交
713 714
	}

715 716
	private _revealInView(viewIndex: number) {
		this._revealInternal(viewIndex, true, CellRevealPosition.Top);
R
rebornix 已提交
717 718
	}

719 720
	private _revealInCenter(viewIndex: number) {
		this._revealInternal(viewIndex, false, CellRevealPosition.Center);
R
rebornix 已提交
721
	}
R
rebornix 已提交
722

723 724
	private _revealInCenterIfOutsideViewport(viewIndex: number) {
		this._revealInternal(viewIndex, true, CellRevealPosition.Center);
R
rebornix 已提交
725
	}
R
rebornix 已提交
726

R
rebornix 已提交
727 728
	setCellSelection(cell: ICellViewModel, range: Range) {
		const element = cell as CellViewModel;
R
rebornix 已提交
729 730 731 732 733
		if (element.editorAttached) {
			element.setSelection(range);
		} else {
			getEditorAttachedPromise(element).then(() => { element.setSelection(range); });
		}
R
rebornix 已提交
734 735
	}

736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855

	style(styles: IListStyles) {
		const selectorSuffix = this.view.domId;
		if (!this.styleElement) {
			this.styleElement = DOM.createStyleSheet(this.view.domNode);
		}
		const suffix = selectorSuffix && `.${selectorSuffix}`;
		const content: string[] = [];

		if (styles.listBackground) {
			if (styles.listBackground.isOpaque()) {
				content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows { background: ${styles.listBackground}; }`);
			} else if (!isMacintosh) { // subpixel AA doesn't exist in macOS
				console.warn(`List with id '${selectorSuffix}' was styled with a non-opaque background color. This will break sub-pixel antialiasing.`);
			}
		}

		if (styles.listFocusBackground) {
			content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`);
			content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case!
		}

		if (styles.listFocusForeground) {
			content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`);
		}

		if (styles.listActiveSelectionBackground) {
			content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { background-color: ${styles.listActiveSelectionBackground}; }`);
			content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected:hover { background-color: ${styles.listActiveSelectionBackground}; }`); // overwrite :hover style in this case!
		}

		if (styles.listActiveSelectionForeground) {
			content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { color: ${styles.listActiveSelectionForeground}; }`);
		}

		if (styles.listFocusAndSelectionBackground) {
			content.push(`
				.monaco-drag-image,
				.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; }
			`);
		}

		if (styles.listFocusAndSelectionForeground) {
			content.push(`
				.monaco-drag-image,
				.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; }
			`);
		}

		if (styles.listInactiveFocusBackground) {
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { background-color:  ${styles.listInactiveFocusBackground}; }`);
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused:hover { background-color:  ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case!
		}

		if (styles.listInactiveSelectionBackground) {
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { background-color:  ${styles.listInactiveSelectionBackground}; }`);
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected:hover { background-color:  ${styles.listInactiveSelectionBackground}; }`); // overwrite :hover style in this case!
		}

		if (styles.listInactiveSelectionForeground) {
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { color: ${styles.listInactiveSelectionForeground}; }`);
		}

		if (styles.listHoverBackground) {
			content.push(`.monaco-list${suffix}:not(.drop-target) > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover:not(.selected):not(.focused) { background-color:  ${styles.listHoverBackground}; }`);
		}

		if (styles.listHoverForeground) {
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover:not(.selected):not(.focused) { color:  ${styles.listHoverForeground}; }`);
		}

		if (styles.listSelectionOutline) {
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`);
		}

		if (styles.listFocusOutline) {
			content.push(`
				.monaco-drag-image,
				.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }
			`);
		}

		if (styles.listInactiveFocusOutline) {
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: 1px dotted ${styles.listInactiveFocusOutline}; outline-offset: -1px; }`);
		}

		if (styles.listHoverOutline) {
			content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`);
		}

		if (styles.listDropBackground) {
			content.push(`
				.monaco-list${suffix}.drop-target,
				.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows.drop-target,
				.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; }
			`);
		}

		if (styles.listFilterWidgetBackground) {
			content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
		}

		if (styles.listFilterWidgetOutline) {
			content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
		}

		if (styles.listFilterWidgetNoMatchesOutline) {
			content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
		}

		if (styles.listMatchesShadow) {
			content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
		}

		const newStyles = content.join('\n');
		if (newStyles !== this.styleElement.innerHTML) {
			this.styleElement.innerHTML = newStyles;
		}
	}

R
rebornix 已提交
856 857 858 859
	dispose() {
		this._localDisposableStore.dispose();
		super.dispose();
	}
R
rebornix 已提交
860
}
R
rebornix 已提交
861

R
rebornix 已提交
862 863
function getEditorAttachedPromise(element: CellViewModel) {
	return new Promise((resolve, reject) => {
864
		Event.once(element.onDidChangeEditorAttachState)(() => element.editorAttached ? resolve() : reject());
R
rebornix 已提交
865 866 867
	});
}

R
rebornix 已提交
868 869 870
function isContextMenuFocused() {
	return !!DOM.findParentWithClass(<HTMLElement>document.activeElement, 'context-view');
}
871 872 873 874 875 876 877 878 879 880


class NotebookMouseController extends MouseController<CellViewModel> {
	protected onDoubleClick(): void {
		const focus = this.list.getFocusedElements()[0];
		if (focus && focus.cellKind === CellKind.Markdown) {
			focus.editState = CellEditState.Editing;
		}
	}
}