Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
5243 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/notebook.css';
7
import './media/notebookCellChat.css';
8
import './media/notebookCellEditorHint.css';
9
import './media/notebookCellInsertToolbar.css';
10
import './media/notebookCellStatusBar.css';
11
import './media/notebookCellTitleToolbar.css';
12
import './media/notebookFocusIndicator.css';
13
import './media/notebookToolbar.css';
14
import './media/notebookDnd.css';
15
import './media/notebookFolding.css';
16
import './media/notebookCellOutput.css';
17
import './media/notebookEditorStickyScroll.css';
18
import './media/notebookKernelActionViewItem.css';
19
import './media/notebookOutline.css';
20
import './media/notebookChatEditController.css';
21
import './media/notebookChatEditorOverlay.css';
22
import * as DOM from '../../../../base/browser/dom.js';
23
import * as domStylesheets from '../../../../base/browser/domStylesheets.js';
24
import { IMouseWheelEvent, StandardMouseEvent } from '../../../../base/browser/mouseEvent.js';
25
import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js';
26
import { mainWindow } from '../../../../base/browser/window.js';
27
import { SequencerByKey } from '../../../../base/common/async.js';
28
import { CancellationToken } from '../../../../base/common/cancellation.js';
29
import { Color, RGBA } from '../../../../base/common/color.js';
30
import { onUnexpectedError } from '../../../../base/common/errors.js';
31
import { Emitter, Event } from '../../../../base/common/event.js';
32
import { combinedDisposable, Disposable, DisposableStore, dispose, MutableDisposable } from '../../../../base/common/lifecycle.js';
33
import { setTimeout0 } from '../../../../base/common/platform.js';
34
import { extname, isEqual } from '../../../../base/common/resources.js';
35
import { URI } from '../../../../base/common/uri.js';
36
import { generateUuid } from '../../../../base/common/uuid.js';
37
import { FontMeasurements } from '../../../../editor/browser/config/fontMeasurements.js';
38
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
39
import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js';
40
import { FontInfo } from '../../../../editor/common/config/fontInfo.js';
41
import { createBareFontInfoFromRawSettings } from '../../../../editor/common/config/fontInfoFromSettings.js';
42
import { Range } from '../../../../editor/common/core/range.js';
43
import { Selection } from '../../../../editor/common/core/selection.js';
44
import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js';
45
import * as nls from '../../../../nls.js';
46
import { MenuId } from '../../../../platform/actions/common/actions.js';
47
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
48
import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
49
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
50
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
51
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
52
import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js';
53
import { registerZIndex, ZIndex } from '../../../../platform/layout/browser/zIndexRegistry.js';
54
import { IEditorProgressService, IProgressRunner } from '../../../../platform/progress/common/progress.js';
55
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
56
import { contrastBorder, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, transparent } from '../../../../platform/theme/common/colorRegistry.js';
57
import { EDITOR_PANE_BACKGROUND, PANEL_BORDER, SIDE_BAR_BACKGROUND } from '../../../common/theme.js';
58
import { debugIconStartForeground } from '../../debug/browser/debugColors.js';
59
import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, CellRevealRangeType, CellRevealType, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookCellOverlayChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookViewZoneChangeAccessor, INotebookWebviewMessage, RenderOutputType, ScrollToRevealBehavior } from './notebookBrowser.js';
60
import { NotebookEditorExtensionsRegistry } from './notebookEditorExtensions.js';
61
import { INotebookEditorService } from './services/notebookEditorService.js';
62
import { notebookDebug } from './notebookLogger.js';
63
import { NotebookCellStateChangedEvent, NotebookLayoutChangedEvent, NotebookLayoutInfo } from './notebookViewEvents.js';
64
import { CellContextKeyManager } from './view/cellParts/cellContextKeys.js';
65
import { CellDragAndDropController } from './view/cellParts/cellDnd.js';
66
import { ListViewInfoAccessor, NotebookCellList, NOTEBOOK_WEBVIEW_BOUNDARY } from './view/notebookCellList.js';
67
import { INotebookCellList } from './view/notebookRenderingCommon.js';
68
import { BackLayerWebView } from './view/renderers/backLayerWebView.js';
69
import { CodeCellRenderer, MarkupCellRenderer, NotebookCellListDelegate } from './view/renderers/cellRenderer.js';
70
import { IAckOutputHeight, IMarkupCellInitialization } from './view/renderers/webviewMessages.js';
71
import { CodeCellViewModel, outputDisplayLimit } from './viewModel/codeCellViewModel.js';
72
import { NotebookEventDispatcher } from './viewModel/eventDispatcher.js';
73
import { MarkupCellViewModel } from './viewModel/markupCellViewModel.js';
74
import { CellViewModel, NotebookViewModel } from './viewModel/notebookViewModelImpl.js';
75
import { ViewContext } from './viewModel/viewContext.js';
76
import { NotebookEditorWorkbenchToolbar } from './viewParts/notebookEditorToolbar.js';
77
import { NotebookEditorContextKeys } from './viewParts/notebookEditorWidgetContextKeys.js';
78
import { NotebookOverviewRuler } from './viewParts/notebookOverviewRuler.js';
79
import { ListTopCellToolbar } from './viewParts/notebookTopCellToolbar.js';
80
import { NotebookTextModel } from '../common/model/notebookTextModel.js';
81
import { CellEditType, CellKind, INotebookFindOptions, NotebookFindScopeType, RENDERER_NOT_AVAILABLE, SelectionStateType } from '../common/notebookCommon.js';
82
import { NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED } from '../common/notebookContextKeys.js';
83
import { INotebookExecutionService } from '../common/notebookExecutionService.js';
84
import { INotebookKernelService } from '../common/notebookKernelService.js';
85
import { NotebookOptions, OutputInnerContainerTopPadding } from './notebookOptions.js';
86
import { cellRangesToIndexes, ICellRange } from '../common/notebookRange.js';
87
import { INotebookRendererMessagingService } from '../common/notebookRendererMessagingService.js';
88
import { INotebookService } from '../common/notebookService.js';
89
import { IWebviewElement } from '../../webview/browser/webview.js';
90
import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js';
91
import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';
92
import { NotebookPerfMarks } from '../common/notebookPerformance.js';
93
import { BaseCellEditorOptions } from './viewModel/cellEditorOptions.js';
94
import { FloatingEditorClickMenu } from '../../../browser/codeeditor.js';
95
import { IDimension } from '../../../../editor/common/core/2d/dimension.js';
96
import { CellFindMatchModel } from './contrib/find/findModel.js';
97
import { INotebookLoggingService } from '../common/notebookLoggingService.js';
98
import { Schemas } from '../../../../base/common/network.js';
99
import { DropIntoEditorController } from '../../../../editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.js';
100
import { CopyPasteController } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js';
101
import { NotebookStickyScroll } from './viewParts/notebookEditorStickyScroll.js';
102
import { PixelRatio } from '../../../../base/browser/pixelRatio.js';
103
import { PreventDefaultContextMenuItemsContextKeyName } from '../../webview/browser/webview.contribution.js';
104
import { NotebookAccessibilityProvider } from './notebookAccessibilityProvider.js';
105
import { NotebookHorizontalTracker } from './viewParts/notebookHorizontalTracker.js';
106
import { NotebookCellEditorPool } from './view/notebookCellEditorPool.js';
107
import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js';
108
import { NotebookCellLayoutManager } from './notebookCellLayoutManager.js';
109
import { FloatingEditorToolbar } from '../../../../editor/contrib/floatingMenu/browser/floatingMenu.js';
110
111
const $ = DOM.$;
112
113
export function getDefaultNotebookCreationOptions(): INotebookEditorCreationOptions {
114
// We inlined the id to avoid loading comment contrib in tests
115
const skipContributions = [
116
'editor.contrib.review',
117
FloatingEditorClickMenu.ID,
118
FloatingEditorToolbar.ID,
119
'editor.contrib.dirtydiff',
120
'editor.contrib.testingOutputPeek',
121
'editor.contrib.testingDecorations',
122
'store.contrib.stickyScrollController',
123
'editor.contrib.findController',
124
'editor.contrib.emptyTextEditorHint'
125
];
126
const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1);
127
128
return {
129
menuIds: {
130
notebookToolbar: MenuId.NotebookToolbar,
131
cellTitleToolbar: MenuId.NotebookCellTitle,
132
cellDeleteToolbar: MenuId.NotebookCellDelete,
133
cellInsertToolbar: MenuId.NotebookCellBetween,
134
cellTopInsertToolbar: MenuId.NotebookCellListTop,
135
cellExecuteToolbar: MenuId.NotebookCellExecute,
136
cellExecutePrimary: MenuId.NotebookCellExecutePrimary,
137
},
138
cellEditorContributions: contributions
139
};
140
}
141
142
export class NotebookEditorWidget extends Disposable implements INotebookEditorDelegate, INotebookEditor {
143
//#region Eventing
144
private readonly _onDidChangeCellState = this._register(new Emitter<NotebookCellStateChangedEvent>());
145
readonly onDidChangeCellState = this._onDidChangeCellState.event;
146
private readonly _onDidChangeViewCells = this._register(new Emitter<INotebookViewCellsUpdateEvent>());
147
readonly onDidChangeViewCells: Event<INotebookViewCellsUpdateEvent> = this._onDidChangeViewCells.event;
148
private readonly _onWillChangeModel = this._register(new Emitter<NotebookTextModel | undefined>());
149
readonly onWillChangeModel: Event<NotebookTextModel | undefined> = this._onWillChangeModel.event;
150
private readonly _onDidChangeModel = this._register(new Emitter<NotebookTextModel | undefined>());
151
readonly onDidChangeModel: Event<NotebookTextModel | undefined> = this._onDidChangeModel.event;
152
private readonly _onDidAttachViewModel = this._register(new Emitter<void>());
153
readonly onDidAttachViewModel: Event<void> = this._onDidAttachViewModel.event;
154
private readonly _onDidChangeOptions = this._register(new Emitter<void>());
155
readonly onDidChangeOptions: Event<void> = this._onDidChangeOptions.event;
156
private readonly _onDidChangeDecorations = this._register(new Emitter<void>());
157
readonly onDidChangeDecorations: Event<void> = this._onDidChangeDecorations.event;
158
private readonly _onDidScroll = this._register(new Emitter<void>());
159
readonly onDidScroll: Event<void> = this._onDidScroll.event;
160
private readonly _onDidChangeLayout = this._register(new Emitter<void>());
161
readonly onDidChangeLayout: Event<void> = this._onDidChangeLayout.event;
162
private readonly _onDidChangeActiveCell = this._register(new Emitter<void>());
163
readonly onDidChangeActiveCell: Event<void> = this._onDidChangeActiveCell.event;
164
private readonly _onDidChangeFocus = this._register(new Emitter<void>());
165
readonly onDidChangeFocus: Event<void> = this._onDidChangeFocus.event;
166
private readonly _onDidChangeSelection = this._register(new Emitter<void>());
167
readonly onDidChangeSelection: Event<void> = this._onDidChangeSelection.event;
168
private readonly _onDidChangeVisibleRanges = this._register(new Emitter<void>());
169
readonly onDidChangeVisibleRanges: Event<void> = this._onDidChangeVisibleRanges.event;
170
private readonly _onDidFocusEmitter = this._register(new Emitter<void>());
171
readonly onDidFocusWidget = this._onDidFocusEmitter.event;
172
private readonly _onDidBlurEmitter = this._register(new Emitter<void>());
173
readonly onDidBlurWidget = this._onDidBlurEmitter.event;
174
private readonly _onDidChangeActiveEditor = this._register(new Emitter<this>());
175
readonly onDidChangeActiveEditor: Event<this> = this._onDidChangeActiveEditor.event;
176
private readonly _onDidChangeActiveKernel = this._register(new Emitter<void>());
177
readonly onDidChangeActiveKernel: Event<void> = this._onDidChangeActiveKernel.event;
178
private readonly _onMouseUp: Emitter<INotebookEditorMouseEvent> = this._register(new Emitter<INotebookEditorMouseEvent>());
179
readonly onMouseUp: Event<INotebookEditorMouseEvent> = this._onMouseUp.event;
180
private readonly _onMouseDown: Emitter<INotebookEditorMouseEvent> = this._register(new Emitter<INotebookEditorMouseEvent>());
181
readonly onMouseDown: Event<INotebookEditorMouseEvent> = this._onMouseDown.event;
182
private readonly _onDidReceiveMessage = this._register(new Emitter<INotebookWebviewMessage>());
183
readonly onDidReceiveMessage: Event<INotebookWebviewMessage> = this._onDidReceiveMessage.event;
184
private readonly _onDidRenderOutput = this._register(new Emitter<ICellOutputViewModel>());
185
private readonly onDidRenderOutput = this._onDidRenderOutput.event;
186
private readonly _onDidRemoveOutput = this._register(new Emitter<ICellOutputViewModel>());
187
private readonly onDidRemoveOutput = this._onDidRemoveOutput.event;
188
private readonly _onDidResizeOutputEmitter = this._register(new Emitter<ICellViewModel>());
189
readonly onDidResizeOutput = this._onDidResizeOutputEmitter.event;
190
191
//#endregion
192
private _overlayContainer!: HTMLElement;
193
private _notebookTopToolbarContainer!: HTMLElement;
194
private _notebookTopToolbar!: NotebookEditorWorkbenchToolbar;
195
private _notebookStickyScrollContainer!: HTMLElement;
196
private _notebookStickyScroll!: NotebookStickyScroll;
197
private _notebookOverviewRulerContainer!: HTMLElement;
198
private _notebookOverviewRuler!: NotebookOverviewRuler;
199
private _body!: HTMLElement;
200
private _styleElement!: HTMLStyleElement;
201
private _overflowContainer!: HTMLElement;
202
private _webview: BackLayerWebView<ICommonCellInfo> | null = null;
203
private _webviewResolvePromise: Promise<BackLayerWebView<ICommonCellInfo> | null> | null = null;
204
private _webviewTransparentCover: HTMLElement | null = null;
205
private _listDelegate: NotebookCellListDelegate | null = null;
206
private _list!: INotebookCellList;
207
private _listViewInfoAccessor!: ListViewInfoAccessor;
208
private _dndController: CellDragAndDropController | null = null;
209
private _listTopCellToolbar: ListTopCellToolbar | null = null;
210
private _renderedEditors: Map<ICellViewModel, ICodeEditor> = new Map();
211
private _editorPool!: NotebookCellEditorPool;
212
private _viewContext: ViewContext;
213
private _notebookViewModel: NotebookViewModel | undefined;
214
private readonly _localStore: DisposableStore = this._register(new DisposableStore());
215
private _localCellStateListeners: DisposableStore[] = [];
216
private _fontInfo: FontInfo | undefined;
217
private _dimension?: DOM.Dimension;
218
private _position?: DOM.IDomPosition;
219
private _shadowElement?: HTMLElement;
220
private _shadowElementViewInfo: { height: number; width: number; top: number; left: number } | null = null;
221
private _cellLayoutManager: NotebookCellLayoutManager | undefined;
222
223
private readonly _editorFocus: IContextKey<boolean>;
224
private readonly _outputFocus: IContextKey<boolean>;
225
private readonly _editorEditable: IContextKey<boolean>;
226
private readonly _cursorNavMode: IContextKey<boolean>;
227
private readonly _outputInputFocus: IContextKey<boolean>;
228
protected readonly _contributions = new Map<string, INotebookEditorContribution>();
229
private _scrollBeyondLastLine: boolean;
230
private readonly _insetModifyQueueByOutputId = new SequencerByKey<string>();
231
private _cellContextKeyManager: CellContextKeyManager | null = null;
232
private readonly _uuid = generateUuid();
233
private _focusTracker!: DOM.IFocusTracker;
234
private _webviewFocused: boolean = false;
235
private _isVisible = false;
236
get isVisible() {
237
return this._isVisible;
238
}
239
240
private _isDisposed: boolean = false;
241
242
get isDisposed() {
243
return this._isDisposed;
244
}
245
246
set viewModel(newModel: NotebookViewModel | undefined) {
247
this._onWillChangeModel.fire(this._notebookViewModel?.notebookDocument);
248
this._notebookViewModel = newModel;
249
this._onDidChangeModel.fire(newModel?.notebookDocument);
250
}
251
252
get viewModel() {
253
return this._notebookViewModel;
254
}
255
256
get textModel() {
257
return this._notebookViewModel?.notebookDocument;
258
}
259
260
get isReadOnly() {
261
return this._notebookViewModel?.options.isReadOnly ?? false;
262
}
263
264
get activeCodeEditor(): ICodeEditor | undefined {
265
if (this._isDisposed) {
266
return;
267
}
268
269
const [focused] = this._list.getFocusedElements();
270
return this._renderedEditors.get(focused);
271
}
272
273
get activeCellAndCodeEditor(): [ICellViewModel, ICodeEditor] | undefined {
274
if (this._isDisposed) {
275
return;
276
}
277
278
const [focused] = this._list.getFocusedElements();
279
const editor = this._renderedEditors.get(focused);
280
if (!editor) {
281
return;
282
}
283
return [focused, editor];
284
}
285
286
get codeEditors(): [ICellViewModel, ICodeEditor][] {
287
return [...this._renderedEditors];
288
}
289
290
get visibleRanges() {
291
return this._list ? (this._list.visibleRanges || []) : [];
292
}
293
294
private _baseCellEditorOptions = new Map<string, IBaseCellEditorOptions>();
295
296
readonly isReplHistory: boolean;
297
private _readOnly: boolean;
298
299
public readonly scopedContextKeyService: IContextKeyService;
300
private readonly instantiationService: IInstantiationService;
301
private readonly _notebookOptions: NotebookOptions;
302
303
private _currentProgress: IProgressRunner | undefined;
304
305
get notebookOptions() {
306
return this._notebookOptions;
307
}
308
309
constructor(
310
readonly creationOptions: INotebookEditorCreationOptions,
311
dimension: DOM.Dimension | undefined,
312
@IInstantiationService instantiationService: IInstantiationService,
313
@IEditorGroupsService editorGroupsService: IEditorGroupsService,
314
@INotebookRendererMessagingService private readonly notebookRendererMessaging: INotebookRendererMessagingService,
315
@INotebookEditorService private readonly notebookEditorService: INotebookEditorService,
316
@INotebookKernelService private readonly notebookKernelService: INotebookKernelService,
317
@INotebookService private readonly _notebookService: INotebookService,
318
@IConfigurationService private readonly configurationService: IConfigurationService,
319
@IContextKeyService contextKeyService: IContextKeyService,
320
@ILayoutService private readonly layoutService: ILayoutService,
321
@IContextMenuService private readonly contextMenuService: IContextMenuService,
322
@ITelemetryService private readonly telemetryService: ITelemetryService,
323
@INotebookExecutionService private readonly notebookExecutionService: INotebookExecutionService,
324
@IEditorProgressService private editorProgressService: IEditorProgressService,
325
@INotebookLoggingService private readonly logService: INotebookLoggingService,
326
) {
327
super();
328
329
this._dimension = dimension;
330
331
this.isReplHistory = creationOptions.isReplHistory ?? false;
332
this._readOnly = creationOptions.isReadOnly ?? false;
333
334
this._overlayContainer = document.createElement('div');
335
this.scopedContextKeyService = this._register(contextKeyService.createScoped(this._overlayContainer));
336
this.instantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])));
337
338
this._notebookOptions = creationOptions.options ??
339
this.instantiationService.createInstance(NotebookOptions, this.creationOptions?.codeWindow ?? mainWindow, this._readOnly, undefined);
340
this._register(this._notebookOptions);
341
const eventDispatcher = this._register(new NotebookEventDispatcher());
342
this._viewContext = new ViewContext(
343
this._notebookOptions,
344
eventDispatcher,
345
language => this.getBaseCellEditorOptions(language));
346
this._register(this._viewContext.eventDispatcher.onDidChangeLayout(() => {
347
this._onDidChangeLayout.fire();
348
}));
349
this._register(this._viewContext.eventDispatcher.onDidChangeCellState(e => {
350
this._onDidChangeCellState.fire(e);
351
}));
352
353
354
this._register(_notebookService.onDidChangeOutputRenderers(() => {
355
this._updateOutputRenderers();
356
}));
357
358
this._register(this.instantiationService.createInstance(NotebookEditorContextKeys, this));
359
360
this._register(notebookKernelService.onDidChangeSelectedNotebooks(e => {
361
if (isEqual(e.notebook, this.viewModel?.uri)) {
362
this._loadKernelPreloads();
363
this._onDidChangeActiveKernel.fire();
364
}
365
}));
366
367
this._scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');
368
369
this._register(this.configurationService.onDidChangeConfiguration(e => {
370
if (e.affectsConfiguration('editor.scrollBeyondLastLine')) {
371
this._scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');
372
if (this._dimension && this._isVisible) {
373
this.layout(this._dimension);
374
}
375
}
376
}));
377
378
this._register(this._notebookOptions.onDidChangeOptions(e => {
379
if (e.cellStatusBarVisibility || e.cellToolbarLocation || e.cellToolbarInteraction) {
380
this._updateForNotebookConfiguration();
381
}
382
383
if (e.fontFamily) {
384
this._generateFontInfo();
385
}
386
387
if (e.compactView
388
|| e.focusIndicator
389
|| e.insertToolbarPosition
390
|| e.cellToolbarLocation
391
|| e.dragAndDropEnabled
392
|| e.fontSize
393
|| e.markupFontSize
394
|| e.markdownLineHeight
395
|| e.fontFamily
396
|| e.insertToolbarAlignment
397
|| e.outputFontSize
398
|| e.outputLineHeight
399
|| e.outputFontFamily
400
|| e.outputWordWrap
401
|| e.outputScrolling
402
|| e.outputLinkifyFilePaths
403
|| e.minimalError
404
) {
405
this._styleElement?.remove();
406
this._createLayoutStyles();
407
this._webview?.updateOptions({
408
...this.notebookOptions.computeWebviewOptions(),
409
fontFamily: this._generateFontFamily()
410
});
411
}
412
413
if (this._dimension && this._isVisible) {
414
this.layout(this._dimension);
415
}
416
}));
417
418
const container = creationOptions.codeWindow ? this.layoutService.getContainer(creationOptions.codeWindow) : this.layoutService.mainContainer;
419
this._register(editorGroupsService.getPart(container).onDidScroll(e => {
420
if (!this._shadowElement || !this._isVisible) {
421
return;
422
}
423
424
this.updateShadowElement(this._shadowElement, this._dimension);
425
this.layoutContainerOverShadowElement(this._dimension, this._position);
426
}));
427
428
this.notebookEditorService.addNotebookEditor(this);
429
430
const id = generateUuid();
431
this._overlayContainer.id = `notebook-${id}`;
432
this._overlayContainer.className = 'notebookOverlay';
433
this._overlayContainer.classList.add('notebook-editor');
434
this._overlayContainer.inert = true;
435
this._overlayContainer.style.visibility = 'hidden';
436
437
container.appendChild(this._overlayContainer);
438
439
this._createBody(this._overlayContainer);
440
this._generateFontInfo();
441
this._isVisible = true;
442
this._editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.scopedContextKeyService);
443
this._outputFocus = NOTEBOOK_OUTPUT_FOCUSED.bindTo(this.scopedContextKeyService);
444
this._outputInputFocus = NOTEBOOK_OUTPUT_INPUT_FOCUSED.bindTo(this.scopedContextKeyService);
445
this._editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.scopedContextKeyService);
446
this._cursorNavMode = NOTEBOOK_CURSOR_NAVIGATION_MODE.bindTo(this.scopedContextKeyService);
447
// Never display the native cut/copy context menu items in notebooks
448
new RawContextKey<boolean>(PreventDefaultContextMenuItemsContextKeyName, false).bindTo(this.scopedContextKeyService).set(true);
449
450
this._editorEditable.set(!creationOptions.isReadOnly);
451
452
let contributions: INotebookEditorContributionDescription[];
453
if (Array.isArray(this.creationOptions.contributions)) {
454
contributions = this.creationOptions.contributions;
455
} else {
456
contributions = NotebookEditorExtensionsRegistry.getEditorContributions();
457
}
458
for (const desc of contributions) {
459
let contribution: INotebookEditorContribution | undefined;
460
try {
461
contribution = this.instantiationService.createInstance(desc.ctor, this);
462
} catch (err) {
463
onUnexpectedError(err);
464
}
465
if (contribution) {
466
if (!this._contributions.has(desc.id)) {
467
this._contributions.set(desc.id, contribution);
468
} else {
469
contribution.dispose();
470
throw new Error(`DUPLICATE notebook editor contribution: '${desc.id}'`);
471
}
472
}
473
}
474
475
this._updateForNotebookConfiguration();
476
}
477
478
private _debugFlag: boolean = false;
479
480
private _debug(...args: unknown[]) {
481
if (!this._debugFlag) {
482
return;
483
}
484
485
notebookDebug(...args);
486
}
487
488
/**
489
* EditorId
490
*/
491
public getId(): string {
492
return this._uuid;
493
}
494
495
getViewModel(): NotebookViewModel | undefined {
496
return this.viewModel;
497
}
498
499
getLength() {
500
return this.viewModel?.length ?? 0;
501
}
502
503
getSelections() {
504
return this.viewModel?.getSelections() ?? [{ start: 0, end: 0 }];
505
}
506
507
setSelections(selections: ICellRange[]) {
508
if (!this.viewModel) {
509
return;
510
}
511
512
const focus = this.viewModel.getFocus();
513
this.viewModel.updateSelectionsState({
514
kind: SelectionStateType.Index,
515
focus: focus,
516
selections: selections
517
});
518
}
519
520
getFocus() {
521
return this.viewModel?.getFocus() ?? { start: 0, end: 0 };
522
}
523
524
setFocus(focus: ICellRange) {
525
if (!this.viewModel) {
526
return;
527
}
528
529
const selections = this.viewModel.getSelections();
530
this.viewModel.updateSelectionsState({
531
kind: SelectionStateType.Index,
532
focus: focus,
533
selections: selections
534
});
535
}
536
537
getSelectionViewModels(): ICellViewModel[] {
538
if (!this.viewModel) {
539
return [];
540
}
541
542
const cellsSet = new Set<number>();
543
544
return this.viewModel.getSelections().map(range => this.viewModel!.viewCells.slice(range.start, range.end)).reduce((a, b) => {
545
b.forEach(cell => {
546
if (!cellsSet.has(cell.handle)) {
547
cellsSet.add(cell.handle);
548
a.push(cell);
549
}
550
});
551
552
return a;
553
}, [] as ICellViewModel[]);
554
}
555
556
hasModel(): this is IActiveNotebookEditorDelegate {
557
return !!this._notebookViewModel;
558
}
559
560
showProgress(): void {
561
this._currentProgress = this.editorProgressService.show(true);
562
}
563
564
hideProgress(): void {
565
if (this._currentProgress) {
566
this._currentProgress.done();
567
this._currentProgress = undefined;
568
}
569
}
570
571
//#region Editor Core
572
573
getBaseCellEditorOptions(language: string): IBaseCellEditorOptions {
574
const existingOptions = this._baseCellEditorOptions.get(language);
575
576
if (existingOptions) {
577
return existingOptions;
578
} else {
579
const options = new BaseCellEditorOptions(this, this.notebookOptions, this.configurationService, language);
580
this._baseCellEditorOptions.set(language, options);
581
return options;
582
}
583
}
584
585
private _updateForNotebookConfiguration() {
586
if (!this._overlayContainer) {
587
return;
588
}
589
590
this._overlayContainer.classList.remove('cell-title-toolbar-left');
591
this._overlayContainer.classList.remove('cell-title-toolbar-right');
592
this._overlayContainer.classList.remove('cell-title-toolbar-hidden');
593
const cellToolbarLocation = this._notebookOptions.computeCellToolbarLocation(this.viewModel?.viewType);
594
this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocation}`);
595
596
const cellToolbarInteraction = this._notebookOptions.getDisplayOptions().cellToolbarInteraction;
597
let cellToolbarInteractionState = 'hover';
598
this._overlayContainer.classList.remove('cell-toolbar-hover');
599
this._overlayContainer.classList.remove('cell-toolbar-click');
600
601
if (cellToolbarInteraction === 'hover' || cellToolbarInteraction === 'click') {
602
cellToolbarInteractionState = cellToolbarInteraction;
603
}
604
this._overlayContainer.classList.add(`cell-toolbar-${cellToolbarInteractionState}`);
605
606
}
607
608
private _generateFontInfo(): void {
609
const editorOptions = this.configurationService.getValue<IEditorOptions>('editor');
610
const targetWindow = DOM.getWindow(this.getDomNode());
611
this._fontInfo = FontMeasurements.readFontInfo(targetWindow, createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value));
612
}
613
614
private _createBody(parent: HTMLElement): void {
615
this._notebookTopToolbarContainer = document.createElement('div');
616
this._notebookTopToolbarContainer.classList.add('notebook-toolbar-container');
617
this._notebookTopToolbarContainer.style.display = 'none';
618
DOM.append(parent, this._notebookTopToolbarContainer);
619
620
this._notebookStickyScrollContainer = document.createElement('div');
621
this._notebookStickyScrollContainer.classList.add('notebook-sticky-scroll-container');
622
DOM.append(parent, this._notebookStickyScrollContainer);
623
624
this._body = document.createElement('div');
625
DOM.append(parent, this._body);
626
627
this._body.classList.add('cell-list-container');
628
this._createLayoutStyles();
629
this._createCellList();
630
631
this._notebookOverviewRulerContainer = document.createElement('div');
632
this._notebookOverviewRulerContainer.classList.add('notebook-overview-ruler-container');
633
this._list.scrollableElement.appendChild(this._notebookOverviewRulerContainer);
634
this._registerNotebookOverviewRuler();
635
636
this._register(this.instantiationService.createInstance(NotebookHorizontalTracker, this, this._list.scrollableElement));
637
638
this._overflowContainer = document.createElement('div');
639
this._overflowContainer.classList.add('notebook-overflow-widget-container', 'monaco-editor');
640
DOM.append(parent, this._overflowContainer);
641
}
642
643
private _generateFontFamily() {
644
return this._fontInfo?.fontFamily ?? `"SF Mono", Monaco, Menlo, Consolas, "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace`;
645
}
646
647
private _createLayoutStyles(): void {
648
this._styleElement = domStylesheets.createStyleSheet(this._body);
649
const {
650
cellRightMargin,
651
cellTopMargin,
652
cellRunGutter,
653
cellBottomMargin,
654
codeCellLeftMargin,
655
markdownCellGutter,
656
markdownCellLeftMargin,
657
markdownCellBottomMargin,
658
markdownCellTopMargin,
659
collapsedIndicatorHeight,
660
focusIndicator,
661
insertToolbarPosition,
662
outputFontSize,
663
focusIndicatorLeftMargin,
664
focusIndicatorGap
665
} = this._notebookOptions.getLayoutConfiguration();
666
667
const {
668
insertToolbarAlignment,
669
compactView,
670
fontSize
671
} = this._notebookOptions.getDisplayOptions();
672
673
const getCellEditorContainerLeftMargin = this._notebookOptions.getCellEditorContainerLeftMargin();
674
675
const { bottomToolbarGap, bottomToolbarHeight } = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType);
676
677
const styleSheets: string[] = [];
678
if (!this._fontInfo) {
679
this._generateFontInfo();
680
}
681
682
const fontFamily = this._generateFontFamily();
683
684
styleSheets.push(`
685
.notebook-editor {
686
--notebook-cell-output-font-size: ${outputFontSize}px;
687
--notebook-cell-input-preview-font-size: ${fontSize}px;
688
--notebook-cell-input-preview-font-family: ${fontFamily};
689
}
690
`);
691
692
if (compactView) {
693
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row div.cell.code { margin-left: ${getCellEditorContainerLeftMargin}px; }`);
694
} else {
695
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row div.cell.code { margin-left: ${codeCellLeftMargin}px; }`);
696
}
697
698
// focus indicator
699
if (focusIndicator === 'border') {
700
styleSheets.push(`
701
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top:before,
702
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom:before,
703
.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row .cell-inner-container:before,
704
.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row .cell-inner-container:after {
705
content: "";
706
position: absolute;
707
width: 100%;
708
height: 1px;
709
}
710
711
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before,
712
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-right:before {
713
content: "";
714
position: absolute;
715
width: 1px;
716
height: 100%;
717
z-index: 10;
718
}
719
720
/* top border */
721
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top:before {
722
border-top: 1px solid transparent;
723
}
724
725
/* left border */
726
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before {
727
border-left: 1px solid transparent;
728
}
729
730
/* bottom border */
731
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom:before {
732
border-bottom: 1px solid transparent;
733
}
734
735
/* right border */
736
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-right:before {
737
border-right: 1px solid transparent;
738
}
739
`);
740
741
// left and right border margins
742
styleSheets.push(`
743
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-left:before,
744
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-right:before,
745
.monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-left:before,
746
.monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-right:before {
747
top: -${cellTopMargin}px; height: calc(100% + ${cellTopMargin + cellBottomMargin}px)
748
}`);
749
} else {
750
styleSheets.push(`
751
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left .codeOutput-focus-indicator {
752
border-left: 3px solid transparent;
753
border-radius: 4px;
754
width: 0px;
755
margin-left: ${focusIndicatorLeftMargin}px;
756
border-color: var(--vscode-notebook-inactiveFocusedCellBorder) !important;
757
}
758
759
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-left .codeOutput-focus-indicator-container,
760
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-output-hover .cell-focus-indicator-left .codeOutput-focus-indicator-container,
761
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .markdown-cell-hover .cell-focus-indicator-left .codeOutput-focus-indicator-container,
762
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row:hover .cell-focus-indicator-left .codeOutput-focus-indicator-container {
763
display: block;
764
}
765
766
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left .codeOutput-focus-indicator-container:hover .codeOutput-focus-indicator {
767
border-left: 5px solid transparent;
768
margin-left: ${focusIndicatorLeftMargin - 1}px;
769
}
770
`);
771
772
styleSheets.push(`
773
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-inner-container.cell-output-focus .cell-focus-indicator-left .codeOutput-focus-indicator,
774
.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container .cell-focus-indicator-left .codeOutput-focus-indicator {
775
border-color: var(--vscode-notebook-focusedCellBorder) !important;
776
}
777
778
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-inner-container .cell-focus-indicator-left .output-focus-indicator {
779
margin-top: ${focusIndicatorGap}px;
780
}
781
`);
782
}
783
784
// between cell insert toolbar
785
if (insertToolbarPosition === 'betweenCells' || insertToolbarPosition === 'both') {
786
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { display: flex; }`);
787
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .cell-list-top-cell-toolbar-container { display: flex; }`);
788
} else {
789
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { display: none; }`);
790
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .cell-list-top-cell-toolbar-container { display: none; }`);
791
}
792
793
if (insertToolbarAlignment === 'left') {
794
styleSheets.push(`
795
.monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container .action-item:first-child,
796
.monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container .action-item:first-child, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .action-item:first-child {
797
margin-right: 0px !important;
798
}`);
799
800
styleSheets.push(`
801
.monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container .monaco-toolbar .action-label,
802
.monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container .monaco-toolbar .action-label, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .monaco-toolbar .action-label {
803
padding: 0px !important;
804
justify-content: center;
805
border-radius: 4px;
806
}`);
807
808
styleSheets.push(`
809
.monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container,
810
.monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container {
811
align-items: flex-start;
812
justify-content: left;
813
margin: 0 16px 0 ${8 + codeCellLeftMargin}px;
814
}`);
815
816
styleSheets.push(`
817
.monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container,
818
.notebookOverlay .cell-bottom-toolbar-container .action-item {
819
border: 0px;
820
}`);
821
}
822
823
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .code-cell-row div.cell.code { margin-left: ${getCellEditorContainerLeftMargin}px; }`);
824
// Chat Edit, deleted Cell Overlay
825
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .code-cell-row div.cell.code { margin-left: ${getCellEditorContainerLeftMargin}px; }`);
826
// Chat Edit, deleted Cell Overlay
827
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .code-cell-row div.cell { margin-right: ${cellRightMargin}px; }`);
828
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${cellRightMargin}px; }`);
829
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${cellTopMargin}px; }`);
830
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container { padding-bottom: ${markdownCellBottomMargin}px; padding-top: ${markdownCellTopMargin}px; }`);
831
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container.webview-backed-markdown-cell { padding: 0; }`);
832
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .webview-backed-markdown-cell.markdown-cell-edit-mode .cell.code { padding-bottom: ${markdownCellBottomMargin}px; padding-top: ${markdownCellTopMargin}px; }`);
833
styleSheets.push(`.notebookOverlay .output { margin: 0px ${cellRightMargin}px 0px ${getCellEditorContainerLeftMargin}px; }`);
834
styleSheets.push(`.notebookOverlay .output { width: calc(100% - ${getCellEditorContainerLeftMargin + cellRightMargin}px); }`);
835
836
// comment
837
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-comment-container { left: ${getCellEditorContainerLeftMargin}px; }`);
838
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-comment-container { width: calc(100% - ${getCellEditorContainerLeftMargin + cellRightMargin}px); }`);
839
840
// output collapse button
841
styleSheets.push(`.monaco-workbench .notebookOverlay .output .output-collapse-container .expandButton { left: -${cellRunGutter}px; }`);
842
styleSheets.push(`.monaco-workbench .notebookOverlay .output .output-collapse-container .expandButton {
843
position: absolute;
844
width: ${cellRunGutter}px;
845
padding: 6px 0px;
846
}`);
847
848
// show more container
849
styleSheets.push(`.notebookOverlay .output-show-more-container { margin: 0px ${cellRightMargin}px 0px ${getCellEditorContainerLeftMargin}px; }`);
850
styleSheets.push(`.notebookOverlay .output-show-more-container { width: calc(100% - ${getCellEditorContainerLeftMargin + cellRightMargin}px); }`);
851
852
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.markdown { padding-left: ${cellRunGutter}px; }`);
853
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator { left: ${(markdownCellGutter - 20) / 2 + markdownCellLeftMargin}px; }`);
854
styleSheets.push(`.notebookOverlay > .cell-list-container .notebook-folded-hint { left: ${markdownCellGutter + markdownCellLeftMargin + 8}px; }`);
855
styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row :not(.webview-backed-markdown-cell) .cell-focus-indicator-top { height: ${cellTopMargin}px; }`);
856
styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${bottomToolbarGap}px; }`);
857
styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left { width: ${getCellEditorContainerLeftMargin}px; }`);
858
styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .cell-focus-indicator-left { width: ${codeCellLeftMargin}px; }`);
859
styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${cellRightMargin}px; }`);
860
styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${cellBottomMargin}px; }`);
861
styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${cellBottomMargin}px; }`);
862
863
styleSheets.push(`
864
.notebookOverlay .monaco-list.selection-multiple .monaco-list-row:has(+ .monaco-list-row.selected) .cell-focus-indicator-bottom {
865
height: ${bottomToolbarGap + cellBottomMargin}px;
866
}
867
`);
868
869
styleSheets.push(`
870
.notebookOverlay .monaco-list .monaco-list-row.code-cell-row.nb-multiCellHighlight:has(+ .monaco-list-row.nb-multiCellHighlight) .cell-focus-indicator-bottom {
871
height: ${bottomToolbarGap + cellBottomMargin}px;
872
background-color: var(--vscode-notebook-symbolHighlightBackground) !important;
873
}
874
875
.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row.nb-multiCellHighlight:has(+ .monaco-list-row.nb-multiCellHighlight) .cell-focus-indicator-bottom {
876
height: ${bottomToolbarGap + cellBottomMargin - 6}px;
877
background-color: var(--vscode-notebook-symbolHighlightBackground) !important;
878
}
879
`);
880
881
882
styleSheets.push(`
883
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .input-collapse-container .cell-collapse-preview {
884
line-height: ${collapsedIndicatorHeight}px;
885
}
886
887
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .input-collapse-container .cell-collapse-preview .monaco-tokenized-source {
888
max-height: ${collapsedIndicatorHeight}px;
889
}
890
`);
891
892
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .monaco-toolbar { height: ${bottomToolbarHeight}px }`);
893
styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .view-zones .cell-list-top-cell-toolbar-container .monaco-toolbar { height: ${bottomToolbarHeight}px }`);
894
895
// cell toolbar
896
styleSheets.push(`.monaco-workbench .notebookOverlay.cell-title-toolbar-right > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar {
897
right: ${cellRightMargin + 26}px;
898
}
899
.monaco-workbench .notebookOverlay.cell-title-toolbar-left > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar {
900
left: ${getCellEditorContainerLeftMargin + 16}px;
901
}
902
.monaco-workbench .notebookOverlay.cell-title-toolbar-hidden > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar {
903
display: none;
904
}`);
905
906
// cell output innert container
907
styleSheets.push(`
908
.monaco-workbench .notebookOverlay .output > div.foreground.output-inner-container {
909
padding: ${OutputInnerContainerTopPadding}px 8px;
910
}
911
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .output-collapse-container {
912
padding: ${OutputInnerContainerTopPadding}px 8px;
913
}
914
`);
915
916
// chat
917
styleSheets.push(`
918
.monaco-workbench .notebookOverlay .cell-chat-part {
919
margin: 0 ${cellRightMargin}px 6px 4px;
920
}
921
`);
922
923
this._styleElement.textContent = styleSheets.join('\n');
924
}
925
926
private _createCellList(): void {
927
this._body.classList.add('cell-list-container');
928
this._dndController = this._register(new CellDragAndDropController(this, this._body));
929
const getScopedContextKeyService = (container: HTMLElement) => this._list.contextKeyService.createScoped(container);
930
this._editorPool = this._register(this.instantiationService.createInstance(NotebookCellEditorPool, this, getScopedContextKeyService));
931
const renderers = [
932
this.instantiationService.createInstance(CodeCellRenderer, this, this._renderedEditors, this._editorPool, this._dndController, getScopedContextKeyService),
933
this.instantiationService.createInstance(MarkupCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService),
934
];
935
936
renderers.forEach(renderer => {
937
this._register(renderer);
938
});
939
940
this._listDelegate = this.instantiationService.createInstance(NotebookCellListDelegate, DOM.getWindow(this.getDomNode()));
941
this._register(this._listDelegate);
942
943
const accessibilityProvider = this.instantiationService.createInstance(NotebookAccessibilityProvider, () => this.viewModel, this.isReplHistory);
944
this._register(accessibilityProvider);
945
946
this._list = this.instantiationService.createInstance(
947
NotebookCellList,
948
'NotebookCellList',
949
this._body,
950
this._viewContext.notebookOptions,
951
this._listDelegate,
952
renderers,
953
this.scopedContextKeyService,
954
{
955
setRowLineHeight: false,
956
setRowHeight: false,
957
supportDynamicHeights: true,
958
horizontalScrolling: false,
959
keyboardSupport: false,
960
mouseSupport: true,
961
multipleSelectionSupport: true,
962
selectionNavigation: true,
963
typeNavigationEnabled: true,
964
paddingTop: 0,
965
paddingBottom: 0,
966
transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
967
initialSize: this._dimension,
968
styleController: (_suffix: string) => { return this._list; },
969
overrideStyles: {
970
listBackground: notebookEditorBackground,
971
listActiveSelectionBackground: notebookEditorBackground,
972
listActiveSelectionForeground: foreground,
973
listFocusAndSelectionBackground: notebookEditorBackground,
974
listFocusAndSelectionForeground: foreground,
975
listFocusBackground: notebookEditorBackground,
976
listFocusForeground: foreground,
977
listHoverForeground: foreground,
978
listHoverBackground: notebookEditorBackground,
979
listHoverOutline: focusBorder,
980
listFocusOutline: focusBorder,
981
listInactiveSelectionBackground: notebookEditorBackground,
982
listInactiveSelectionForeground: foreground,
983
listInactiveFocusBackground: notebookEditorBackground,
984
listInactiveFocusOutline: notebookEditorBackground,
985
},
986
accessibilityProvider
987
},
988
);
989
this._cellLayoutManager = new NotebookCellLayoutManager(this, this._list, this.logService);
990
this._dndController.setList(this._list);
991
992
// create Webview
993
994
this._register(this._list);
995
this._listViewInfoAccessor = new ListViewInfoAccessor(this._list);
996
this._register(this._listViewInfoAccessor);
997
998
this._register(combinedDisposable(...renderers));
999
1000
// top cell toolbar
1001
this._listTopCellToolbar = this._register(this.instantiationService.createInstance(ListTopCellToolbar, this, this.notebookOptions));
1002
1003
// transparent cover
1004
this._webviewTransparentCover = DOM.append(this._list.rowsContainer, $('.webview-cover'));
1005
this._webviewTransparentCover.style.display = 'none';
1006
1007
this._register(DOM.addStandardDisposableGenericMouseDownListener(this._overlayContainer, (e: StandardMouseEvent) => {
1008
if (e.target.classList.contains('slider') && this._webviewTransparentCover) {
1009
this._webviewTransparentCover.style.display = 'block';
1010
}
1011
}));
1012
1013
this._register(DOM.addStandardDisposableGenericMouseUpListener(this._overlayContainer, () => {
1014
if (this._webviewTransparentCover) {
1015
// no matter when
1016
this._webviewTransparentCover.style.display = 'none';
1017
}
1018
}));
1019
1020
this._register(this._list.onMouseDown(e => {
1021
if (e.element) {
1022
this._onMouseDown.fire({ event: e.browserEvent, target: e.element });
1023
}
1024
}));
1025
1026
this._register(this._list.onMouseUp(e => {
1027
if (e.element) {
1028
this._onMouseUp.fire({ event: e.browserEvent, target: e.element });
1029
}
1030
}));
1031
1032
this._register(this._list.onDidChangeFocus(_e => {
1033
this._onDidChangeActiveEditor.fire(this);
1034
this._onDidChangeActiveCell.fire();
1035
this._onDidChangeFocus.fire();
1036
this._cursorNavMode.set(false);
1037
}));
1038
1039
this._register(this._list.onContextMenu(e => {
1040
this.showListContextMenu(e);
1041
}));
1042
1043
this._register(this._list.onDidChangeVisibleRanges(() => {
1044
this._onDidChangeVisibleRanges.fire();
1045
}));
1046
1047
this._register(this._list.onDidScroll((e) => {
1048
if (e.scrollTop !== e.oldScrollTop) {
1049
this._onDidScroll.fire();
1050
this.clearActiveCellWidgets();
1051
}
1052
1053
if (e.scrollTop === e.oldScrollTop && e.scrollHeightChanged) {
1054
this._onDidChangeLayout.fire();
1055
}
1056
}));
1057
1058
this._focusTracker = this._register(DOM.trackFocus(this.getDomNode()));
1059
this._register(this._focusTracker.onDidBlur(() => {
1060
this._editorFocus.set(false);
1061
this.viewModel?.setEditorFocus(false);
1062
this._onDidBlurEmitter.fire();
1063
}));
1064
this._register(this._focusTracker.onDidFocus(() => {
1065
this._editorFocus.set(true);
1066
this.viewModel?.setEditorFocus(true);
1067
this._onDidFocusEmitter.fire();
1068
}));
1069
1070
this._registerNotebookActionsToolbar();
1071
this._registerNotebookStickyScroll();
1072
1073
this._register(this.configurationService.onDidChangeConfiguration(e => {
1074
if (e.affectsConfiguration(accessibilityProvider.verbositySettingId)) {
1075
this._list.ariaLabel = accessibilityProvider?.getWidgetAriaLabel();
1076
}
1077
}));
1078
}
1079
1080
private showListContextMenu(e: IListContextMenuEvent<CellViewModel>) {
1081
this.contextMenuService.showContextMenu({
1082
menuId: MenuId.NotebookCellTitle,
1083
menuActionOptions: {
1084
shouldForwardArgs: true
1085
},
1086
contextKeyService: this.scopedContextKeyService,
1087
getAnchor: () => e.anchor,
1088
getActionsContext: () => {
1089
return {
1090
from: 'cellContainer'
1091
};
1092
}
1093
});
1094
}
1095
1096
private _registerNotebookOverviewRuler() {
1097
this._notebookOverviewRuler = this._register(this.instantiationService.createInstance(NotebookOverviewRuler, this, this._notebookOverviewRulerContainer));
1098
}
1099
1100
private _registerNotebookActionsToolbar() {
1101
this._notebookTopToolbar = this._register(this.instantiationService.createInstance(NotebookEditorWorkbenchToolbar, this, this.scopedContextKeyService, this._notebookOptions, this._notebookTopToolbarContainer));
1102
this._register(this._notebookTopToolbar.onDidChangeVisibility(() => {
1103
if (this._dimension && this._isVisible) {
1104
this.layout(this._dimension);
1105
}
1106
}));
1107
}
1108
1109
private _registerNotebookStickyScroll() {
1110
this._notebookStickyScroll = this._register(this.instantiationService.createInstance(NotebookStickyScroll, this._notebookStickyScrollContainer, this, this._list, (sizeDelta) => {
1111
if (this.isDisposed) {
1112
return;
1113
}
1114
1115
if (this._dimension && this._isVisible) {
1116
if (sizeDelta > 0) { // delta > 0 ==> sticky is growing, cell list shrinking
1117
this.layout(this._dimension);
1118
this.setScrollTop(this.scrollTop + sizeDelta);
1119
} else if (sizeDelta < 0) { // delta < 0 ==> sticky is shrinking, cell list growing
1120
this.setScrollTop(this.scrollTop + sizeDelta);
1121
this.layout(this._dimension);
1122
}
1123
}
1124
1125
this._onDidScroll.fire();
1126
}));
1127
}
1128
1129
private _updateOutputRenderers() {
1130
if (!this.viewModel || !this._webview) {
1131
return;
1132
}
1133
1134
this._webview.updateOutputRenderers();
1135
this.viewModel.viewCells.forEach(cell => {
1136
cell.outputsViewModels.forEach(output => {
1137
if (output.pickedMimeType?.rendererId === RENDERER_NOT_AVAILABLE) {
1138
output.resetRenderer();
1139
}
1140
});
1141
});
1142
}
1143
1144
getDomNode() {
1145
return this._overlayContainer;
1146
}
1147
1148
getOverflowContainerDomNode() {
1149
return this._overflowContainer;
1150
}
1151
1152
getInnerWebview(): IWebviewElement | undefined {
1153
return this._webview?.webview;
1154
}
1155
1156
setEditorProgressService(editorProgressService: IEditorProgressService): void {
1157
this.editorProgressService = editorProgressService;
1158
}
1159
1160
setParentContextKeyService(parentContextKeyService: IContextKeyService): void {
1161
this.scopedContextKeyService.updateParent(parentContextKeyService);
1162
}
1163
1164
async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks, viewType?: string): Promise<void> {
1165
if (this.viewModel === undefined || !this.viewModel.equal(textModel)) {
1166
const oldBottomToolbarDimensions = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType);
1167
this._detachModel();
1168
await this._attachModel(textModel, viewType ?? textModel.viewType, viewState, perf);
1169
const newBottomToolbarDimensions = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType);
1170
1171
if (oldBottomToolbarDimensions.bottomToolbarGap !== newBottomToolbarDimensions.bottomToolbarGap
1172
|| oldBottomToolbarDimensions.bottomToolbarHeight !== newBottomToolbarDimensions.bottomToolbarHeight) {
1173
this._styleElement?.remove();
1174
this._createLayoutStyles();
1175
this._webview?.updateOptions({
1176
...this.notebookOptions.computeWebviewOptions(),
1177
fontFamily: this._generateFontFamily()
1178
});
1179
}
1180
type WorkbenchNotebookOpenClassification = {
1181
owner: 'rebornix';
1182
comment: 'Identify the notebook editor view type';
1183
scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the resource' };
1184
ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the resource' };
1185
viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'View type of the notebook editor' };
1186
isRepl: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the notebook editor is within a REPL editor' };
1187
};
1188
1189
type WorkbenchNotebookOpenEvent = {
1190
scheme: string;
1191
ext: string;
1192
viewType: string;
1193
isRepl: boolean;
1194
};
1195
1196
this.telemetryService.publicLog2<WorkbenchNotebookOpenEvent, WorkbenchNotebookOpenClassification>('notebook/editorOpened', {
1197
scheme: textModel.uri.scheme,
1198
ext: extname(textModel.uri),
1199
viewType: textModel.viewType,
1200
isRepl: this.isReplHistory
1201
});
1202
} else {
1203
this.restoreListViewState(viewState);
1204
}
1205
1206
this._restoreSelectedKernel(viewState);
1207
1208
// load preloads for matching kernel
1209
this._loadKernelPreloads();
1210
1211
// clear state
1212
this._dndController?.clearGlobalDragState();
1213
1214
this._localStore.add(this._list.onDidChangeFocus(() => {
1215
this.updateContextKeysOnFocusChange();
1216
}));
1217
1218
this.updateContextKeysOnFocusChange();
1219
// render markdown top down on idle
1220
this._backgroundMarkdownRendering();
1221
}
1222
1223
private _backgroundMarkdownRenderRunning = false;
1224
private _backgroundMarkdownRendering() {
1225
if (this._backgroundMarkdownRenderRunning) {
1226
return;
1227
}
1228
1229
this._backgroundMarkdownRenderRunning = true;
1230
DOM.runWhenWindowIdle(DOM.getWindow(this.getDomNode()), (deadline) => {
1231
this._backgroundMarkdownRenderingWithDeadline(deadline);
1232
});
1233
}
1234
1235
private _backgroundMarkdownRenderingWithDeadline(deadline: IdleDeadline) {
1236
const endTime = Date.now() + deadline.timeRemaining();
1237
1238
const execute = () => {
1239
try {
1240
this._backgroundMarkdownRenderRunning = true;
1241
if (this._isDisposed) {
1242
return;
1243
}
1244
1245
if (!this.viewModel) {
1246
return;
1247
}
1248
1249
const firstMarkupCell = this.viewModel.viewCells.find(cell => cell.cellKind === CellKind.Markup && !this._webview?.markupPreviewMapping.has(cell.id) && !this.cellIsHidden(cell)) as MarkupCellViewModel | undefined;
1250
if (!firstMarkupCell) {
1251
return;
1252
}
1253
1254
this.createMarkupPreview(firstMarkupCell);
1255
} finally {
1256
this._backgroundMarkdownRenderRunning = false;
1257
}
1258
1259
if (Date.now() < endTime) {
1260
setTimeout0(execute);
1261
} else {
1262
this._backgroundMarkdownRendering();
1263
}
1264
};
1265
1266
execute();
1267
}
1268
1269
private updateContextKeysOnFocusChange() {
1270
if (!this.viewModel) {
1271
return;
1272
}
1273
1274
const focused = this._list.getFocusedElements()[0];
1275
if (focused) {
1276
if (!this._cellContextKeyManager) {
1277
this._cellContextKeyManager = this._localStore.add(this.instantiationService.createInstance(CellContextKeyManager, this, focused as CellViewModel));
1278
}
1279
1280
this._cellContextKeyManager.updateForElement(focused as CellViewModel);
1281
}
1282
}
1283
1284
async setOptions(options: INotebookEditorOptions | undefined) {
1285
if (options?.isReadOnly !== undefined) {
1286
this._readOnly = options?.isReadOnly;
1287
}
1288
1289
if (!this.viewModel) {
1290
return;
1291
}
1292
1293
this.viewModel.updateOptions({ isReadOnly: this._readOnly });
1294
this.notebookOptions.updateOptions(this._readOnly);
1295
1296
// reveal cell if editor options tell to do so
1297
const cellOptions = options?.cellOptions ?? this._parseIndexedCellOptions(options);
1298
if (cellOptions) {
1299
const cell = this.viewModel.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString());
1300
if (cell) {
1301
this.focusElement(cell);
1302
const selection = cellOptions.options?.selection;
1303
if (selection) {
1304
cell.updateEditState(CellEditState.Editing, 'setOptions');
1305
cell.focusMode = CellFocusMode.Editor;
1306
await this.revealRangeInCenterIfOutsideViewportAsync(cell, new Range(selection.startLineNumber, selection.startColumn, selection.endLineNumber || selection.startLineNumber, selection.endColumn || selection.startColumn));
1307
} else {
1308
this._list.revealCell(cell, options?.cellRevealType ?? CellRevealType.CenterIfOutsideViewport);
1309
}
1310
1311
const editor = this._renderedEditors.get(cell)!;
1312
if (editor) {
1313
if (cellOptions.options?.selection) {
1314
const { selection } = cellOptions.options;
1315
const editorSelection = new Range(selection.startLineNumber, selection.startColumn, selection.endLineNumber || selection.startLineNumber, selection.endColumn || selection.startColumn);
1316
editor.setSelection(editorSelection);
1317
editor.revealPositionInCenterIfOutsideViewport({
1318
lineNumber: selection.startLineNumber,
1319
column: selection.startColumn
1320
});
1321
await this.revealRangeInCenterIfOutsideViewportAsync(cell, editorSelection);
1322
}
1323
if (!cellOptions.options?.preserveFocus) {
1324
editor.focus();
1325
}
1326
}
1327
}
1328
}
1329
1330
// select cells if options tell to do so
1331
// todo@rebornix https://github.com/microsoft/vscode/issues/118108 support selections not just focus
1332
// todo@rebornix support multipe selections
1333
if (options?.cellSelections) {
1334
const focusCellIndex = options.cellSelections[0].start;
1335
const focusedCell = this.viewModel.cellAt(focusCellIndex);
1336
if (focusedCell) {
1337
this.viewModel.updateSelectionsState({
1338
kind: SelectionStateType.Index,
1339
focus: { start: focusCellIndex, end: focusCellIndex + 1 },
1340
selections: options.cellSelections
1341
});
1342
this.revealInCenterIfOutsideViewport(focusedCell);
1343
}
1344
}
1345
1346
this._updateForOptions();
1347
this._onDidChangeOptions.fire();
1348
}
1349
1350
private _parseIndexedCellOptions(options: INotebookEditorOptions | undefined) {
1351
if (options?.indexedCellOptions) {
1352
// convert index based selections
1353
const cell = this.cellAt(options.indexedCellOptions.index);
1354
if (cell) {
1355
return {
1356
resource: cell.uri,
1357
options: {
1358
selection: options.indexedCellOptions.selection,
1359
preserveFocus: false
1360
}
1361
};
1362
}
1363
}
1364
1365
return undefined;
1366
}
1367
1368
private _detachModel() {
1369
this._localStore.clear();
1370
dispose(this._localCellStateListeners);
1371
this._list.detachViewModel();
1372
this.viewModel?.dispose();
1373
// avoid event
1374
this.viewModel = undefined;
1375
this._webview?.dispose();
1376
this._webview?.element.remove();
1377
this._webview = null;
1378
this._list.clear();
1379
}
1380
1381
1382
private _updateForOptions(): void {
1383
if (!this.viewModel) {
1384
return;
1385
}
1386
1387
this._editorEditable.set(!this.viewModel.options.isReadOnly);
1388
this._overflowContainer.classList.toggle('notebook-editor-editable', !this.viewModel.options.isReadOnly);
1389
this.getDomNode().classList.toggle('notebook-editor-editable', !this.viewModel.options.isReadOnly);
1390
}
1391
1392
private async _resolveWebview(): Promise<BackLayerWebView<ICommonCellInfo> | null> {
1393
if (!this.textModel) {
1394
return null;
1395
}
1396
1397
if (this._webviewResolvePromise) {
1398
return this._webviewResolvePromise;
1399
}
1400
1401
if (!this._webview) {
1402
this._ensureWebview(this.getId(), this.textModel.viewType, this.textModel.uri);
1403
}
1404
1405
this._webviewResolvePromise = (async () => {
1406
if (!this._webview) {
1407
throw new Error('Notebook output webview object is not created successfully.');
1408
}
1409
1410
await this._webview.createWebview(this.creationOptions.codeWindow ?? mainWindow);
1411
if (!this._webview.webview) {
1412
throw new Error('Notebook output webview element was not created successfully.');
1413
}
1414
1415
this._localStore.add(this._webview.webview.onDidBlur(() => {
1416
this._outputFocus.set(false);
1417
this._webviewFocused = false;
1418
1419
this.updateEditorFocus();
1420
this.updateCellFocusMode();
1421
}));
1422
1423
this._localStore.add(this._webview.webview.onDidFocus(() => {
1424
this._outputFocus.set(true);
1425
this.updateEditorFocus();
1426
this._webviewFocused = true;
1427
}));
1428
1429
this._localStore.add(this._webview.onMessage(e => {
1430
this._onDidReceiveMessage.fire(e);
1431
}));
1432
1433
return this._webview;
1434
})();
1435
1436
return this._webviewResolvePromise;
1437
}
1438
1439
private _ensureWebview(id: string, viewType: string, resource: URI) {
1440
if (this._webview) {
1441
return;
1442
}
1443
1444
const that = this;
1445
1446
this._webview = this.instantiationService.createInstance(BackLayerWebView, {
1447
get creationOptions() { return that.creationOptions; },
1448
setScrollTop(scrollTop: number) { that._list.scrollTop = scrollTop; },
1449
triggerScroll(event: IMouseWheelEvent) { that._list.triggerScrollFromMouseWheelEvent(event); },
1450
getCellByInfo: that.getCellByInfo.bind(that),
1451
getCellById: that._getCellById.bind(that),
1452
toggleNotebookCellSelection: that._toggleNotebookCellSelection.bind(that),
1453
focusNotebookCell: that.focusNotebookCell.bind(that),
1454
focusNextNotebookCell: that.focusNextNotebookCell.bind(that),
1455
updateOutputHeight: that._updateOutputHeight.bind(that),
1456
scheduleOutputHeightAck: that._scheduleOutputHeightAck.bind(that),
1457
updateMarkupCellHeight: that._updateMarkupCellHeight.bind(that),
1458
setMarkupCellEditState: that._setMarkupCellEditState.bind(that),
1459
didStartDragMarkupCell: that._didStartDragMarkupCell.bind(that),
1460
didDragMarkupCell: that._didDragMarkupCell.bind(that),
1461
didDropMarkupCell: that._didDropMarkupCell.bind(that),
1462
didEndDragMarkupCell: that._didEndDragMarkupCell.bind(that),
1463
didResizeOutput: that._didResizeOutput.bind(that),
1464
updatePerformanceMetadata: that._updatePerformanceMetadata.bind(that),
1465
didFocusOutputInputChange: that._didFocusOutputInputChange.bind(that),
1466
}, id, viewType, resource, {
1467
...this._notebookOptions.computeWebviewOptions(),
1468
fontFamily: this._generateFontFamily()
1469
}, this.notebookRendererMessaging.getScoped(this._uuid));
1470
1471
this._webview.element.style.width = '100%';
1472
1473
// attach the webview container to the DOM tree first
1474
this._list.attachWebview(this._webview.element);
1475
}
1476
1477
private async _attachModel(textModel: NotebookTextModel, viewType: string, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks) {
1478
this._ensureWebview(this.getId(), textModel.viewType, textModel.uri);
1479
1480
this.viewModel = this.instantiationService.createInstance(NotebookViewModel, viewType, textModel, this._viewContext, this.getLayoutInfo(), { isReadOnly: this._readOnly });
1481
this._viewContext.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
1482
this.notebookOptions.updateOptions(this._readOnly);
1483
1484
this._updateForOptions();
1485
this._updateForNotebookConfiguration();
1486
1487
// restore view states, including contributions
1488
1489
{
1490
// restore view state
1491
this.viewModel.restoreEditorViewState(viewState);
1492
1493
// contribution state restore
1494
1495
const contributionsState = viewState?.contributionsState || {};
1496
for (const [id, contribution] of this._contributions) {
1497
if (typeof contribution.restoreViewState === 'function') {
1498
contribution.restoreViewState(contributionsState[id]);
1499
}
1500
}
1501
}
1502
1503
this._localStore.add(this.viewModel.onDidChangeViewCells(e => {
1504
this._onDidChangeViewCells.fire(e);
1505
}));
1506
1507
this._localStore.add(this.viewModel.onDidChangeSelection(() => {
1508
this._onDidChangeSelection.fire();
1509
this.updateSelectedMarkdownPreviews();
1510
}));
1511
1512
this._localStore.add(this._list.onWillScroll(e => {
1513
if (this._webview?.isResolved()) {
1514
this._webviewTransparentCover!.style.transform = `translateY(${e.scrollTop})`;
1515
}
1516
}));
1517
1518
let hasPendingChangeContentHeight = false;
1519
const renderScrollHeightDisposable = this._localStore.add(new MutableDisposable());
1520
this._localStore.add(this._list.onDidChangeContentHeight(() => {
1521
if (hasPendingChangeContentHeight) {
1522
return;
1523
}
1524
hasPendingChangeContentHeight = true;
1525
1526
renderScrollHeightDisposable.value = DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.getDomNode()), () => {
1527
hasPendingChangeContentHeight = false;
1528
this._updateScrollHeight();
1529
}, 100);
1530
}));
1531
1532
this._localStore.add(this._list.onDidRemoveOutputs(outputs => {
1533
outputs.forEach(output => this.removeInset(output));
1534
}));
1535
this._localStore.add(this._list.onDidHideOutputs(outputs => {
1536
outputs.forEach(output => this.hideInset(output));
1537
}));
1538
this._localStore.add(this._list.onDidRemoveCellsFromView(cells => {
1539
const hiddenCells: MarkupCellViewModel[] = [];
1540
const deletedCells: MarkupCellViewModel[] = [];
1541
1542
for (const cell of cells) {
1543
if (cell.cellKind === CellKind.Markup) {
1544
const mdCell = cell as MarkupCellViewModel;
1545
if (this.viewModel?.viewCells.find(cell => cell.handle === mdCell.handle)) {
1546
// Cell has been folded but is still in model
1547
hiddenCells.push(mdCell);
1548
} else {
1549
// Cell was deleted
1550
deletedCells.push(mdCell);
1551
}
1552
}
1553
}
1554
1555
this.hideMarkupPreviews(hiddenCells);
1556
this.deleteMarkupPreviews(deletedCells);
1557
}));
1558
1559
// init rendering
1560
await this._warmupWithMarkdownRenderer(this.viewModel, viewState, perf);
1561
1562
perf?.mark('customMarkdownLoaded');
1563
1564
// model attached
1565
this._localCellStateListeners = this.viewModel.viewCells.map(cell => this._bindCellListener(cell));
1566
this._lastCellWithEditorFocus = this.viewModel.viewCells.find(viewCell => this.getActiveCell() === viewCell && viewCell.focusMode === CellFocusMode.Editor) ?? null;
1567
1568
this._localStore.add(this.viewModel.onDidChangeViewCells((e) => {
1569
if (this._isDisposed) {
1570
return;
1571
}
1572
1573
// update cell listener
1574
[...e.splices].reverse().forEach(splice => {
1575
const [start, deleted, newCells] = splice;
1576
const deletedCells = this._localCellStateListeners.splice(start, deleted, ...newCells.map(cell => this._bindCellListener(cell)));
1577
1578
dispose(deletedCells);
1579
});
1580
1581
if (e.splices.some(s => s[2].some(cell => cell.cellKind === CellKind.Markup))) {
1582
this._backgroundMarkdownRendering();
1583
}
1584
}));
1585
1586
if (this._dimension) {
1587
this._list.layout(this.getBodyHeight(this._dimension.height), this._dimension.width);
1588
} else {
1589
this._list.layout();
1590
}
1591
1592
this._dndController?.clearGlobalDragState();
1593
1594
// restore list state at last, it must be after list layout
1595
this.restoreListViewState(viewState);
1596
}
1597
1598
private _bindCellListener(cell: ICellViewModel) {
1599
const store = new DisposableStore();
1600
1601
store.add(cell.onDidChangeLayout(e => {
1602
// e.totalHeight will be false it's not changed
1603
if (e.totalHeight || e.outerWidth) {
1604
this.layoutNotebookCell(cell, cell.layoutInfo.totalHeight, e.context);
1605
}
1606
}));
1607
1608
if (cell.cellKind === CellKind.Code) {
1609
store.add((cell as CodeCellViewModel).onDidRemoveOutputs((outputs) => {
1610
outputs.forEach(output => this.removeInset(output));
1611
}));
1612
}
1613
1614
store.add((cell as CellViewModel).onDidChangeState(e => {
1615
if (e.inputCollapsedChanged && cell.isInputCollapsed && cell.cellKind === CellKind.Markup) {
1616
this.hideMarkupPreviews([(cell as MarkupCellViewModel)]);
1617
}
1618
1619
if (e.outputCollapsedChanged && cell.isOutputCollapsed && cell.cellKind === CellKind.Code) {
1620
cell.outputsViewModels.forEach(output => this.hideInset(output));
1621
}
1622
1623
if (e.focusModeChanged) {
1624
this._validateCellFocusMode(cell);
1625
}
1626
}));
1627
1628
store.add(cell.onCellDecorationsChanged(e => {
1629
e.added.forEach(options => {
1630
if (options.className) {
1631
this.deltaCellContainerClassNames(cell.id, [options.className], [], cell.cellKind);
1632
}
1633
1634
if (options.outputClassName) {
1635
this.deltaCellContainerClassNames(cell.id, [options.outputClassName], [], cell.cellKind);
1636
}
1637
});
1638
1639
e.removed.forEach(options => {
1640
if (options.className) {
1641
this.deltaCellContainerClassNames(cell.id, [], [options.className], cell.cellKind);
1642
}
1643
1644
if (options.outputClassName) {
1645
this.deltaCellContainerClassNames(cell.id, [], [options.outputClassName], cell.cellKind);
1646
}
1647
});
1648
}));
1649
1650
return store;
1651
}
1652
1653
1654
private _lastCellWithEditorFocus: ICellViewModel | null = null;
1655
private _validateCellFocusMode(cell: ICellViewModel) {
1656
if (cell.focusMode !== CellFocusMode.Editor) {
1657
return;
1658
}
1659
1660
if (this._lastCellWithEditorFocus && this._lastCellWithEditorFocus !== cell) {
1661
this._lastCellWithEditorFocus.focusMode = CellFocusMode.Container;
1662
}
1663
1664
this._lastCellWithEditorFocus = cell;
1665
}
1666
1667
private async _warmupWithMarkdownRenderer(viewModel: NotebookViewModel, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks) {
1668
1669
this.logService.debug('NotebookEditorWidget', 'warmup ' + this.viewModel?.uri.toString());
1670
await this._resolveWebview();
1671
perf?.mark('webviewCommLoaded');
1672
1673
this.logService.debug('NotebookEditorWidget', 'warmup - webview resolved');
1674
1675
// make sure that the webview is not visible otherwise users will see pre-rendered markdown cells in wrong position as the list view doesn't have a correct `top` offset yet
1676
this._webview!.element.style.visibility = 'hidden';
1677
// warm up can take around 200ms to load markdown libraries, etc.
1678
await this._warmupViewportMarkdownCells(viewModel, viewState);
1679
this.logService.debug('NotebookEditorWidget', 'warmup - viewport warmed up');
1680
1681
// todo@rebornix @mjbvz, is this too complicated?
1682
1683
/* now the webview is ready, and requests to render markdown are fast enough
1684
* we can start rendering the list view
1685
* render
1686
* - markdown cell -> request to webview to (10ms, basically just latency between UI and iframe)
1687
* - code cell -> render in place
1688
*/
1689
this._list.layout(0, 0);
1690
this._list.attachViewModel(viewModel);
1691
1692
// now the list widget has a correct contentHeight/scrollHeight
1693
// setting scrollTop will work properly
1694
// after setting scroll top, the list view will update `top` of the scrollable element, e.g. `top: -584px`
1695
this._list.scrollTop = viewState?.scrollPosition?.top ?? 0;
1696
this._debug('finish initial viewport warmup and view state restore.');
1697
this._webview!.element.style.visibility = 'visible';
1698
this.logService.debug('NotebookEditorWidget', 'warmup - list view model attached, set to visible');
1699
this._onDidAttachViewModel.fire();
1700
}
1701
1702
private async _warmupViewportMarkdownCells(viewModel: NotebookViewModel, viewState: INotebookEditorViewState | undefined) {
1703
if (viewState && viewState.cellTotalHeights) {
1704
const totalHeightCache = viewState.cellTotalHeights;
1705
const scrollTop = viewState.scrollPosition?.top ?? 0;
1706
const scrollBottom = scrollTop + Math.max(this._dimension?.height ?? 0, 1080);
1707
1708
let offset = 0;
1709
const requests: [ICellViewModel, number][] = [];
1710
1711
for (let i = 0; i < viewModel.length; i++) {
1712
const cell = viewModel.cellAt(i)!;
1713
const cellHeight = totalHeightCache[i] ?? 0;
1714
1715
if (offset + cellHeight < scrollTop) {
1716
offset += cellHeight;
1717
continue;
1718
}
1719
1720
if (cell.cellKind === CellKind.Markup) {
1721
requests.push([cell, offset]);
1722
}
1723
1724
offset += cellHeight;
1725
1726
if (offset > scrollBottom) {
1727
break;
1728
}
1729
}
1730
1731
await this._webview!.initializeMarkup(requests.map(([model, offset]) => this.createMarkupCellInitialization(model, offset)));
1732
} else {
1733
const initRequests = viewModel.viewCells
1734
.filter(cell => cell.cellKind === CellKind.Markup)
1735
.slice(0, 5)
1736
.map(cell => this.createMarkupCellInitialization(cell, -10000));
1737
1738
await this._webview!.initializeMarkup(initRequests);
1739
1740
// no cached view state so we are rendering the first viewport
1741
// after above async call, we already get init height for markdown cells, we can update their offset
1742
let offset = 0;
1743
const offsetUpdateRequests: { id: string; top: number }[] = [];
1744
const scrollBottom = Math.max(this._dimension?.height ?? 0, 1080);
1745
for (const cell of viewModel.viewCells) {
1746
if (cell.cellKind === CellKind.Markup) {
1747
offsetUpdateRequests.push({ id: cell.id, top: offset });
1748
}
1749
1750
offset += cell.getHeight(this.getLayoutInfo().fontInfo.lineHeight);
1751
1752
if (offset > scrollBottom) {
1753
break;
1754
}
1755
}
1756
1757
this._webview?.updateScrollTops([], offsetUpdateRequests);
1758
}
1759
}
1760
1761
private createMarkupCellInitialization(model: ICellViewModel, offset: number): IMarkupCellInitialization {
1762
return ({
1763
mime: model.mime,
1764
cellId: model.id,
1765
cellHandle: model.handle,
1766
content: model.getText(),
1767
offset: offset,
1768
visible: false,
1769
metadata: model.metadata,
1770
});
1771
}
1772
1773
restoreListViewState(viewState: INotebookEditorViewState | undefined): void {
1774
if (!this.viewModel) {
1775
return;
1776
}
1777
1778
if (viewState?.scrollPosition !== undefined) {
1779
this._list.scrollTop = viewState.scrollPosition.top;
1780
this._list.scrollLeft = viewState.scrollPosition.left;
1781
} else {
1782
this._list.scrollTop = 0;
1783
this._list.scrollLeft = 0;
1784
}
1785
1786
const focusIdx = typeof viewState?.focus === 'number' ? viewState.focus : 0;
1787
if (focusIdx < this.viewModel.length) {
1788
const element = this.viewModel.cellAt(focusIdx);
1789
if (element) {
1790
this.viewModel?.updateSelectionsState({
1791
kind: SelectionStateType.Handle,
1792
primary: element.handle,
1793
selections: [element.handle]
1794
});
1795
}
1796
} else if (this._list.length > 0) {
1797
this.viewModel.updateSelectionsState({
1798
kind: SelectionStateType.Index,
1799
focus: { start: 0, end: 1 },
1800
selections: [{ start: 0, end: 1 }]
1801
});
1802
}
1803
1804
if (viewState?.editorFocused) {
1805
const cell = this.viewModel.cellAt(focusIdx);
1806
if (cell) {
1807
cell.focusMode = CellFocusMode.Editor;
1808
}
1809
}
1810
}
1811
1812
private _restoreSelectedKernel(viewState: INotebookEditorViewState | undefined): void {
1813
if (viewState?.selectedKernelId && this.textModel) {
1814
const matching = this.notebookKernelService.getMatchingKernel(this.textModel);
1815
const kernel = matching.all.find(k => k.id === viewState.selectedKernelId);
1816
// Selected kernel may have already been picked prior to the view state loading
1817
// If so, don't overwrite it with the saved kernel.
1818
if (kernel && !matching.selected) {
1819
this.notebookKernelService.selectKernelForNotebook(kernel, this.textModel);
1820
}
1821
}
1822
}
1823
1824
getEditorViewState(): INotebookEditorViewState {
1825
const state = this.viewModel?.getEditorViewState();
1826
if (!state) {
1827
return {
1828
editingCells: {},
1829
cellLineNumberStates: {},
1830
editorViewStates: {},
1831
collapsedInputCells: {},
1832
collapsedOutputCells: {},
1833
};
1834
}
1835
1836
if (this._list) {
1837
state.scrollPosition = { left: this._list.scrollLeft, top: this._list.scrollTop };
1838
const cellHeights: { [key: number]: number } = {};
1839
for (let i = 0; i < this.viewModel!.length; i++) {
1840
const elm = this.viewModel!.cellAt(i) as CellViewModel;
1841
cellHeights[i] = elm.layoutInfo.totalHeight;
1842
}
1843
1844
state.cellTotalHeights = cellHeights;
1845
1846
if (this.viewModel) {
1847
const focusRange = this.viewModel.getFocus();
1848
const element = this.viewModel.cellAt(focusRange.start);
1849
if (element) {
1850
const itemDOM = this._list.domElementOfElement(element);
1851
const editorFocused = element.getEditState() === CellEditState.Editing && !!(itemDOM && itemDOM.ownerDocument.activeElement && itemDOM.contains(itemDOM.ownerDocument.activeElement));
1852
1853
state.editorFocused = editorFocused;
1854
state.focus = focusRange.start;
1855
}
1856
}
1857
}
1858
1859
// Save contribution view states
1860
const contributionsState: { [key: string]: unknown } = {};
1861
for (const [id, contribution] of this._contributions) {
1862
if (typeof contribution.saveViewState === 'function') {
1863
contributionsState[id] = contribution.saveViewState();
1864
}
1865
}
1866
state.contributionsState = contributionsState;
1867
if (this.textModel?.uri.scheme === Schemas.untitled) {
1868
state.selectedKernelId = this.activeKernel?.id;
1869
}
1870
1871
return state;
1872
}
1873
1874
private _allowScrollBeyondLastLine() {
1875
return this._scrollBeyondLastLine && !this.isReplHistory;
1876
}
1877
1878
private getBodyHeight(dimensionHeight: number) {
1879
return Math.max(dimensionHeight - (this._notebookTopToolbar?.useGlobalToolbar ? /** Toolbar height */ 26 : 0), 0);
1880
}
1881
1882
layout(dimension: DOM.Dimension, shadowElement?: HTMLElement, position?: DOM.IDomPosition): void {
1883
if (!shadowElement && this._shadowElementViewInfo === null) {
1884
this._dimension = dimension;
1885
this._position = position;
1886
return;
1887
}
1888
1889
if (dimension.width <= 0 || dimension.height <= 0) {
1890
this.onWillHide();
1891
return;
1892
}
1893
1894
const whenContainerStylesLoaded = this.layoutService.whenContainerStylesLoaded(DOM.getWindow(this.getDomNode()));
1895
if (whenContainerStylesLoaded) {
1896
// In floating windows, we need to ensure that the
1897
// container is ready for us to compute certain
1898
// layout related properties.
1899
whenContainerStylesLoaded.then(() => this.layoutNotebook(dimension, shadowElement, position));
1900
} else {
1901
this.layoutNotebook(dimension, shadowElement, position);
1902
}
1903
1904
}
1905
1906
private layoutNotebook(dimension: DOM.Dimension, shadowElement?: HTMLElement, position?: DOM.IDomPosition) {
1907
if (shadowElement) {
1908
this.updateShadowElement(shadowElement, dimension, position);
1909
}
1910
1911
if (this._shadowElementViewInfo && this._shadowElementViewInfo.width <= 0 && this._shadowElementViewInfo.height <= 0) {
1912
this.onWillHide();
1913
return;
1914
}
1915
1916
this._dimension = dimension;
1917
this._position = position;
1918
const newBodyHeight = this.getBodyHeight(dimension.height) - this.getLayoutInfo().stickyHeight;
1919
DOM.size(this._body, dimension.width, newBodyHeight);
1920
1921
const newCellListHeight = newBodyHeight;
1922
if (this._list.getRenderHeight() < newCellListHeight) {
1923
// the new dimension is larger than the list viewport, update its additional height first, otherwise the list view will move down a bit (as the `scrollBottom` will move down)
1924
this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: 0 });
1925
this._list.layout(newCellListHeight, dimension.width);
1926
} else {
1927
// the new dimension is smaller than the list viewport, if we update the additional height, the `scrollBottom` will move up, which moves the whole list view upwards a bit. So we run a layout first.
1928
this._list.layout(newCellListHeight, dimension.width);
1929
this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: 0 });
1930
}
1931
1932
this._overlayContainer.inert = false;
1933
this._overlayContainer.style.visibility = 'visible';
1934
this._overlayContainer.style.display = 'block';
1935
this._overlayContainer.style.position = 'absolute';
1936
this._overlayContainer.style.overflow = 'hidden';
1937
1938
this.layoutContainerOverShadowElement(dimension, position);
1939
1940
if (this._webviewTransparentCover) {
1941
this._webviewTransparentCover.style.height = `${dimension.height}px`;
1942
this._webviewTransparentCover.style.width = `${dimension.width}px`;
1943
}
1944
1945
this._notebookTopToolbar.layout(this._dimension);
1946
this._notebookOverviewRuler.layout();
1947
1948
this._viewContext?.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
1949
}
1950
1951
private updateShadowElement(shadowElement: HTMLElement, dimension?: IDimension, position?: DOM.IDomPosition) {
1952
this._shadowElement = shadowElement;
1953
if (dimension && position) {
1954
this._shadowElementViewInfo = {
1955
height: dimension.height,
1956
width: dimension.width,
1957
top: position.top,
1958
left: position.left,
1959
};
1960
} else {
1961
// We have to recompute position and size ourselves (which is slow)
1962
const containerRect = shadowElement.getBoundingClientRect();
1963
this._shadowElementViewInfo = {
1964
height: containerRect.height,
1965
width: containerRect.width,
1966
top: containerRect.top,
1967
left: containerRect.left
1968
};
1969
}
1970
}
1971
1972
private layoutContainerOverShadowElement(dimension?: DOM.Dimension, position?: DOM.IDomPosition): void {
1973
if (dimension && position) {
1974
this._overlayContainer.style.top = `${position.top}px`;
1975
this._overlayContainer.style.left = `${position.left}px`;
1976
this._overlayContainer.style.width = `${dimension.width}px`;
1977
this._overlayContainer.style.height = `${dimension.height}px`;
1978
return;
1979
}
1980
1981
if (!this._shadowElementViewInfo) {
1982
return;
1983
}
1984
1985
const elementContainerRect = this._overlayContainer.parentElement?.getBoundingClientRect();
1986
this._overlayContainer.style.top = `${this._shadowElementViewInfo.top - (elementContainerRect?.top || 0)}px`;
1987
this._overlayContainer.style.left = `${this._shadowElementViewInfo.left - (elementContainerRect?.left || 0)}px`;
1988
this._overlayContainer.style.width = `${dimension ? dimension.width : this._shadowElementViewInfo.width}px`;
1989
this._overlayContainer.style.height = `${dimension ? dimension.height : this._shadowElementViewInfo.height}px`;
1990
}
1991
1992
//#endregion
1993
1994
//#region Focus tracker
1995
focus() {
1996
this._isVisible = true;
1997
this._editorFocus.set(true);
1998
1999
if (this._webviewFocused) {
2000
this._webview?.focusWebview();
2001
} else {
2002
if (this.viewModel) {
2003
const focusRange = this.viewModel.getFocus();
2004
const element = this.viewModel.cellAt(focusRange.start);
2005
2006
// The notebook editor doesn't have focus yet
2007
if (!this.hasEditorFocus()) {
2008
this.focusContainer();
2009
// trigger editor to update as FocusTracker might not emit focus change event
2010
this.updateEditorFocus();
2011
}
2012
2013
if (element && element.focusMode === CellFocusMode.Editor) {
2014
element.updateEditState(CellEditState.Editing, 'editorWidget.focus');
2015
element.focusMode = CellFocusMode.Editor;
2016
this.focusEditor(element);
2017
return;
2018
}
2019
}
2020
2021
this._list.domFocus();
2022
}
2023
2024
if (this._currentProgress) {
2025
// The editor forces progress to hide when switching editors. So if progress should be visible, force it to show when the editor is focused.
2026
this.showProgress();
2027
}
2028
}
2029
2030
onShow() {
2031
this._isVisible = true;
2032
}
2033
2034
private focusEditor(activeElement: CellViewModel): void {
2035
for (const [element, editor] of this._renderedEditors.entries()) {
2036
if (element === activeElement) {
2037
editor.focus();
2038
return;
2039
}
2040
}
2041
}
2042
2043
focusContainer(clearSelection: boolean = false) {
2044
if (this._webviewFocused) {
2045
this._webview?.focusWebview();
2046
} else {
2047
this._list.focusContainer(clearSelection);
2048
}
2049
}
2050
2051
selectOutputContent(cell: ICellViewModel) {
2052
this._webview?.selectOutputContents(cell);
2053
}
2054
2055
selectInputContents(cell: ICellViewModel) {
2056
this._webview?.selectInputContents(cell);
2057
}
2058
2059
onWillHide() {
2060
this._isVisible = false;
2061
this._editorFocus.set(false);
2062
this._overlayContainer.inert = true;
2063
this._overlayContainer.style.visibility = 'hidden';
2064
this._overlayContainer.style.left = '-50000px';
2065
this._notebookTopToolbarContainer.style.display = 'none';
2066
this.clearActiveCellWidgets();
2067
}
2068
2069
private clearActiveCellWidgets() {
2070
this._renderedEditors.forEach((editor, cell) => {
2071
if (this.getActiveCell() === cell && editor) {
2072
SuggestController.get(editor)?.cancelSuggestWidget();
2073
DropIntoEditorController.get(editor)?.clearWidgets();
2074
CopyPasteController.get(editor)?.clearWidgets();
2075
}
2076
});
2077
2078
this._renderedEditors.forEach((editor, cell) => {
2079
const controller = InlineCompletionsController.get(editor);
2080
if (controller?.model.get()?.inlineEditState.get()) {
2081
editor.render(true);
2082
}
2083
});
2084
}
2085
2086
private editorHasDomFocus(): boolean {
2087
return DOM.isAncestorOfActiveElement(this.getDomNode());
2088
}
2089
2090
updateEditorFocus() {
2091
// Note - focus going to the webview will fire 'blur', but the webview element will be
2092
// a descendent of the notebook editor root.
2093
this._focusTracker.refreshState();
2094
const focused = this.editorHasDomFocus();
2095
this._editorFocus.set(focused);
2096
this.viewModel?.setEditorFocus(focused);
2097
}
2098
2099
updateCellFocusMode() {
2100
const activeCell = this.getActiveCell();
2101
2102
if (activeCell?.focusMode === CellFocusMode.Output && !this._webviewFocused) {
2103
// output previously has focus, but now it's blurred.
2104
activeCell.focusMode = CellFocusMode.Container;
2105
}
2106
}
2107
2108
hasEditorFocus() {
2109
// _editorFocus is driven by the FocusTracker, which is only guaranteed to _eventually_ fire blur.
2110
// If we need to know whether we have focus at this instant, we need to check the DOM manually.
2111
this.updateEditorFocus();
2112
return this.editorHasDomFocus();
2113
}
2114
2115
hasWebviewFocus() {
2116
return this._webviewFocused;
2117
}
2118
2119
hasOutputTextSelection() {
2120
if (!this.hasEditorFocus()) {
2121
return false;
2122
}
2123
2124
const windowSelection = DOM.getWindow(this.getDomNode()).getSelection();
2125
if (windowSelection?.rangeCount !== 1) {
2126
return false;
2127
}
2128
2129
const activeSelection = windowSelection.getRangeAt(0);
2130
if (activeSelection.startContainer === activeSelection.endContainer && activeSelection.endOffset - activeSelection.startOffset === 0) {
2131
return false;
2132
}
2133
2134
let container: Node | null = activeSelection.commonAncestorContainer;
2135
2136
if (!this._body.contains(container)) {
2137
return false;
2138
}
2139
2140
while (container
2141
&&
2142
container !== this._body) {
2143
if ((container as HTMLElement).classList && (container as HTMLElement).classList.contains('output')) {
2144
return true;
2145
}
2146
2147
container = container.parentNode;
2148
}
2149
2150
return false;
2151
}
2152
2153
_didFocusOutputInputChange(hasFocus: boolean) {
2154
this._outputInputFocus.set(hasFocus);
2155
}
2156
2157
//#endregion
2158
2159
//#region Editor Features
2160
2161
focusElement(cell: ICellViewModel) {
2162
this.viewModel?.updateSelectionsState({
2163
kind: SelectionStateType.Handle,
2164
primary: cell.handle,
2165
selections: [cell.handle]
2166
});
2167
}
2168
2169
get scrollTop() {
2170
return this._list.scrollTop;
2171
}
2172
2173
get scrollBottom() {
2174
return this._list.scrollTop + this._list.getRenderHeight();
2175
}
2176
2177
getAbsoluteTopOfElement(cell: ICellViewModel) {
2178
return this._list.getCellViewScrollTop(cell);
2179
}
2180
2181
getAbsoluteBottomOfElement(cell: ICellViewModel) {
2182
return this._list.getCellViewScrollBottom(cell);
2183
}
2184
2185
getHeightOfElement(cell: ICellViewModel) {
2186
return this._list.elementHeight(cell);
2187
}
2188
2189
scrollToBottom() {
2190
this._list.scrollToBottom();
2191
}
2192
2193
setScrollTop(scrollTop: number): void {
2194
this._list.scrollTop = scrollTop;
2195
}
2196
2197
revealCellRangeInView(range: ICellRange) {
2198
return this._list.revealCells(range);
2199
}
2200
2201
revealInView(cell: ICellViewModel) {
2202
return this._list.revealCell(cell, CellRevealType.Default);
2203
}
2204
2205
revealInViewAtTop(cell: ICellViewModel) {
2206
this._list.revealCell(cell, CellRevealType.Top);
2207
}
2208
2209
revealInCenter(cell: ICellViewModel) {
2210
this._list.revealCell(cell, CellRevealType.Center);
2211
}
2212
2213
async revealInCenterIfOutsideViewport(cell: ICellViewModel) {
2214
await this._list.revealCell(cell, CellRevealType.CenterIfOutsideViewport);
2215
}
2216
2217
async revealFirstLineIfOutsideViewport(cell: ICellViewModel) {
2218
await this._list.revealCell(cell, CellRevealType.FirstLineIfOutsideViewport);
2219
}
2220
2221
async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise<void> {
2222
return this._list.revealRangeInCell(cell, new Range(line, 1, line, 1), CellRevealRangeType.Default);
2223
}
2224
2225
async revealLineInCenterAsync(cell: ICellViewModel, line: number): Promise<void> {
2226
return this._list.revealRangeInCell(cell, new Range(line, 1, line, 1), CellRevealRangeType.Center);
2227
}
2228
2229
async revealLineInCenterIfOutsideViewportAsync(cell: ICellViewModel, line: number): Promise<void> {
2230
return this._list.revealRangeInCell(cell, new Range(line, 1, line, 1), CellRevealRangeType.CenterIfOutsideViewport);
2231
}
2232
2233
async revealRangeInViewAsync(cell: ICellViewModel, range: Selection | Range): Promise<void> {
2234
return this._list.revealRangeInCell(cell, range, CellRevealRangeType.Default);
2235
}
2236
2237
async revealRangeInCenterAsync(cell: ICellViewModel, range: Selection | Range): Promise<void> {
2238
return this._list.revealRangeInCell(cell, range, CellRevealRangeType.Center);
2239
}
2240
2241
async revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Selection | Range): Promise<void> {
2242
return this._list.revealRangeInCell(cell, range, CellRevealRangeType.CenterIfOutsideViewport);
2243
}
2244
2245
revealCellOffsetInCenter(cell: ICellViewModel, offset: number) {
2246
return this._list.revealCellOffsetInCenter(cell, offset);
2247
}
2248
2249
revealOffsetInCenterIfOutsideViewport(offset: number) {
2250
return this._list.revealOffsetInCenterIfOutsideViewport(offset);
2251
}
2252
2253
getViewIndexByModelIndex(index: number): number {
2254
if (!this._listViewInfoAccessor) {
2255
return -1;
2256
}
2257
const cell = this.viewModel?.viewCells[index];
2258
if (!cell) {
2259
return -1;
2260
}
2261
2262
return this._listViewInfoAccessor.getViewIndex(cell);
2263
}
2264
2265
getViewHeight(cell: ICellViewModel): number {
2266
if (!this._listViewInfoAccessor) {
2267
return -1;
2268
}
2269
2270
return this._listViewInfoAccessor.getViewHeight(cell);
2271
}
2272
2273
getCellRangeFromViewRange(startIndex: number, endIndex: number): ICellRange | undefined {
2274
return this._listViewInfoAccessor.getCellRangeFromViewRange(startIndex, endIndex);
2275
}
2276
2277
getCellsInRange(range?: ICellRange): ReadonlyArray<ICellViewModel> {
2278
return this._listViewInfoAccessor.getCellsInRange(range);
2279
}
2280
2281
setCellEditorSelection(cell: ICellViewModel, range: Range): void {
2282
this._list.setCellEditorSelection(cell, range);
2283
}
2284
2285
setHiddenAreas(_ranges: ICellRange[]): boolean {
2286
return this._list.setHiddenAreas(_ranges, true);
2287
}
2288
2289
getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[] {
2290
return this._listViewInfoAccessor.getVisibleRangesPlusViewportAboveAndBelow();
2291
}
2292
2293
//#endregion
2294
2295
//#region Decorations
2296
2297
deltaCellDecorations(oldDecorations: string[], newDecorations: INotebookDeltaDecoration[]): string[] {
2298
const ret = this.viewModel?.deltaCellDecorations(oldDecorations, newDecorations) || [];
2299
this._onDidChangeDecorations.fire();
2300
return ret;
2301
}
2302
2303
deltaCellContainerClassNames(cellId: string, added: string[], removed: string[], cellkind: CellKind): void {
2304
if (cellkind === CellKind.Markup) {
2305
this._webview?.deltaMarkupPreviewClassNames(cellId, added, removed);
2306
} else {
2307
this._webview?.deltaCellOutputContainerClassNames(cellId, added, removed);
2308
}
2309
}
2310
2311
changeModelDecorations<T>(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null {
2312
return this.viewModel?.changeModelDecorations<T>(callback) || null;
2313
}
2314
2315
//#endregion
2316
2317
//#region View Zones
2318
changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void {
2319
this._list.changeViewZones(callback);
2320
this._onDidChangeLayout.fire();
2321
}
2322
2323
getViewZoneLayoutInfo(id: string): { top: number; height: number } | null {
2324
return this._list.getViewZoneLayoutInfo(id);
2325
}
2326
//#endregion
2327
2328
//#region Overlay
2329
changeCellOverlays(callback: (accessor: INotebookCellOverlayChangeAccessor) => void): void {
2330
this._list.changeCellOverlays(callback);
2331
}
2332
//#endregion
2333
2334
//#region Kernel/Execution
2335
2336
private async _loadKernelPreloads(): Promise<void> {
2337
if (!this.hasModel()) {
2338
return;
2339
}
2340
const { selected } = this.notebookKernelService.getMatchingKernel(this.textModel);
2341
if (!this._webview?.isResolved()) {
2342
await this._resolveWebview();
2343
}
2344
this._webview?.updateKernelPreloads(selected);
2345
}
2346
2347
get activeKernel() {
2348
return this.textModel && this.notebookKernelService.getSelectedOrSuggestedKernel(this.textModel);
2349
}
2350
2351
async cancelNotebookCells(cells?: Iterable<ICellViewModel>): Promise<void> {
2352
if (!this.viewModel || !this.hasModel()) {
2353
return;
2354
}
2355
if (!cells) {
2356
cells = this.viewModel.viewCells;
2357
}
2358
return this.notebookExecutionService.cancelNotebookCellHandles(this.textModel, Array.from(cells).map(cell => cell.handle));
2359
}
2360
2361
async executeNotebookCells(cells?: Iterable<ICellViewModel>): Promise<void> {
2362
if (!this.viewModel || !this.hasModel()) {
2363
this.logService.info('notebookEditorWidget', 'No NotebookViewModel, cannot execute cells');
2364
return;
2365
}
2366
if (!cells) {
2367
cells = this.viewModel.viewCells;
2368
}
2369
return this.notebookExecutionService.executeNotebookCells(this.textModel, Array.from(cells).map(c => c.model), this.scopedContextKeyService);
2370
}
2371
2372
//#endregion
2373
2374
async layoutNotebookCell(cell: ICellViewModel, height: number, context?: CellLayoutContext): Promise<void> {
2375
return this._cellLayoutManager?.layoutNotebookCell(cell, height);
2376
}
2377
2378
getActiveCell() {
2379
const elements = this._list.getFocusedElements();
2380
2381
if (elements && elements.length) {
2382
return elements[0];
2383
}
2384
2385
return undefined;
2386
}
2387
2388
private _toggleNotebookCellSelection(selectedCell: ICellViewModel, selectFromPrevious: boolean): void {
2389
const currentSelections = this._list.getSelectedElements();
2390
const isSelected = currentSelections.includes(selectedCell);
2391
2392
const previousSelection = selectFromPrevious ? currentSelections[currentSelections.length - 1] ?? selectedCell : selectedCell;
2393
const selectedIndex = this._list.getViewIndex(selectedCell)!;
2394
const previousIndex = this._list.getViewIndex(previousSelection)!;
2395
2396
const cellsInSelectionRange = this.getCellsInViewRange(selectedIndex, previousIndex);
2397
if (isSelected) {
2398
// Deselect
2399
this._list.selectElements(currentSelections.filter(current => !cellsInSelectionRange.includes(current)));
2400
} else {
2401
// Add to selection
2402
this.focusElement(selectedCell);
2403
this._list.selectElements([...currentSelections.filter(current => !cellsInSelectionRange.includes(current)), ...cellsInSelectionRange]);
2404
}
2405
}
2406
2407
private getCellsInViewRange(fromInclusive: number, toInclusive: number): ICellViewModel[] {
2408
const selectedCellsInRange: ICellViewModel[] = [];
2409
for (let index = 0; index < this._list.length; ++index) {
2410
const cell = this._list.element(index);
2411
if (cell) {
2412
if ((index >= fromInclusive && index <= toInclusive) || (index >= toInclusive && index <= fromInclusive)) {
2413
selectedCellsInRange.push(cell);
2414
}
2415
}
2416
}
2417
return selectedCellsInRange;
2418
}
2419
2420
async focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions) {
2421
if (this._isDisposed) {
2422
return;
2423
}
2424
2425
cell.focusedOutputId = undefined;
2426
2427
if (focusItem === 'editor') {
2428
cell.isInputCollapsed = false;
2429
this.focusElement(cell);
2430
this._list.focusView();
2431
2432
cell.updateEditState(CellEditState.Editing, 'focusNotebookCell');
2433
cell.focusMode = CellFocusMode.Editor;
2434
if (!options?.skipReveal) {
2435
if (typeof options?.focusEditorLine === 'number') {
2436
this._cursorNavMode.set(true);
2437
await this.revealLineInViewAsync(cell, options.focusEditorLine);
2438
const editor = this._renderedEditors.get(cell)!;
2439
const focusEditorLine = options.focusEditorLine;
2440
editor?.setSelection({
2441
startLineNumber: focusEditorLine,
2442
startColumn: 1,
2443
endLineNumber: focusEditorLine,
2444
endColumn: 1
2445
});
2446
} else {
2447
const selectionsStartPosition = cell.getSelectionsStartPosition();
2448
if (selectionsStartPosition?.length) {
2449
const firstSelectionPosition = selectionsStartPosition[0];
2450
await this.revealRangeInViewAsync(cell, Range.fromPositions(firstSelectionPosition, firstSelectionPosition));
2451
} else {
2452
await this.revealInView(cell);
2453
}
2454
2455
}
2456
2457
}
2458
} else if (focusItem === 'output') {
2459
this.focusElement(cell);
2460
2461
if (!this.hasEditorFocus()) {
2462
this._list.focusView();
2463
}
2464
2465
if (!this._webview) {
2466
return;
2467
}
2468
2469
const firstOutputId = cell.outputsViewModels.find(o => o.model.alternativeOutputId)?.model.alternativeOutputId;
2470
const focusElementId = options?.outputId ?? firstOutputId ?? cell.id;
2471
this._webview.focusOutput(focusElementId, options?.altOutputId, options?.outputWebviewFocused || this._webviewFocused);
2472
2473
cell.updateEditState(CellEditState.Preview, 'focusNotebookCell');
2474
cell.focusMode = CellFocusMode.Output;
2475
cell.focusedOutputId = options?.outputId;
2476
this._outputFocus.set(true);
2477
if (!options?.skipReveal) {
2478
this.revealInCenterIfOutsideViewport(cell);
2479
}
2480
} else {
2481
// focus container
2482
const itemDOM = this._list.domElementOfElement(cell);
2483
if (itemDOM && itemDOM.ownerDocument.activeElement && itemDOM.contains(itemDOM.ownerDocument.activeElement)) {
2484
(itemDOM.ownerDocument.activeElement as HTMLElement).blur();
2485
}
2486
2487
this._webview?.blurOutput();
2488
2489
cell.updateEditState(CellEditState.Preview, 'focusNotebookCell');
2490
cell.focusMode = CellFocusMode.Container;
2491
2492
this.focusElement(cell);
2493
if (!options?.skipReveal) {
2494
if (typeof options?.focusEditorLine === 'number') {
2495
this._cursorNavMode.set(true);
2496
await this.revealInView(cell);
2497
} else if (options?.revealBehavior === ScrollToRevealBehavior.firstLine) {
2498
await this.revealFirstLineIfOutsideViewport(cell);
2499
} else if (options?.revealBehavior === ScrollToRevealBehavior.fullCell) {
2500
await this.revealInView(cell);
2501
} else {
2502
await this.revealInCenterIfOutsideViewport(cell);
2503
}
2504
}
2505
this._list.focusView();
2506
this.updateEditorFocus();
2507
}
2508
}
2509
2510
async focusNextNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') {
2511
const idx = this.viewModel?.getCellIndex(cell);
2512
if (typeof idx !== 'number') {
2513
return;
2514
}
2515
2516
const newCell = this.viewModel?.cellAt(idx + 1);
2517
if (!newCell) {
2518
return;
2519
}
2520
2521
await this.focusNotebookCell(newCell, focusItem);
2522
}
2523
2524
//#endregion
2525
2526
//#region Find
2527
2528
private async _warmupCell(viewCell: CodeCellViewModel) {
2529
if (viewCell.isOutputCollapsed) {
2530
return;
2531
}
2532
2533
const outputs = viewCell.outputsViewModels;
2534
for (const output of outputs.slice(0, outputDisplayLimit)) {
2535
const [mimeTypes, pick] = output.resolveMimeTypes(this.textModel!, undefined);
2536
if (!mimeTypes.find(mimeType => mimeType.isTrusted) || mimeTypes.length === 0) {
2537
continue;
2538
}
2539
2540
const pickedMimeTypeRenderer = mimeTypes[pick];
2541
2542
if (!pickedMimeTypeRenderer) {
2543
return;
2544
}
2545
2546
const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId);
2547
2548
if (!renderer) {
2549
return;
2550
}
2551
2552
const result: IInsetRenderOutput = { type: RenderOutputType.Extension, renderer, source: output, mimeType: pickedMimeTypeRenderer.mimeType };
2553
const inset = this._webview?.insetMapping.get(result.source);
2554
if (!inset || !inset.initialized) {
2555
const p = new Promise<void>(resolve => {
2556
this._register(Event.any(this.onDidRenderOutput, this.onDidRemoveOutput)(e => {
2557
if (e.model === result.source.model) {
2558
resolve();
2559
}
2560
}));
2561
});
2562
this.createOutput(viewCell, result, 0, false);
2563
await p;
2564
} else {
2565
// request to update its visibility
2566
this.createOutput(viewCell, result, 0, false);
2567
}
2568
2569
return;
2570
}
2571
2572
}
2573
2574
private async _warmupAll(includeOutput: boolean) {
2575
if (!this.hasModel() || !this.viewModel) {
2576
return;
2577
}
2578
2579
const cells = this.viewModel.viewCells;
2580
const requests = [];
2581
2582
for (let i = 0; i < cells.length; i++) {
2583
if (cells[i].cellKind === CellKind.Markup && !this._webview!.markupPreviewMapping.has(cells[i].id)) {
2584
requests.push(this.createMarkupPreview(cells[i]));
2585
}
2586
}
2587
2588
if (includeOutput && this._list) {
2589
for (let i = 0; i < this._list.length; i++) {
2590
const cell = this._list.element(i);
2591
2592
if (cell?.cellKind === CellKind.Code) {
2593
requests.push(this._warmupCell((cell as CodeCellViewModel)));
2594
}
2595
}
2596
}
2597
2598
return Promise.all(requests);
2599
}
2600
2601
private async _warmupSelection(includeOutput: boolean, selectedCellRanges: ICellRange[]) {
2602
if (!this.hasModel() || !this.viewModel) {
2603
return;
2604
}
2605
2606
const cells = this.viewModel.viewCells;
2607
const requests = [];
2608
2609
for (const range of selectedCellRanges) {
2610
for (let i = range.start; i < range.end; i++) {
2611
if (cells[i].cellKind === CellKind.Markup && !this._webview!.markupPreviewMapping.has(cells[i].id)) {
2612
requests.push(this.createMarkupPreview(cells[i]));
2613
}
2614
}
2615
}
2616
2617
if (includeOutput && this._list) {
2618
for (const range of selectedCellRanges) {
2619
for (let i = range.start; i < range.end; i++) {
2620
const cell = this._list.element(i);
2621
2622
if (cell?.cellKind === CellKind.Code) {
2623
requests.push(this._warmupCell((cell as CodeCellViewModel)));
2624
}
2625
}
2626
}
2627
}
2628
2629
return Promise.all(requests);
2630
}
2631
2632
async find(query: string, options: INotebookFindOptions, token: CancellationToken, skipWarmup: boolean = false, shouldGetSearchPreviewInfo = false, ownerID?: string): Promise<CellFindMatchWithIndex[]> {
2633
if (!this._notebookViewModel) {
2634
return [];
2635
}
2636
2637
if (!ownerID) {
2638
ownerID = this.getId();
2639
}
2640
2641
const findMatches = this._notebookViewModel.find(query, options).filter(match => match.length > 0);
2642
2643
if ((!options.includeMarkupPreview && !options.includeOutput) || options.findScope?.findScopeType === NotebookFindScopeType.Text) {
2644
this._webview?.findStop(ownerID);
2645
return findMatches;
2646
}
2647
2648
// search in webview enabled
2649
2650
const matchMap: { [key: string]: CellFindMatchWithIndex } = {};
2651
findMatches.forEach(match => {
2652
matchMap[match.cell.id] = match;
2653
});
2654
2655
if (this._webview) {
2656
// request all or some outputs to be rendered
2657
// measure perf
2658
const start = Date.now();
2659
if (options.findScope && options.findScope.findScopeType === NotebookFindScopeType.Cells && options.findScope.selectedCellRanges) {
2660
await this._warmupSelection(!!options.includeOutput, options.findScope.selectedCellRanges);
2661
} else {
2662
await this._warmupAll(!!options.includeOutput);
2663
}
2664
const end = Date.now();
2665
this.logService.debug('Find', `Warmup time: ${end - start}ms`);
2666
2667
if (token.isCancellationRequested) {
2668
return [];
2669
}
2670
2671
let findIds: string[] = [];
2672
if (options.findScope && options.findScope.findScopeType === NotebookFindScopeType.Cells && options.findScope.selectedCellRanges) {
2673
const selectedIndexes = cellRangesToIndexes(options.findScope.selectedCellRanges);
2674
findIds = selectedIndexes.map<string>(index => this._notebookViewModel?.viewCells[index].id ?? '');
2675
}
2676
2677
const webviewMatches = await this._webview.find(query, { caseSensitive: options.caseSensitive, wholeWord: options.wholeWord, includeMarkup: !!options.includeMarkupPreview, includeOutput: !!options.includeOutput, shouldGetSearchPreviewInfo, ownerID, findIds: findIds });
2678
2679
if (token.isCancellationRequested) {
2680
return [];
2681
}
2682
2683
// attach webview matches to model find matches
2684
webviewMatches.forEach(match => {
2685
const cell = this._notebookViewModel!.viewCells.find(cell => cell.id === match.cellId);
2686
2687
if (!cell) {
2688
return;
2689
}
2690
2691
if (match.type === 'preview') {
2692
// markup preview
2693
if (cell.getEditState() === CellEditState.Preview && !options.includeMarkupPreview) {
2694
return;
2695
}
2696
2697
if (cell.getEditState() === CellEditState.Editing && options.includeMarkupInput) {
2698
return;
2699
}
2700
} else {
2701
if (!options.includeOutput) {
2702
// skip outputs if not included
2703
return;
2704
}
2705
}
2706
2707
const exisitingMatch = matchMap[match.cellId];
2708
2709
if (exisitingMatch) {
2710
exisitingMatch.webviewMatches.push(match);
2711
} else {
2712
2713
matchMap[match.cellId] = new CellFindMatchModel(
2714
this._notebookViewModel!.viewCells.find(cell => cell.id === match.cellId)!,
2715
this._notebookViewModel!.viewCells.findIndex(cell => cell.id === match.cellId)!,
2716
[],
2717
[match]
2718
);
2719
}
2720
});
2721
}
2722
2723
const ret: CellFindMatchWithIndex[] = [];
2724
this._notebookViewModel.viewCells.forEach((cell, index) => {
2725
if (matchMap[cell.id]) {
2726
ret.push(new CellFindMatchModel(cell, index, matchMap[cell.id].contentMatches, matchMap[cell.id].webviewMatches));
2727
}
2728
});
2729
2730
return ret;
2731
}
2732
2733
async findHighlightCurrent(matchIndex: number, ownerID?: string): Promise<number> {
2734
if (!this._webview) {
2735
return 0;
2736
}
2737
2738
return this._webview?.findHighlightCurrent(matchIndex, ownerID ?? this.getId());
2739
}
2740
2741
async findUnHighlightCurrent(matchIndex: number, ownerID?: string): Promise<void> {
2742
if (!this._webview) {
2743
return;
2744
}
2745
2746
return this._webview?.findUnHighlightCurrent(matchIndex, ownerID ?? this.getId());
2747
}
2748
2749
findStop(ownerID?: string) {
2750
this._webview?.findStop(ownerID ?? this.getId());
2751
}
2752
2753
//#endregion
2754
2755
//#region MISC
2756
2757
getLayoutInfo(): NotebookLayoutInfo {
2758
if (!this._list) {
2759
throw new Error('Editor is not initalized successfully');
2760
}
2761
2762
if (!this._fontInfo) {
2763
this._generateFontInfo();
2764
}
2765
2766
let listViewOffset = 0;
2767
if (this._dimension) {
2768
listViewOffset = (this._notebookTopToolbar?.useGlobalToolbar ? /** Toolbar height */ 26 : 0) + (this._notebookStickyScroll?.getCurrentStickyHeight() ?? 0);
2769
}
2770
2771
return {
2772
width: this._dimension?.width ?? 0,
2773
height: this._dimension?.height ?? 0,
2774
scrollHeight: this._list?.getScrollHeight() ?? 0,
2775
fontInfo: this._fontInfo!,
2776
stickyHeight: this._notebookStickyScroll?.getCurrentStickyHeight() ?? 0,
2777
listViewOffsetTop: listViewOffset
2778
};
2779
}
2780
2781
async createMarkupPreview(cell: MarkupCellViewModel) {
2782
if (!this._webview) {
2783
return;
2784
}
2785
2786
if (!this._webview.isResolved()) {
2787
await this._resolveWebview();
2788
}
2789
2790
if (!this._webview || !this._list.webviewElement) {
2791
return;
2792
}
2793
2794
if (!this.viewModel || !this._list.viewModel) {
2795
return;
2796
}
2797
2798
if (this.viewModel.getCellIndex(cell) === -1) {
2799
return;
2800
}
2801
2802
if (this.cellIsHidden(cell)) {
2803
return;
2804
}
2805
2806
const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10);
2807
const top = !!webviewTop ? (0 - webviewTop) : 0;
2808
2809
const cellTop = this._list.getCellViewScrollTop(cell);
2810
await this._webview.showMarkupPreview({
2811
mime: cell.mime,
2812
cellHandle: cell.handle,
2813
cellId: cell.id,
2814
content: cell.getText(),
2815
offset: cellTop + top,
2816
visible: true,
2817
metadata: cell.metadata,
2818
});
2819
}
2820
2821
private cellIsHidden(cell: ICellViewModel): boolean {
2822
const modelIndex = this.viewModel!.getCellIndex(cell);
2823
const foldedRanges = this.viewModel!.getHiddenRanges();
2824
return foldedRanges.some(range => modelIndex >= range.start && modelIndex <= range.end);
2825
}
2826
2827
async unhideMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
2828
if (!this._webview) {
2829
return;
2830
}
2831
2832
if (!this._webview.isResolved()) {
2833
await this._resolveWebview();
2834
}
2835
2836
await this._webview?.unhideMarkupPreviews(cells.map(cell => cell.id));
2837
}
2838
2839
async hideMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
2840
if (!this._webview || !cells.length) {
2841
return;
2842
}
2843
2844
if (!this._webview.isResolved()) {
2845
await this._resolveWebview();
2846
}
2847
2848
await this._webview?.hideMarkupPreviews(cells.map(cell => cell.id));
2849
}
2850
2851
async deleteMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
2852
if (!this._webview) {
2853
return;
2854
}
2855
2856
if (!this._webview.isResolved()) {
2857
await this._resolveWebview();
2858
}
2859
2860
await this._webview?.deleteMarkupPreviews(cells.map(cell => cell.id));
2861
}
2862
2863
private async updateSelectedMarkdownPreviews(): Promise<void> {
2864
if (!this._webview) {
2865
return;
2866
}
2867
2868
if (!this._webview.isResolved()) {
2869
await this._resolveWebview();
2870
}
2871
2872
const selectedCells = this.getSelectionViewModels().map(cell => cell.id);
2873
2874
// Only show selection when there is more than 1 cell selected
2875
await this._webview?.updateMarkupPreviewSelections(selectedCells.length > 1 ? selectedCells : []);
2876
}
2877
2878
async createOutput(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number, createWhenIdle: boolean): Promise<void> {
2879
this._insetModifyQueueByOutputId.queue(output.source.model.outputId, async () => {
2880
if (this._isDisposed || !this._webview) {
2881
return;
2882
}
2883
2884
if (!this._webview.isResolved()) {
2885
await this._resolveWebview();
2886
}
2887
2888
if (!this._webview) {
2889
return;
2890
}
2891
2892
if (!this._list.webviewElement) {
2893
return;
2894
}
2895
2896
if (output.type === RenderOutputType.Extension) {
2897
this.notebookRendererMessaging.prepare(output.renderer.id);
2898
}
2899
2900
const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10);
2901
const top = !!webviewTop ? (0 - webviewTop) : 0;
2902
2903
const cellTop = this._list.getCellViewScrollTop(cell) + top;
2904
2905
const existingOutput = this._webview.insetMapping.get(output.source);
2906
if (!existingOutput
2907
|| (!existingOutput.renderer && output.type === RenderOutputType.Extension)
2908
) {
2909
if (createWhenIdle) {
2910
this._webview.requestCreateOutputWhenWebviewIdle({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri, executionId: cell.internalMetadata.executionId }, output, cellTop, offset);
2911
} else {
2912
this._webview.createOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri, executionId: cell.internalMetadata.executionId }, output, cellTop, offset);
2913
}
2914
} else if (existingOutput.renderer
2915
&& output.type === RenderOutputType.Extension
2916
&& existingOutput.renderer.id !== output.renderer.id) {
2917
// switch mimetype
2918
this._webview.removeInsets([output.source]);
2919
this._webview.createOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset);
2920
} else if (existingOutput.versionId !== output.source.model.versionId) {
2921
this._webview.updateOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri, executionId: cell.internalMetadata.executionId }, output, cellTop, offset);
2922
} else {
2923
const outputIndex = cell.outputsViewModels.indexOf(output.source);
2924
const outputOffset = cell.getOutputOffset(outputIndex);
2925
this._webview.updateScrollTops([{
2926
cell,
2927
output: output.source,
2928
cellTop,
2929
outputOffset,
2930
forceDisplay: !cell.isOutputCollapsed,
2931
}], []);
2932
}
2933
});
2934
}
2935
2936
async updateOutput(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise<void> {
2937
this._insetModifyQueueByOutputId.queue(output.source.model.outputId, async () => {
2938
if (this._isDisposed || !this._webview || cell.isOutputCollapsed) {
2939
return;
2940
}
2941
2942
if (!this._webview.isResolved()) {
2943
await this._resolveWebview();
2944
}
2945
2946
if (!this._webview || !this._list.webviewElement) {
2947
return;
2948
}
2949
2950
if (!this._webview.insetMapping.has(output.source)) {
2951
return this.createOutput(cell, output, offset, false);
2952
}
2953
2954
if (output.type === RenderOutputType.Extension) {
2955
this.notebookRendererMessaging.prepare(output.renderer.id);
2956
}
2957
2958
const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10);
2959
const top = !!webviewTop ? (0 - webviewTop) : 0;
2960
2961
const cellTop = this._list.getCellViewScrollTop(cell) + top;
2962
this._webview.updateOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset);
2963
});
2964
}
2965
2966
async copyOutputImage(cellOutput: ICellOutputViewModel): Promise<void> {
2967
this._webview?.copyImage(cellOutput);
2968
}
2969
2970
removeInset(output: ICellOutputViewModel) {
2971
this._insetModifyQueueByOutputId.queue(output.model.outputId, async () => {
2972
if (this._isDisposed || !this._webview) {
2973
return;
2974
}
2975
2976
if (this._webview?.isResolved()) {
2977
this._webview.removeInsets([output]);
2978
}
2979
2980
this._onDidRemoveOutput.fire(output);
2981
});
2982
}
2983
2984
hideInset(output: ICellOutputViewModel) {
2985
this._insetModifyQueueByOutputId.queue(output.model.outputId, async () => {
2986
if (this._isDisposed || !this._webview) {
2987
return;
2988
}
2989
2990
if (this._webview?.isResolved()) {
2991
this._webview.hideInset(output);
2992
}
2993
});
2994
}
2995
2996
//#region --- webview IPC ----
2997
postMessage(message: unknown) {
2998
if (this._webview?.isResolved()) {
2999
this._webview.postKernelMessage(message);
3000
}
3001
}
3002
3003
//#endregion
3004
3005
addClassName(className: string) {
3006
this._overlayContainer.classList.add(className);
3007
}
3008
3009
removeClassName(className: string) {
3010
this._overlayContainer.classList.remove(className);
3011
}
3012
3013
cellAt(index: number): ICellViewModel | undefined {
3014
return this.viewModel?.cellAt(index);
3015
}
3016
3017
getCellByInfo(cellInfo: ICommonCellInfo): ICellViewModel {
3018
const { cellHandle } = cellInfo;
3019
return this.viewModel?.viewCells.find(vc => vc.handle === cellHandle) as CodeCellViewModel;
3020
}
3021
3022
getCellByHandle(handle: number): ICellViewModel | undefined {
3023
return this.viewModel?.getCellByHandle(handle);
3024
}
3025
3026
getCellIndex(cell: ICellViewModel) {
3027
return this.viewModel?.getCellIndexByHandle(cell.handle);
3028
}
3029
3030
getNextVisibleCellIndex(index: number): number | undefined {
3031
return this.viewModel?.getNextVisibleCellIndex(index);
3032
}
3033
3034
getPreviousVisibleCellIndex(index: number): number | undefined {
3035
return this.viewModel?.getPreviousVisibleCellIndex(index);
3036
}
3037
3038
private _updateScrollHeight() {
3039
if (this._isDisposed || !this._webview?.isResolved()) {
3040
return;
3041
}
3042
3043
if (!this._list.webviewElement) {
3044
return;
3045
}
3046
3047
const scrollHeight = this._list.scrollHeight;
3048
this._webview.element.style.height = `${scrollHeight + NOTEBOOK_WEBVIEW_BOUNDARY * 2}px`;
3049
3050
const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10);
3051
const top = !!webviewTop ? (0 - webviewTop) : 0;
3052
3053
const updateItems: IDisplayOutputLayoutUpdateRequest[] = [];
3054
const removedItems: ICellOutputViewModel[] = [];
3055
this._webview?.insetMapping.forEach((value, key) => {
3056
const cell = this.viewModel?.getCellByHandle(value.cellInfo.cellHandle);
3057
if (!cell || !(cell instanceof CodeCellViewModel)) {
3058
return;
3059
}
3060
3061
this.viewModel?.viewCells.find(cell => cell.handle === value.cellInfo.cellHandle);
3062
const viewIndex = this._list.getViewIndex(cell);
3063
3064
if (viewIndex === undefined) {
3065
return;
3066
}
3067
3068
if (cell.outputsViewModels.indexOf(key) < 0) {
3069
// output is already gone
3070
removedItems.push(key);
3071
}
3072
3073
const cellTop = this._list.getCellViewScrollTop(cell);
3074
const outputIndex = cell.outputsViewModels.indexOf(key);
3075
const outputOffset = cell.getOutputOffset(outputIndex);
3076
updateItems.push({
3077
cell,
3078
output: key,
3079
cellTop: cellTop + top,
3080
outputOffset,
3081
forceDisplay: false,
3082
});
3083
});
3084
3085
this._webview.removeInsets(removedItems);
3086
3087
const markdownUpdateItems: { id: string; top: number }[] = [];
3088
for (const cellId of this._webview.markupPreviewMapping.keys()) {
3089
const cell = this.viewModel?.viewCells.find(cell => cell.id === cellId);
3090
if (cell) {
3091
const cellTop = this._list.getCellViewScrollTop(cell);
3092
// markdownUpdateItems.push({ id: cellId, top: cellTop });
3093
markdownUpdateItems.push({ id: cellId, top: cellTop + top });
3094
}
3095
}
3096
3097
if (markdownUpdateItems.length || updateItems.length) {
3098
this._debug('_list.onDidChangeContentHeight/markdown', markdownUpdateItems);
3099
this._webview?.updateScrollTops(updateItems, markdownUpdateItems);
3100
}
3101
}
3102
3103
//#endregion
3104
3105
//#region BacklayerWebview delegate
3106
private _updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, outputHeight: number, isInit: boolean, source?: string): void {
3107
const cell = this.viewModel?.viewCells.find(vc => vc.handle === cellInfo.cellHandle);
3108
if (cell && cell instanceof CodeCellViewModel) {
3109
const outputIndex = cell.outputsViewModels.indexOf(output);
3110
if (outputIndex > -1) {
3111
this._debug('update cell output', cell.handle, outputHeight);
3112
cell.updateOutputHeight(outputIndex, outputHeight, source);
3113
this.layoutNotebookCell(cell, cell.layoutInfo.totalHeight);
3114
3115
if (isInit) {
3116
this._onDidRenderOutput.fire(output);
3117
}
3118
} else {
3119
this._debug('tried to update cell output that does not exist');
3120
}
3121
}
3122
}
3123
3124
private readonly _pendingOutputHeightAcks = new Map</* outputId */ string, IAckOutputHeight>();
3125
3126
private _scheduleOutputHeightAck(cellInfo: ICommonCellInfo, outputId: string, height: number) {
3127
const wasEmpty = this._pendingOutputHeightAcks.size === 0;
3128
this._pendingOutputHeightAcks.set(outputId, { cellId: cellInfo.cellId, outputId, height });
3129
3130
if (wasEmpty) {
3131
DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.getDomNode()), () => {
3132
this._debug('ack height');
3133
this._updateScrollHeight();
3134
3135
this._webview?.ackHeight([...this._pendingOutputHeightAcks.values()]);
3136
3137
this._pendingOutputHeightAcks.clear();
3138
}, -1); // -1 priority because this depends on calls to layoutNotebookCell, and that may be called multiple times before this runs
3139
}
3140
}
3141
3142
private _getCellById(cellId: string): ICellViewModel | undefined {
3143
return this.viewModel?.viewCells.find(vc => vc.id === cellId);
3144
}
3145
3146
private _updateMarkupCellHeight(cellId: string, height: number, isInit: boolean) {
3147
const cell = this._getCellById(cellId);
3148
if (cell && cell instanceof MarkupCellViewModel) {
3149
const { bottomToolbarGap } = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType);
3150
this._debug('updateMarkdownCellHeight', cell.handle, height + bottomToolbarGap, isInit);
3151
cell.renderedMarkdownHeight = height;
3152
}
3153
}
3154
3155
private _setMarkupCellEditState(cellId: string, editState: CellEditState): void {
3156
const cell = this._getCellById(cellId);
3157
if (cell instanceof MarkupCellViewModel) {
3158
this.revealInView(cell);
3159
cell.updateEditState(editState, 'setMarkdownCellEditState');
3160
}
3161
}
3162
3163
private _didStartDragMarkupCell(cellId: string, event: { dragOffsetY: number }): void {
3164
const cell = this._getCellById(cellId);
3165
if (cell instanceof MarkupCellViewModel) {
3166
const webviewOffset = this._list.webviewElement ? -parseInt(this._list.webviewElement.domNode.style.top, 10) : 0;
3167
this._dndController?.startExplicitDrag(cell, event.dragOffsetY - webviewOffset);
3168
}
3169
}
3170
3171
private _didDragMarkupCell(cellId: string, event: { dragOffsetY: number }): void {
3172
const cell = this._getCellById(cellId);
3173
if (cell instanceof MarkupCellViewModel) {
3174
const webviewOffset = this._list.webviewElement ? -parseInt(this._list.webviewElement.domNode.style.top, 10) : 0;
3175
this._dndController?.explicitDrag(cell, event.dragOffsetY - webviewOffset);
3176
}
3177
}
3178
3179
private _didDropMarkupCell(cellId: string, event: { dragOffsetY: number; ctrlKey: boolean; altKey: boolean }): void {
3180
const cell = this._getCellById(cellId);
3181
if (cell instanceof MarkupCellViewModel) {
3182
const webviewOffset = this._list.webviewElement ? -parseInt(this._list.webviewElement.domNode.style.top, 10) : 0;
3183
event.dragOffsetY -= webviewOffset;
3184
this._dndController?.explicitDrop(cell, event);
3185
}
3186
}
3187
3188
private _didEndDragMarkupCell(cellId: string): void {
3189
const cell = this._getCellById(cellId);
3190
if (cell instanceof MarkupCellViewModel) {
3191
this._dndController?.endExplicitDrag(cell);
3192
}
3193
}
3194
3195
private _didResizeOutput(cellId: string): void {
3196
const cell = this._getCellById(cellId);
3197
if (cell) {
3198
this._onDidResizeOutputEmitter.fire(cell);
3199
}
3200
}
3201
3202
private _updatePerformanceMetadata(cellId: string, executionId: string, duration: number, rendererId: string): void {
3203
if (!this.hasModel()) {
3204
return;
3205
}
3206
3207
const cell = this._getCellById(cellId);
3208
const cellIndex = !cell ? undefined : this.getCellIndex(cell);
3209
if (cell?.internalMetadata.executionId === executionId && cellIndex !== undefined) {
3210
const renderDurationMap = cell.internalMetadata.renderDuration || {};
3211
renderDurationMap[rendererId] = (renderDurationMap[rendererId] ?? 0) + duration;
3212
3213
this.textModel.applyEdits([
3214
{
3215
editType: CellEditType.PartialInternalMetadata,
3216
index: cellIndex,
3217
internalMetadata: {
3218
executionId: executionId,
3219
renderDuration: renderDurationMap
3220
}
3221
}
3222
], true, undefined, () => undefined, undefined, false);
3223
3224
}
3225
}
3226
3227
//#endregion
3228
3229
//#region Editor Contributions
3230
getContribution<T extends INotebookEditorContribution>(id: string): T {
3231
return <T>(this._contributions.get(id) || null);
3232
}
3233
3234
//#endregion
3235
3236
override dispose() {
3237
this._isDisposed = true;
3238
// dispose webview first
3239
this._webview?.dispose();
3240
this._webview = null;
3241
3242
this.notebookEditorService.removeNotebookEditor(this);
3243
dispose(this._contributions.values());
3244
this._contributions.clear();
3245
3246
this._localStore.clear();
3247
dispose(this._localCellStateListeners);
3248
this._list.dispose();
3249
this._cellLayoutManager?.dispose();
3250
this._listTopCellToolbar?.dispose();
3251
3252
this._overlayContainer.remove();
3253
this.viewModel?.dispose();
3254
3255
this._renderedEditors.clear();
3256
this._baseCellEditorOptions.forEach(v => v.dispose());
3257
this._baseCellEditorOptions.clear();
3258
3259
this._notebookOverviewRulerContainer.remove();
3260
3261
super.dispose();
3262
3263
// unref
3264
this._webview = null;
3265
this._webviewResolvePromise = null;
3266
this._webviewTransparentCover = null;
3267
this._dndController = null;
3268
this._listTopCellToolbar = null;
3269
this._notebookViewModel = undefined;
3270
this._cellContextKeyManager = null;
3271
this._notebookTopToolbar = null!;
3272
this._list = null!;
3273
this._listViewInfoAccessor = null!;
3274
this._listDelegate = null;
3275
}
3276
3277
toJSON(): { notebookUri: URI | undefined } {
3278
return {
3279
notebookUri: this.viewModel?.uri,
3280
};
3281
}
3282
}
3283
3284
registerZIndex(ZIndex.Base, 5, 'notebook-progress-bar',);
3285
registerZIndex(ZIndex.Base, 10, 'notebook-list-insertion-indicator');
3286
registerZIndex(ZIndex.Base, 20, 'notebook-cell-editor-outline');
3287
registerZIndex(ZIndex.Base, 25, 'notebook-scrollbar');
3288
registerZIndex(ZIndex.Base, 26, 'notebook-cell-status');
3289
registerZIndex(ZIndex.Base, 26, 'notebook-folding-indicator');
3290
registerZIndex(ZIndex.Base, 27, 'notebook-output');
3291
registerZIndex(ZIndex.Base, 28, 'notebook-cell-bottom-toolbar-container');
3292
registerZIndex(ZIndex.Base, 29, 'notebook-run-button-container');
3293
registerZIndex(ZIndex.Base, 29, 'notebook-input-collapse-condicon');
3294
registerZIndex(ZIndex.Base, 30, 'notebook-cell-output-toolbar');
3295
registerZIndex(ZIndex.Sash, 1, 'notebook-cell-expand-part-button');
3296
registerZIndex(ZIndex.Sash, 2, 'notebook-cell-toolbar');
3297
registerZIndex(ZIndex.Sash, 3, 'notebook-cell-toolbar-dropdown-active');
3298
3299
export const notebookCellBorder = registerColor('notebook.cellBorderColor', {
3300
dark: transparent(listInactiveSelectionBackground, 1),
3301
light: transparent(listInactiveSelectionBackground, 1),
3302
hcDark: PANEL_BORDER,
3303
hcLight: PANEL_BORDER
3304
}, nls.localize('notebook.cellBorderColor', "The border color for notebook cells."));
3305
3306
export const focusedEditorBorderColor = registerColor('notebook.focusedEditorBorder', focusBorder, nls.localize('notebook.focusedEditorBorder', "The color of the notebook cell editor border."));
3307
3308
export const cellStatusIconSuccess = registerColor('notebookStatusSuccessIcon.foreground', debugIconStartForeground, nls.localize('notebookStatusSuccessIcon.foreground', "The error icon color of notebook cells in the cell status bar."));
3309
3310
export const runningCellRulerDecorationColor = registerColor('notebookEditorOverviewRuler.runningCellForeground', debugIconStartForeground, nls.localize('notebookEditorOverviewRuler.runningCellForeground', "The color of the running cell decoration in the notebook editor overview ruler."));
3311
3312
export const cellStatusIconError = registerColor('notebookStatusErrorIcon.foreground', errorForeground, nls.localize('notebookStatusErrorIcon.foreground', "The error icon color of notebook cells in the cell status bar."));
3313
3314
export const cellStatusIconRunning = registerColor('notebookStatusRunningIcon.foreground', foreground, nls.localize('notebookStatusRunningIcon.foreground', "The running icon color of notebook cells in the cell status bar."));
3315
3316
export const notebookOutputContainerBorderColor = registerColor('notebook.outputContainerBorderColor', null, nls.localize('notebook.outputContainerBorderColor', "The border color of the notebook output container."));
3317
3318
export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', null, nls.localize('notebook.outputContainerBackgroundColor', "The color of the notebook output container background."));
3319
3320
// TODO@rebornix currently also used for toolbar border, if we keep all of this, pick a generic name
3321
export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeparator', {
3322
dark: Color.fromHex('#808080').transparent(0.35),
3323
light: Color.fromHex('#808080').transparent(0.35),
3324
hcDark: contrastBorder,
3325
hcLight: contrastBorder
3326
}, nls.localize('notebook.cellToolbarSeparator', "The color of the separator in the cell bottom toolbar"));
3327
3328
export const focusedCellBackground = registerColor('notebook.focusedCellBackground', null, nls.localize('focusedCellBackground', "The background color of a cell when the cell is focused."));
3329
3330
export const selectedCellBackground = registerColor('notebook.selectedCellBackground', {
3331
dark: listInactiveSelectionBackground,
3332
light: listInactiveSelectionBackground,
3333
hcDark: null,
3334
hcLight: null
3335
}, nls.localize('selectedCellBackground', "The background color of a cell when the cell is selected."));
3336
3337
3338
export const cellHoverBackground = registerColor('notebook.cellHoverBackground', {
3339
dark: transparent(focusedCellBackground, .5),
3340
light: transparent(focusedCellBackground, .7),
3341
hcDark: null,
3342
hcLight: null
3343
}, nls.localize('notebook.cellHoverBackground', "The background color of a cell when the cell is hovered."));
3344
3345
export const selectedCellBorder = registerColor('notebook.selectedCellBorder', {
3346
dark: notebookCellBorder,
3347
light: notebookCellBorder,
3348
hcDark: contrastBorder,
3349
hcLight: contrastBorder
3350
}, nls.localize('notebook.selectedCellBorder', "The color of the cell's top and bottom border when the cell is selected but not focused."));
3351
3352
export const inactiveSelectedCellBorder = registerColor('notebook.inactiveSelectedCellBorder', {
3353
dark: null,
3354
light: null,
3355
hcDark: focusBorder,
3356
hcLight: focusBorder
3357
}, nls.localize('notebook.inactiveSelectedCellBorder', "The color of the cell's borders when multiple cells are selected."));
3358
3359
export const focusedCellBorder = registerColor('notebook.focusedCellBorder', focusBorder, nls.localize('notebook.focusedCellBorder', "The color of the cell's focus indicator borders when the cell is focused."));
3360
3361
export const inactiveFocusedCellBorder = registerColor('notebook.inactiveFocusedCellBorder', notebookCellBorder, nls.localize('notebook.inactiveFocusedCellBorder', "The color of the cell's top and bottom border when a cell is focused while the primary focus is outside of the editor."));
3362
3363
export const cellStatusBarItemHover = registerColor('notebook.cellStatusBarItemHoverBackground', {
3364
light: new Color(new RGBA(0, 0, 0, 0.08)),
3365
dark: new Color(new RGBA(255, 255, 255, 0.15)),
3366
hcDark: new Color(new RGBA(255, 255, 255, 0.15)),
3367
hcLight: new Color(new RGBA(0, 0, 0, 0.08)),
3368
}, nls.localize('notebook.cellStatusBarItemHoverBackground', "The background color of notebook cell status bar items."));
3369
3370
export const cellInsertionIndicator = registerColor('notebook.cellInsertionIndicator', focusBorder, nls.localize('notebook.cellInsertionIndicator', "The color of the notebook cell insertion indicator."));
3371
3372
export const listScrollbarSliderBackground = registerColor('notebookScrollbarSlider.background', scrollbarSliderBackground, nls.localize('notebookScrollbarSliderBackground', "Notebook scrollbar slider background color."));
3373
3374
export const listScrollbarSliderHoverBackground = registerColor('notebookScrollbarSlider.hoverBackground', scrollbarSliderHoverBackground, nls.localize('notebookScrollbarSliderHoverBackground', "Notebook scrollbar slider background color when hovering."));
3375
3376
export const listScrollbarSliderActiveBackground = registerColor('notebookScrollbarSlider.activeBackground', scrollbarSliderActiveBackground, nls.localize('notebookScrollbarSliderActiveBackground', "Notebook scrollbar slider background color when clicked on."));
3377
3378
export const cellSymbolHighlight = registerColor('notebook.symbolHighlightBackground', {
3379
dark: Color.fromHex('#ffffff0b'),
3380
light: Color.fromHex('#fdff0033'),
3381
hcDark: null,
3382
hcLight: null
3383
}, nls.localize('notebook.symbolHighlightBackground', "Background color of highlighted cell"));
3384
3385
export const cellEditorBackground = registerColor('notebook.cellEditorBackground', {
3386
light: SIDE_BAR_BACKGROUND,
3387
dark: SIDE_BAR_BACKGROUND,
3388
hcDark: null,
3389
hcLight: null
3390
}, nls.localize('notebook.cellEditorBackground', "Cell editor background color."));
3391
3392
const notebookEditorBackground = registerColor('notebook.editorBackground', {
3393
light: EDITOR_PANE_BACKGROUND,
3394
dark: EDITOR_PANE_BACKGROUND,
3395
hcDark: null,
3396
hcLight: null
3397
}, nls.localize('notebook.editorBackground', "Notebook background color."));
3398
3399