notebookEditor.ts 15.7 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';
P
Peng Lyu 已提交
21
import { NotebookHandler, ViewCell, MarkdownCellRenderer, CodeCellRenderer, NotebookCellListDelegate } 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 } 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';
P
Peng Lyu 已提交
32 33

const $ = DOM.$;
R
rebornix 已提交
34 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;
P
Peng Lyu 已提交
54 55 56 57 58

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

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

P
Peng Lyu 已提交
72 73 74 75 76 77 78 79 80 81 82
	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);
83 84 85 86 87 88
		this.generateFontInfo();
	}

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

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

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

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

P
Peng Lyu 已提交
105
		const renders = [
P
Peng Lyu 已提交
106 107
			this.instantiationService.createInstance(MarkdownCellRenderer, this),
			this.instantiationService.createInstance(CodeCellRenderer, this)
P
Peng Lyu 已提交
108 109
		];

P
Peng Lyu 已提交
110
		this.list = this.instantiationService.createInstance<typeof WorkbenchList, WorkbenchList<ViewCell>>(
P
Peng Lyu 已提交
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
			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,
133 134
					listHoverOutline: focusBorder,
					listFocusOutline: focusBorder,
135 136 137 138
					listInactiveSelectionBackground: editorBackground,
					listInactiveSelectionForeground: foreground,
					listInactiveFocusBackground: editorBackground,
					listInactiveFocusOutline: editorBackground,
P
Peng Lyu 已提交
139 140 141
				}
			}
		);
P
Peng Lyu 已提交
142

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

148 149 150 151
	getFontInfo(): BareFontInfo | undefined {
		return this.fontInfo;
	}

P
Peng Lyu 已提交
152 153
	triggerWheel(event: IMouseWheelEvent) {
		this.list?.triggerScrollFromMouseWheelEvent(event);
P
Peng Lyu 已提交
154 155
	}

R
rebornix 已提交
156
	createContentWidget(cell: ViewCell, outputIndex: number, shadowContent: string, offset: number) {
157 158 159 160
		if (!this.webview) {
			return;
		}

P
Peng Lyu 已提交
161 162 163
		if (!this.webview!.mapping.has(cell.id)) {
			let index = this.model!.getNotebook().cells.indexOf(cell.cell);
			let top = this.list?.getElementTop(index) || 0;
164
			this.webview!.createContentWidget(cell, offset, shadowContent, top + offset);
R
rebornix 已提交
165 166 167 168 169 170
			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 已提交
171
		} else {
P
Peng Lyu 已提交
172 173 174 175
			let index = this.model!.getNotebook().cells.indexOf(cell.cell);
			let top = this.list?.getElementTop(index) || 0;
			let scrollTop = this.list?.scrollTop || 0;

176
			this.webview!.updateViewScrollTop(-scrollTop, [{ id: cell.id, top: top + offset }]);
P
Peng Lyu 已提交
177
		}
P
Peng Lyu 已提交
178
	}
P
Peng Lyu 已提交
179

P
Peng Lyu 已提交
180 181 182
	disposeViewCell(cell: ViewCell) {
	}

P
Peng Lyu 已提交
183
	onHide() {
R
rebornix 已提交
184 185 186 187 188
		this.viewCells.forEach(cell => {
			if (cell.getText() !== '') {
				cell.isEditing = false;
			}
		});
189 190 191 192 193 194 195 196 197 198

		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 已提交
199 200
	}

201
	setVisible(visible: boolean, group?: IEditorGroup): void {
R
rebornix 已提交
202
		super.setVisible(visible, group);
203
		if (!visible) {
R
rebornix 已提交
204 205 206 207 208
			this.viewCells.forEach(cell => {
				if (cell.getText() !== '') {
					cell.isEditing = false;
				}
			});
209 210 211
		}
	}

P
Peng Lyu 已提交
212
	setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
R
rebornix 已提交
213 214 215 216
		if (this.input instanceof NotebookEditorInput) {
			this.saveTextEditorViewState(this.input);
		}

217 218 219 220 221
		return super.setInput(input, options, token)
			.then(() => {
				return input.resolve();
			})
			.then(model => {
222
				if (this.model !== undefined && this.model.textModel === model.textModel && this.webview !== null) {
P
Peng Lyu 已提交
223 224 225
					return;
				}

226
				this.localStore.clear();
P
Peng Lyu 已提交
227 228 229 230
				this.viewCells.forEach(cell => {
					cell.save();
				});

231 232 233
				if (this.webview) {
					this.webview?.clearContentWidgets();
				} else {
R
rebornix 已提交
234
					this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this);
235 236 237
					this.list?.view.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element);
				}

P
Peng Lyu 已提交
238
				this.model = model;
R
rebornix 已提交
239 240 241 242
				this.localStore.add(this.model.onDidChangeCells(() => {
					this.updateViewCells();
				}));

R
rebornix 已提交
243
				let viewState = this.loadTextEditorViewState(input);
P
Peng Lyu 已提交
244
				this.viewCells = model.getNotebook().cells.map(cell => {
R
rebornix 已提交
245 246
					const isEditing = viewState && viewState.editingCells[cell.handle];
					return new ViewCell(cell, !!isEditing, this.modelService, this.modeService);
P
Peng Lyu 已提交
247
				});
248 249 250 251 252 253

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

R
rebornix 已提交
254
					// const date = new Date();
255 256 257 258 259 260 261 262 263 264 265 266 267 268
					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 已提交
269
						// console.log('----- did scroll ----  ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
270 271 272 273
						this.webview?.updateViewScrollTop(-scrollTop, updateItems);
					}
				};
				this.localStore.add(this.list!.onWillScroll(e => {
R
rebornix 已提交
274 275
					// const date = new Date();
					// console.log('----- will scroll ----  ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
276 277 278 279 280
					this.webview?.updateViewScrollTop(-e.scrollTop, []);
				}));
				this.localStore.add(this.list!.onDidScroll(() => updateScrollPosition()));
				this.localStore.add(this.list!.onDidChangeContentHeight(() => updateScrollPosition()));

281 282
				this.list?.splice(0, this.list?.length);
				this.list?.splice(0, 0, this.viewCells);
283 284
				this.list?.layout();
			});
P
Peng Lyu 已提交
285 286
	}

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

		if (this.list?.view.isRendering) {
			DOM.scheduleAtNextAnimationFrame(() => {
				relayout(cell, height);
			});
		} else {
			relayout(cell, height);
		}
302 303
	}

R
rebornix 已提交
304
	updateViewCells() {
R
rebornix 已提交
305 306 307 308 309
		if (this.list?.view.isRendering) {
			DOM.scheduleAtNextAnimationFrame(() => {
				this.list?.rerender();
			});
		} else {
R
rebornix 已提交
310
			this.list?.rerender();
R
rebornix 已提交
311
		}
R
rebornix 已提交
312 313
	}

314
	insertEmptyNotebookCell(listIndex: number | undefined, cell: ViewCell, type: 'code' | 'markdown', direction: 'above' | 'below') {
P
Peng Lyu 已提交
315
		let newCell = new ViewCell({
R
rebornix 已提交
316
			handle: -1,
P
Peng Lyu 已提交
317
			cell_type: type,
P
Peng Lyu 已提交
318
			source: [],
P
Peng Lyu 已提交
319
			outputs: []
P
Peng Lyu 已提交
320
		}, false, this.modelService, this.modeService);
P
Peng Lyu 已提交
321

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

P
Peng Lyu 已提交
325 326
		this.viewCells!.splice(insertIndex, 0, newCell);
		this.model!.insertCell(newCell.cell, insertIndex);
P
Peng Lyu 已提交
327
		this.list?.splice(insertIndex, 0, [newCell]);
P
Peng Lyu 已提交
328 329 330 331 332 333

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

334
	editNotebookCell(listIndex: number | undefined, cell: ViewCell): void {
P
Peng Lyu 已提交
335 336 337
		cell.isEditing = true;
	}

338
	saveNotebookCell(listIndex: number | undefined, cell: ViewCell): void {
P
Peng Lyu 已提交
339
		cell.isEditing = false;
P
Peng Lyu 已提交
340 341
	}

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

P
Peng Lyu 已提交
345 346
		this.viewCells!.splice(index, 1);
		this.model!.deleteCell(cell.cell);
P
Peng Lyu 已提交
347 348 349
		this.list?.splice(index, 1);
	}

P
Peng Lyu 已提交
350 351 352
	layout(dimension: DOM.Dimension): void {
		DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600);
		DOM.toggleClass(this.rootElement, 'narrow-width', dimension.width < 600);
P
Peng Lyu 已提交
353 354
		DOM.size(this.body, dimension.width - 20, dimension.height);
		this.list?.layout(dimension.height, dimension.width - 20);
P
Peng Lyu 已提交
355
	}
R
rebornix 已提交
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381

	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;
	}
382

P
Peng Lyu 已提交
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
}

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}; }`);
	}
});