notebookEditor.ts 17.0 KB
Newer Older
P
Peng Lyu 已提交
1 2 3 4 5 6 7 8 9 10 11
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./notebook';
import * as DOM from 'vs/base/browser/dom';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IStorageService } from 'vs/platform/storage/common/storage';
P
Peng Lyu 已提交
12
import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
R
rebornix 已提交
13
import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor';
P
Peng Lyu 已提交
14 15 16 17
import { CancellationToken } from 'vs/base/common/cancellation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModelService } from 'vs/editor/common/services/modelService';
import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils';
P
Peng Lyu 已提交
18 19
import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground, contrastBorder, textBlockQuoteBackground, textBlockQuoteBorder, editorBackground, foreground } from 'vs/platform/theme/common/colorRegistry';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
P
Peng Lyu 已提交
20
import { IModeService } from 'vs/editor/common/services/modeService';
21
import { NotebookHandler, ViewCell, MarkdownCellRenderer, CodeCellRenderer, NotebookCellListDelegate, CELL_MARGIN } from 'vs/workbench/contrib/notebook/browser/cellRenderer';
R
rebornix 已提交
22
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
P
Peng Lyu 已提交
23
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
P
Peng Lyu 已提交
24
import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/contentWidget';
P
Peng Lyu 已提交
25
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
26
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
R
rebornix 已提交
27
import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
28 29 30 31
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { getZoomLevel } from 'vs/base/browser/browser';
R
rebornix 已提交
32
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
P
Peng Lyu 已提交
33 34

const $ = DOM.$;
R
rebornix 已提交
35 36 37 38 39
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';

interface INotebookEditorViewState {
	editingCells: { [key: number]: boolean };
}
P
Peng Lyu 已提交
40

P
Peng Lyu 已提交
41
export class NotebookEditor extends BaseEditor implements NotebookHandler {
P
Peng Lyu 已提交
42 43 44
	static readonly ID: string = 'workbench.editor.notebook';
	private rootElement!: HTMLElement;
	private body!: HTMLElement;
P
Peng Lyu 已提交
45
	private contentWidgets!: HTMLElement;
P
Peng Lyu 已提交
46
	private webview: BackLayerWebView | null = null;
P
Peng Lyu 已提交
47

P
Peng Lyu 已提交
48
	private list: WorkbenchList<ViewCell> | undefined;
P
Peng Lyu 已提交
49
	private model: NotebookEditorModel | undefined;
P
Peng Lyu 已提交
50
	private viewCells: ViewCell[] = [];
51
	private localStore: DisposableStore = new DisposableStore();
R
rebornix 已提交
52
	private editorMemento: IEditorMemento<INotebookEditorViewState>;
53
	private fontInfo: BareFontInfo | undefined;
54 55
	private relayoutDisposable: IDisposable | null = null;
	private dimension: DOM.Dimension | null = null;
P
Peng Lyu 已提交
56 57 58 59 60

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IThemeService themeService: IThemeService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
P
Peng Lyu 已提交
61 62
		@IModelService private readonly modelService: IModelService,
		@IModeService private readonly modeService: IModeService,
P
Peng Lyu 已提交
63
		@IStorageService storageService: IStorageService,
R
rebornix 已提交
64
		@IWebviewService private webviewService: IWebviewService,
R
rebornix 已提交
65
		@INotebookService private notebookService: INotebookService,
66
		@IEditorGroupsService editorGroupService: IEditorGroupsService,
R
rebornix 已提交
67 68
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IEnvironmentService private readonly environmentSerice: IEnvironmentService
P
Peng Lyu 已提交
69 70
	) {
		super(NotebookEditor.ID, telemetryService, themeService, storageService);
R
rebornix 已提交
71 72

		this.editorMemento = this.getEditorMemento<INotebookEditorViewState>(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
P
Peng Lyu 已提交
73
	}
P
Peng Lyu 已提交
74

P
Peng Lyu 已提交
75 76 77 78 79 80 81 82 83 84 85
	get minimumWidth(): number { return 375; }
	get maximumWidth(): number { return Number.POSITIVE_INFINITY; }

	// these setters need to exist because this extends from BaseEditor
	set minimumWidth(value: number) { /*noop*/ }
	set maximumWidth(value: number) { /*noop*/ }


	protected createEditor(parent: HTMLElement): void {
		this.rootElement = DOM.append(parent, $('.notebook-editor'));
		this.createBody(this.rootElement);
86 87 88 89 90 91
		this.generateFontInfo();
	}

	private generateFontInfo(): void {
		const editorOptions = this.configurationService.getValue<IEditorOptions>('editor');
		this.fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel());
P
Peng Lyu 已提交
92 93 94
	}

	private createBody(parent: HTMLElement): void {
P
Peng Lyu 已提交
95
		this.body = document.createElement('div');
P
Peng Lyu 已提交
96 97 98
		DOM.addClass(this.body, 'cell-list-container');
		this.createCellList();
		DOM.append(parent, this.body);
P
Peng Lyu 已提交
99 100 101 102

		this.contentWidgets = document.createElement('div');
		DOM.addClass(this.contentWidgets, 'notebook-content-widgets');
		DOM.append(this.body, this.contentWidgets);
P
Peng Lyu 已提交
103 104
	}

P
Peng Lyu 已提交
105 106
	private createCellList(): void {
		DOM.addClass(this.body, 'cell-list-container');
P
Peng Lyu 已提交
107

P
Peng Lyu 已提交
108
		const renders = [
P
Peng Lyu 已提交
109 110
			this.instantiationService.createInstance(MarkdownCellRenderer, this),
			this.instantiationService.createInstance(CodeCellRenderer, this)
P
Peng Lyu 已提交
111 112
		];

P
Peng Lyu 已提交
113
		this.list = this.instantiationService.createInstance<typeof WorkbenchList, WorkbenchList<ViewCell>>(
P
Peng Lyu 已提交
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
			WorkbenchList,
			'NotebookCellList',
			this.body,
			this.instantiationService.createInstance(NotebookCellListDelegate),
			renders,
			{
				setRowLineHeight: false,
				supportDynamicHeights: true,
				horizontalScrolling: false,
				keyboardSupport: false,
				mouseSupport: false,
				multipleSelectionSupport: false,
				overrideStyles: {
					listBackground: editorBackground,
					listActiveSelectionBackground: editorBackground,
					listActiveSelectionForeground: foreground,
					listFocusAndSelectionBackground: editorBackground,
					listFocusAndSelectionForeground: foreground,
					listFocusBackground: editorBackground,
					listFocusForeground: foreground,
					listHoverForeground: foreground,
					listHoverBackground: editorBackground,
136 137
					listHoverOutline: focusBorder,
					listFocusOutline: focusBorder,
138 139 140 141
					listInactiveSelectionBackground: editorBackground,
					listInactiveSelectionForeground: foreground,
					listInactiveFocusBackground: editorBackground,
					listInactiveFocusOutline: editorBackground,
P
Peng Lyu 已提交
142 143 144
				}
			}
		);
P
Peng Lyu 已提交
145

R
rebornix 已提交
146
		this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this, this.environmentSerice);
P
Peng Lyu 已提交
147
		this.list.view.rowsContainer.appendChild(this.webview.element);
P
Peng Lyu 已提交
148 149 150
		this._register(this.list);
	}

151 152 153 154
	getFontInfo(): BareFontInfo | undefined {
		return this.fontInfo;
	}

155 156 157 158
	getListDimension(): DOM.Dimension | null {
		return this.dimension;
	}

P
Peng Lyu 已提交
159 160
	triggerWheel(event: IMouseWheelEvent) {
		this.list?.triggerScrollFromMouseWheelEvent(event);
P
Peng Lyu 已提交
161 162
	}

R
rebornix 已提交
163
	createContentWidget(cell: ViewCell, outputIndex: number, shadowContent: string, offset: number) {
164 165 166 167
		if (!this.webview) {
			return;
		}

P
Peng Lyu 已提交
168 169 170
		if (!this.webview!.mapping.has(cell.id)) {
			let index = this.model!.getNotebook().cells.indexOf(cell.cell);
			let top = this.list?.getElementTop(index) || 0;
171
			this.webview!.createContentWidget(cell, offset, shadowContent, top + offset);
R
rebornix 已提交
172 173 174 175 176 177
			this.webview!.outputMapping.set(cell.id + `-${outputIndex}`, true);
		} else if (!this.webview!.outputMapping.has(cell.id + `-${outputIndex}`)) {
			let index = this.model!.getNotebook().cells.indexOf(cell.cell);
			let top = this.list?.getElementTop(index) || 0;
			this.webview!.outputMapping.set(cell.id + `-${outputIndex}`, true);
			this.webview!.createContentWidget(cell, offset, shadowContent, top + offset);
P
Peng Lyu 已提交
178
		} else {
P
Peng Lyu 已提交
179 180 181 182
			let index = this.model!.getNotebook().cells.indexOf(cell.cell);
			let top = this.list?.getElementTop(index) || 0;
			let scrollTop = this.list?.scrollTop || 0;

183
			this.webview!.updateViewScrollTop(-scrollTop, [{ id: cell.id, top: top + offset }]);
P
Peng Lyu 已提交
184
		}
P
Peng Lyu 已提交
185
	}
P
Peng Lyu 已提交
186

P
Peng Lyu 已提交
187 188 189
	disposeViewCell(cell: ViewCell) {
	}

P
Peng Lyu 已提交
190
	onHide() {
R
rebornix 已提交
191 192 193 194 195
		this.viewCells.forEach(cell => {
			if (cell.getText() !== '') {
				cell.isEditing = false;
			}
		});
196 197 198 199 200 201 202 203 204 205

		if (this.webview) {
			this.localStore.clear();
			this.list?.view.rowsContainer.removeChild(this.webview?.element);
			this.webview?.dispose();
			this.webview = null;
		}

		this.list?.splice(0, this.list?.length);
		super.onHide();
P
Peng Lyu 已提交
206 207
	}

208
	setVisible(visible: boolean, group?: IEditorGroup): void {
R
rebornix 已提交
209
		super.setVisible(visible, group);
210
		if (!visible) {
R
rebornix 已提交
211 212 213 214 215
			this.viewCells.forEach(cell => {
				if (cell.getText() !== '') {
					cell.isEditing = false;
				}
			});
216 217 218
		}
	}

P
Peng Lyu 已提交
219
	setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
R
rebornix 已提交
220 221 222 223
		if (this.input instanceof NotebookEditorInput) {
			this.saveTextEditorViewState(this.input);
		}

224 225 226 227 228
		return super.setInput(input, options, token)
			.then(() => {
				return input.resolve();
			})
			.then(model => {
229
				if (this.model !== undefined && this.model.textModel === model.textModel && this.webview !== null) {
P
Peng Lyu 已提交
230 231 232
					return;
				}

233
				this.localStore.clear();
P
Peng Lyu 已提交
234 235 236 237
				this.viewCells.forEach(cell => {
					cell.save();
				});

238 239 240
				if (this.webview) {
					this.webview?.clearContentWidgets();
				} else {
R
rebornix 已提交
241
					this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this, this.environmentSerice);
242 243 244
					this.list?.view.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element);
				}

P
Peng Lyu 已提交
245
				this.model = model;
R
rebornix 已提交
246 247 248 249
				this.localStore.add(this.model.onDidChangeCells(() => {
					this.updateViewCells();
				}));

R
rebornix 已提交
250
				let viewState = this.loadTextEditorViewState(input);
P
Peng Lyu 已提交
251
				this.viewCells = model.getNotebook().cells.map(cell => {
R
rebornix 已提交
252 253
					const isEditing = viewState && viewState.editingCells[cell.handle];
					return new ViewCell(cell, !!isEditing, this.modelService, this.modeService);
P
Peng Lyu 已提交
254
				});
255 256 257 258 259 260

				const updateScrollPosition = () => {
					let scrollTop = this.list?.scrollTop || 0;
					this.webview!.element.style.top = `${scrollTop}px`;
					let updateItems: { top: number, id: string }[] = [];

R
rebornix 已提交
261
					// const date = new Date();
262 263 264 265 266 267 268 269 270 271 272 273 274 275
					this.webview?.mapping.forEach((item) => {
						let index = this.model!.getNotebook().cells.indexOf(item.cell.cell);
						let top = this.list?.getElementTop(index) || 0;
						let newTop = this.webview!.shouldRenderContentWidget(item.cell.id, top);

						if (newTop !== undefined) {
							updateItems.push({
								top: newTop,
								id: item.cell.id
							});
						}
					});

					if (updateItems.length > 0) {
R
rebornix 已提交
276
						// console.log('----- did scroll ----  ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
277 278 279 280
						this.webview?.updateViewScrollTop(-scrollTop, updateItems);
					}
				};
				this.localStore.add(this.list!.onWillScroll(e => {
R
rebornix 已提交
281 282
					// const date = new Date();
					// console.log('----- will scroll ----  ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
283 284 285 286 287
					this.webview?.updateViewScrollTop(-e.scrollTop, []);
				}));
				this.localStore.add(this.list!.onDidScroll(() => updateScrollPosition()));
				this.localStore.add(this.list!.onDidChangeContentHeight(() => updateScrollPosition()));

288 289
				this.list?.splice(0, this.list?.length);
				this.list?.splice(0, 0, this.viewCells);
290 291
				this.list?.layout();
			});
P
Peng Lyu 已提交
292 293
	}

P
Peng Lyu 已提交
294
	layoutElement(cell: ViewCell, height: number) {
R
rebornix 已提交
295
		let relayout = (cell: ViewCell, height: number) => {
P
Peng Lyu 已提交
296
			let index = this.model!.getNotebook().cells.indexOf(cell.cell);
R
rebornix 已提交
297 298 299
			if (index >= 0) {
				this.list?.updateDynamicHeight(index, cell, height);
			}
R
rebornix 已提交
300 301 302
		};

		if (this.list?.view.isRendering) {
303 304 305 306 307
			if (this.relayoutDisposable) {
				this.relayoutDisposable.dispose();
				this.relayoutDisposable = null;
			}
			this.relayoutDisposable = DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
308
				relayout(cell, height);
309
				this.relayoutDisposable = null;
R
rebornix 已提交
310 311 312 313
			});
		} else {
			relayout(cell, height);
		}
314 315
	}

R
rebornix 已提交
316
	updateViewCells() {
R
rebornix 已提交
317
		if (this.list?.view.isRendering) {
318 319 320 321 322 323
			if (this.relayoutDisposable) {
				this.relayoutDisposable.dispose();
				this.relayoutDisposable = null;
			}

			this.relayoutDisposable = DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
324
				this.list?.rerender();
325
				this.relayoutDisposable = null;
R
rebornix 已提交
326 327
			});
		} else {
R
rebornix 已提交
328
			this.list?.rerender();
R
rebornix 已提交
329
		}
R
rebornix 已提交
330 331
	}

332
	insertEmptyNotebookCell(listIndex: number | undefined, cell: ViewCell, type: 'code' | 'markdown', direction: 'above' | 'below') {
P
Peng Lyu 已提交
333
		let newCell = new ViewCell({
R
rebornix 已提交
334
			handle: -1,
P
Peng Lyu 已提交
335
			cell_type: type,
P
Peng Lyu 已提交
336
			source: [],
P
Peng Lyu 已提交
337
			outputs: []
P
Peng Lyu 已提交
338
		}, false, this.modelService, this.modeService);
P
Peng Lyu 已提交
339

340
		let index = listIndex ? listIndex : this.model!.getNotebook().cells.indexOf(cell.cell);
P
Peng Lyu 已提交
341 342
		const insertIndex = direction === 'above' ? index : index + 1;

P
Peng Lyu 已提交
343 344
		this.viewCells!.splice(insertIndex, 0, newCell);
		this.model!.insertCell(newCell.cell, insertIndex);
P
Peng Lyu 已提交
345
		this.list?.splice(insertIndex, 0, [newCell]);
P
Peng Lyu 已提交
346 347 348 349 350 351

		if (type === 'markdown') {
			newCell.isEditing = true;
		}
	}

352
	editNotebookCell(listIndex: number | undefined, cell: ViewCell): void {
P
Peng Lyu 已提交
353 354 355
		cell.isEditing = true;
	}

356
	saveNotebookCell(listIndex: number | undefined, cell: ViewCell): void {
P
Peng Lyu 已提交
357
		cell.isEditing = false;
P
Peng Lyu 已提交
358 359
	}

360
	deleteNotebookCell(listIndex: number | undefined, cell: ViewCell) {
P
Peng Lyu 已提交
361
		let index = this.model!.getNotebook().cells.indexOf(cell.cell);
P
Peng Lyu 已提交
362

P
Peng Lyu 已提交
363 364
		this.viewCells!.splice(index, 1);
		this.model!.deleteCell(cell.cell);
P
Peng Lyu 已提交
365 366 367
		this.list?.splice(index, 1);
	}

P
Peng Lyu 已提交
368
	layout(dimension: DOM.Dimension): void {
369
		this.dimension = new DOM.Dimension(dimension.width, dimension.height);
P
Peng Lyu 已提交
370 371
		DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600);
		DOM.toggleClass(this.rootElement, 'narrow-width', dimension.width < 600);
372 373
		DOM.size(this.body, dimension.width, dimension.height);
		this.list?.layout(dimension.height, dimension.width);
P
Peng Lyu 已提交
374
	}
R
rebornix 已提交
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400

	protected saveState(): void {
		if (this.input instanceof NotebookEditorInput) {
			this.saveTextEditorViewState(this.input);
		}

		super.saveState();
	}

	private saveTextEditorViewState(input: NotebookEditorInput): void {
		if (this.group) {
			let state: { [key: number]: boolean } = {};
			this.viewCells.filter(cell => cell.isEditing).forEach(cell => state[cell.cell.handle] = true);
			this.editorMemento.saveEditorState(this.group, input, {
				editingCells: state
			});
		}
	}

	private loadTextEditorViewState(input: NotebookEditorInput): INotebookEditorViewState | undefined {
		if (this.group) {
			return this.editorMemento.loadEditorState(this.group, input);
		}

		return;
	}
401

P
Peng Lyu 已提交
402 403 404 405 406 407 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
}

const embeddedEditorBackground = 'walkThrough.embeddedEditorBackground';

registerThemingParticipant((theme, collector) => {
	const color = getExtraColor(theme, embeddedEditorBackground, { dark: 'rgba(0, 0, 0, .4)', extra_dark: 'rgba(200, 235, 255, .064)', light: '#f4f4f4', hc: null });
	if (color) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-editor-background,
			.monaco-workbench .part.editor > .content .notebook-editor .margin-view-overlays { background: ${color}; }`);
	}
	const link = theme.getColor(textLinkForeground);
	if (link) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor a { color: ${link}; }`);
	}
	const activeLink = theme.getColor(textLinkActiveForeground);
	if (activeLink) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor a:hover,
			.monaco-workbench .part.editor > .content .notebook-editor a:active { color: ${activeLink}; }`);
	}
	const shortcut = theme.getColor(textPreformatForeground);
	if (shortcut) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor code,
			.monaco-workbench .part.editor > .content .notebook-editor .shortcut { color: ${shortcut}; }`);
	}
	const border = theme.getColor(contrastBorder);
	if (border) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-editor { border-color: ${border}; }`);
	}
	const quoteBackground = theme.getColor(textBlockQuoteBackground);
	if (quoteBackground) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor blockquote { background: ${quoteBackground}; }`);
	}
	const quoteBorder = theme.getColor(textBlockQuoteBorder);
	if (quoteBorder) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor blockquote { border-color: ${quoteBorder}; }`);
	}
438 439 440 441 442 443

	const inactiveListItem = theme.getColor('list.inactiveSelectionBackground');

	if (inactiveListItem) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { background-color: ${inactiveListItem}; }`);
	}
444 445 446 447

	// Cell Margin
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { padding: 8px ${CELL_MARGIN}px 8px ${CELL_MARGIN}px; }`);
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 0px ${CELL_MARGIN}px; }`);
P
Peng Lyu 已提交
448
});