cellRenderer.ts 39.4 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 { getZoomLevel } from 'vs/base/browser/browser';
P
Peng Lyu 已提交
7
import * as DOM from 'vs/base/browser/dom';
8
import { domEvent } from 'vs/base/browser/event';
R
rebornix 已提交
9
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
R
Rob Lourens 已提交
10
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
11
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
12
import { IAction } from 'vs/base/common/actions';
13
import { renderCodiconsAsElement } from 'vs/base/browser/codicons';
14
import { Color } from 'vs/base/common/color';
15
import { Emitter, Event } from 'vs/base/common/event';
16
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
P
Peng Lyu 已提交
17
import { deepClone } from 'vs/base/common/objects';
18
import * as platform from 'vs/base/common/platform';
19
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
R
rebornix 已提交
20
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
21
import { EditorOption, EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
P
Peng Lyu 已提交
22
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
23
import { Range } from 'vs/editor/common/core/range';
C
Christopher Maynard 已提交
24
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
25 26 27
import { ITextModel } from 'vs/editor/common/model';
import * as modes from 'vs/editor/common/modes';
import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
28
import { localize } from 'vs/nls';
29 30
import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenu, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
R
rebornix 已提交
31
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
32
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
P
Peng Lyu 已提交
33
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
R
rebornix 已提交
34
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
C
Christopher Maynard 已提交
35
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
R
Rob Lourens 已提交
36 37
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
38
import { BOTTOM_CELL_TOOLBAR_GAP, CELL_BOTTOM_MARGIN, CELL_TOP_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants';
R
Rob Lourens 已提交
39
import { CancelCellAction, DeleteCellAction, ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
R
rebornix 已提交
40
import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, EXPAND_CELL_CONTENT_COMMAND_ID, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
41
import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys';
42
import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus';
43
import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets';
R
rebornix 已提交
44
import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell';
45 46
import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents';
import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd';
47
import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell';
48 49 50
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
51
import { CellKind, NotebookCellMetadata, NotebookCellRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
52
import { createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView';
53

R
Rob Lourens 已提交
54 55 56
const $ = DOM.$;

export class NotebookCellListDelegate implements IListVirtualDelegate<CellViewModel> {
57
	private readonly lineHeight: number;
58

P
Peng Lyu 已提交
59 60 61 62
	constructor(
		@IConfigurationService private readonly configurationService: IConfigurationService
	) {
		const editorOptions = this.configurationService.getValue<IEditorOptions>('editor');
63
		this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()).lineHeight;
P
Peng Lyu 已提交
64 65
	}

R
rebornix 已提交
66
	getHeight(element: CellViewModel): number {
67
		return element.getHeight(this.lineHeight);
P
Peng Lyu 已提交
68 69
	}

R
rebornix 已提交
70
	hasDynamicHeight(element: CellViewModel): boolean {
P
Peng Lyu 已提交
71 72 73
		return element.hasDynamicHeight();
	}

R
rebornix 已提交
74
	getTemplateId(element: CellViewModel): string {
R
rebornix 已提交
75
		if (element.cellKind === CellKind.Markdown) {
P
Peng Lyu 已提交
76 77 78 79 80 81 82
			return MarkdownCellRenderer.TEMPLATE_ID;
		} else {
			return CodeCellRenderer.TEMPLATE_ID;
		}
	}
}

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
export class CellEditorOptions {

	private static fixedEditorOptions: IEditorOptions = {
		scrollBeyondLastLine: false,
		scrollbar: {
			verticalScrollbarSize: 14,
			horizontal: 'auto',
			useShadows: true,
			verticalHasArrows: false,
			horizontalHasArrows: false,
			alwaysConsumeMouseWheel: false
		},
		renderLineHighlightOnlyWhenFocus: true,
		overviewRulerLanes: 0,
		selectOnLineNumbers: false,
		lineNumbers: 'off',
		lineDecorationsWidth: 0,
		glyphMargin: false,
101
		fixedOverflowWidgets: true,
102
		minimap: { enabled: false },
J
Johannes Rieken 已提交
103
		renderValidationDecorations: 'on'
104 105 106
	};

	private _value: IEditorOptions;
107
	private disposable: IDisposable;
108 109 110 111 112 113

	private readonly _onDidChange = new Emitter<IEditorOptions>();
	readonly onDidChange: Event<IEditorOptions> = this._onDidChange.event;

	constructor(configurationService: IConfigurationService, language: string) {

114
		this.disposable = configurationService.onDidChangeConfiguration(e => {
115
			if (e.affectsConfiguration('editor') || e.affectsConfiguration(ShowCellStatusBarKey)) {
116 117 118 119 120 121
				this._value = computeEditorOptions();
				this._onDidChange.fire(this.value);
			}
		});

		const computeEditorOptions = () => {
122
			const showCellStatusBar = configurationService.getValue<boolean>(ShowCellStatusBarKey);
123 124 125 126 127
			const editorPadding = {
				top: EDITOR_TOP_PADDING,
				bottom: showCellStatusBar ? EDITOR_BOTTOM_PADDING : EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR
			};

128
			const editorOptions = deepClone(configurationService.getValue<IEditorOptions>('editor', { overrideIdentifier: language }));
129
			const computed = {
130
				...editorOptions,
131 132
				...CellEditorOptions.fixedEditorOptions,
				...{ padding: editorPadding }
133
			};
134 135 136 137 138 139

			if (!computed.folding) {
				computed.lineDecorationsWidth = 16;
			}

			return computed;
140 141 142 143 144 145 146
		};

		this._value = computeEditorOptions();
	}

	dispose(): void {
		this._onDidChange.dispose();
147
		this.disposable.dispose();
148 149 150 151 152
	}

	get value(): IEditorOptions {
		return this._value;
	}
153 154 155 156 157 158 159

	setGlyphMargin(gm: boolean): void {
		if (gm !== this._value.glyphMargin) {
			this._value.glyphMargin = gm;
			this._onDidChange.fire(this.value);
		}
	}
160 161
}

162
abstract class AbstractCellRenderer {
163 164
	protected readonly editorOptions: CellEditorOptions;
	protected readonly cellMenus: CellMenus;
P
Peng Lyu 已提交
165 166

	constructor(
167 168 169
		protected readonly instantiationService: IInstantiationService,
		protected readonly notebookEditor: INotebookEditor,
		protected readonly contextMenuService: IContextMenuService,
170
		configurationService: IConfigurationService,
171 172
		private readonly keybindingService: IKeybindingService,
		private readonly notificationService: INotificationService,
173
		protected readonly contextKeyServiceProvider: (container?: HTMLElement) => IContextKeyService,
174
		language: string,
R
Rob Lourens 已提交
175
		protected readonly dndController: CellDragAndDropController
P
Peng Lyu 已提交
176
	) {
177
		this.editorOptions = new CellEditorOptions(configurationService, language);
178
		this.cellMenus = this.instantiationService.createInstance(CellMenus);
179 180 181 182
	}

	dispose() {
		this.editorOptions.dispose();
P
Peng Lyu 已提交
183 184
	}

185
	protected createBetweenCellToolbar(container: HTMLElement, disposables: DisposableStore, contextKeyService: IContextKeyService): ToolBar {
R
rebornix 已提交
186 187 188 189 190 191 192 193 194 195 196
		const toolbar = new ToolBar(container, this.contextMenuService, {
			actionViewItemProvider: action => {
				if (action instanceof MenuItemAction) {
					const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
					return item;
				}

				return undefined;
			}
		});

197 198
		const cellMenu = this.instantiationService.createInstance(CellMenus);
		const menu = disposables.add(cellMenu.getCellInsertionMenu(contextKeyService));
R
rebornix 已提交
199

R
Rob Lourens 已提交
200
		const actions = this.getCellToolbarActions(menu, false);
J
João Moreno 已提交
201
		toolbar.setActions(actions.primary, actions.secondary);
R
rebornix 已提交
202

203 204
		return toolbar;
	}
205

206 207
	protected setBetweenCellToolbarContext(templateData: BaseCellRenderTemplate, element: CodeCellViewModel | MarkdownCellViewModel, context: INotebookCellActionContext): void {
		templateData.betweenCellToolbar.context = context;
R
rebornix 已提交
208

209
		const container = templateData.bottomCellContainer;
210 211 212 213
		const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset;
		container.style.top = `${bottomToolbarOffset}px`;

		templateData.elementDisposables.add(element.onDidChangeLayout(() => {
R
rebornix 已提交
214 215
			const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset;
			container.style.top = `${bottomToolbarOffset}px`;
216
		}));
R
rebornix 已提交
217 218
	}

R
Rob Lourens 已提交
219
	protected createToolbar(container: HTMLElement, elementClass?: string): ToolBar {
220
		const toolbar = new ToolBar(container, this.contextMenuService, {
221
			getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
222 223
			actionViewItemProvider: action => {
				if (action instanceof MenuItemAction) {
224 225 226
					return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
				} else if (action instanceof SubmenuItemAction) {
					return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action);
227 228
				}

R
rebornix 已提交
229 230 231 232
				if (action.id === VerticalSeparator.ID) {
					return new VerticalSeparatorViewItem(undefined, action);
				}

233
				return undefined;
234 235
			},
			renderDropdownAsChildElement: true
236 237
		});

R
Rob Lourens 已提交
238 239 240 241
		if (elementClass) {
			toolbar.getElement().classList.add(elementClass);
		}

242 243 244
		return toolbar;
	}

R
Rob Lourens 已提交
245
	private getCellToolbarActions(menu: IMenu, alwaysFillSecondaryActions: boolean): { primary: IAction[], secondary: IAction[] } {
R
Rob Lourens 已提交
246 247
		const primary: IAction[] = [];
		const secondary: IAction[] = [];
248
		const result = { primary, secondary };
249

R
Rob Lourens 已提交
250
		createAndFillInActionBarActionsWithVerticalSeparators(menu, { shouldForwardArgs: true }, result, alwaysFillSecondaryActions, g => /^inline/.test(g));
251

252
		return result;
253
	}
254

255
	protected setupCellToolbarActions(templateData: BaseCellRenderTemplate, disposables: DisposableStore): void {
256
		const updateActions = () => {
R
Rob Lourens 已提交
257
			const actions = this.getCellToolbarActions(templateData.titleMenu, true);
258

R
Rob Lourens 已提交
259
			const hadFocus = DOM.isAncestor(document.activeElement, templateData.toolbar.getElement());
J
João Moreno 已提交
260
			templateData.toolbar.setActions(actions.primary, actions.secondary);
261 262 263
			if (hadFocus) {
				this.notebookEditor.focus();
			}
264

265 266 267
			if (actions.primary.length || actions.secondary.length) {
				templateData.container.classList.add('cell-has-toolbar-actions');
				if (isCodeCellRenderTemplate(templateData)) {
268
					templateData.focusIndicatorLeft.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`;
269
					templateData.focusIndicatorRight.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`;
270 271 272 273
				}
			} else {
				templateData.container.classList.remove('cell-has-toolbar-actions');
				if (isCodeCellRenderTemplate(templateData)) {
274
					templateData.focusIndicatorLeft.style.top = `${CELL_TOP_MARGIN}px`;
275
					templateData.focusIndicatorRight.style.top = `${CELL_TOP_MARGIN}px`;
276 277 278 279
				}
			}
		};

280 281 282 283
		// #103926
		let dropdownIsVisible = false;
		let deferredUpdate: (() => void) | undefined;

284
		updateActions();
285
		disposables.add(templateData.titleMenu.onDidChange(() => {
286 287 288 289
			if (this.notebookEditor.isDisposed) {
				return;
			}

290 291 292 293 294
			if (dropdownIsVisible) {
				deferredUpdate = () => updateActions();
				return;
			}

295 296
			updateActions();
		}));
297 298 299 300 301 302 303 304 305 306 307 308
		disposables.add(templateData.toolbar.onDidChangeDropdownVisibility(visible => {
			dropdownIsVisible = visible;

			if (deferredUpdate && !visible) {
				setTimeout(() => {
					if (deferredUpdate) {
						deferredUpdate();
					}
				}, 0);
				deferredUpdate = undefined;
			}
		}));
309
	}
310

R
Rob Lourens 已提交
311
	protected commonRenderTemplate(templateData: BaseCellRenderTemplate): void {
312
		templateData.disposables.add(DOM.addDisposableListener(templateData.container, DOM.EventType.FOCUS, () => {
R
Rob Lourens 已提交
313 314 315
			if (templateData.currentRenderedCell) {
				this.notebookEditor.selectElement(templateData.currentRenderedCell);
			}
316
		}, true));
R
Rob Lourens 已提交
317

318
		this.addExpandListener(templateData);
R
Rob Lourens 已提交
319
	}
320

R
rebornix 已提交
321
	protected commonRenderElement(element: ICellViewModel, templateData: BaseCellRenderTemplate): void {
322 323 324 325 326 327
		if (element.dragging) {
			templateData.container.classList.add(DRAGGING_CLASS);
		} else {
			templateData.container.classList.remove(DRAGGING_CLASS);
		}
	}
R
Rob Lourens 已提交
328

329 330
	protected addExpandListener(templateData: BaseCellRenderTemplate): void {
		templateData.disposables.add(domEvent(templateData.expandButton, DOM.EventType.CLICK)(() => {
R
Rob Lourens 已提交
331 332 333 334
			if (!templateData.currentRenderedCell) {
				return;
			}

335
			if (templateData.currentRenderedCell.metadata?.inputCollapsed) {
R
rebornix 已提交
336
				this.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(templateData.currentRenderedCell.handle, { inputCollapsed: false });
337
			} else if (templateData.currentRenderedCell.metadata?.outputCollapsed) {
R
rebornix 已提交
338
				this.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(templateData.currentRenderedCell.handle, { outputCollapsed: false });
339
			}
R
Rob Lourens 已提交
340 341
		}));
	}
342 343

	protected setupCollapsedPart(container: HTMLElement): { collapsedPart: HTMLElement, expandButton: HTMLElement } {
344
		const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part', undefined, ...renderCodiconsAsElement('$(unfold)')));
345
		const expandButton = collapsedPart.querySelector('.codicon') as HTMLElement;
346 347 348 349 350 351 352
		const keybinding = this.keybindingService.lookupKeybinding(EXPAND_CELL_CONTENT_COMMAND_ID);
		let title = localize('cellExpandButtonLabel', "Expand");
		if (keybinding) {
			title += ` (${keybinding.getLabel()})`;
		}

		collapsedPart.title = title;
353 354 355 356
		DOM.hide(collapsedPart);

		return { collapsedPart, expandButton };
	}
P
Peng Lyu 已提交
357 358
}

359
export class MarkdownCellRenderer extends AbstractCellRenderer implements IListRenderer<MarkdownCellViewModel, MarkdownCellRenderTemplate> {
P
Peng Lyu 已提交
360 361 362
	static readonly TEMPLATE_ID = 'markdown_cell';

	constructor(
363
		notebookEditor: INotebookEditor,
R
Rob Lourens 已提交
364
		dndController: CellDragAndDropController,
365
		private renderedEditors: Map<ICellViewModel, ICodeEditor | undefined>,
366
		contextKeyServiceProvider: (container?: HTMLElement) => IContextKeyService,
367
		@IInstantiationService instantiationService: IInstantiationService,
P
Peng Lyu 已提交
368
		@IConfigurationService configurationService: IConfigurationService,
369 370 371
		@IContextMenuService contextMenuService: IContextMenuService,
		@IKeybindingService keybindingService: IKeybindingService,
		@INotificationService notificationService: INotificationService,
P
Peng Lyu 已提交
372
	) {
373
		super(instantiationService, notebookEditor, contextMenuService, configurationService, keybindingService, notificationService, contextKeyServiceProvider, 'markdown', dndController);
P
Peng Lyu 已提交
374 375 376 377 378 379
	}

	get templateId() {
		return MarkdownCellRenderer.TEMPLATE_ID;
	}

380 381 382
	renderTemplate(rootContainer: HTMLElement): MarkdownCellRenderTemplate {
		rootContainer.classList.add('markdown-cell-row');
		const container = DOM.append(rootContainer, DOM.$('.cell-inner-container'));
R
Rob Lourens 已提交
383
		const disposables = new DisposableStore();
R
Rob Lourens 已提交
384
		const contextKeyService = disposables.add(this.contextKeyServiceProvider(container));
385
		const decorationContainer = DOM.append(container, $('.cell-decoration'));
R
Rob Lourens 已提交
386 387 388 389 390
		const titleToolbarContainer = DOM.append(container, $('.cell-title-toolbar'));
		const toolbar = disposables.add(this.createToolbar(titleToolbarContainer));
		const deleteToolbar = disposables.add(this.createToolbar(titleToolbarContainer, 'cell-delete-toolbar'));
		deleteToolbar.setActions([this.instantiationService.createInstance(DeleteCellAction)]);

R
Rob Lourens 已提交
391
		const focusIndicatorLeft = DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-left'));
R
Rob Lourens 已提交
392

R
Rob Lourens 已提交
393
		const codeInnerContent = DOM.append(container, $('.cell.code'));
394
		const editorPart = DOM.append(codeInnerContent, $('.cell-editor-part'));
R
Rob Lourens 已提交
395
		const editorContainer = DOM.append(editorPart, $('.cell-editor-container'));
396
		editorPart.style.display = 'none';
P
Peng Lyu 已提交
397

R
Rob Lourens 已提交
398
		const innerContent = DOM.append(container, $('.cell.markdown'));
R
Rob Lourens 已提交
399
		const foldingIndicator = DOM.append(focusIndicatorLeft, DOM.$('.notebook-folding-indicator'));
R
rebornix 已提交
400

401
		const { collapsedPart, expandButton } = this.setupCollapsedPart(container);
R
Rob Lourens 已提交
402

R
rebornix 已提交
403
		const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container'));
404
		const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService));
405

406
		const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart));
407
		const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService));
408

R
Rob Lourens 已提交
409
		const templateData: MarkdownCellRenderTemplate = {
R
Rob Lourens 已提交
410
			collapsedPart,
411
			expandButton,
412
			contextKeyService,
413
			container,
414
			decorationContainer,
P
Peng Lyu 已提交
415
			cellContainer: innerContent,
416 417
			editorPart,
			editorContainer,
R
Rob Lourens 已提交
418
			focusIndicatorLeft,
R
rebornix 已提交
419
			foldingIndicator,
420
			disposables,
421
			elementDisposables: new DisposableStore(),
422
			toolbar,
R
Rob Lourens 已提交
423
			deleteToolbar,
424
			betweenCellToolbar,
R
Rob Lourens 已提交
425
			bottomCellContainer,
426
			titleMenu,
427
			statusBar,
R
rebornix 已提交
428
			toJSON: () => { return {}; }
P
Peng Lyu 已提交
429
		};
430
		this.dndController.registerDragHandle(templateData, rootContainer, container, () => this.getDragImage(templateData));
R
Rob Lourens 已提交
431
		this.commonRenderTemplate(templateData);
432

R
Rob Lourens 已提交
433
		return templateData;
P
Peng Lyu 已提交
434 435
	}

436
	private getDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
R
Rob Lourens 已提交
437
		if (templateData.currentRenderedCell!.editState === CellEditState.Editing) {
438
			return this.getEditDragImage(templateData);
R
Rob Lourens 已提交
439
		} else {
440
			return this.getMarkdownDragImage(templateData);
R
Rob Lourens 已提交
441 442 443
		}
	}

444
	private getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
445
		const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row');
R
Rob Lourens 已提交
446
		dragImageContainer.innerHTML = templateData.container.outerHTML;
R
Rob Lourens 已提交
447 448 449 450 451 452 453 454

		// Remove all rendered content nodes after the
		const markdownContent = dragImageContainer.querySelector('.cell.markdown')!;
		const contentNodes = markdownContent.children[0].children;
		for (let i = contentNodes.length - 1; i >= 1; i--) {
			contentNodes.item(i)!.remove();
		}

455 456 457
		return dragImageContainer;
	}

458
	private getEditDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
R
Rob Lourens 已提交
459 460
		return new CodeCellDragImageRenderer().getDragImage(templateData, templateData.currentEditor!, 'markdown');
	}
461

462
	renderElement(element: MarkdownCellViewModel, index: number, templateData: MarkdownCellRenderTemplate, height: number | undefined): void {
R
rebornix 已提交
463
		this.commonRenderElement(element, templateData);
464

R
Rob Lourens 已提交
465
		templateData.currentRenderedCell = element;
R
Rob Lourens 已提交
466
		templateData.currentEditor = undefined;
467
		templateData.editorPart!.style.display = 'none';
468
		templateData.cellContainer.innerText = '';
P
Peng Lyu 已提交
469

470 471 472
		if (height === undefined) {
			return;
		}
P
Peng Lyu 已提交
473

474
		const elementDisposables = templateData.elementDisposables;
475

R
rebornix 已提交
476
		elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, this.notebookEditor.viewModel?.notebookDocument!, element));
R
rebornix 已提交
477

478
		// render toolbar first
479
		this.setupCellToolbarActions(templateData, elementDisposables);
R
rebornix 已提交
480

481 482 483 484 485 486
		const toolbarContext = <INotebookCellActionContext>{
			cell: element,
			notebookEditor: this.notebookEditor,
			$mid: 12
		};
		templateData.toolbar.context = toolbarContext;
R
Rob Lourens 已提交
487
		templateData.deleteToolbar.context = toolbarContext;
R
rebornix 已提交
488

489
		this.setBetweenCellToolbarContext(templateData, element, toolbarContext);
490

491 492
		const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService]));
		const markdownCell = scopedInstaService.createInstance(StatefulMarkdownCell, this.notebookEditor, element, templateData, this.editorOptions.value, this.renderedEditors);
493 494
		elementDisposables.add(this.editorOptions.onDidChange(newValue => markdownCell.updateEditorOptions(newValue)));
		elementDisposables.add(markdownCell);
495

496
		templateData.statusBar.update(toolbarContext);
497 498
	}

499 500
	disposeTemplate(templateData: MarkdownCellRenderTemplate): void {
		templateData.disposables.clear();
P
Peng Lyu 已提交
501 502
	}

503 504 505 506 507 508 509
	disposeElement(element: ICellViewModel, _index: number, templateData: MarkdownCellRenderTemplate): void {
		templateData.elementDisposables.clear();
		element.getCellDecorations().forEach(e => {
			if (e.className) {
				templateData.container.classList.remove(e.className);
			}
		});
P
Peng Lyu 已提交
510 511 512
	}
}

513 514 515 516 517 518 519 520
class EditorTextRenderer {

	getRichText(editor: ICodeEditor, modelRange: Range): string | null {
		const model = editor.getModel();
		if (!model) {
			return null;
		}

521
		const colorMap = this.getDefaultColorMap();
522 523 524 525 526 527 528 529 530 531 532 533
		const fontInfo = editor.getOptions().get(EditorOption.fontInfo);
		const fontFamily = fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily ? fontInfo.fontFamily : `'${fontInfo.fontFamily}', ${EDITOR_FONT_DEFAULTS.fontFamily}`;

		return `<div style="`
			+ `color: ${colorMap[modes.ColorId.DefaultForeground]};`
			+ `background-color: ${colorMap[modes.ColorId.DefaultBackground]};`
			+ `font-family: ${fontFamily};`
			+ `font-weight: ${fontInfo.fontWeight};`
			+ `font-size: ${fontInfo.fontSize}px;`
			+ `line-height: ${fontInfo.lineHeight}px;`
			+ `white-space: pre;`
			+ `">`
534
			+ this.getRichTextLines(model, modelRange, colorMap)
535 536 537
			+ '</div>';
	}

538
	private getRichTextLines(model: ITextModel, modelRange: Range, colorMap: string[]): string {
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
		const startLineNumber = modelRange.startLineNumber;
		const startColumn = modelRange.startColumn;
		const endLineNumber = modelRange.endLineNumber;
		const endColumn = modelRange.endColumn;

		const tabSize = model.getOptions().tabSize;

		let result = '';

		for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
			const lineTokens = model.getLineTokens(lineNumber);
			const lineContent = lineTokens.getLineContent();
			const startOffset = (lineNumber === startLineNumber ? startColumn - 1 : 0);
			const endOffset = (lineNumber === endLineNumber ? endColumn - 1 : lineContent.length);

			if (lineContent === '') {
				result += '<br>';
			} else {
				result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize, platform.isWindows);
			}
		}

		return result;
	}

564
	private getDefaultColorMap(): string[] {
R
Rob Lourens 已提交
565 566
		const colorMap = modes.TokenizationRegistry.getColorMap();
		const result: string[] = ['#000000'];
567 568 569 570 571 572 573 574
		if (colorMap) {
			for (let i = 1, len = colorMap.length; i < len; i++) {
				result[i] = Color.Format.CSS.formatHex(colorMap[i]);
			}
		}
		return result;
	}
}
R
Rob Lourens 已提交
575

576
class CodeCellDragImageRenderer {
R
Rob Lourens 已提交
577
	getDragImage(templateData: BaseCellRenderTemplate, editor: ICodeEditor, type: 'code' | 'markdown'): HTMLElement {
578
		let dragImage = this.getDragImageImpl(templateData, editor, type);
579
		if (!dragImage) {
580
			// TODO@roblourens I don't think this can happen
581 582 583 584 585 586 587
			dragImage = document.createElement('div');
			dragImage.textContent = '1 cell';
		}

		return dragImage;
	}

588
	private getDragImageImpl(templateData: BaseCellRenderTemplate, editor: ICodeEditor, type: 'code' | 'markdown'): HTMLElement | null {
R
Rob Lourens 已提交
589
		const dragImageContainer = DOM.$(`.cell-drag-image.monaco-list-row.focused.${type}-cell-row`);
590 591 592 593 594 595 596
		dragImageContainer.innerHTML = templateData.container.innerHTML;

		const editorContainer = dragImageContainer.querySelector('.cell-editor-container');
		if (!editorContainer) {
			return null;
		}

R
Rob Lourens 已提交
597
		const richEditorText = new EditorTextRenderer().getRichText(editor, new Range(1, 1, 1, 1000));
598 599 600 601 602 603 604
		if (!richEditorText) {
			return null;
		}

		editorContainer.innerHTML = richEditorText;

		return dragImageContainer;
R
Rob Lourens 已提交
605 606 607
	}
}

608
export class CodeCellRenderer extends AbstractCellRenderer implements IListRenderer<CodeCellViewModel, CodeCellRenderTemplate> {
P
Peng Lyu 已提交
609
	static readonly TEMPLATE_ID = 'code_cell';
R
rebornix 已提交
610

P
Peng Lyu 已提交
611
	constructor(
R
rebornix 已提交
612
		protected notebookEditor: INotebookEditor,
R
rebornix 已提交
613
		private renderedEditors: Map<ICellViewModel, ICodeEditor | undefined>,
R
Rob Lourens 已提交
614
		dndController: CellDragAndDropController,
615
		contextKeyServiceProvider: (container?: HTMLElement) => IContextKeyService,
P
Peng Lyu 已提交
616 617
		@IContextMenuService contextMenuService: IContextMenuService,
		@IConfigurationService configurationService: IConfigurationService,
618
		@IInstantiationService instantiationService: IInstantiationService,
619 620
		@IKeybindingService keybindingService: IKeybindingService,
		@INotificationService notificationService: INotificationService,
P
Peng Lyu 已提交
621
	) {
622
		super(instantiationService, notebookEditor, contextMenuService, configurationService, keybindingService, notificationService, contextKeyServiceProvider, 'python', dndController);
P
Peng Lyu 已提交
623 624 625 626 627 628
	}

	get templateId() {
		return CodeCellRenderer.TEMPLATE_ID;
	}

629 630 631
	renderTemplate(rootContainer: HTMLElement): CodeCellRenderTemplate {
		rootContainer.classList.add('code-cell-row');
		const container = DOM.append(rootContainer, DOM.$('.cell-inner-container'));
632
		const disposables = new DisposableStore();
R
Rob Lourens 已提交
633
		const contextKeyService = disposables.add(this.contextKeyServiceProvider(container));
634
		const decorationContainer = DOM.append(container, $('.cell-decoration'));
R
Rob Lourens 已提交
635
		DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-top'));
R
Rob Lourens 已提交
636 637 638 639 640
		const titleToolbarContainer = DOM.append(container, $('.cell-title-toolbar'));
		const toolbar = disposables.add(this.createToolbar(titleToolbarContainer));
		const deleteToolbar = disposables.add(this.createToolbar(titleToolbarContainer, 'cell-delete-toolbar'));
		deleteToolbar.setActions([this.instantiationService.createInstance(DeleteCellAction)]);

641
		const focusIndicator = DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-left'));
642
		const dragHandle = DOM.append(container, DOM.$('.cell-drag-handle'));
643

R
Rob Lourens 已提交
644
		const cellContainer = DOM.append(container, $('.cell.code'));
645
		const runButtonContainer = DOM.append(cellContainer, $('.run-button-container'));
646
		const runToolbar = disposables.add(this.createToolbar(runButtonContainer));
647 648

		const executionOrderLabel = DOM.append(cellContainer, $('div.execution-count-label'));
649

650
		// create a special context key service that set the inCompositeEditor-contextkey
R
Rob Lourens 已提交
651
		const editorContextKeyService = disposables.add(this.contextKeyServiceProvider(container));
652 653 654
		const editorInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService]));
		EditorContextKeys.inCompositeEditor.bindTo(editorContextKeyService).set(true);

R
Rob Lourens 已提交
655 656
		const editorPart = DOM.append(cellContainer, $('.cell-editor-part'));
		const editorContainer = DOM.append(editorPart, $('.cell-editor-container'));
657
		const editor = editorInstaService.createInstance(CodeEditorWidget, editorContainer, {
658
			...this.editorOptions.value,
P
Peng Lyu 已提交
659 660 661
			dimension: {
				width: 0,
				height: 0
662
			},
R
rebornix 已提交
663
			// overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode()
P
Peng Lyu 已提交
664 665
		}, {});

666 667
		disposables.add(this.editorOptions.onDidChange(newValue => editor.updateOptions(newValue)));

668
		const { collapsedPart, expandButton } = this.setupCollapsedPart(container);
R
Rob Lourens 已提交
669

R
Rob Lourens 已提交
670
		const progressBar = new ProgressBar(editorPart);
R
Rob Lourens 已提交
671 672 673
		progressBar.hide();
		disposables.add(progressBar);

674
		const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart));
675
		const timer = new TimerRenderer(statusBar.durationContainer);
676
		const cellRunState = new RunStateRenderer(statusBar.cellRunStatusContainer, runToolbar, this.instantiationService);
R
Rob Lourens 已提交
677 678

		const outputContainer = DOM.append(container, $('.output'));
679 680 681

		const focusIndicatorRight = DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-right'));

682 683
		const focusSinkElement = DOM.append(container, $('.cell-editor-focus-sink'));
		focusSinkElement.setAttribute('tabindex', '0');
684
		const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container'));
685
		const focusIndicatorBottom = DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom'));
686
		const betweenCellToolbar = this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService);
687

688 689
		const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService));

R
Rob Lourens 已提交
690
		const templateData: CodeCellRenderTemplate = {
R
Rob Lourens 已提交
691 692
			editorPart,
			collapsedPart,
693
			expandButton,
694
			contextKeyService,
695
			container,
696
			decorationContainer,
697
			cellContainer,
698
			cellRunState,
R
Rob Lourens 已提交
699
			progressBar,
700
			statusBar,
701
			focusIndicatorLeft: focusIndicator,
702 703
			focusIndicatorRight,
			focusIndicatorBottom,
704
			toolbar,
R
Rob Lourens 已提交
705
			deleteToolbar,
706
			betweenCellToolbar,
707
			focusSinkElement,
R
Rob Lourens 已提交
708
			runToolbar,
R
rebornix 已提交
709
			runButtonContainer,
710
			executionOrderLabel,
711 712
			outputContainer,
			editor,
713
			disposables,
714 715
			elementDisposables: new DisposableStore(),
			bottomCellContainer,
716
			timer,
717
			titleMenu,
718
			dragHandle,
R
rebornix 已提交
719
			toJSON: () => { return {}; }
P
Peng Lyu 已提交
720
		};
R
Rob Lourens 已提交
721

722
		this.dndController.registerDragHandle(templateData, rootContainer, dragHandle, () => new CodeCellDragImageRenderer().getDragImage(templateData, templateData.editor, 'code'));
723

724 725
		disposables.add(DOM.addDisposableListener(focusSinkElement, DOM.EventType.FOCUS, () => {
			if (templateData.currentRenderedCell && (templateData.currentRenderedCell as CodeCellViewModel).outputs.length) {
726 727 728 729
				this.notebookEditor.focusNotebookCell(templateData.currentRenderedCell, 'output');
			}
		}));

R
Rob Lourens 已提交
730 731
		this.commonRenderTemplate(templateData);

R
Rob Lourens 已提交
732
		return templateData;
P
Peng Lyu 已提交
733 734
	}

735 736 737 738 739 740 741 742
	private updateForOutputs(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void {
		if (element.outputs.length) {
			DOM.show(templateData.focusSinkElement);
		} else {
			DOM.hide(templateData.focusSinkElement);
		}
	}

743
	private updateForMetadata(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void {
R
Rob Lourens 已提交
744
		const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata);
745
		DOM.toggleClass(templateData.container, 'runnable', !!metadata.runnable);
746
		this.updateExecutionOrder(metadata, templateData);
747
		templateData.statusBar.cellStatusMessageContainer.textContent = metadata?.statusMessage || '';
R
Rob Lourens 已提交
748

749
		templateData.cellRunState.renderState(element.metadata?.runState);
750 751 752 753 754 755 756 757 758 759 760 761

		if (metadata.runState === NotebookCellRunState.Running) {
			if (metadata.runStartTime) {
				templateData.elementDisposables.add(templateData.timer.start(metadata.runStartTime));
			} else {
				templateData.timer.clear();
			}
		} else if (typeof metadata.lastRunDuration === 'number') {
			templateData.timer.show(metadata.lastRunDuration);
		} else {
			templateData.timer.clear();
		}
762 763 764 765

		if (typeof metadata.breakpointMargin === 'boolean') {
			this.editorOptions.setGlyphMargin(metadata.breakpointMargin);
		}
766

767 768 769 770 771
		if (metadata.runState === NotebookCellRunState.Running) {
			templateData.progressBar.infinite().show(500);
		} else {
			templateData.progressBar.hide();
		}
R
Rob Lourens 已提交
772 773
	}

774 775 776 777
	private updateExecutionOrder(metadata: NotebookCellMetadata, templateData: CodeCellRenderTemplate): void {
		if (metadata.hasExecutionOrder) {
			const executionOrderLabel = typeof metadata.executionOrder === 'number' ?
				`[${metadata.executionOrder}]` :
778
				'[ ]';
779
			templateData.executionOrderLabel.innerText = executionOrderLabel;
R
Rob Lourens 已提交
780 781 782 783 784
		} else {
			templateData.executionOrderLabel.innerText = '';
		}
	}

785 786 787 788
	private updateForHover(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void {
		DOM.toggleClass(templateData.container, 'cell-output-hover', element.outputIsHovered);
	}

789
	private updateForLayout(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void {
790
		templateData.focusIndicatorLeft.style.height = `${element.layoutInfo.indicatorHeight}px`;
791
		templateData.focusIndicatorRight.style.height = `${element.layoutInfo.indicatorHeight}px`;
792
		templateData.focusIndicatorBottom.style.top = `${element.layoutInfo.totalHeight - BOTTOM_CELL_TOOLBAR_GAP - CELL_BOTTOM_MARGIN}px`;
793
		templateData.outputContainer.style.top = `${element.layoutInfo.outputContainerOffset}px`;
794
		templateData.dragHandle.style.height = `${element.layoutInfo.totalHeight - BOTTOM_CELL_TOOLBAR_GAP}px`;
795 796
	}

797
	renderElement(element: CodeCellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void {
R
rebornix 已提交
798 799 800 801 802 803 804 805 806 807 808
		const removedClassNames: string[] = [];
		templateData.container.classList.forEach(className => {
			if (/^nb\-.*$/.test(className)) {
				removedClassNames.push(className);
			}
		});

		removedClassNames.forEach(className => {
			templateData.container.classList.remove(className);
		});

R
rebornix 已提交
809
		this.commonRenderElement(element, templateData);
810

R
Rob Lourens 已提交
811 812
		templateData.currentRenderedCell = element;

R
rebornix 已提交
813 814 815 816
		if (height === undefined) {
			return;
		}

817
		templateData.outputContainer.innerText = '';
818

819
		const elementDisposables = templateData.elementDisposables;
R
rebornix 已提交
820

821
		elementDisposables.add(this.instantiationService.createInstance(CodeCell, this.notebookEditor, element, templateData));
J
Johannes Rieken 已提交
822
		this.renderedEditors.set(element, templateData.editor);
823

R
rebornix 已提交
824
		elementDisposables.add(new CellContextKeyManager(templateData.contextKeyService, this.notebookEditor, this.notebookEditor.viewModel?.notebookDocument!, element));
825

826
		this.updateForLayout(element, templateData);
827
		elementDisposables.add(element.onDidChangeLayout(() => {
828
			this.updateForLayout(element, templateData);
829 830
		}));

831
		templateData.cellRunState.clear();
832
		this.updateForMetadata(element, templateData);
833
		this.updateForHover(element, templateData);
834
		elementDisposables.add(element.onDidChangeState((e) => {
835
			if (e.metadataChanged) {
836
				this.updateForMetadata(element, templateData);
837
			}
838 839 840 841

			if (e.outputIsHoveredChanged) {
				this.updateForHover(element, templateData);
			}
842
		}));
843

844 845 846
		this.updateForOutputs(element, templateData);
		elementDisposables.add(element.onDidChangeOutputs(_e => this.updateForOutputs(element, templateData)));

847
		this.setupCellToolbarActions(templateData, elementDisposables);
848 849 850 851 852 853 854

		const toolbarContext = <INotebookCellActionContext>{
			cell: element,
			cellTemplate: templateData,
			notebookEditor: this.notebookEditor,
			$mid: 12
		};
855
		templateData.toolbar.context = toolbarContext;
856
		templateData.runToolbar.context = toolbarContext;
R
Rob Lourens 已提交
857
		templateData.deleteToolbar.context = toolbarContext;
R
rebornix 已提交
858

859
		this.setBetweenCellToolbarContext(templateData, element, toolbarContext);
860

861
		templateData.statusBar.update(toolbarContext);
R
rebornix 已提交
862 863
	}

864
	disposeTemplate(templateData: CodeCellRenderTemplate): void {
865
		templateData.disposables.clear();
P
Peng Lyu 已提交
866 867
	}

868
	disposeElement(element: ICellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void {
869
		templateData.elementDisposables.clear();
J
Johannes Rieken 已提交
870
		this.renderedEditors.delete(element);
P
Peng Lyu 已提交
871 872
	}
}
873 874 875 876 877 878

export class TimerRenderer {
	constructor(private readonly container: HTMLElement) {
		DOM.hide(container);
	}

R
Rob Lourens 已提交
879
	private intervalTimer: number | undefined;
880 881 882 883 884 885 886 887 888

	start(startTime: number): IDisposable {
		this.stop();

		DOM.show(this.container);
		const intervalTimer = setInterval(() => {
			const duration = Date.now() - startTime;
			this.container.textContent = this.formatDuration(duration);
		}, 100);
R
rebornix 已提交
889
		this.intervalTimer = intervalTimer as unknown as number | undefined;
890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921

		return toDisposable(() => {
			clearInterval(intervalTimer);
		});
	}

	stop() {
		if (this.intervalTimer) {
			clearInterval(this.intervalTimer);
		}
	}

	show(duration: number) {
		this.stop();

		DOM.show(this.container);
		this.container.textContent = this.formatDuration(duration);
	}

	clear() {
		DOM.hide(this.container);
		this.stop();
		this.container.textContent = '';
	}

	private formatDuration(duration: number) {
		const seconds = Math.floor(duration / 1000);
		const tenths = String(duration - seconds * 1000).charAt(0);

		return `${seconds}.${tenths}s`;
	}
}
922 923 924 925

export class RunStateRenderer {
	private static readonly MIN_SPINNER_TIME = 200;

M
Matt Bierner 已提交
926
	private spinnerTimer: any | undefined;
927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
	private pendingNewState: NotebookCellRunState | undefined;

	constructor(private readonly element: HTMLElement, private readonly runToolbar: ToolBar, private readonly instantiationService: IInstantiationService) {
	}

	clear() {
		if (this.spinnerTimer) {
			clearTimeout(this.spinnerTimer);
		}
	}

	renderState(runState: NotebookCellRunState = NotebookCellRunState.Idle) {
		if (this.spinnerTimer) {
			this.pendingNewState = runState;
			return;
		}

		if (runState === NotebookCellRunState.Running) {
			this.runToolbar.setActions([this.instantiationService.createInstance(CancelCellAction)]);
		} else {
			this.runToolbar.setActions([this.instantiationService.createInstance(ExecuteCellAction)]);
		}

		if (runState === NotebookCellRunState.Success) {
951
			DOM.reset(this.element, ...renderCodiconsAsElement('$(check)'));
952
		} else if (runState === NotebookCellRunState.Error) {
953
			DOM.reset(this.element, ...renderCodiconsAsElement('$(error)'));
954
		} else if (runState === NotebookCellRunState.Running) {
955
			DOM.reset(this.element, ...renderCodiconsAsElement('$(sync~spin)'));
956 957 958 959 960 961 962 963 964

			this.spinnerTimer = setTimeout(() => {
				this.spinnerTimer = undefined;
				if (this.pendingNewState) {
					this.renderState(this.pendingNewState);
					this.pendingNewState = undefined;
				}
			}, RunStateRenderer.MIN_SPINNER_TIME);
		} else {
965
			this.element.innerText = '';
966 967 968
		}
	}
}
R
rebornix 已提交
969 970 971

export class ListTopCellToolbar extends Disposable {
	private topCellToolbar: HTMLElement;
972
	private _modelDisposables = new DisposableStore();
R
rebornix 已提交
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
	constructor(
		protected readonly notebookEditor: INotebookEditor,

		insertionIndicatorContainer: HTMLElement,
		@IInstantiationService protected readonly instantiationService: IInstantiationService,
		@IContextMenuService protected readonly contextMenuService: IContextMenuService,
		@IKeybindingService private readonly keybindingService: IKeybindingService,
		@INotificationService private readonly notificationService: INotificationService,
		@IContextKeyService readonly contextKeyService: IContextKeyService,
	) {
		super();

		this.topCellToolbar = DOM.append(insertionIndicatorContainer, $('.cell-list-top-cell-toolbar-container'));

		const toolbar = new ToolBar(this.topCellToolbar, this.contextMenuService, {
			actionViewItemProvider: action => {
				if (action instanceof MenuItemAction) {
					const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
					return item;
				}

				return undefined;
			}
		});

		const cellMenu = this.instantiationService.createInstance(CellMenus);
		const menu = this._register(cellMenu.getCellTopInsertionMenu(contextKeyService));

		const actions = this.getCellToolbarActions(menu, false);
		toolbar.setActions(actions.primary, actions.secondary);

		this._register(toolbar);

1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
		this._register(this.notebookEditor.onDidChangeModel(() => {
			this._modelDisposables.clear();

			if (this.notebookEditor.viewModel) {
				this._modelDisposables.add(this.notebookEditor.viewModel.onDidChangeViewCells(() => {
					this.updateClass();
				}));
			}
		}));

		this.updateClass();
	}

	private updateClass() {
		if (this.notebookEditor.viewModel?.length === 0) {
			DOM.addClass(this.topCellToolbar, 'emptyNotebook');
		} else {
			DOM.removeClass(this.topCellToolbar, 'emptyNotebook');
		}
R
rebornix 已提交
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036
	}

	private getCellToolbarActions(menu: IMenu, alwaysFillSecondaryActions: boolean): { primary: IAction[], secondary: IAction[] } {
		const primary: IAction[] = [];
		const secondary: IAction[] = [];
		const result = { primary, secondary };

		createAndFillInActionBarActionsWithVerticalSeparators(menu, { shouldForwardArgs: true }, result, alwaysFillSecondaryActions, g => /^inline/.test(g));

		return result;
	}
}