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