notebookEditor.ts 31.0 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';
P
Peng Lyu 已提交
8
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
9 10 11
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 已提交
12
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
R
rebornix 已提交
13
import 'vs/css!./notebook';
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 33
import { NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget';
import { CellEditState, CellFocusMode, ICellViewModel, INotebookEditor, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } 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';
P
Peng Lyu 已提交
46 47

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

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

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

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

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

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

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

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

		this.editorMemento = this.getEditorMemento<INotebookEditorViewState>(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
R
rebornix 已提交
122
		this.outputRenderer = new OutputRenderer(this, this.instantiationService);
R
rebornix 已提交
123
		this.findWidget = this.instantiationService.createInstance(NotebookFindWidget, this);
124
		this.findWidget.updateTheme(this.themeService.getColorTheme());
R
rebornix 已提交
125 126
	}

R
rebornix 已提交
127 128 129 130
	get viewModel() {
		return this.notebookViewModel;
	}

P
Peng Lyu 已提交
131 132 133 134 135 136 137 138
	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 已提交
139
	//#region Editor Core
R
rebornix 已提交
140

R
rebornix 已提交
141 142 143 144 145

	public get isNotebookEditor() {
		return true;
	}

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

		this._register(this.onDidBlur(() => {
			this.editorFocus?.set(false);
		}));
R
rebornix 已提交
159 160 161

		this.editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService);
		this.editorEditable.set(true);
162 163 164 165 166
	}

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

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

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

P
Peng Lyu 已提交
180
		const renders = [
R
rebornix 已提交
181 182
			this.instantiationService.createInstance(CodeCellRenderer, this, this.contextKeyService, this.renderedEditors),
			this.instantiationService.createInstance(MarkdownCellRenderer, this.contextKeyService, this),
P
Peng Lyu 已提交
183 184
		];

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

223
		this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
224
		this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
225 226 227 228 229
		this._register(this.webview.onMessage(message => {
			if (this.viewModel) {
				this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message);
			}
		}));
R
rebornix 已提交
230
		this.list.rowsContainer.appendChild(this.webview.element);
P
Peng Lyu 已提交
231 232 233
		this._register(this.list);
	}

234 235 236 237
	getControl() {
		return this.control;
	}

P
Peng Lyu 已提交
238
	onHide() {
R
rebornix 已提交
239
		this.editorFocus?.set(false);
240 241
		if (this.webview) {
			this.localStore.clear();
R
rebornix 已提交
242
			this.list?.rowsContainer.removeChild(this.webview?.element);
243 244 245 246 247 248
			this.webview?.dispose();
			this.webview = null;
		}

		this.list?.splice(0, this.list?.length);
		super.onHide();
P
Peng Lyu 已提交
249 250
	}

R
rebornix 已提交
251 252 253 254 255 256 257 258 259 260 261 262 263
	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);
264 265 266
		}
	}

R
rebornix 已提交
267 268 269 270 271
	focus() {
		super.focus();
		this.editorFocus?.set(true);
	}

R
rebornix 已提交
272
	async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
R
rebornix 已提交
273 274 275 276
		if (this.input instanceof NotebookEditorInput) {
			this.saveTextEditorViewState(this.input);
		}

R
rebornix 已提交
277 278
		await super.setInput(input, options, token);
		const model = await input.resolve();
P
Peng Lyu 已提交
279

280 281 282
		if (this.notebookViewModel === undefined || !this.notebookViewModel.equal(model) || this.webview === null) {
			this.detachModel();
			await this.attachModel(input, model);
R
rebornix 已提交
283
		}
P
Peng Lyu 已提交
284

285 286 287
		// reveal cell if editor options tell to do so
		if (options instanceof NotebookEditorOptions && options.cellOptions) {
			const cellOptions = options.cellOptions;
R
rebornix 已提交
288
			const cell = this.notebookViewModel!.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString());
289
			if (cell) {
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
				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();
					}
				}
305 306
			}
		}
R
rebornix 已提交
307
	}
308

R
rebornix 已提交
309 310 311 312 313 314 315 316
	clearInput(): void {
		if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) {
			this.saveTextEditorViewState(this.input);
		}

		super.clearInput();
	}

R
rebornix 已提交
317 318 319 320 321 322
	private detachModel() {
		this.localStore.clear();
		this.notebookViewModel?.dispose();
		this.notebookViewModel = undefined;
		this.webview?.clearInsets();
		this.webview?.clearPreloadsCache();
323
		this.findWidget.clear();
R
rebornix 已提交
324
	}
R
rebornix 已提交
325

R
rebornix 已提交
326 327
	private async attachModel(input: NotebookEditorInput, model: NotebookEditorModel) {
		if (!this.webview) {
328
			this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
R
rebornix 已提交
329 330
			this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element);
		}
331

R
rebornix 已提交
332
		this.eventDispatcher = new NotebookEventDispatcher();
333
		this.notebookViewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model, this.eventDispatcher, this.getLayoutInfo());
334
		this.editorEditable?.set(!!this.notebookViewModel.metadata?.editable);
R
rebornix 已提交
335
		this.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
R
rebornix 已提交
336
		const viewState = this.loadTextEditorViewState(input);
R
rebornix 已提交
337
		this.notebookViewModel.restoreEditorViewState(viewState);
338

R
rebornix 已提交
339 340 341 342 343 344 345 346
		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;
		}

R
rebornix 已提交
347 348 349 350
		this.localStore.add(this.eventDispatcher.onDidChangeMetadata((e) => {
			this.editorEditable?.set(e.source.editable);
		}));

R
rebornix 已提交
351 352 353
		this.localStore.add(this.notebookViewModel.onDidChangeViewCells((e) => {
			if (e.synchronous) {
				e.splices.reverse().forEach((diff) => {
354 355 356 357 358 359 360 361
					// remove output in the webview
					for (let i = diff[0]; i < diff[0] + diff[1]; i++) {
						const cell = this.list?.element(i);
						cell?.cell.outputs.forEach(output => {
							this.removeInset(output);
						});
					}

R
rebornix 已提交
362 363 364 365 366
					this.list?.splice(diff[0], diff[1], diff[2]);
				});
			} else {
				DOM.scheduleAtNextAnimationFrame(() => {
					e.splices.reverse().forEach((diff) => {
367 368 369 370 371 372 373 374
						// remove output in the webview
						for (let i = diff[0]; i < diff[0] + diff[1]; i++) {
							const cell = this.list?.element(i);
							cell?.cell.outputs.forEach(output => {
								this.removeInset(output);
							});
						}

R
rebornix 已提交
375 376 377 378
						this.list?.splice(diff[0], diff[1], diff[2]);
					});
				});
			}
R
rebornix 已提交
379 380 381 382 383 384 385 386 387 388 389 390
		}));

		this.webview?.updateRendererPreloads(this.notebookViewModel.renderers);

		this.localStore.add(this.list!.onWillScroll(e => {
			this.webview!.updateViewScrollTop(-e.scrollTop, []);
		}));

		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 已提交
391
			let updateItems: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[] = [];
R
rebornix 已提交
392 393 394 395 396 397 398 399 400 401 402 403

			if (this.webview?.insetMapping) {
				this.webview?.insetMapping.forEach((value, key) => {
					let cell = value.cell;
					let index = this.notebookViewModel!.getViewCellIndex(cell);
					let cellTop = this.list?.getAbsoluteTop(index) || 0;
					if (this.webview!.shouldUpdateInset(cell, key, cellTop)) {
						updateItems.push({
							cell: cell,
							output: key,
							cellTop: cellTop
						});
R
rebornix 已提交
404
					}
R
rebornix 已提交
405
				});
406

R
rebornix 已提交
407 408 409 410 411 412
				if (updateItems.length) {
					this.webview?.updateViewScrollTop(-scrollTop, updateItems);
				}
			}
		}));

R
rebornix 已提交
413
		this.list?.splice(0, this.list?.length || 0);
414
		this.list?.splice(0, 0, this.notebookViewModel!.viewCells as CellViewModel[]);
R
rebornix 已提交
415
		this.list?.layout();
P
Peng Lyu 已提交
416 417
	}

R
rebornix 已提交
418
	private saveTextEditorViewState(input: NotebookEditorInput): void {
R
npe  
rebornix 已提交
419
		if (this.group && this.notebookViewModel) {
R
rebornix 已提交
420
			const state = this.notebookViewModel.saveEditorViewState();
R
rebornix 已提交
421 422 423 424
			if (this.list) {
				state.scrollPosition = { left: this.list.scrollLeft, top: this.list.scrollTop };
			}

R
rebornix 已提交
425
			this.editorMemento.saveEditorState(this.group, input.resource, state);
R
rebornix 已提交
426 427 428 429 430
		}
	}

	private loadTextEditorViewState(input: NotebookEditorInput): INotebookEditorViewState | undefined {
		if (this.group) {
R
rebornix 已提交
431
			return this.editorMemento.loadEditorState(this.group, input.resource);
R
rebornix 已提交
432 433 434 435 436 437 438 439 440 441
		}

		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);
442
		this.list?.updateOptions({ additionalScrollHeight: dimension.height });
R
rebornix 已提交
443
		this.list?.layout(dimension.height, dimension.width);
R
rebornix 已提交
444
		this.eventDispatcher?.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
R
rebornix 已提交
445 446 447 448 449 450 451 452 453 454 455 456
	}

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

		super.saveState();
	}

	//#endregion

R
rebornix 已提交
457 458
	//#region Editor Features

R
rebornix 已提交
459
	selectElement(cell: ICellViewModel) {
R
rebornix 已提交
460 461 462 463 464 465 466 467
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
			this.list?.setSelection([index]);
			this.list?.setFocus([index]);
		}
	}

R
rebornix 已提交
468
	revealInView(cell: ICellViewModel) {
R
rebornix 已提交
469 470 471
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
R
rebornix 已提交
472
			this.list?.revealInView(index);
R
rebornix 已提交
473 474 475
		}
	}

R
rebornix 已提交
476
	revealInCenterIfOutsideViewport(cell: ICellViewModel) {
R
rebornix 已提交
477 478 479
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
R
rebornix 已提交
480
			this.list?.revealInCenterIfOutsideViewport(index);
R
rebornix 已提交
481 482 483
		}
	}

R
rebornix 已提交
484
	revealInCenter(cell: ICellViewModel) {
R
rebornix 已提交
485 486 487
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
R
rebornix 已提交
488 489 490 491
			this.list?.revealInCenter(index);
		}
	}

R
rebornix 已提交
492
	revealLineInView(cell: ICellViewModel, line: number): void {
R
rebornix 已提交
493 494 495 496
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
			this.list?.revealLineInView(index, line);
R
rebornix 已提交
497 498
		}
	}
R
rebornix 已提交
499

R
rebornix 已提交
500
	revealLineInCenter(cell: ICellViewModel, line: number) {
R
rebornix 已提交
501 502 503
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
R
rebornix 已提交
504
			this.list?.revealLineInCenter(index, line);
R
rebornix 已提交
505 506 507
		}
	}

R
rebornix 已提交
508
	revealLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number) {
R
rebornix 已提交
509 510 511 512 513 514 515
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
			this.list?.revealLineInCenterIfOutsideViewport(index, line);
		}
	}

R
rebornix 已提交
516
	revealRangeInView(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
517 518 519 520 521 522 523
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
			this.list?.revealRangeInView(index, range);
		}
	}

R
rebornix 已提交
524
	revealRangeInCenter(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
525 526 527 528 529 530 531
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
			this.list?.revealRangeInCenter(index, range);
		}
	}

R
rebornix 已提交
532
	revealRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
533 534 535 536 537 538 539
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
			this.list?.revealRangeInCenterIfOutsideViewport(index, range);
		}
	}

R
rebornix 已提交
540
	setCellSelection(cell: ICellViewModel, range: Range): void {
R
rebornix 已提交
541 542 543 544 545 546 547
		const index = this.notebookViewModel?.getViewCellIndex(cell);

		if (index !== undefined) {
			this.list?.setCellSelection(index, range);
		}
	}

R
rebornix 已提交
548 549 550 551 552 553
	changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
		return this.notebookViewModel?.changeDecorations(callback);
	}

	//#endregion

R
rebornix 已提交
554 555 556 557 558 559 560 561
	//#region Find Delegate

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

	public hideFind() {
		this.findWidget.hide();
R
rebornix 已提交
562
		this.focus();
R
rebornix 已提交
563 564 565 566
	}

	//#endregion

R
rebornix 已提交
567
	//#region Cell operations
568
	async layoutNotebookCell(cell: ICellViewModel, height: number): Promise<void> {
R
rebornix 已提交
569
		let relayout = (cell: ICellViewModel, height: number) => {
R
rebornix 已提交
570
			let index = this.notebookViewModel!.getViewCellIndex(cell);
R
rebornix 已提交
571
			if (index >= 0) {
R
rebornix 已提交
572
				this.list?.updateElementHeight(index, height);
R
rebornix 已提交
573
			}
R
rebornix 已提交
574 575
		};

576
		let r: () => void;
R
rebornix 已提交
577
		DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
578
			relayout(cell, height);
579
			r();
R
rebornix 已提交
580
		});
581 582

		return new Promise(resolve => { r = resolve; });
583 584
	}

R
rebornix 已提交
585
	async insertNotebookCell(cell: ICellViewModel, type: CellKind, direction: 'above' | 'below', initialText: string = ''): Promise<void> {
R
rebornix 已提交
586 587 588
		const newLanguages = this.notebookViewModel!.languages;
		const language = newLanguages && newLanguages.length ? newLanguages[0] : 'markdown';
		const index = this.notebookViewModel!.getViewCellIndex(cell);
P
Peng Lyu 已提交
589
		const insertIndex = direction === 'above' ? index : index + 1;
R
rebornix 已提交
590
		const newCell = this.notebookViewModel!.createCell(insertIndex, initialText.split(/\r?\n/g), language, type, true);
R
rebornix 已提交
591
		this.list?.setFocus([insertIndex]);
P
Peng Lyu 已提交
592

R
rebornix 已提交
593
		if (type === CellKind.Markdown) {
594
			newCell.editState = CellEditState.Editing;
P
Peng Lyu 已提交
595
		}
R
rebornix 已提交
596

597
		let r: () => void;
R
rebornix 已提交
598
		DOM.scheduleAtNextAnimationFrame(() => {
R
rebornix 已提交
599
			this.list?.revealInCenterIfOutsideViewport(insertIndex);
600
			r();
R
rebornix 已提交
601
		});
602 603

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

R
rebornix 已提交
606
	async deleteNotebookCell(cell: ICellViewModel): Promise<void> {
607
		(cell as CellViewModel).save();
R
rebornix 已提交
608
		const index = this.notebookViewModel!.getViewCellIndex(cell);
R
rebornix 已提交
609
		this.notebookViewModel!.deleteCell(index, true);
R
rebornix 已提交
610 611
	}

612
	async moveCellDown(cell: ICellViewModel): Promise<void> {
613
		const index = this.notebookViewModel!.getViewCellIndex(cell);
R
rebornix 已提交
614 615 616 617
		if (index === this.notebookViewModel!.viewCells.length - 1) {
			return;
		}

618
		const newIdx = index + 1;
619
		return this.moveCellToIndex(index, newIdx);
620 621
	}

622
	async moveCellUp(cell: ICellViewModel): Promise<void> {
623
		const index = this.notebookViewModel!.getViewCellIndex(cell);
R
rebornix 已提交
624 625 626 627
		if (index === 0) {
			return;
		}

628
		const newIdx = index - 1;
629
		return this.moveCellToIndex(index, newIdx);
630 631
	}

632
	private async moveCellToIndex(index: number, newIdx: number): Promise<void> {
R
rebornix 已提交
633
		if (!this.notebookViewModel!.moveCellToIdx(index, newIdx, true)) {
634 635 636
			return;
		}

637
		let r: () => void;
638 639
		DOM.scheduleAtNextAnimationFrame(() => {
			this.list?.revealInCenterIfOutsideViewport(index + 1);
640
			r();
641
		});
642 643

		return new Promise(resolve => { r = resolve; });
644 645
	}

R
rebornix 已提交
646
	editNotebookCell(cell: CellViewModel): void {
647
		cell.editState = CellEditState.Editing;
R
rebornix 已提交
648 649

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

R
rebornix 已提交
652
	saveNotebookCell(cell: ICellViewModel): void {
653
		cell.editState = CellEditState.Preview;
P
Peng Lyu 已提交
654 655
	}

R
rebornix 已提交
656 657 658 659 660 661 662 663 664 665
	getActiveCell() {
		let elements = this.list?.getFocusedElements();

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

		return undefined;
	}

666
	async executeNotebookCell(cell: ICellViewModel): Promise<void> {
667
		const tokenSource = new CancellationTokenSource();
668
		try {
669
			cell.currentTokenSource = tokenSource;
R
rebornix 已提交
670
			const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
671 672 673 674
			if (provider) {
				const viewType = provider.id;
				const notebookUri = CellUri.parse(cell.uri)?.notebook;
				if (notebookUri) {
675
					return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, tokenSource.token);
676 677 678
				}
			}
		} finally {
679 680
			cell.currentTokenSource = undefined;
			tokenSource.dispose();
681 682 683
		}
	}

R
rebornix 已提交
684
	focusNotebookCell(cell: ICellViewModel, focusEditor: boolean) {
R
rebornix 已提交
685
		const index = this.notebookViewModel!.getViewCellIndex(cell);
R
rebornix 已提交
686 687

		if (focusEditor) {
R
rebornix 已提交
688 689 690
			this.list?.setFocus([index]);
			this.list?.setSelection([index]);
			this.list?.focusView();
R
rebornix 已提交
691

692
			cell.editState = CellEditState.Editing;
R
rebornix 已提交
693
			cell.focusMode = CellFocusMode.Editor;
694
			this.revealInCenterIfOutsideViewport(cell);
R
rebornix 已提交
695
		} else {
R
rebornix 已提交
696
			let itemDOM = this.list?.domElementAtIndex(index);
R
rebornix 已提交
697 698 699
			if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
				(document.activeElement as HTMLElement).blur();
			}
700

701
			cell.editState = CellEditState.Preview;
702
			cell.focusMode = CellFocusMode.Editor;
R
rebornix 已提交
703

R
rebornix 已提交
704 705
			this.list?.setFocus([index]);
			this.list?.setSelection([index]);
706
			this.revealInCenterIfOutsideViewport(cell);
R
rebornix 已提交
707
			this.list?.focusView();
R
rebornix 已提交
708 709 710
		}
	}

R
rebornix 已提交
711 712 713 714
	//#endregion

	//#region MISC

R
rebornix 已提交
715 716 717 718 719 720 721 722 723 724 725
	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 已提交
726

R
rebornix 已提交
727 728
	triggerScroll(event: IMouseWheelEvent) {
		this.list?.triggerScrollFromMouseWheelEvent(event);
R
rebornix 已提交
729 730
	}

R
rebornix 已提交
731
	createInset(cell: CodeCellViewModel, output: IOutput, shadowContent: string, offset: number) {
R
rebornix 已提交
732 733
		if (!this.webview) {
			return;
R
rebornix 已提交
734 735
		}

R
rebornix 已提交
736
		let preloads = this.notebookViewModel!.renderers;
R
rebornix 已提交
737

738
		if (!this.webview!.insetMapping.has(output)) {
R
rebornix 已提交
739
			let index = this.notebookViewModel!.getViewCellIndex(cell);
740 741 742
			let cellTop = this.list?.getAbsoluteTop(index) || 0;

			this.webview!.createInset(cell, output, cellTop, offset, shadowContent, preloads);
R
rebornix 已提交
743
		} else {
R
rebornix 已提交
744
			let index = this.notebookViewModel!.getViewCellIndex(cell);
745
			let cellTop = this.list?.getAbsoluteTop(index) || 0;
R
rebornix 已提交
746 747
			let scrollTop = this.list?.scrollTop || 0;

748
			this.webview!.updateViewScrollTop(-scrollTop, [{ cell: cell, output: output, cellTop: cellTop }]);
R
rebornix 已提交
749
		}
R
rebornix 已提交
750
	}
R
rebornix 已提交
751

R
rebornix 已提交
752 753 754 755 756 757 758 759
	removeInset(output: IOutput) {
		if (!this.webview) {
			return;
		}

		this.webview!.removeInset(output);
	}

R
rebornix 已提交
760 761
	getOutputRenderer(): OutputRenderer {
		return this.outputRenderer;
R
rebornix 已提交
762
	}
763

764 765 766 767
	postMessage(message: any) {
		this.webview?.webview.sendMessage(message);
	}

R
rebornix 已提交
768
	//#endregion
769 770 771 772 773 774

	toJSON(): any {
		return {
			notebookHandle: this.viewModel?.handle
		};
	}
P
Peng Lyu 已提交
775 776 777 778
}

const embeddedEditorBackground = 'walkThrough.embeddedEditorBackground';

779 780 781 782 783 784
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."));

785 786 787 788 789 790 791
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 已提交
792 793 794 795 796 797
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"));

798

P
Peng Lyu 已提交
799 800 801
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 已提交
802 803
		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 已提交
804 805 806
	}
	const link = theme.getColor(textLinkForeground);
	if (link) {
R
Rob Lourens 已提交
807
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .output a { color: ${link}; }`);
P
Peng Lyu 已提交
808 809 810
	}
	const activeLink = theme.getColor(textLinkActiveForeground);
	if (activeLink) {
R
Rob Lourens 已提交
811 812
		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 已提交
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
	}
	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}; }`);
	}
831

832
	const containerBackground = theme.getColor(notebookOutputContainerColor);
833

834 835
	if (containerBackground) {
		collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { background-color: ${containerBackground}; }`);
836
	}
837

838 839 840 841 842 843
	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 已提交
844 845 846 847 848 849
	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} }`);
	}

850
	// Cell Margin
R
rebornix 已提交
851
	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; }`);
852
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`);
R
rebornix 已提交
853
	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 已提交
854

R
Rob Lourens 已提交
855
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { width: calc(100% - ${CELL_RUN_GUTTER}px); }`);
856
	collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .markdown-editor-container { margin-left: ${CELL_RUN_GUTTER}px; }`);
R
Rob Lourens 已提交
857 858
	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 已提交
859
});