notebookEditor.ts 9.9 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';
P
Peng Lyu 已提交
13 14 15 16 17
import { EditorOptions } from 'vs/workbench/common/editor';
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';
22
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
P
Peng Lyu 已提交
23 24 25

const $ = DOM.$;

P
Peng Lyu 已提交
26
export class NotebookEditor extends BaseEditor implements NotebookHandler {
P
Peng Lyu 已提交
27 28 29
	static readonly ID: string = 'workbench.editor.notebook';
	private rootElement!: HTMLElement;
	private body!: HTMLElement;
P
Peng Lyu 已提交
30
	private list: WorkbenchList<ViewCell> | undefined;
P
Peng Lyu 已提交
31
	private model: NotebookEditorModel | undefined;
P
Peng Lyu 已提交
32
	private viewCells: ViewCell[] = [];
P
Peng Lyu 已提交
33
	private trackingMap: Map<HTMLElement, { element: HTMLElement, offset: number }> = new Map();
P
Peng Lyu 已提交
34 35 36 37 38

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IThemeService themeService: IThemeService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
P
Peng Lyu 已提交
39 40
		@IModelService private readonly modelService: IModelService,
		@IModeService private readonly modeService: IModeService,
P
Peng Lyu 已提交
41
		@IStorageService storageService: IStorageService
P
Peng Lyu 已提交
42 43 44
	) {
		super(NotebookEditor.ID, telemetryService, themeService, storageService);
	}
P
Peng Lyu 已提交
45

P
Peng Lyu 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58 59
	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);
	}

	private createBody(parent: HTMLElement): void {
P
Peng Lyu 已提交
60
		this.body = document.createElement('div');
P
Peng Lyu 已提交
61 62 63
		DOM.addClass(this.body, 'cell-list-container');
		this.createCellList();
		DOM.append(parent, this.body);
P
Peng Lyu 已提交
64 65
	}

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

P
Peng Lyu 已提交
69
		const renders = [
P
Peng Lyu 已提交
70 71
			this.instantiationService.createInstance(MarkdownCellRenderer, this),
			this.instantiationService.createInstance(CodeCellRenderer, this)
P
Peng Lyu 已提交
72 73
		];

P
Peng Lyu 已提交
74
		this.list = this.instantiationService.createInstance<typeof WorkbenchList, WorkbenchList<ViewCell>>(
P
Peng Lyu 已提交
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
			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,
97 98
					listHoverOutline: focusBorder,
					listFocusOutline: focusBorder,
99 100 101 102
					listInactiveSelectionBackground: editorBackground,
					listInactiveSelectionForeground: foreground,
					listInactiveFocusBackground: editorBackground,
					listInactiveFocusOutline: editorBackground,
P
Peng Lyu 已提交
103 104 105
				}
			}
		);
P
Peng Lyu 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118

		this.list.onDidScroll((e) => {
			// console.log(-e.scrollTop);
			// this.trackingMap.forEach((val, index) => {
			// 	val.element.style.top = `${Number(index.style.top.replace(/px$/, '')) - e.scrollTop + val.offset}px`;
			// });
		});
	}

	trackScrolling(trackingElement: HTMLElement, targetElement: HTMLElement, offset: number): void {
		// this.trackingMap.set(targetElement, { element: trackingElement, offset });
		// let top = Number(targetElement.style.top.replace(/px$/, '')) - this.list!.scrollTop + offset;
		// trackingElement.style.top = `${top}px`;
P
Peng Lyu 已提交
119
	}
P
Peng Lyu 已提交
120

P
Peng Lyu 已提交
121 122 123 124 125 126
	onHide() {
		super.onHide();

		this.viewCells.forEach(cell => cell.isEditing = false);
	}

127 128 129 130 131 132 133
	setVisible(visible: boolean, group?: IEditorGroup): void {
		super.onHide();
		if (!visible) {
			this.viewCells.forEach(cell => cell.isEditing = false);
		}
	}

P
Peng Lyu 已提交
134
	setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
135 136 137 138 139
		return super.setInput(input, options, token)
			.then(() => {
				return input.resolve();
			})
			.then(model => {
P
Peng Lyu 已提交
140 141 142 143
				if (this.model !== undefined && this.model.textModel === model.textModel) {
					return;
				}

P
Peng Lyu 已提交
144 145 146 147
				this.viewCells.forEach(cell => {
					cell.save();
				});

P
Peng Lyu 已提交
148
				this.model = model;
P
Peng Lyu 已提交
149
				this.viewCells = model.getNotebook().cells.map(cell => {
P
Peng Lyu 已提交
150 151
					return new ViewCell(cell, false, this.modelService, this.modeService);
				});
P
Peng Lyu 已提交
152
				this.list?.splice(0, this.list?.length, this.viewCells);
153 154
				this.list?.layout();
			});
P
Peng Lyu 已提交
155 156
	}

P
Peng Lyu 已提交
157
	layoutElement(cell: ViewCell, height: number) {
P
Peng Lyu 已提交
158 159 160 161
		setTimeout(() => {
			// list.splice -> renderElement -> resize -> layoutElement
			// above flow will actually break how list view renders it self as it messes up with the internal state
			// instead we run the layout update in next tick
P
Peng Lyu 已提交
162
			let index = this.model!.getNotebook().cells.indexOf(cell.cell);
P
Peng Lyu 已提交
163 164
			this.list?.updateDynamicHeight(index, cell, height);
		}, 0);
165 166
	}

P
Peng Lyu 已提交
167
	insertEmptyNotebookCell(cell: ViewCell, type: 'code' | 'markdown', direction: 'above' | 'below') {
P
Peng Lyu 已提交
168
		let newCell = new ViewCell({
P
Peng Lyu 已提交
169
			cell_type: type,
P
Peng Lyu 已提交
170
			source: [],
P
Peng Lyu 已提交
171
			outputs: []
P
Peng Lyu 已提交
172
		}, false, this.modelService, this.modeService);
P
Peng Lyu 已提交
173

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

P
Peng Lyu 已提交
177 178
		this.viewCells!.splice(insertIndex, 0, newCell);
		this.model!.insertCell(newCell.cell, insertIndex);
P
Peng Lyu 已提交
179
		this.list?.splice(insertIndex, 0, [newCell]);
P
Peng Lyu 已提交
180 181 182 183 184 185 186 187 188 189 190 191

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

	editNotebookCell(cell: ViewCell): void {
		cell.isEditing = true;
	}

	saveNotebookCell(cell: ViewCell): void {
		cell.isEditing = false;
P
Peng Lyu 已提交
192 193
	}

P
Peng Lyu 已提交
194
	deleteNotebookCell(cell: ViewCell) {
P
Peng Lyu 已提交
195
		let index = this.model!.getNotebook().cells.indexOf(cell.cell);
P
Peng Lyu 已提交
196

P
Peng Lyu 已提交
197 198
		this.viewCells!.splice(index, 1);
		this.model!.deleteCell(cell.cell);
P
Peng Lyu 已提交
199 200 201
		this.list?.splice(index, 1);
	}

P
Peng Lyu 已提交
202 203 204
	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 已提交
205 206
		DOM.size(this.body, dimension.width - 20, dimension.height);
		this.list?.layout(dimension.height, dimension.width - 20);
P
Peng Lyu 已提交
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
	}
}

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 focusColor = theme.getColor(focusBorder);
	if (focusColor) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor a:focus { outline-color: ${focusColor}; }`);
	}
	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}; }`);
	}
});