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