cellRenderer.ts 11.2 KB
Newer Older
P
Peng Lyu 已提交
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 'vs/css!../notebook';
P
Peng Lyu 已提交
7 8 9 10
import * as DOM from 'vs/base/browser/dom';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
11
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
P
Peng Lyu 已提交
12 13 14 15 16 17 18
import { deepClone } from 'vs/base/common/objects';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { getZoomLevel } from 'vs/base/browser/browser';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action } from 'vs/base/common/actions';
R
rebornix 已提交
19
import { DisposableStore } from 'vs/base/common/lifecycle';
R
rebornix 已提交
20
import { StatefullMarkdownCell } from 'vs/workbench/contrib/notebook/browser/renderers/markdownCell';
R
rebornix 已提交
21
import { CellViewModel } from './cellViewModel';
R
rebornix 已提交
22
import { CodeCell } from 'vs/workbench/contrib/notebook/browser/renderers/codeCell';
R
rebornix 已提交
23 24
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
25
import { CellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
26

R
rebornix 已提交
27
export class NotebookCellListDelegate implements IListVirtualDelegate<CellViewModel> {
P
Peng Lyu 已提交
28 29 30 31 32 33 34 35
	private _lineHeight: number;
	constructor(
		@IConfigurationService private readonly configurationService: IConfigurationService
	) {
		const editorOptions = this.configurationService.getValue<IEditorOptions>('editor');
		this._lineHeight = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()).lineHeight;
	}

R
rebornix 已提交
36
	getHeight(element: CellViewModel): number {
P
Peng Lyu 已提交
37 38 39
		return element.getHeight(this._lineHeight);
	}

R
rebornix 已提交
40
	hasDynamicHeight(element: CellViewModel): boolean {
P
Peng Lyu 已提交
41 42 43
		return element.hasDynamicHeight();
	}

R
rebornix 已提交
44
	getDynamicHeight(element: CellViewModel) {
R
rebornix 已提交
45
		return element.dynamicHeight || 0;
P
Peng Lyu 已提交
46 47
	}

R
rebornix 已提交
48
	getTemplateId(element: CellViewModel): string {
P
Peng Lyu 已提交
49 50 51 52 53 54 55 56 57 58 59 60
		if (element.cellType === 'markdown') {
			return MarkdownCellRenderer.TEMPLATE_ID;
		} else {
			return CodeCellRenderer.TEMPLATE_ID;
		}
	}
}

class AbstractCellRenderer {
	protected editorOptions: IEditorOptions;

	constructor(
61
		protected handler: INotebookEditor,
P
Peng Lyu 已提交
62 63 64 65 66 67 68 69 70 71 72 73 74
		private contextMenuService: IContextMenuService,
		private configurationService: IConfigurationService,
		language: string
	) {
		const editorOptions = deepClone(this.configurationService.getValue<IEditorOptions>('editor', { overrideIdentifier: language }));
		this.editorOptions = {
			...editorOptions,
			scrollBeyondLastLine: false,
			scrollbar: {
				verticalScrollbarSize: 14,
				horizontal: 'auto',
				useShadows: true,
				verticalHasArrows: false,
R
rebornix 已提交
75 76
				horizontalHasArrows: false,
				alwaysConsumeMouseWheel: false
P
Peng Lyu 已提交
77 78 79 80 81 82 83 84
			},
			overviewRulerLanes: 3,
			fixedOverflowWidgets: false,
			lineNumbersMinChars: 1,
			minimap: { enabled: false },
		};
	}

R
rebornix 已提交
85
	showContextMenu(listIndex: number | undefined, element: CellViewModel, x: number, y: number) {
P
Peng Lyu 已提交
86 87 88 89 90 91 92
		const actions: Action[] = [];
		const insertAbove = new Action(
			'workbench.notebook.code.insertCellAbove',
			'Insert Code Cell Above',
			undefined,
			true,
			async () => {
R
rebornix 已提交
93
				await this.handler.insertEmptyNotebookCell(listIndex, element, 'code', 'above');
P
Peng Lyu 已提交
94 95 96 97 98 99 100 101 102 103
			}
		);
		actions.push(insertAbove);

		const insertBelow = new Action(
			'workbench.notebook.code.insertCellBelow',
			'Insert Code Cell Below',
			undefined,
			true,
			async () => {
R
rebornix 已提交
104
				await this.handler.insertEmptyNotebookCell(listIndex, element, 'code', 'below');
P
Peng Lyu 已提交
105 106 107 108 109 110 111 112 113 114
			}
		);
		actions.push(insertBelow);

		const insertMarkdownAbove = new Action(
			'workbench.notebook.markdown.insertCellAbove',
			'Insert Markdown Cell Above',
			undefined,
			true,
			async () => {
R
rebornix 已提交
115
				await this.handler.insertEmptyNotebookCell(listIndex, element, 'markdown', 'above');
P
Peng Lyu 已提交
116 117 118 119 120 121 122 123 124 125
			}
		);
		actions.push(insertMarkdownAbove);

		const insertMarkdownBelow = new Action(
			'workbench.notebook.markdown.insertCellBelow',
			'Insert Markdown Cell Below',
			undefined,
			true,
			async () => {
R
rebornix 已提交
126
				await this.handler.insertEmptyNotebookCell(listIndex, element, 'markdown', 'below');
P
Peng Lyu 已提交
127 128 129 130 131 132 133 134 135 136 137
			}
		);
		actions.push(insertMarkdownBelow);

		if (element.cellType === 'markdown') {
			const editAction = new Action(
				'workbench.notebook.editCell',
				'Edit Cell',
				undefined,
				true,
				async () => {
138
					this.handler.editNotebookCell(listIndex, element);
P
Peng Lyu 已提交
139 140 141 142 143 144 145 146 147 148 149
				}
			);

			actions.push(editAction);

			const saveAction = new Action(
				'workbench.notebook.saveCell',
				'Save Cell',
				undefined,
				true,
				async () => {
150
					this.handler.saveNotebookCell(listIndex, element);
P
Peng Lyu 已提交
151 152 153 154 155 156 157 158 159 160 161 162
				}
			);

			actions.push(saveAction);
		}

		const deleteCell = new Action(
			'workbench.notebook.deleteCell',
			'Delete Cell',
			undefined,
			true,
			async () => {
163
				this.handler.deleteNotebookCell(listIndex, element);
P
Peng Lyu 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
			}
		);

		actions.push(deleteCell);

		this.contextMenuService.showContextMenu({
			getAnchor: () => {
				return {
					x,
					y
				};
			},
			getActions: () => {
				return actions;
			},
			autoSelectFirstItem: true
		});
	}
}

R
rebornix 已提交
184
export class MarkdownCellRenderer extends AbstractCellRenderer implements IListRenderer<CellViewModel, CellRenderTemplate> {
P
Peng Lyu 已提交
185
	static readonly TEMPLATE_ID = 'markdown_cell';
R
rebornix 已提交
186
	private disposables: Map<CellViewModel, DisposableStore> = new Map();
P
Peng Lyu 已提交
187 188

	constructor(
189
		handler: INotebookEditor,
P
Peng Lyu 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IConfigurationService configurationService: IConfigurationService,
		@IContextMenuService contextMenuService: IContextMenuService
	) {
		super(handler, contextMenuService, configurationService, 'markdown');
	}

	get templateId() {
		return MarkdownCellRenderer.TEMPLATE_ID;
	}

	renderTemplate(container: HTMLElement): CellRenderTemplate {
		const codeInnerContent = document.createElement('div');
		DOM.addClasses(codeInnerContent, 'cell', 'code');
		codeInnerContent.style.display = 'none';

		container.appendChild(codeInnerContent);

		const innerContent = document.createElement('div');
		DOM.addClasses(innerContent, 'cell', 'markdown');
		container.appendChild(innerContent);

		const action = document.createElement('div');
		DOM.addClasses(action, 'menu', 'codicon-settings-gear', 'codicon');
		container.appendChild(action);

P
Peng Lyu 已提交
216
		const template = {
P
Peng Lyu 已提交
217 218 219
			container: container,
			cellContainer: innerContent,
			menuContainer: action,
R
💄  
rebornix 已提交
220
			editingContainer: codeInnerContent
P
Peng Lyu 已提交
221
		};
P
Peng Lyu 已提交
222 223

		return template;
P
Peng Lyu 已提交
224 225
	}

R
rebornix 已提交
226
	renderElement(element: CellViewModel, index: number, templateData: CellRenderTemplate, height: number | undefined): void {
P
Peng Lyu 已提交
227
		templateData.editingContainer!.style.display = 'none';
R
rebornix 已提交
228 229 230 231 232
		templateData.cellContainer.innerHTML = '';
		let renderedHTML = element.getHTML();
		if (renderedHTML) {
			templateData.cellContainer.appendChild(renderedHTML);
		}
P
Peng Lyu 已提交
233 234 235 236 237 238 239 240 241 242 243 244

		if (height) {
			this.disposables.get(element)?.clear();
			if (!this.disposables.has(element)) {
				this.disposables.set(element, new DisposableStore());
			}
			let elementDisposable = this.disposables.get(element);

			elementDisposable!.add(DOM.addStandardDisposableListener(templateData.menuContainer!, 'mousedown', e => {
				const { top, height } = DOM.getDomNodePagePosition(templateData.menuContainer!);
				e.preventDefault();

245 246 247
				const listIndexAttr = templateData.menuContainer?.parentElement?.getAttribute('data-index');
				const listIndex = listIndexAttr ? Number(listIndexAttr) : undefined;
				this.showContextMenu(listIndex, element, e.posx, top + height);
P
Peng Lyu 已提交
248 249 250 251 252 253 254 255
			}));

			elementDisposable!.add(new StatefullMarkdownCell(this.handler, element, templateData, this.editorOptions, this.instantiationService));
		}
	}

	disposeTemplate(templateData: CellRenderTemplate): void {
		// throw nerendererw Error('Method not implemented.');
P
Peng Lyu 已提交
256

P
Peng Lyu 已提交
257 258
	}

R
rebornix 已提交
259
	disposeElement(element: CellViewModel, index: number, templateData: CellRenderTemplate, height: number | undefined): void {
P
Peng Lyu 已提交
260 261 262 263 264 265
		if (height) {
			this.disposables.get(element)?.clear();
		}
	}
}

R
rebornix 已提交
266
export class CodeCellRenderer extends AbstractCellRenderer implements IListRenderer<CellViewModel, CellRenderTemplate> {
P
Peng Lyu 已提交
267
	static readonly TEMPLATE_ID = 'code_cell';
R
rebornix 已提交
268 269
	private disposables: Map<CellViewModel, DisposableStore> = new Map();

P
Peng Lyu 已提交
270
	constructor(
271
		protected handler: INotebookEditor,
P
Peng Lyu 已提交
272 273 274
		@IContextMenuService contextMenuService: IContextMenuService,
		@IConfigurationService configurationService: IConfigurationService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
P
Peng Lyu 已提交
275
		@IThemeService private readonly themeService: IThemeService,
R
rebornix 已提交
276 277
		@IModelService private readonly modelService: IModelService,
		@IModeService private readonly modeService: IModeService
P
Peng Lyu 已提交
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
	) {
		super(handler, contextMenuService, configurationService, 'python');
	}

	get templateId() {
		return CodeCellRenderer.TEMPLATE_ID;
	}

	renderTemplate(container: HTMLElement): CellRenderTemplate {
		const innerContent = document.createElement('div');
		DOM.addClasses(innerContent, 'cell', 'code');
		container.appendChild(innerContent);
		const editor = this.instantiationService.createInstance(CodeEditorWidget, innerContent, {
			...this.editorOptions,
			dimension: {
				width: 0,
				height: 0
			}
		}, {});
		const action = document.createElement('div');
		DOM.addClasses(action, 'menu', 'codicon-settings-gear', 'codicon');
		container.appendChild(action);

		const outputContainer = document.createElement('div');
		DOM.addClasses(outputContainer, 'output');
		container.appendChild(outputContainer);

P
Peng Lyu 已提交
305
		let tempalte = {
P
Peng Lyu 已提交
306 307 308 309
			container: container,
			cellContainer: innerContent,
			menuContainer: action,
			outputContainer: outputContainer,
R
💄  
rebornix 已提交
310
			editor
P
Peng Lyu 已提交
311
		};
P
Peng Lyu 已提交
312 313

		return tempalte;
P
Peng Lyu 已提交
314 315
	}

R
rebornix 已提交
316
	renderElement(element: CellViewModel, index: number, templateData: CellRenderTemplate, height: number | undefined): void {
R
rebornix 已提交
317 318
		if (templateData.outputContainer) {
			templateData.outputContainer!.innerHTML = '';
319 320
		}

R
rebornix 已提交
321 322 323
		this.disposables.get(element)?.clear();
		if (!this.disposables.has(element)) {
			this.disposables.set(element, new DisposableStore());
R
rebornix 已提交
324 325
		}

R
rebornix 已提交
326
		let elementDisposable = this.disposables.get(element);
R
rebornix 已提交
327

R
rebornix 已提交
328
		elementDisposable?.add(DOM.addStandardDisposableListener(templateData.menuContainer!, 'mousedown', e => {
329
			let { top, height } = DOM.getDomNodePagePosition(templateData.menuContainer!);
P
Peng Lyu 已提交
330 331
			e.preventDefault();

332 333 334 335
			const listIndexAttr = templateData.menuContainer?.parentElement?.getAttribute('data-index');
			const listIndex = listIndexAttr ? Number(listIndexAttr) : undefined;

			this.showContextMenu(listIndex, element, e.posx, top + height);
R
rebornix 已提交
336
		}));
P
Peng Lyu 已提交
337

R
rebornix 已提交
338
		elementDisposable?.add(new CodeCell(this.handler, element, templateData, this.themeService, this.instantiationService, this.modelService, this.modeService, height));
P
Peng Lyu 已提交
339 340 341 342 343
	}

	disposeTemplate(templateData: CellRenderTemplate): void {
	}

R
rebornix 已提交
344
	disposeElement(element: CellViewModel, index: number, templateData: CellRenderTemplate, height: number | undefined): void {
R
rebornix 已提交
345
		this.disposables.get(element)?.clear();
P
Peng Lyu 已提交
346 347
	}
}