notebookEditor.ts 32.2 KB
Newer Older
P
Peng Lyu 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

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

const $ = DOM.$;
R
rebornix 已提交
49 50
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
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 已提交
66
export class NotebookCodeEditors implements ICompositeCodeEditor {
67

68
	private readonly _disposables = new DisposableStore();
69 70 71 72
	private readonly _onDidChangeActiveEditor = new Emitter<this>();
	readonly onDidChangeActiveEditor: Event<this> = this._onDidChangeActiveEditor.event;

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

	dispose(): void {
		this._onDidChangeActiveEditor.dispose();
		this._disposables.dispose();
	}
83 84 85

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

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

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IThemeService themeService: IThemeService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
P
Peng Lyu 已提交
116
		@IStorageService storageService: IStorageService,
R
rebornix 已提交
117
		@INotebookService private notebookService: INotebookService,
118
		@IEditorGroupsService editorGroupService: IEditorGroupsService,
R
rebornix 已提交
119
		@IConfigurationService private readonly configurationService: IConfigurationService,
R
rebornix 已提交
120
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
121
		// @IEditorProgressService private readonly progressService: IEditorProgressService,
P
Peng Lyu 已提交
122 123
	) {
		super(NotebookEditor.ID, telemetryService, themeService, storageService);
R
rebornix 已提交
124 125

		this.editorMemento = this.getEditorMemento<INotebookEditorViewState>(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
R
rebornix 已提交
126
		this.outputRenderer = new OutputRenderer(this, this.instantiationService);
R
rebornix 已提交
127
		this.findWidget = this.instantiationService.createInstance(NotebookFindWidget, this);
128
		this.findWidget.updateTheme(this.themeService.getColorTheme());
R
rebornix 已提交
129 130
	}

R
rebornix 已提交
131 132 133 134
	get viewModel() {
		return this.notebookViewModel;
	}

P
Peng Lyu 已提交
135 136 137 138 139 140 141 142
	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 已提交
143
	//#region Editor Core
R
rebornix 已提交
144

R
rebornix 已提交
145 146 147 148 149

	public get isNotebookEditor() {
		return true;
	}

P
Peng Lyu 已提交
150 151 152
	protected createEditor(parent: HTMLElement): void {
		this.rootElement = DOM.append(parent, $('.notebook-editor'));
		this.createBody(this.rootElement);
153
		this.generateFontInfo();
R
rebornix 已提交
154
		this.editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService);
155
		this.editorFocus.set(true);
R
rebornix 已提交
156 157 158 159 160 161 162
		this._register(this.onDidFocus(() => {
			this.editorFocus?.set(true);
		}));

		this._register(this.onDidBlur(() => {
			this.editorFocus?.set(false);
		}));
R
rebornix 已提交
163 164 165

		this.editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService);
		this.editorEditable.set(true);
166
		this.editorExecutingNotebook = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.contextKeyService);
167 168 169 170 171
	}

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

	private createBody(parent: HTMLElement): void {
P
Peng Lyu 已提交
175
		this.body = document.createElement('div');
P
Peng Lyu 已提交
176 177 178
		DOM.addClass(this.body, 'cell-list-container');
		this.createCellList();
		DOM.append(parent, this.body);
R
rebornix 已提交
179
		DOM.append(parent, this.findWidget.getDomNode());
P
Peng Lyu 已提交
180 181
	}

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

P
Peng Lyu 已提交
185
		const renders = [
R
rebornix 已提交
186 187
			this.instantiationService.createInstance(CodeCellRenderer, this, this.contextKeyService, this.renderedEditors),
			this.instantiationService.createInstance(MarkdownCellRenderer, this.contextKeyService, this),
P
Peng Lyu 已提交
188 189
		];

J
João Moreno 已提交
190
		this.list = this.instantiationService.createInstance(
R
rebornix 已提交
191
			NotebookCellList,
P
Peng Lyu 已提交
192 193 194 195
			'NotebookCellList',
			this.body,
			this.instantiationService.createInstance(NotebookCellListDelegate),
			renders,
R
rebornix 已提交
196
			this.contextKeyService,
P
Peng Lyu 已提交
197 198
			{
				setRowLineHeight: false,
R
rebornix 已提交
199
				setRowHeight: false,
P
Peng Lyu 已提交
200 201 202
				supportDynamicHeights: true,
				horizontalScrolling: false,
				keyboardSupport: false,
R
rebornix 已提交
203
				mouseSupport: true,
P
Peng Lyu 已提交
204
				multipleSelectionSupport: false,
R
rebornix 已提交
205
				enableKeyboardNavigation: true,
206
				additionalScrollHeight: 0,
207
				styleController: (_suffix: string) => { return this.list!; },
P
Peng Lyu 已提交
208 209 210 211 212 213 214 215 216 217
				overrideStyles: {
					listBackground: editorBackground,
					listActiveSelectionBackground: editorBackground,
					listActiveSelectionForeground: foreground,
					listFocusAndSelectionBackground: editorBackground,
					listFocusAndSelectionForeground: foreground,
					listFocusBackground: editorBackground,
					listFocusForeground: foreground,
					listHoverForeground: foreground,
					listHoverBackground: editorBackground,
218 219
					listHoverOutline: focusBorder,
					listFocusOutline: focusBorder,
220 221 222 223
					listInactiveSelectionBackground: editorBackground,
					listInactiveSelectionForeground: foreground,
					listInactiveFocusBackground: editorBackground,
					listInactiveFocusOutline: editorBackground,
J
João Moreno 已提交
224 225 226
				},
				accessibilityProvider: {
					getAriaLabel() { return null; }
P
Peng Lyu 已提交
227
				}
R
rebornix 已提交
228
			},
P
Peng Lyu 已提交
229
		);
P
Peng Lyu 已提交
230

231
		this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
232
		this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
233 234 235 236 237
		this._register(this.webview.onMessage(message => {
			if (this.viewModel) {
				this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message);
			}
		}));
R
rebornix 已提交
238
		this.list.rowsContainer.appendChild(this.webview.element);
R
rebornix 已提交
239

P
Peng Lyu 已提交
240
		this._register(this.list);
R
rebornix 已提交
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258

		// transparent cover
		this.webviewTransparentCover = DOM.append(this.list.rowsContainer, $('.webview-cover'));
		this.webviewTransparentCover.style.display = 'none';

		this._register(DOM.addStandardDisposableGenericMouseDownListner(this.rootElement, (e: StandardMouseEvent) => {
			if (DOM.hasClass(e.target, 'slider') && this.webviewTransparentCover) {
				this.webviewTransparentCover.style.display = 'block';
			}
		}));

		this._register(DOM.addStandardDisposableGenericMouseUpListner(this.rootElement, (e: StandardMouseEvent) => {
			if (this.webviewTransparentCover) {
				// no matter when
				this.webviewTransparentCover.style.display = 'none';
			}
		}));

P
Peng Lyu 已提交
259 260
	}

261 262 263 264
	getControl() {
		return this.control;
	}

R
rebornix 已提交
265 266 267 268
	getInnerWebview(): Webview | undefined {
		return this.webview?.webview;
	}

P
Peng Lyu 已提交
269
	onHide() {
R
rebornix 已提交
270
		this.editorFocus?.set(false);
271 272
		if (this.webview) {
			this.localStore.clear();
R
rebornix 已提交
273
			this.list?.rowsContainer.removeChild(this.webview?.element);
274 275 276 277
			this.webview?.dispose();
			this.webview = null;
		}

R
rebornix 已提交
278
		this.list?.clear();
279
		super.onHide();
P
Peng Lyu 已提交
280 281
	}

R
rebornix 已提交
282 283 284 285 286 287 288 289 290 291 292 293 294
	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) {
			this.saveTextEditorViewState(editor);
295 296 297
		}
	}

R
rebornix 已提交
298 299 300 301 302
	focus() {
		super.focus();
		this.editorFocus?.set(true);
	}

R
rebornix 已提交
303
	async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
R
rebornix 已提交
304 305 306 307
		if (this.input instanceof NotebookEditorInput) {
			this.saveTextEditorViewState(this.input);
		}

R
rebornix 已提交
308 309
		await super.setInput(input, options, token);
		const model = await input.resolve();
P
Peng Lyu 已提交
310

311 312 313
		if (this.notebookViewModel === undefined || !this.notebookViewModel.equal(model) || this.webview === null) {
			this.detachModel();
			await this.attachModel(input, model);
R
rebornix 已提交
314
		}
P
Peng Lyu 已提交
315

316 317 318
		// reveal cell if editor options tell to do so
		if (options instanceof NotebookEditorOptions && options.cellOptions) {
			const cellOptions = options.cellOptions;
R
rebornix 已提交
319
			const cell = this.notebookViewModel!.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString());
320
			if (cell) {
321
				this.selectElement(cell);
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
				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();
					}
				}
337 338
			}
		}
R
rebornix 已提交
339
	}
340

R
rebornix 已提交
341 342 343 344 345 346 347 348
	clearInput(): void {
		if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) {
			this.saveTextEditorViewState(this.input);
		}

		super.clearInput();
	}

R
rebornix 已提交
349 350
	private detachModel() {
		this.localStore.clear();
R
rebornix 已提交
351
		this.list?.detachViewModel();
R
rebornix 已提交
352 353 354 355
		this.notebookViewModel?.dispose();
		this.notebookViewModel = undefined;
		this.webview?.clearInsets();
		this.webview?.clearPreloadsCache();
356
		this.findWidget.clear();
R
rebornix 已提交
357
		this.list?.clear();
R
rebornix 已提交
358
	}
R
rebornix 已提交
359

R
rebornix 已提交
360 361
	private async attachModel(input: NotebookEditorInput, model: NotebookEditorModel) {
		if (!this.webview) {
362
			this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
R
rebornix 已提交
363 364
			this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element);
		}
365

R
rebornix 已提交
366
		this.eventDispatcher = new NotebookEventDispatcher();
367
		this.notebookViewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model, this.eventDispatcher, this.getLayoutInfo());
368
		this.editorEditable?.set(!!this.notebookViewModel.metadata?.editable);
R
rebornix 已提交
369
		this.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
R
rebornix 已提交
370
		const viewState = this.loadTextEditorViewState(input);
R
rebornix 已提交
371
		this.notebookViewModel.restoreEditorViewState(viewState);
372

R
rebornix 已提交
373 374 375 376
		this.localStore.add(this.eventDispatcher.onDidChangeMetadata((e) => {
			this.editorEditable?.set(e.source.editable);
		}));

R
rebornix 已提交
377 378 379 380
		this.webview?.updateRendererPreloads(this.notebookViewModel.renderers);

		this.localStore.add(this.list!.onWillScroll(e => {
			this.webview!.updateViewScrollTop(-e.scrollTop, []);
R
rebornix 已提交
381
			this.webviewTransparentCover!.style.top = `${e.scrollTop}px`;
R
rebornix 已提交
382 383 384 385 386 387
		}));

		this.localStore.add(this.list!.onDidChangeContentHeight(() => {
			const scrollTop = this.list?.scrollTop || 0;
			const scrollHeight = this.list?.scrollHeight || 0;
			this.webview!.element.style.height = `${scrollHeight}px`;
R
rebornix 已提交
388
			let updateItems: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[] = [];
R
rebornix 已提交
389 390 391

			if (this.webview?.insetMapping) {
				this.webview?.insetMapping.forEach((value, key) => {
R
rebornix 已提交
392 393
					const cell = value.cell;
					const cellTop = this.list?.getAbsoluteTopOfElement(cell) || 0;
R
rebornix 已提交
394 395 396 397 398 399
					if (this.webview!.shouldUpdateInset(cell, key, cellTop)) {
						updateItems.push({
							cell: cell,
							output: key,
							cellTop: cellTop
						});
R
rebornix 已提交
400
					}
R
rebornix 已提交
401
				});
402

R
rebornix 已提交
403 404 405 406 407 408
				if (updateItems.length) {
					this.webview?.updateViewScrollTop(-scrollTop, updateItems);
				}
			}
		}));

R
rebornix 已提交
409 410 411 412 413 414
		this.list!.attachViewModel(this.notebookViewModel);
		this.localStore.add(this.list!.onDidRemoveOutput(output => {
			this.removeInset(output);
		}));

		this.list!.layout();
R
rebornix 已提交
415 416 417 418 419 420 421 422

		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;
		}
P
Peng Lyu 已提交
423 424
	}

R
rebornix 已提交
425
	private saveTextEditorViewState(input: NotebookEditorInput): void {
R
npe  
rebornix 已提交
426
		if (this.group && this.notebookViewModel) {
R
rebornix 已提交
427
			const state = this.notebookViewModel.saveEditorViewState();
R
rebornix 已提交
428 429
			if (this.list) {
				state.scrollPosition = { left: this.list.scrollLeft, top: this.list.scrollTop };
430 431 432 433 434 435 436 437 438 439 440
				let cellHeights: { [key: number]: number } = {};
				for (let i = 0; i < this.list.length; i++) {
					const elm = this.list.element(i)!;
					if (elm.cellKind === CellKind.Code) {
						cellHeights[i] = elm.layoutInfo.totalHeight;
					} else {
						cellHeights[i] = 0;
					}
				}

				state.cellTotalHeights = cellHeights;
R
rebornix 已提交
441 442
			}

R
rebornix 已提交
443
			this.editorMemento.saveEditorState(this.group, input.resource, state);
R
rebornix 已提交
444 445 446 447 448
		}
	}

	private loadTextEditorViewState(input: NotebookEditorInput): INotebookEditorViewState | undefined {
		if (this.group) {
R
rebornix 已提交
449
			return this.editorMemento.loadEditorState(this.group, input.resource);
R
rebornix 已提交
450 451 452 453 454 455 456 457 458 459
		}

		return;
	}

	layout(dimension: DOM.Dimension): void {
		this.dimension = new DOM.Dimension(dimension.width, dimension.height);
		DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600);
		DOM.toggleClass(this.rootElement, 'narrow-width', dimension.width < 600);
		DOM.size(this.body, dimension.width, dimension.height);
460
		this.list?.updateOptions({ additionalScrollHeight: dimension.height });
R
rebornix 已提交
461
		this.list?.layout(dimension.height, dimension.width);
R
rebornix 已提交
462 463 464 465 466 467

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

R
rebornix 已提交
468
		this.eventDispatcher?.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
R
rebornix 已提交
469 470 471 472 473 474 475 476 477 478 479 480
	}

	protected saveState(): void {
		if (this.input instanceof NotebookEditorInput) {
			this.saveTextEditorViewState(this.input);
		}

		super.saveState();
	}

	//#endregion

R
rebornix 已提交
481 482
	//#region Editor Features

R
rebornix 已提交
483
	selectElement(cell: ICellViewModel) {
R
rebornix 已提交
484
		this.list?.selectElement(cell);
R
rebornix 已提交
485 486
	}

R
rebornix 已提交
487
	revealInView(cell: ICellViewModel) {
R
rebornix 已提交
488
		this.list?.revealElementInView(cell);
R
rebornix 已提交
489 490
	}

R
rebornix 已提交
491
	revealInCenterIfOutsideViewport(cell: ICellViewModel) {
R
rebornix 已提交
492
		this.list?.revealElementInCenterIfOutsideViewport(cell);
R
rebornix 已提交
493 494
	}

R
rebornix 已提交
495
	revealInCenter(cell: ICellViewModel) {
R
rebornix 已提交
496
		this.list?.revealElementInCenter(cell);
R
rebornix 已提交
497 498
	}

R
rebornix 已提交
499
	revealLineInView(cell: ICellViewModel, line: number): void {
R
rebornix 已提交
500
		this.list?.revealElementLineInView(cell, line);
R
rebornix 已提交
501
	}
R
rebornix 已提交
502

R
rebornix 已提交
503
	revealLineInCenter(cell: ICellViewModel, line: number) {
R
rebornix 已提交
504
		this.list?.revealElementLineInCenter(cell, line);
R
rebornix 已提交
505 506
	}

R
rebornix 已提交
507
	revealLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number) {
R
rebornix 已提交
508
		this.list?.revealElementLineInCenterIfOutsideViewport(cell, line);
R
rebornix 已提交
509 510
	}

R
rebornix 已提交
511
	revealRangeInView(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
512
		this.list?.revealElementRangeInView(cell, range);
R
rebornix 已提交
513 514
	}

R
rebornix 已提交
515
	revealRangeInCenter(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
516
		this.list?.revealElementRangeInCenter(cell, range);
R
rebornix 已提交
517 518
	}

R
rebornix 已提交
519
	revealRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
520
		this.list?.revealElementRangeInCenterIfOutsideViewport(cell, range);
R
rebornix 已提交
521 522
	}

R
rebornix 已提交
523
	setCellSelection(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
524
		this.list?.setCellSelection(cell, range);
R
rebornix 已提交
525 526
	}

R
rebornix 已提交
527 528 529 530
	changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
		return this.notebookViewModel?.changeDecorations(callback);
	}

R
rebornix 已提交
531 532 533 534
	setHiddenAreas(_ranges: ICellRange[]): boolean {
		return this.list!.setHiddenAreas(_ranges);
	}

R
rebornix 已提交
535 536
	//#endregion

R
rebornix 已提交
537 538 539 540 541 542 543 544
	//#region Find Delegate

	public showFind() {
		this.findWidget.reveal();
	}

	public hideFind() {
		this.findWidget.hide();
R
rebornix 已提交
545
		this.focus();
R
rebornix 已提交
546 547 548 549
	}

	//#endregion

R
rebornix 已提交
550
	//#region Cell operations
551
	async layoutNotebookCell(cell: ICellViewModel, height: number): Promise<void> {
R
rebornix 已提交
552
		let relayout = (cell: ICellViewModel, height: number) => {
R
rebornix 已提交
553
			this.list?.updateElementHeight2(cell, height);
R
rebornix 已提交
554 555
		};

556
		let r: () => void;
R
rebornix 已提交
557
		DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
558
			relayout(cell, height);
559
			r();
R
rebornix 已提交
560
		});
561 562

		return new Promise(resolve => { r = resolve; });
563 564
	}

R
rebornix 已提交
565
	async insertNotebookCell(cell: ICellViewModel, type: CellKind, direction: 'above' | 'below', initialText: string = ''): Promise<void> {
R
rebornix 已提交
566 567
		const newLanguages = this.notebookViewModel!.languages;
		const language = newLanguages && newLanguages.length ? newLanguages[0] : 'markdown';
R
rebornix 已提交
568
		const index = this.notebookViewModel!.getCellIndex(cell);
P
Peng Lyu 已提交
569
		const insertIndex = direction === 'above' ? index : index + 1;
R
rebornix 已提交
570
		const newCell = this.notebookViewModel!.createCell(insertIndex, initialText.split(/\r?\n/g), language, type, true);
R
rebornix 已提交
571
		this.list?.focusElement(newCell);
P
Peng Lyu 已提交
572

R
rebornix 已提交
573
		if (type === CellKind.Markdown) {
574
			newCell.editState = CellEditState.Editing;
P
Peng Lyu 已提交
575
		}
R
rebornix 已提交
576

577
		let r: () => void;
R
rebornix 已提交
578
		DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
579
			this.list?.revealElementInCenterIfOutsideViewport(cell);
580
			r();
R
rebornix 已提交
581
		});
582 583

		return new Promise(resolve => { r = resolve; });
P
Peng Lyu 已提交
584 585
	}

R
rebornix 已提交
586
	async deleteNotebookCell(cell: ICellViewModel): Promise<void> {
587
		(cell as CellViewModel).save();
R
rebornix 已提交
588
		const index = this.notebookViewModel!.getCellIndex(cell);
R
rebornix 已提交
589
		this.notebookViewModel!.deleteCell(index, true);
R
rebornix 已提交
590 591
	}

592
	async moveCellDown(cell: ICellViewModel): Promise<void> {
R
rebornix 已提交
593
		const index = this.notebookViewModel!.getCellIndex(cell);
R
rebornix 已提交
594 595 596 597
		if (index === this.notebookViewModel!.viewCells.length - 1) {
			return;
		}

598
		const newIdx = index + 1;
599
		return this.moveCellToIndex(index, newIdx);
600 601
	}

602
	async moveCellUp(cell: ICellViewModel): Promise<void> {
R
rebornix 已提交
603
		const index = this.notebookViewModel!.getCellIndex(cell);
R
rebornix 已提交
604 605 606 607
		if (index === 0) {
			return;
		}

608
		const newIdx = index - 1;
609
		return this.moveCellToIndex(index, newIdx);
610 611
	}

612
	private async moveCellToIndex(index: number, newIdx: number): Promise<void> {
R
rebornix 已提交
613
		if (!this.notebookViewModel!.moveCellToIdx(index, newIdx, true)) {
614 615 616
			return;
		}

617
		let r: () => void;
618
		DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
619
			this.list?.revealElementInCenterIfOutsideViewport(this.notebookViewModel!.viewCells[index + 1]);
620
			r();
621
		});
622 623

		return new Promise(resolve => { r = resolve; });
624 625
	}

R
rebornix 已提交
626
	editNotebookCell(cell: CellViewModel): void {
627
		cell.editState = CellEditState.Editing;
R
rebornix 已提交
628 629

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

R
rebornix 已提交
632
	saveNotebookCell(cell: ICellViewModel): void {
633
		cell.editState = CellEditState.Preview;
P
Peng Lyu 已提交
634 635
	}

R
rebornix 已提交
636 637 638 639 640 641 642 643 644 645
	getActiveCell() {
		let elements = this.list?.getFocusedElements();

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

		return undefined;
	}

646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
	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> {
		// 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;
	}

692
	async executeNotebookCell(cell: ICellViewModel): Promise<void> {
693
		const tokenSource = new CancellationTokenSource();
694 695 696 697 698 699 700 701
		try {
			this._executeNotebookCell(cell, tokenSource);
		} finally {
			tokenSource.dispose();
		}
	}

	async _executeNotebookCell(cell: ICellViewModel, tokenSource: CancellationTokenSource): Promise<void> {
702
		try {
703
			cell.currentTokenSource = tokenSource;
R
rebornix 已提交
704
			const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
705 706 707 708
			if (provider) {
				const viewType = provider.id;
				const notebookUri = CellUri.parse(cell.uri)?.notebook;
				if (notebookUri) {
709
					return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, tokenSource.token);
710 711 712
				}
			}
		} finally {
713
			cell.currentTokenSource = undefined;
714 715 716
		}
	}

R
rebornix 已提交
717
	focusNotebookCell(cell: ICellViewModel, focusEditor: boolean) {
R
rebornix 已提交
718
		if (focusEditor) {
R
rebornix 已提交
719
			this.selectElement(cell);
R
rebornix 已提交
720
			this.list?.focusView();
R
rebornix 已提交
721

722
			cell.editState = CellEditState.Editing;
R
rebornix 已提交
723
			cell.focusMode = CellFocusMode.Editor;
724
			this.revealInCenterIfOutsideViewport(cell);
R
rebornix 已提交
725
		} else {
R
rebornix 已提交
726
			let itemDOM = this.list?.domElementOfElement(cell);
R
rebornix 已提交
727 728 729
			if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
				(document.activeElement as HTMLElement).blur();
			}
730

731
			cell.editState = CellEditState.Preview;
732
			cell.focusMode = CellFocusMode.Editor;
R
rebornix 已提交
733

R
rebornix 已提交
734
			this.selectElement(cell);
735
			this.revealInCenterIfOutsideViewport(cell);
R
rebornix 已提交
736
			this.list?.focusView();
R
rebornix 已提交
737 738 739
		}
	}

R
rebornix 已提交
740 741 742 743
	//#endregion

	//#region MISC

R
rebornix 已提交
744 745 746 747 748 749 750 751 752 753 754
	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 已提交
755

R
rebornix 已提交
756 757
	triggerScroll(event: IMouseWheelEvent) {
		this.list?.triggerScrollFromMouseWheelEvent(event);
R
rebornix 已提交
758 759
	}

R
rebornix 已提交
760
	createInset(cell: CodeCellViewModel, output: IOutput, shadowContent: string, offset: number) {
R
rebornix 已提交
761 762
		if (!this.webview) {
			return;
R
rebornix 已提交
763 764
		}

R
rebornix 已提交
765
		let preloads = this.notebookViewModel!.renderers;
R
rebornix 已提交
766

767
		if (!this.webview!.insetMapping.has(output)) {
R
rebornix 已提交
768
			let cellTop = this.list?.getAbsoluteTopOfElement(cell) || 0;
769
			this.webview!.createInset(cell, output, cellTop, offset, shadowContent, preloads);
R
rebornix 已提交
770
		} else {
R
rebornix 已提交
771
			let cellTop = this.list?.getAbsoluteTopOfElement(cell) || 0;
R
rebornix 已提交
772 773
			let scrollTop = this.list?.scrollTop || 0;

774
			this.webview!.updateViewScrollTop(-scrollTop, [{ cell: cell, output: output, cellTop: cellTop }]);
R
rebornix 已提交
775
		}
R
rebornix 已提交
776
	}
R
rebornix 已提交
777

R
rebornix 已提交
778 779 780 781 782 783 784 785
	removeInset(output: IOutput) {
		if (!this.webview) {
			return;
		}

		this.webview!.removeInset(output);
	}

R
rebornix 已提交
786 787
	getOutputRenderer(): OutputRenderer {
		return this.outputRenderer;
R
rebornix 已提交
788
	}
789

790 791 792 793
	postMessage(message: any) {
		this.webview?.webview.sendMessage(message);
	}

R
rebornix 已提交
794
	//#endregion
795 796 797 798 799 800

	toJSON(): any {
		return {
			notebookHandle: this.viewModel?.handle
		};
	}
P
Peng Lyu 已提交
801 802 803 804
}

const embeddedEditorBackground = 'walkThrough.embeddedEditorBackground';

805 806 807 808 809 810
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."));

811 812 813 814 815 816 817
export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', {
	dark: new Color(new RGBA(255, 255, 255, 0.06)),
	light: new Color(new RGBA(228, 230, 241)),
	hc: null
}
	, nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background."));

R
rebornix 已提交
818 819 820 821 822 823
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"));

824

P
Peng Lyu 已提交
825 826 827
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 已提交
828 829
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .monaco-editor-background,
			.monaco-workbench .part.editor > .content .notebook-editor .cell .margin-view-overlays { background: ${color}; }`);
P
Peng Lyu 已提交
830 831 832
	}
	const link = theme.getColor(textLinkForeground);
	if (link) {
R
Rob Lourens 已提交
833
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .output a { color: ${link}; }`);
P
Peng Lyu 已提交
834 835 836
	}
	const activeLink = theme.getColor(textLinkActiveForeground);
	if (activeLink) {
R
Rob Lourens 已提交
837 838
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .output a:hover,
			.monaco-workbench .part.editor > .content .notebook-editor .cell .output a:active { color: ${activeLink}; }`);
P
Peng Lyu 已提交
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
	}
	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}; }`);
	}
857

858
	const containerBackground = theme.getColor(notebookOutputContainerColor);
859

860 861
	if (containerBackground) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { background-color: ${containerBackground}; }`);
862
	}
863

864 865 866 867 868 869
	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}; }`);
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.selected .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`);
	}

R
rebornix 已提交
870 871 872 873 874 875
	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} }`);
	}

876
	// Cell Margin
R
rebornix 已提交
877
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { margin: ${EDITOR_TOP_MARGIN}px ${CELL_MARGIN}px 0px ${CELL_MARGIN}px; }`);
878
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`);
R
rebornix 已提交
879
	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 已提交
880

R
Rob Lourens 已提交
881
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { width: calc(100% - ${CELL_RUN_GUTTER}px); }`);
882
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .markdown-editor-container { margin-left: ${CELL_RUN_GUTTER}px; }`);
R
Rob Lourens 已提交
883 884
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`);
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`);
P
Peng Lyu 已提交
885
});