notebookEditor.ts 48.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';
R
rebornix 已提交
8
import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
9 10
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Color, RGBA } from 'vs/base/common/color';
11
import { onUnexpectedError } from 'vs/base/common/errors';
12
import { Emitter, Event } from 'vs/base/common/event';
13 14
import { combinedDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import 'vs/css!./media/notebook';
15
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
16
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
R
rebornix 已提交
17
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
18
import { IPosition, Position } from 'vs/editor/common/core/position';
19 20 21
import { Range } from 'vs/editor/common/core/range';
import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon';
import * as nls from 'vs/nls';
22
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
R
Rob Lourens 已提交
23
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
24
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
R
rebornix 已提交
25 26 27
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
28
import { contrastBorder, editorBackground, focusBorder, foreground, registerColor, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry';
R
rebornix 已提交
29 30
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
31 32
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { EditorOptions, IEditorCloseEvent, IEditorMemento } from 'vs/workbench/common/editor';
33 34 35
import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants';
import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, IEditableCellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
36
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
37
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
R
rebornix 已提交
38 39
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
40
import { CellDragAndDropController, CodeCellRenderer, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer';
R
rebornix 已提交
41
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
R
rebornix 已提交
42
import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
43 44
import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellKind, CellUri, IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
45 46
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
R
rebornix 已提交
47
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
48 49
import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
P
Peng Lyu 已提交
50 51

const $ = DOM.$;
R
rebornix 已提交
52 53
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
export class NotebookEditorOptions extends EditorOptions {

	readonly cellOptions?: IResourceEditorInput;

	constructor(options: Partial<NotebookEditorOptions>) {
		super();
		this.overwrite(options);
		this.cellOptions = options.cellOptions;
	}

	with(options: Partial<NotebookEditorOptions>): NotebookEditorOptions {
		return new NotebookEditorOptions({ ...this, ...options });
	}
}

R
rebornix 已提交
69
export class NotebookCodeEditors implements ICompositeCodeEditor {
70

71
	private readonly _disposables = new DisposableStore();
72 73 74 75
	private readonly _onDidChangeActiveEditor = new Emitter<this>();
	readonly onDidChangeActiveEditor: Event<this> = this._onDidChangeActiveEditor.event;

	constructor(
R
rebornix 已提交
76
		private _list: INotebookCellList,
R
rebornix 已提交
77
		private _renderedEditors: Map<ICellViewModel, ICodeEditor | undefined>
78
	) {
79
		_list.onDidChangeFocus(_e => this._onDidChangeActiveEditor.fire(this), undefined, this._disposables);
80 81 82 83 84 85
	}

	dispose(): void {
		this._onDidChangeActiveEditor.dispose();
		this._disposables.dispose();
	}
86 87 88

	get activeCodeEditor(): IEditor | undefined {
		const [focused] = this._list.getFocusedElements();
R
rebornix 已提交
89
		return this._renderedEditors.get(focused);
90 91 92
	}
}

R
rebornix 已提交
93
export class NotebookEditor extends BaseEditor implements INotebookEditor {
P
Peng Lyu 已提交
94
	static readonly ID: string = 'workbench.editor.notebook';
R
rebornix 已提交
95
	private _rootElement!: HTMLElement;
P
Peng Lyu 已提交
96
	private body!: HTMLElement;
97
	private titleBar: HTMLElement | null = null;
P
Peng Lyu 已提交
98
	private webview: BackLayerWebView | null = null;
R
rebornix 已提交
99
	private webviewTransparentCover: HTMLElement | null = null;
R
rebornix 已提交
100
	private list: INotebookCellList | undefined;
101
	private control: ICompositeCodeEditor | undefined;
R
rebornix 已提交
102
	private renderedEditors: Map<ICellViewModel, ICodeEditor | undefined> = new Map();
R
rebornix 已提交
103
	private eventDispatcher: NotebookEventDispatcher | undefined;
R
rebornix 已提交
104
	private notebookViewModel: NotebookViewModel | undefined;
105
	private localStore: DisposableStore = this._register(new DisposableStore());
R
rebornix 已提交
106
	private editorMemento: IEditorMemento<INotebookEditorViewState>;
R
rebornix 已提交
107
	private readonly groupListener = this._register(new MutableDisposable());
108
	private fontInfo: BareFontInfo | undefined;
109
	private dimension: DOM.Dimension | null = null;
R
rebornix 已提交
110
	private editorFocus: IContextKey<boolean> | null = null;
R
rebornix 已提交
111
	private editorEditable: IContextKey<boolean> | null = null;
112
	private editorRunnable: IContextKey<boolean> | null = null;
113
	private editorExecutingNotebook: IContextKey<boolean> | null = null;
R
rebornix 已提交
114
	private outputRenderer: OutputRenderer;
R
rebornix 已提交
115
	protected readonly _contributions: { [key: string]: INotebookEditorContribution; };
R
rebornix 已提交
116
	private scrollBeyondLastLine: boolean;
P
Peng Lyu 已提交
117 118 119 120 121

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IThemeService themeService: IThemeService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
P
Peng Lyu 已提交
122
		@IStorageService storageService: IStorageService,
R
rebornix 已提交
123
		@INotebookService private notebookService: INotebookService,
124
		@IEditorGroupsService editorGroupService: IEditorGroupsService,
R
rebornix 已提交
125
		@IConfigurationService private readonly configurationService: IConfigurationService,
R
rebornix 已提交
126
		@IContextKeyService private readonly contextKeyService: IContextKeyService
P
Peng Lyu 已提交
127 128
	) {
		super(NotebookEditor.ID, telemetryService, themeService, storageService);
R
rebornix 已提交
129 130

		this.editorMemento = this.getEditorMemento<INotebookEditorViewState>(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
R
rebornix 已提交
131
		this.outputRenderer = new OutputRenderer(this, this.instantiationService);
R
rebornix 已提交
132
		this._contributions = {};
R
rebornix 已提交
133 134 135 136 137 138 139 140 141 142
		this.scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');

		this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration('editor.scrollBeyondLastLine')) {
				this.scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');
				if (this.dimension) {
					this.layout(this.dimension);
				}
			}
		});
R
rebornix 已提交
143 144 145 146 147 148 149 150 151
	}

	private readonly _onDidChangeModel = new Emitter<void>();
	readonly onDidChangeModel: Event<void> = this._onDidChangeModel.event;


	set viewModel(newModel: NotebookViewModel | undefined) {
		this.notebookViewModel = newModel;
		this._onDidChangeModel.fire();
R
rebornix 已提交
152 153
	}

R
rebornix 已提交
154 155 156 157
	get viewModel() {
		return this.notebookViewModel;
	}

P
Peng Lyu 已提交
158 159 160 161 162 163 164 165
	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*/ }


R
rebornix 已提交
166
	//#region Editor Core
R
rebornix 已提交
167

R
rebornix 已提交
168 169 170 171 172

	public get isNotebookEditor() {
		return true;
	}

P
Peng Lyu 已提交
173
	protected createEditor(parent: HTMLElement): void {
R
rebornix 已提交
174 175
		this._rootElement = DOM.append(parent, $('.notebook-editor'));
		this.createBody(this._rootElement);
176
		this.generateFontInfo();
R
rebornix 已提交
177
		this.editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService);
178
		this.editorFocus.set(true);
R
rebornix 已提交
179 180 181 182 183 184 185
		this._register(this.onDidFocus(() => {
			this.editorFocus?.set(true);
		}));

		this._register(this.onDidBlur(() => {
			this.editorFocus?.set(false);
		}));
R
rebornix 已提交
186 187 188

		this.editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService);
		this.editorEditable.set(true);
189 190
		this.editorRunnable = NOTEBOOK_EDITOR_RUNNABLE.bindTo(this.contextKeyService);
		this.editorRunnable.set(true);
191
		this.editorExecutingNotebook = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.contextKeyService);
R
rebornix 已提交
192 193 194 195 196 197 198 199 200 201 202

		const contributions = NotebookEditorExtensionsRegistry.getEditorContributions();

		for (const desc of contributions) {
			try {
				const contribution = this.instantiationService.createInstance(desc.ctor, this);
				this._contributions[desc.id] = contribution;
			} catch (err) {
				onUnexpectedError(err);
			}
		}
203 204
	}

205 206 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
	populateEditorTitlebar() {
		for (let element: HTMLElement | null = this._rootElement.parentElement; element; element = element.parentElement) {
			if (DOM.hasClass(element, 'editor-group-container')) {
				// elemnet is editor group container
				for (let i = 0; i < element.childElementCount; i++) {
					const child = element.childNodes.item(i) as HTMLElement;

					if (DOM.hasClass(child, 'title')) {
						this.titleBar = child;
						break;
					}
				}
				break;
			}
		}
	}

	clearEditorTitlebarZindex() {
		if (this.titleBar === null) {
			this.populateEditorTitlebar();
		}

		if (this.titleBar) {
			this.titleBar.style.zIndex = 'auto';
		}
	}

	increaseEditorTitlebarZindex() {
		if (this.titleBar === null) {
			this.populateEditorTitlebar();
		}

		if (this.titleBar) {
			this.titleBar.style.zIndex = '500';
		}
	}

242 243 244
	private generateFontInfo(): void {
		const editorOptions = this.configurationService.getValue<IEditorOptions>('editor');
		this.fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel());
P
Peng Lyu 已提交
245 246 247
	}

	private createBody(parent: HTMLElement): void {
P
Peng Lyu 已提交
248
		this.body = document.createElement('div');
P
Peng Lyu 已提交
249 250 251
		DOM.addClass(this.body, 'cell-list-container');
		this.createCellList();
		DOM.append(parent, this.body);
P
Peng Lyu 已提交
252 253
	}

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

R
Rob Lourens 已提交
257
		const dndController = this._register(new CellDragAndDropController(this));
P
Peng Lyu 已提交
258
		const renders = [
259
			this.instantiationService.createInstance(CodeCellRenderer, this, this.renderedEditors, dndController),
260
			this.instantiationService.createInstance(MarkdownCellRenderer, this.contextKeyService, this, dndController, this.renderedEditors),
P
Peng Lyu 已提交
261 262
		];

J
João Moreno 已提交
263
		this.list = this.instantiationService.createInstance(
R
rebornix 已提交
264
			NotebookCellList,
P
Peng Lyu 已提交
265 266 267 268
			'NotebookCellList',
			this.body,
			this.instantiationService.createInstance(NotebookCellListDelegate),
			renders,
R
rebornix 已提交
269
			this.contextKeyService,
P
Peng Lyu 已提交
270 271
			{
				setRowLineHeight: false,
R
rebornix 已提交
272
				setRowHeight: false,
P
Peng Lyu 已提交
273 274 275
				supportDynamicHeights: true,
				horizontalScrolling: false,
				keyboardSupport: false,
R
rebornix 已提交
276
				mouseSupport: true,
P
Peng Lyu 已提交
277
				multipleSelectionSupport: false,
R
rebornix 已提交
278
				enableKeyboardNavigation: true,
279
				additionalScrollHeight: 0,
280
				transformOptimization: false,
281
				styleController: (_suffix: string) => { return this.list!; },
P
Peng Lyu 已提交
282 283 284 285 286 287 288 289 290 291
				overrideStyles: {
					listBackground: editorBackground,
					listActiveSelectionBackground: editorBackground,
					listActiveSelectionForeground: foreground,
					listFocusAndSelectionBackground: editorBackground,
					listFocusAndSelectionForeground: foreground,
					listFocusBackground: editorBackground,
					listFocusForeground: foreground,
					listHoverForeground: foreground,
					listHoverBackground: editorBackground,
292 293
					listHoverOutline: focusBorder,
					listFocusOutline: focusBorder,
294 295 296 297
					listInactiveSelectionBackground: editorBackground,
					listInactiveSelectionForeground: foreground,
					listInactiveFocusBackground: editorBackground,
					listInactiveFocusOutline: editorBackground,
J
João Moreno 已提交
298 299
				},
				accessibilityProvider: {
R
rebornix 已提交
300 301 302 303
					getAriaLabel() { return null; },
					getWidgetAriaLabel() {
						return nls.localize('notebookTreeAriaLabel', "Notebook");
					}
P
Peng Lyu 已提交
304
				}
R
rebornix 已提交
305
			},
P
Peng Lyu 已提交
306
		);
P
Peng Lyu 已提交
307

308
		this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
309
		this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
310 311 312 313 314
		this._register(this.webview.onMessage(message => {
			if (this.viewModel) {
				this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message);
			}
		}));
R
rebornix 已提交
315
		this.list.rowsContainer.appendChild(this.webview.element);
R
rebornix 已提交
316

P
Peng Lyu 已提交
317
		this._register(this.list);
318
		this._register(combinedDisposable(...renders));
R
rebornix 已提交
319 320 321

		// transparent cover
		this.webviewTransparentCover = DOM.append(this.list.rowsContainer, $('.webview-cover'));
322
		this.webviewTransparentCover.style.display = 'none';
R
rebornix 已提交
323

R
rebornix 已提交
324
		this._register(DOM.addStandardDisposableGenericMouseDownListner(this._rootElement, (e: StandardMouseEvent) => {
R
rebornix 已提交
325 326 327 328 329
			if (DOM.hasClass(e.target, 'slider') && this.webviewTransparentCover) {
				this.webviewTransparentCover.style.display = 'block';
			}
		}));

R
rebornix 已提交
330
		this._register(DOM.addStandardDisposableGenericMouseUpListner(this._rootElement, (e: StandardMouseEvent) => {
R
rebornix 已提交
331 332
			if (this.webviewTransparentCover) {
				// no matter when
333
				this.webviewTransparentCover.style.display = 'none';
R
rebornix 已提交
334 335 336
			}
		}));

R
rebornix 已提交
337 338 339 340 341 342 343 344 345 346 347 348
		this._register(this.list.onMouseDown(e => {
			if (e.element) {
				this._onMouseDown.fire({ event: e.browserEvent, target: e.element });
			}
		}));

		this._register(this.list.onMouseUp(e => {
			if (e.element) {
				this._onMouseUp.fire({ event: e.browserEvent, target: e.element });
			}
		}));

P
Peng Lyu 已提交
349 350
	}

R
rebornix 已提交
351 352 353 354
	getDomNode() {
		return this._rootElement;
	}

355 356 357 358
	getControl() {
		return this.control;
	}

R
rebornix 已提交
359 360 361 362
	getInnerWebview(): Webview | undefined {
		return this.webview?.webview;
	}

363 364 365 366 367 368 369 370 371 372
	setVisible(visible: boolean, group?: IEditorGroup): void {
		if (visible) {
			this.increaseEditorTitlebarZindex();
		} else {
			this.clearEditorTitlebarZindex();
		}

		super.setVisible(visible, group);
	}

373 374
	onWillHide() {
		if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) {
R
rebornix 已提交
375
			this.saveEditorViewState(this.input);
376 377
		}

R
rebornix 已提交
378
		this.editorFocus?.set(false);
379 380
		if (this.webview) {
			this.localStore.clear();
R
rebornix 已提交
381
			this.list?.rowsContainer.removeChild(this.webview?.element);
382 383 384 385
			this.webview?.dispose();
			this.webview = null;
		}

R
rebornix 已提交
386
		this.list?.clear();
387
		super.onHide();
P
Peng Lyu 已提交
388 389
	}

R
rebornix 已提交
390 391 392 393 394 395 396 397 398 399 400 401
	setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
		super.setEditorVisible(visible, group);
		this.groupListener.value = ((group as IEditorGroupView).onWillCloseEditor(e => this.onWillCloseEditorInGroup(e)));
	}

	private onWillCloseEditorInGroup(e: IEditorCloseEvent): void {
		const editor = e.editor;
		if (!(editor instanceof NotebookEditorInput)) {
			return; // only handle files
		}

		if (editor === this.input) {
402
			this.clearEditorTitlebarZindex();
R
rebornix 已提交
403
			this.saveEditorViewState(editor);
404 405 406
		}
	}

R
rebornix 已提交
407 408 409
	focus() {
		super.focus();
		this.editorFocus?.set(true);
410
		this.list?.domFocus();
R
rebornix 已提交
411 412
	}

R
rebornix 已提交
413
	async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
R
rebornix 已提交
414
		if (this.input instanceof NotebookEditorInput) {
R
rebornix 已提交
415
			this.saveEditorViewState(this.input);
R
rebornix 已提交
416 417
		}

R
rebornix 已提交
418 419
		await super.setInput(input, options, token);
		const model = await input.resolve();
P
Peng Lyu 已提交
420

421
		if (this.notebookViewModel === undefined || !this.notebookViewModel.equal(model.notebook) || this.webview === null) {
422 423
			this.detachModel();
			await this.attachModel(input, model);
R
rebornix 已提交
424
		}
P
Peng Lyu 已提交
425

426 427 428
		// reveal cell if editor options tell to do so
		if (options instanceof NotebookEditorOptions && options.cellOptions) {
			const cellOptions = options.cellOptions;
R
rebornix 已提交
429
			const cell = this.notebookViewModel!.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString());
430
			if (cell) {
431
				this.selectElement(cell);
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
				this.revealInCenterIfOutsideViewport(cell);
				const editor = this.renderedEditors.get(cell)!;
				if (editor) {
					if (cellOptions.options?.selection) {
						const { selection } = cellOptions.options;
						editor.setSelection({
							...selection,
							endLineNumber: selection.endLineNumber || selection.startLineNumber,
							endColumn: selection.endColumn || selection.startColumn
						});
					}
					if (!cellOptions.options?.preserveFocus) {
						editor.focus();
					}
				}
447 448
			}
		}
R
rebornix 已提交
449
	}
450

R
rebornix 已提交
451 452 453 454
	clearInput(): void {
		super.clearInput();
	}

R
rebornix 已提交
455 456
	private detachModel() {
		this.localStore.clear();
R
rebornix 已提交
457
		this.list?.detachViewModel();
R
rebornix 已提交
458 459
		this.viewModel?.dispose();
		// avoid event
R
rebornix 已提交
460 461 462
		this.notebookViewModel = undefined;
		this.webview?.clearInsets();
		this.webview?.clearPreloadsCache();
R
rebornix 已提交
463
		this.list?.clear();
R
rebornix 已提交
464
	}
R
rebornix 已提交
465

466 467 468 469 470 471
	private updateForMetadata(): void {
		this.editorEditable?.set(!!this.viewModel!.metadata?.editable);
		this.editorRunnable?.set(!!this.viewModel!.metadata?.runnable);
		DOM.toggleClass(this.getDomNode(), 'notebook-editor-editable', !!this.viewModel!.metadata?.editable);
	}

R
rebornix 已提交
472 473
	private async attachModel(input: NotebookEditorInput, model: NotebookEditorModel) {
		if (!this.webview) {
474
			this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
R
rebornix 已提交
475 476
			this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element);
		}
477

478 479
		await this.webview.waitForInitialization();

R
rebornix 已提交
480
		this.eventDispatcher = new NotebookEventDispatcher();
481
		this.viewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model.notebook, this.eventDispatcher, this.getLayoutInfo());
R
rebornix 已提交
482
		this.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
483

484
		this.updateForMetadata();
R
rebornix 已提交
485
		this.localStore.add(this.eventDispatcher.onDidChangeMetadata((e) => {
486
			this.updateForMetadata();
R
rebornix 已提交
487 488
		}));

R
rebornix 已提交
489
		// restore view states, including contributions
R
rebornix 已提交
490
		const viewState = this.loadTextEditorViewState(input);
R
rebornix 已提交
491 492 493

		{
			// restore view state
R
rebornix 已提交
494
			this.viewModel.restoreEditorViewState(viewState);
R
rebornix 已提交
495 496

			// contribution state restore
R
rebornix 已提交
497 498 499 500 501 502 503 504 505 506

			const contributionsState = viewState?.contributionsState || {};
			const keys = Object.keys(this._contributions);
			for (let i = 0, len = keys.length; i < len; i++) {
				const id = keys[i];
				const contribution = this._contributions[id];
				if (typeof contribution.restoreViewState === 'function') {
					contribution.restoreViewState(contributionsState[id]);
				}
			}
R
rebornix 已提交
507
		}
508

R
rebornix 已提交
509
		this.webview?.updateRendererPreloads(this.viewModel.renderers);
R
rebornix 已提交
510 511 512

		this.localStore.add(this.list!.onWillScroll(e => {
			this.webview!.updateViewScrollTop(-e.scrollTop, []);
R
rebornix 已提交
513
			this.webviewTransparentCover!.style.top = `${e.scrollTop}px`;
R
rebornix 已提交
514 515 516
		}));

		this.localStore.add(this.list!.onDidChangeContentHeight(() => {
517 518 519 520 521 522
			DOM.scheduleAtNextAnimationFrame(() => {
				const scrollTop = this.list?.scrollTop || 0;
				const scrollHeight = this.list?.scrollHeight || 0;
				this.webview!.element.style.height = `${scrollHeight}px`;

				if (this.webview?.insetMapping) {
R
rebornix 已提交
523 524
					let updateItems: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[] = [];
					let removedItems: IOutput[] = [];
525 526 527 528 529 530 531 532
					this.webview?.insetMapping.forEach((value, key) => {
						const cell = value.cell;
						const viewIndex = this.list?.getViewIndex(cell);

						if (viewIndex === undefined) {
							return;
						}

R
rebornix 已提交
533 534 535 536 537
						if (cell.outputs.indexOf(key) < 0) {
							// output is already gone
							removedItems.push(key);
						}

538 539 540 541 542 543 544 545 546 547
						const cellTop = this.list?.getAbsoluteTopOfElement(cell) || 0;
						if (this.webview!.shouldUpdateInset(cell, key, cellTop)) {
							updateItems.push({
								cell: cell,
								output: key,
								cellTop: cellTop
							});
						}
					});

R
rebornix 已提交
548 549
					removedItems.forEach(output => this.webview?.removeInset(output));

550 551
					if (updateItems.length) {
						this.webview?.updateViewScrollTop(-scrollTop, updateItems);
R
rebornix 已提交
552
					}
R
rebornix 已提交
553
				}
554
			});
R
rebornix 已提交
555 556
		}));

R
rebornix 已提交
557
		this.list!.attachViewModel(this.viewModel);
R
rebornix 已提交
558 559 560
		this.localStore.add(this.list!.onDidRemoveOutput(output => {
			this.removeInset(output);
		}));
561 562 563
		this.localStore.add(this.list!.onDidHideOutput(output => {
			this.hideInset(output);
		}));
R
rebornix 已提交
564 565

		this.list!.layout();
R
rebornix 已提交
566

R
rebornix 已提交
567
		// restore list state at last, it must be after list layout
R
rebornix 已提交
568
		this.restoreListViewState(viewState);
569 570
	}

R
rebornix 已提交
571
	private restoreListViewState(viewState: INotebookEditorViewState | undefined): void {
R
rebornix 已提交
572 573 574 575 576 577 578
		if (viewState?.scrollPosition !== undefined) {
			this.list!.scrollTop = viewState!.scrollPosition.top;
			this.list!.scrollLeft = viewState!.scrollPosition.left;
		} else {
			this.list!.scrollTop = 0;
			this.list!.scrollLeft = 0;
		}
579

580
		const focusIdx = typeof viewState?.focus === 'number' ? viewState.focus : 0;
R
rebornix 已提交
581 582 583 584 585 586
		if (focusIdx < this.list!.length) {
			this.list!.setFocus([focusIdx]);
			this.list!.setSelection([focusIdx]);
		} else if (this.list!.length > 0) {
			this.list!.setFocus([0]);
		}
587 588 589 590 591 592 593

		if (viewState?.editorFocused) {
			this.list?.focusView();
			const cell = this.notebookViewModel?.viewCells[focusIdx];
			if (cell) {
				cell.focusMode = CellFocusMode.Editor;
			}
594
		}
P
Peng Lyu 已提交
595 596
	}

R
rebornix 已提交
597
	private saveEditorViewState(input: NotebookEditorInput): void {
R
npe  
rebornix 已提交
598
		if (this.group && this.notebookViewModel) {
R
rebornix 已提交
599
			const state = this.notebookViewModel.geteEditorViewState();
R
rebornix 已提交
600 601
			if (this.list) {
				state.scrollPosition = { left: this.list.scrollLeft, top: this.list.scrollTop };
602
				let cellHeights: { [key: number]: number } = {};
R
rebornix 已提交
603
				for (let i = 0; i < this.viewModel!.length; i++) {
R
rebornix 已提交
604
					const elm = this.viewModel!.viewCells[i] as CellViewModel;
605 606 607 608 609 610 611 612
					if (elm.cellKind === CellKind.Code) {
						cellHeights[i] = elm.layoutInfo.totalHeight;
					} else {
						cellHeights[i] = 0;
					}
				}

				state.cellTotalHeights = cellHeights;
613 614 615

				const focus = this.list.getFocus()[0];
				if (focus) {
616 617 618 619 620 621 622 623
					const element = this.notebookViewModel!.viewCells[focus];
					const itemDOM = this.list?.domElementOfElement(element!);
					let editorFocused = false;
					if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
						editorFocused = true;
					}

					state.editorFocused = editorFocused;
624 625
					state.focus = focus;
				}
R
rebornix 已提交
626 627
			}

R
rebornix 已提交
628
			// Save contribution view states
R
rebornix 已提交
629 630 631 632 633 634 635 636
			const contributionsState: { [key: string]: any } = {};

			const keys = Object.keys(this._contributions);
			for (const id of keys) {
				const contribution = this._contributions[id];
				if (typeof contribution.saveViewState === 'function') {
					contributionsState[id] = contribution.saveViewState();
				}
R
rebornix 已提交
637 638
			}

R
rebornix 已提交
639
			state.contributionsState = contributionsState;
R
rebornix 已提交
640
			this.editorMemento.saveEditorState(this.group, input.resource, state);
R
rebornix 已提交
641 642 643 644 645
		}
	}

	private loadTextEditorViewState(input: NotebookEditorInput): INotebookEditorViewState | undefined {
		if (this.group) {
R
rebornix 已提交
646
			return this.editorMemento.loadEditorState(this.group, input.resource);
R
rebornix 已提交
647 648 649 650 651 652 653
		}

		return;
	}

	layout(dimension: DOM.Dimension): void {
		this.dimension = new DOM.Dimension(dimension.width, dimension.height);
R
rebornix 已提交
654 655
		DOM.toggleClass(this._rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600);
		DOM.toggleClass(this._rootElement, 'narrow-width', dimension.width < 600);
R
rebornix 已提交
656
		DOM.size(this.body, dimension.width, dimension.height);
R
rebornix 已提交
657
		this.list?.updateOptions({ additionalScrollHeight: this.scrollBeyondLastLine ? dimension.height : 0 });
R
rebornix 已提交
658
		this.list?.layout(dimension.height, dimension.width);
R
rebornix 已提交
659 660 661 662 663 664

		if (this.webviewTransparentCover) {
			this.webviewTransparentCover.style.height = `${dimension.height}px`;
			this.webviewTransparentCover.style.width = `${dimension.width}px`;
		}

R
rebornix 已提交
665
		this.eventDispatcher?.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
R
rebornix 已提交
666 667 668 669
	}

	protected saveState(): void {
		if (this.input instanceof NotebookEditorInput) {
R
rebornix 已提交
670
			this.saveEditorViewState(this.input);
R
rebornix 已提交
671 672 673 674 675 676 677
		}

		super.saveState();
	}

	//#endregion

R
rebornix 已提交
678 679
	//#region Editor Features

R
rebornix 已提交
680
	selectElement(cell: ICellViewModel) {
R
rebornix 已提交
681 682
		this.list?.selectElement(cell);
		// this.viewModel!.selectionHandles = [cell.handle];
R
rebornix 已提交
683 684
	}

R
rebornix 已提交
685
	revealInView(cell: ICellViewModel) {
R
rebornix 已提交
686
		this.list?.revealElementInView(cell);
R
rebornix 已提交
687 688
	}

R
rebornix 已提交
689
	revealInCenterIfOutsideViewport(cell: ICellViewModel) {
R
rebornix 已提交
690
		this.list?.revealElementInCenterIfOutsideViewport(cell);
R
rebornix 已提交
691 692
	}

R
rebornix 已提交
693
	revealInCenter(cell: ICellViewModel) {
R
rebornix 已提交
694
		this.list?.revealElementInCenter(cell);
R
rebornix 已提交
695 696
	}

R
rebornix 已提交
697
	revealLineInView(cell: ICellViewModel, line: number): void {
R
rebornix 已提交
698
		this.list?.revealElementLineInView(cell, line);
R
rebornix 已提交
699
	}
R
rebornix 已提交
700

R
rebornix 已提交
701
	revealLineInCenter(cell: ICellViewModel, line: number) {
R
rebornix 已提交
702
		this.list?.revealElementLineInCenter(cell, line);
R
rebornix 已提交
703 704
	}

R
rebornix 已提交
705
	revealLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number) {
R
rebornix 已提交
706
		this.list?.revealElementLineInCenterIfOutsideViewport(cell, line);
R
rebornix 已提交
707 708
	}

R
rebornix 已提交
709
	revealRangeInView(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
710
		this.list?.revealElementRangeInView(cell, range);
R
rebornix 已提交
711 712
	}

R
rebornix 已提交
713
	revealRangeInCenter(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
714
		this.list?.revealElementRangeInCenter(cell, range);
R
rebornix 已提交
715 716
	}

R
rebornix 已提交
717
	revealRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
718
		this.list?.revealElementRangeInCenterIfOutsideViewport(cell, range);
R
rebornix 已提交
719 720
	}

R
rebornix 已提交
721
	setCellSelection(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
722
		this.list?.setCellSelection(cell, range);
R
rebornix 已提交
723 724
	}

R
rebornix 已提交
725 726 727 728
	changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
		return this.notebookViewModel?.changeDecorations(callback);
	}

R
rebornix 已提交
729
	setHiddenAreas(_ranges: ICellRange[]): boolean {
R
rebornix 已提交
730
		return this.list!.setHiddenAreas(_ranges, true);
R
rebornix 已提交
731 732
	}

R
rebornix 已提交
733 734
	//#endregion

R
rebornix 已提交
735 736 737 738 739 740 741 742 743
	//#region Mouse Events
	private readonly _onMouseUp: Emitter<INotebookEditorMouseEvent> = this._register(new Emitter<INotebookEditorMouseEvent>());
	public readonly onMouseUp: Event<INotebookEditorMouseEvent> = this._onMouseUp.event;

	private readonly _onMouseDown: Emitter<INotebookEditorMouseEvent> = this._register(new Emitter<INotebookEditorMouseEvent>());
	public readonly onMouseDown: Event<INotebookEditorMouseEvent> = this._onMouseDown.event;

	//#endregion

R
rebornix 已提交
744
	//#region Cell operations
745
	async layoutNotebookCell(cell: ICellViewModel, height: number): Promise<void> {
746 747 748 749 750 751
		const viewIndex = this.list!.getViewIndex(cell);
		if (viewIndex === undefined) {
			// the cell is hidden
			return;
		}

R
rebornix 已提交
752
		let relayout = (cell: ICellViewModel, height: number) => {
R
rebornix 已提交
753
			this.list?.updateElementHeight2(cell, height);
R
rebornix 已提交
754 755
		};

756
		let r: () => void;
R
rebornix 已提交
757
		DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
758
			relayout(cell, height);
759
			r();
R
rebornix 已提交
760
		});
761 762

		return new Promise(resolve => { r = resolve; });
763 764
	}

R
rebornix 已提交
765
	insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction: 'above' | 'below' = 'above', initialText: string = '', ui: boolean = false): CellViewModel | null {
766 767 768 769
		if (!this.notebookViewModel!.metadata.editable) {
			return null;
		}

R
rebornix 已提交
770
		const newLanguages = this.notebookViewModel!.languages;
771
		const language = (type === CellKind.Code && newLanguages && newLanguages.length) ? newLanguages[0] : 'markdown';
772
		const index = cell ? this.notebookViewModel!.getCellIndex(cell) : 0;
R
rebornix 已提交
773
		const nextIndex = ui ? this.notebookViewModel!.getNextVisibleCellIndex(index) : index + 1;
774
		const insertIndex = cell ?
R
rebornix 已提交
775
			(direction === 'above' ? index : nextIndex) :
776
			index;
R
rebornix 已提交
777
		const newCell = this.notebookViewModel!.createCell(insertIndex, initialText.split(/\r?\n/g), language, type, true);
778
		return newCell;
P
Peng Lyu 已提交
779 780
	}

K
kieferrm 已提交
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824
	private isAtEOL(p: IPosition, lines: string[]) {
		const line = lines[p.lineNumber - 1];
		return line.length + 1 === p.column;
	}

	private pushIfAbsent(positions: IPosition[], p: IPosition) {
		const last = positions.length > 0 ? positions[positions.length - 1] : undefined;
		if (!last || last.lineNumber !== p.lineNumber || last.column !== p.column) {
			positions.push(p);
		}
	}

	/**
	 * Add split point at the beginning and the end;
	 * Move end of line split points to the beginning of the next line;
	 * Avoid duplicate split points
	 */
	private splitPointsToBoundaries(splitPoints: IPosition[], lines: string[]): IPosition[] | null {
		const boundaries: IPosition[] = [];

		// split points need to be sorted
		splitPoints = splitPoints.sort((l, r) => {
			const lineDiff = l.lineNumber - r.lineNumber;
			const columnDiff = l.column - r.column;
			return lineDiff !== 0 ? lineDiff : columnDiff;
		});

		// eat-up any split point at the beginning, i.e. we ignore the split point at the very beginning
		this.pushIfAbsent(boundaries, new Position(1, 1));

		for (let sp of splitPoints) {
			if (this.isAtEOL(sp, lines) && sp.lineNumber < lines.length) {
				sp = new Position(sp.lineNumber + 1, 1);
			}
			this.pushIfAbsent(boundaries, sp);
		}

		// eat-up any split point at the beginning, i.e. we ignore the split point at the very end
		this.pushIfAbsent(boundaries, new Position(lines.length, lines[lines.length - 1].length + 1));

		// if we only have two then they describe the whole range and nothing needs to be split
		return boundaries.length > 2 ? boundaries : null;
	}

825
	private computeCellLinesContents(cell: IEditableCellViewModel, splitPoints: IPosition[]): string[] | null {
K
kieferrm 已提交
826 827 828 829 830
		const lines = cell.getLinesContent();
		const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, lines);
		if (!rangeBoundaries) {
			return null;
		}
831
		const newLineModels: string[] = [];
K
kieferrm 已提交
832 833 834 835
		for (let i = 1; i < rangeBoundaries.length; i++) {
			const start = rangeBoundaries[i - 1];
			const end = rangeBoundaries[i];

836
			newLineModels.push(cell.textModel.getValueInRange(new Range(start.lineNumber, start.column, end.lineNumber, end.column)));
K
kieferrm 已提交
837
		}
838

K
kieferrm 已提交
839 840 841
		return newLineModels;
	}

842
	async splitNotebookCell(cell: ICellViewModel): Promise<CellViewModel[] | null> {
K
kieferrm 已提交
843 844 845 846
		if (!this.notebookViewModel!.metadata.editable) {
			return null;
		}

K
kieferrm 已提交
847
		let splitPoints = cell.getSelectionsStartPosition();
K
kieferrm 已提交
848
		if (splitPoints && splitPoints.length > 0) {
849 850 851 852 853 854
			await cell.resolveTextModel();

			if (!cell.hasModel()) {
				return null;
			}

K
kieferrm 已提交
855 856 857 858
			let newLinesContents = this.computeCellLinesContents(cell, splitPoints);
			if (newLinesContents) {

				// update the contents of the first cell
859 860
				cell.textModel.applyEdits([
					{ range: cell.textModel.getFullModelRange(), text: newLinesContents[0] }
R
rebornix 已提交
861
				], true);
K
kieferrm 已提交
862 863 864 865 866 867 868 869 870 871

				// create new cells based on the new text models
				const language = cell.model.language;
				const kind = cell.cellKind;
				let insertIndex = this.notebookViewModel!.getCellIndex(cell) + 1;
				const newCells = [];
				for (let j = 1; j < newLinesContents.length; j++, insertIndex++) {
					newCells.push(this.notebookViewModel!.createCell(insertIndex, newLinesContents[j], language, kind, true));
				}
				return newCells;
K
kieferrm 已提交
872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
			}
		}

		return null;
	}

	async joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise<ICellViewModel | null> {
		if (!this.notebookViewModel!.metadata.editable) {
			return null;
		}

		if (constraint && cell.cellKind !== constraint) {
			return null;
		}

		const index = this.notebookViewModel!.getCellIndex(cell);
		if (index === 0 && direction === 'above') {
			return null;
		}

		if (index === this.notebookViewModel!.length - 1 && direction === 'below') {
			return null;
		}

		if (direction === 'above') {
			const above = this.notebookViewModel!.viewCells[index - 1];
			if (constraint && above.cellKind !== constraint) {
				return null;
			}
901 902 903 904 905 906 907 908 909 910 911 912 913

			await above.resolveTextModel();
			if (!above.hasModel()) {
				return null;
			}

			const insertContent = cell.getText();
			const aboveCellLineCount = above.textModel.getLineCount();
			const aboveCellLastLineEndColumn = above.textModel.getLineLength(aboveCellLineCount);
			above.textModel.applyEdits([
				{ range: new Range(aboveCellLineCount, aboveCellLastLineEndColumn + 1, aboveCellLineCount, aboveCellLastLineEndColumn + 1), text: insertContent }
			]);

K
kieferrm 已提交
914 915 916 917 918 919 920
			await this.deleteNotebookCell(cell);
			return above;
		} else {
			const below = this.notebookViewModel!.viewCells[index + 1];
			if (constraint && below.cellKind !== constraint) {
				return null;
			}
921 922 923 924 925 926 927 928 929 930 931 932 933 934

			await cell.resolveTextModel();
			if (!cell.hasModel()) {
				return null;
			}

			const insertContent = below.getText();

			const cellLineCount = cell.textModel.getLineCount();
			const cellLastLineEndColumn = cell.textModel.getLineLength(cellLineCount);
			cell.textModel.applyEdits([
				{ range: new Range(cellLineCount, cellLastLineEndColumn + 1, cellLineCount, cellLastLineEndColumn + 1), text: insertContent }
			]);

K
kieferrm 已提交
935 936 937 938 939
			await this.deleteNotebookCell(below);
			return cell;
		}
	}

940
	async deleteNotebookCell(cell: ICellViewModel): Promise<boolean> {
941 942 943 944
		if (!this.notebookViewModel!.metadata.editable) {
			return false;
		}

R
rebornix 已提交
945
		const index = this.notebookViewModel!.getCellIndex(cell);
R
rebornix 已提交
946
		this.notebookViewModel!.deleteCell(index, true);
947
		return true;
R
rebornix 已提交
948 949
	}

950
	async moveCellDown(cell: ICellViewModel): Promise<boolean> {
951 952 953 954
		if (!this.notebookViewModel!.metadata.editable) {
			return false;
		}

R
rebornix 已提交
955
		const index = this.notebookViewModel!.getCellIndex(cell);
R
rebornix 已提交
956
		if (index === this.notebookViewModel!.length - 1) {
957
			return false;
R
rebornix 已提交
958 959
		}

960
		const newIdx = index + 1;
961
		return this.moveCellToIndex(index, newIdx);
962 963
	}

964
	async moveCellUp(cell: ICellViewModel): Promise<boolean> {
965 966 967 968
		if (!this.notebookViewModel!.metadata.editable) {
			return false;
		}

R
rebornix 已提交
969
		const index = this.notebookViewModel!.getCellIndex(cell);
R
rebornix 已提交
970
		if (index === 0) {
971
			return false;
R
rebornix 已提交
972 973
		}

974
		const newIdx = index - 1;
975
		return this.moveCellToIndex(index, newIdx);
976 977
	}

978
	async moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise<boolean> {
979 980 981 982
		if (!this.notebookViewModel!.metadata.editable) {
			return false;
		}

R
Rob Lourens 已提交
983
		if (cell === relativeToCell) {
984
			return false;
R
Rob Lourens 已提交
985 986 987 988 989
		}

		const originalIdx = this.notebookViewModel!.getCellIndex(cell);
		const relativeToIndex = this.notebookViewModel!.getCellIndex(relativeToCell);

R
Rob Lourens 已提交
990 991 992 993 994
		let newIdx = direction === 'above' ? relativeToIndex : relativeToIndex + 1;
		if (originalIdx < newIdx) {
			newIdx--;
		}

R
Rob Lourens 已提交
995 996 997
		return this.moveCellToIndex(originalIdx, newIdx);
	}

998
	private async moveCellToIndex(index: number, newIdx: number): Promise<boolean> {
R
Rob Lourens 已提交
999 1000 1001 1002
		if (index === newIdx) {
			return false;
		}

R
rebornix 已提交
1003
		if (!this.notebookViewModel!.moveCellToIdx(index, newIdx, true)) {
1004
			throw new Error('Notebook Editor move cell, index out of range');
1005 1006
		}

1007
		let r: (val: boolean) => void;
1008
		DOM.scheduleAtNextAnimationFrame(() => {
1009
			this.list?.revealElementInView(this.notebookViewModel!.viewCells[newIdx]);
1010
			r(true);
1011
		});
1012 1013

		return new Promise(resolve => { r = resolve; });
1014 1015
	}

R
rebornix 已提交
1016
	editNotebookCell(cell: CellViewModel): void {
1017 1018 1019 1020
		if (!cell.getEvaluatedMetadata(this.notebookViewModel!.metadata).editable) {
			return;
		}

1021
		cell.editState = CellEditState.Editing;
R
rebornix 已提交
1022 1023

		this.renderedEditors.get(cell)?.focus();
P
Peng Lyu 已提交
1024 1025
	}

R
rebornix 已提交
1026
	saveNotebookCell(cell: ICellViewModel): void {
1027
		cell.editState = CellEditState.Preview;
P
Peng Lyu 已提交
1028 1029
	}

R
rebornix 已提交
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
	getActiveCell() {
		let elements = this.list?.getFocusedElements();

		if (elements && elements.length) {
			return elements[0];
		}

		return undefined;
	}

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
	cancelNotebookExecution(): void {
		if (!this.notebookViewModel!.currentTokenSource) {
			throw new Error('Notebook is not executing');
		}


		this.notebookViewModel!.currentTokenSource.cancel();
		this.notebookViewModel!.currentTokenSource = undefined;
	}

	async executeNotebook(): Promise<void> {
1051 1052 1053 1054
		if (!this.notebookViewModel!.metadata.runnable) {
			return;
		}

1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
		// return this.progressService.showWhile(this._executeNotebook());
		return this._executeNotebook();
	}

	async _executeNotebook(): Promise<void> {
		if (this.notebookViewModel!.currentTokenSource) {
			return;
		}

		const tokenSource = new CancellationTokenSource();
		try {
			this.editorExecutingNotebook!.set(true);
			this.notebookViewModel!.currentTokenSource = tokenSource;

			for (let cell of this.notebookViewModel!.viewCells) {
				if (cell.cellKind === CellKind.Code) {
					await this._executeNotebookCell(cell, tokenSource);
				}
			}
		} finally {
			this.editorExecutingNotebook!.set(false);
			this.notebookViewModel!.currentTokenSource = undefined;
			tokenSource.dispose();
		}
	}

	cancelNotebookCellExecution(cell: ICellViewModel): void {
		if (!cell.currentTokenSource) {
			throw new Error('Cell is not executing');
		}

		cell.currentTokenSource.cancel();
		cell.currentTokenSource = undefined;
	}

1090
	async executeNotebookCell(cell: ICellViewModel): Promise<void> {
1091 1092 1093 1094
		if (!cell.getEvaluatedMetadata(this.notebookViewModel!.metadata).runnable) {
			return;
		}

1095
		const tokenSource = new CancellationTokenSource();
1096 1097 1098 1099 1100 1101 1102
		try {
			this._executeNotebookCell(cell, tokenSource);
		} finally {
			tokenSource.dispose();
		}
	}

R
Rob Lourens 已提交
1103
	private async _executeNotebookCell(cell: ICellViewModel, tokenSource: CancellationTokenSource): Promise<void> {
1104
		try {
1105
			cell.currentTokenSource = tokenSource;
R
rebornix 已提交
1106
			const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
1107 1108 1109 1110
			if (provider) {
				const viewType = provider.id;
				const notebookUri = CellUri.parse(cell.uri)?.notebook;
				if (notebookUri) {
1111
					return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, tokenSource.token);
1112 1113 1114
				}
			}
		} finally {
1115
			cell.currentTokenSource = undefined;
1116 1117 1118
		}
	}

1119
	focusNotebookCell(cell: ICellViewModel, focusEditor: boolean, focusOuput?: boolean) {
R
rebornix 已提交
1120
		if (focusEditor) {
R
rebornix 已提交
1121
			this.selectElement(cell);
R
rebornix 已提交
1122
			this.list?.focusView();
R
rebornix 已提交
1123

1124
			cell.editState = CellEditState.Editing;
R
rebornix 已提交
1125
			cell.focusMode = CellFocusMode.Editor;
1126
			this.revealInCenterIfOutsideViewport(cell);
1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
		} else if (focusOuput) {
			this.selectElement(cell);
			this.list?.focusView();

			if (!this.webview) {
				return;
			}
			this.webview.focusOutput(cell.id);

			cell.editState = CellEditState.Preview;
			cell.focusMode = CellFocusMode.Container;
			this.revealInCenterIfOutsideViewport(cell);
R
rebornix 已提交
1139
		} else {
R
rebornix 已提交
1140
			let itemDOM = this.list?.domElementOfElement(cell);
R
rebornix 已提交
1141 1142 1143
			if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
				(document.activeElement as HTMLElement).blur();
			}
1144

1145
			cell.editState = CellEditState.Preview;
1146
			cell.focusMode = CellFocusMode.Container;
R
rebornix 已提交
1147

R
rebornix 已提交
1148
			this.selectElement(cell);
1149
			this.revealInCenterIfOutsideViewport(cell);
R
rebornix 已提交
1150
			this.list?.focusView();
R
rebornix 已提交
1151 1152 1153
		}
	}

R
rebornix 已提交
1154 1155 1156 1157
	//#endregion

	//#region MISC

R
rebornix 已提交
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168
	getLayoutInfo(): NotebookLayoutInfo {
		if (!this.list) {
			throw new Error('Editor is not initalized successfully');
		}

		return {
			width: this.dimension!.width,
			height: this.dimension!.height,
			fontInfo: this.fontInfo!
		};
	}
R
rebornix 已提交
1169

R
rebornix 已提交
1170 1171
	triggerScroll(event: IMouseWheelEvent) {
		this.list?.triggerScrollFromMouseWheelEvent(event);
R
rebornix 已提交
1172 1173
	}

R
rebornix 已提交
1174
	createInset(cell: CodeCellViewModel, output: IOutput, shadowContent: string, offset: number) {
R
rebornix 已提交
1175 1176
		if (!this.webview) {
			return;
R
rebornix 已提交
1177 1178
		}

R
rebornix 已提交
1179
		let preloads = this.notebookViewModel!.renderers;
R
rebornix 已提交
1180

1181
		if (!this.webview!.insetMapping.has(output)) {
R
rebornix 已提交
1182
			let cellTop = this.list?.getAbsoluteTopOfElement(cell) || 0;
1183
			this.webview!.createInset(cell, output, cellTop, offset, shadowContent, preloads);
R
rebornix 已提交
1184
		} else {
R
rebornix 已提交
1185
			let cellTop = this.list?.getAbsoluteTopOfElement(cell) || 0;
R
rebornix 已提交
1186 1187
			let scrollTop = this.list?.scrollTop || 0;

1188
			this.webview!.updateViewScrollTop(-scrollTop, [{ cell: cell, output: output, cellTop: cellTop }]);
R
rebornix 已提交
1189
		}
R
rebornix 已提交
1190
	}
R
rebornix 已提交
1191

R
rebornix 已提交
1192 1193 1194 1195 1196 1197 1198 1199
	removeInset(output: IOutput) {
		if (!this.webview) {
			return;
		}

		this.webview!.removeInset(output);
	}

1200 1201 1202 1203 1204 1205 1206 1207
	hideInset(output: IOutput) {
		if (!this.webview) {
			return;
		}

		this.webview!.hideInset(output);
	}

R
rebornix 已提交
1208 1209
	getOutputRenderer(): OutputRenderer {
		return this.outputRenderer;
R
rebornix 已提交
1210
	}
1211

1212 1213 1214 1215
	postMessage(message: any) {
		this.webview?.webview.sendMessage(message);
	}

R
rebornix 已提交
1216
	//#endregion
1217

R
rebornix 已提交
1218 1219 1220 1221 1222 1223
	//#region Editor Contributions
	public getContribution<T extends INotebookEditorContribution>(id: string): T {
		return <T>(this._contributions[id] || null);
	}

	//#endregion
R
rebornix 已提交
1224

R
rebornix 已提交
1225 1226 1227 1228 1229 1230 1231 1232 1233 1234
	dispose() {
		const keys = Object.keys(this._contributions);
		for (let i = 0, len = keys.length; i < len; i++) {
			const contributionId = keys[i];
			this._contributions[contributionId].dispose();
		}

		super.dispose();
	}

1235 1236 1237 1238 1239
	toJSON(): any {
		return {
			notebookHandle: this.viewModel?.handle
		};
	}
P
Peng Lyu 已提交
1240 1241 1242 1243
}

const embeddedEditorBackground = 'walkThrough.embeddedEditorBackground';

1244 1245 1246 1247 1248 1249
export const focusedCellIndicator = registerColor('notebook.focusedCellIndicator', {
	light: new Color(new RGBA(102, 175, 224)),
	dark: new Color(new RGBA(12, 125, 157)),
	hc: new Color(new RGBA(0, 73, 122))
}, nls.localize('notebook.focusedCellIndicator', "The color of the focused notebook cell indicator."));

1250 1251
export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', {
	dark: new Color(new RGBA(255, 255, 255, 0.06)),
M
Miguel Solorio 已提交
1252
	light: new Color(new RGBA(237, 239, 249)),
1253 1254 1255 1256
	hc: null
}
	, nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background."));

R
Rob Lourens 已提交
1257
// TODO currently also used for toolbar border, if we keep all of this, pick a generic name
R
rebornix 已提交
1258 1259 1260 1261 1262 1263
export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeperator', {
	dark: Color.fromHex('#808080').transparent(0.35),
	light: Color.fromHex('#808080').transparent(0.35),
	hc: contrastBorder
}, nls.localize('cellToolbarSeperator', "The color of seperator in Cell bottom toolbar"));

1264

P
Peng Lyu 已提交
1265 1266 1267
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) {
R
rebornix 已提交
1268
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .monaco-editor-background,
R
Rob Lourens 已提交
1269 1270
			.monaco-workbench .part.editor > .content .notebook-editor .cell .margin-view-overlays,
			.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-statusbar-container { background: ${color}; }`);
1271
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-drag-image .cell-editor-container > div { background: ${color} !important; }`);
P
Peng Lyu 已提交
1272 1273 1274
	}
	const link = theme.getColor(textLinkForeground);
	if (link) {
R
rebornix 已提交
1275
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output a,
M
Miguel Solorio 已提交
1276
			.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown a { color: ${link};} `);
P
Peng Lyu 已提交
1277 1278 1279
	}
	const activeLink = theme.getColor(textLinkActiveForeground);
	if (activeLink) {
R
rebornix 已提交
1280
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output a:hover,
R
Rob Lourens 已提交
1281
			.monaco-workbench .part.editor > .content .notebook-editor .cell .output a:active { color: ${activeLink}; }`);
P
Peng Lyu 已提交
1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299
	}
	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}; }`);
	}
1300

1301 1302 1303
	const containerBackground = theme.getColor(notebookOutputContainerColor);
	if (containerBackground) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { background-color: ${containerBackground}; }`);
1304
	}
1305

R
Rob Lourens 已提交
1306 1307 1308
	const editorBackgroundColor = theme.getColor(editorBackground);
	if (editorBackgroundColor) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container { border-top: solid 1px ${editorBackgroundColor}; }`);
R
Rob Lourens 已提交
1309
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > .monaco-toolbar { background-color: ${editorBackgroundColor}; }`);
R
Rob Lourens 已提交
1310
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.cell-drag-image { background-color: ${editorBackgroundColor}; }`);
1311 1312
	}

R
rebornix 已提交
1313 1314 1315 1316
	const cellToolbarSeperator = theme.getColor(CELL_TOOLBAR_SEPERATOR);
	if (cellToolbarSeperator) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container .seperator { background-color: ${cellToolbarSeperator} }`);
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container .seperator-short { background-color: ${cellToolbarSeperator} }`);
R
Rob Lourens 已提交
1317
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > .monaco-toolbar { border: solid 1px ${cellToolbarSeperator}; }`);
1318 1319
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row:hover .notebook-cell-focus-indicator,
			.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.cell-output-hover .notebook-cell-focus-indicator { border-color: ${cellToolbarSeperator}; }`);
R
Rob Lourens 已提交
1320 1321 1322 1323 1324
	}

	const focusedCellIndicatorColor = theme.getColor(focusedCellIndicator);
	if (focusedCellIndicatorColor) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.focused .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`);
1325
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`);
R
Rob Lourens 已提交
1326
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .cell-insertion-indicator { background-color: ${focusedCellIndicatorColor}; }`);
R
Rob Lourens 已提交
1327
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.cell-editor-focus .cell-editor-part:before { outline: solid 1px ${focusedCellIndicatorColor}; }`);
R
rebornix 已提交
1328 1329
	}

R
rebornix 已提交
1330 1331 1332 1333 1334 1335 1336
	// const widgetShadowColor = theme.getColor(widgetShadow);
	// if (widgetShadowColor) {
	// 	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .monaco-toolbar {
	// 		box-shadow:  0 0 8px 4px ${widgetShadowColor}
	// 	}`)
	// }

1337
	// Cell Margin
M
Miguel Solorio 已提交
1338
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row  > div.cell { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN}px; }`);
R
rebornix 已提交
1339
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { padding-top: ${EDITOR_TOP_MARGIN}px; }`);
R
Rob Lourens 已提交
1340
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`);
R
rebornix 已提交
1341
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container { width: calc(100% - ${CELL_MARGIN * 2 + CELL_RUN_GUTTER}px); margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`);
R
Rob Lourens 已提交
1342

1343
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .markdown-cell-row .cell .cell-editor-part { margin-left: ${CELL_RUN_GUTTER}px; }`);
R
rebornix 已提交
1344
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row  > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`);
R
Rob Lourens 已提交
1345
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`);
R
Rob Lourens 已提交
1346
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .cell-insertion-indicator { left: ${CELL_MARGIN + CELL_RUN_GUTTER}px; right: ${CELL_MARGIN}px; }`);
1347
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-drag-image .cell-editor-container > div { padding: ${EDITOR_TOP_PADDING}px 16px ${EDITOR_BOTTOM_PADDING}px 16px; }`);
R
Rob Lourens 已提交
1348
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .notebook-cell-focus-indicator { left: ${CELL_MARGIN}px; }`);
P
Peng Lyu 已提交
1349
});