Path: blob/main/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as DOM from '../../../../base/browser/dom.js';6import { IActionViewItem } from '../../../../base/browser/ui/actionbar/actionbar.js';7import { IAction, toAction } from '../../../../base/common/actions.js';8import { timeout } from '../../../../base/common/async.js';9import { CancellationToken } from '../../../../base/common/cancellation.js';10import { Emitter, Event } from '../../../../base/common/event.js';11import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';12import { extname, isEqual } from '../../../../base/common/resources.js';13import { URI } from '../../../../base/common/uri.js';14import { generateUuid } from '../../../../base/common/uuid.js';15import { ITextResourceConfigurationService } from '../../../../editor/common/services/textResourceConfiguration.js';16import { localize } from '../../../../nls.js';17import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';18import { IEditorOptions } from '../../../../platform/editor/common/editor.js';19import { ByteSize, FileOperationError, FileOperationResult, IFileService, TooLargeFileOperationError } from '../../../../platform/files/common/files.js';20import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';21import { IStorageService } from '../../../../platform/storage/common/storage.js';22import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';23import { IThemeService } from '../../../../platform/theme/common/themeService.js';24import { Selection } from '../../../../editor/common/core/selection.js';25import { EditorPane } from '../../../browser/parts/editor/editorPane.js';26import { DEFAULT_EDITOR_ASSOCIATION, EditorPaneSelectionChangeReason, EditorPaneSelectionCompareResult, EditorResourceAccessor, IEditorMemento, IEditorOpenContext, IEditorPane, IEditorPaneScrollPosition, IEditorPaneSelection, IEditorPaneSelectionChangeEvent, IEditorPaneWithScrolling, createEditorOpenError, createTooLargeFileError, isEditorOpenError } from '../../../common/editor.js';27import { EditorInput } from '../../../common/editor/editorInput.js';28import { SELECT_KERNEL_ID } from './controller/coreActions.js';29import { INotebookEditorOptions, INotebookEditorPane, INotebookEditorViewState } from './notebookBrowser.js';30import { IBorrowValue, INotebookEditorService } from './services/notebookEditorService.js';31import { NotebookEditorWidget } from './notebookEditorWidget.js';32import { NotebooKernelActionViewItem } from './viewParts/notebookKernelView.js';33import { NotebookTextModel } from '../common/model/notebookTextModel.js';34import { CellKind, NOTEBOOK_EDITOR_ID, NotebookWorkingCopyTypeIdentifier } from '../common/notebookCommon.js';35import { NotebookEditorInput } from '../common/notebookEditorInput.js';36import { NotebookPerfMarks } from '../common/notebookPerformance.js';37import { GroupsOrder, IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';38import { IEditorService } from '../../../services/editor/common/editorService.js';39import { IEditorProgressService } from '../../../../platform/progress/common/progress.js';40import { InstallRecommendedExtensionAction } from '../../extensions/browser/extensionsActions.js';41import { INotebookService } from '../common/notebookService.js';42import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';43import { EnablementState } from '../../../services/extensionManagement/common/extensionManagement.js';44import { IWorkingCopyBackupService } from '../../../services/workingCopy/common/workingCopyBackup.js';45import { streamToBuffer } from '../../../../base/common/buffer.js';46import { ILogService } from '../../../../platform/log/common/log.js';47import { IPreferencesService } from '../../../services/preferences/common/preferences.js';48import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';49import { StopWatch } from '../../../../base/common/stopwatch.js';50import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';5152const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';5354export class NotebookEditor extends EditorPane implements INotebookEditorPane, IEditorPaneWithScrolling {55static readonly ID: string = NOTEBOOK_EDITOR_ID;5657private readonly _editorMemento: IEditorMemento<INotebookEditorViewState>;58private readonly _groupListener = this._register(new DisposableStore());59private readonly _widgetDisposableStore: DisposableStore = this._register(new DisposableStore());60private _widget: IBorrowValue<NotebookEditorWidget> = { value: undefined };61private _rootElement!: HTMLElement;62private _pagePosition?: { readonly dimension: DOM.Dimension; readonly position: DOM.IDomPosition };6364private readonly _inputListener = this._register(new MutableDisposable());6566// override onDidFocus and onDidBlur to be based on the NotebookEditorWidget element67private readonly _onDidFocusWidget = this._register(new Emitter<void>());68override get onDidFocus(): Event<void> { return this._onDidFocusWidget.event; }69private readonly _onDidBlurWidget = this._register(new Emitter<void>());70override get onDidBlur(): Event<void> { return this._onDidBlurWidget.event; }7172private readonly _onDidChangeModel = this._register(new Emitter<void>());73readonly onDidChangeModel: Event<void> = this._onDidChangeModel.event;7475private readonly _onDidChangeSelection = this._register(new Emitter<IEditorPaneSelectionChangeEvent>());76readonly onDidChangeSelection = this._onDidChangeSelection.event;7778protected readonly _onDidChangeScroll = this._register(new Emitter<void>());79readonly onDidChangeScroll = this._onDidChangeScroll.event;8081constructor(82group: IEditorGroup,83@ITelemetryService telemetryService: ITelemetryService,84@IThemeService themeService: IThemeService,85@IInstantiationService private readonly _instantiationService: IInstantiationService,86@IStorageService storageService: IStorageService,87@IEditorService private readonly _editorService: IEditorService,88@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,89@INotebookEditorService private readonly _notebookWidgetService: INotebookEditorService,90@IContextKeyService private readonly _contextKeyService: IContextKeyService,91@IFileService private readonly _fileService: IFileService,92@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,93@IEditorProgressService private readonly _editorProgressService: IEditorProgressService,94@INotebookService private readonly _notebookService: INotebookService,95@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,96@IWorkingCopyBackupService private readonly _workingCopyBackupService: IWorkingCopyBackupService,97@ILogService private readonly logService: ILogService,98@IPreferencesService private readonly _preferencesService: IPreferencesService99) {100super(NotebookEditor.ID, group, telemetryService, themeService, storageService);101this._editorMemento = this.getEditorMemento<INotebookEditorViewState>(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);102103this._register(this._fileService.onDidChangeFileSystemProviderCapabilities(e => this._onDidChangeFileSystemProvider(e.scheme)));104this._register(this._fileService.onDidChangeFileSystemProviderRegistrations(e => this._onDidChangeFileSystemProvider(e.scheme)));105}106107private _onDidChangeFileSystemProvider(scheme: string): void {108if (this.input instanceof NotebookEditorInput && this.input.resource?.scheme === scheme) {109this._updateReadonly(this.input);110}111}112113private _onDidChangeInputCapabilities(input: NotebookEditorInput): void {114if (this.input === input) {115this._updateReadonly(input);116}117}118119private _updateReadonly(input: NotebookEditorInput): void {120this._widget.value?.setOptions({ isReadOnly: !!input.isReadonly() });121}122123get textModel(): NotebookTextModel | undefined {124return this._widget.value?.textModel;125}126127override get minimumWidth(): number { return 220; }128override get maximumWidth(): number { return Number.POSITIVE_INFINITY; }129130// these setters need to exist because this extends from EditorPane131override set minimumWidth(value: number) { /*noop*/ }132override set maximumWidth(value: number) { /*noop*/ }133134//#region Editor Core135override get scopedContextKeyService(): IContextKeyService | undefined {136return this._widget.value?.scopedContextKeyService;137}138139protected createEditor(parent: HTMLElement): void {140this._rootElement = DOM.append(parent, DOM.$('.notebook-editor'));141this._rootElement.id = `notebook-editor-element-${generateUuid()}`;142}143144override getActionViewItem(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined {145if (action.id === SELECT_KERNEL_ID) {146// this is being disposed by the consumer147return this._register(this._instantiationService.createInstance(NotebooKernelActionViewItem, action, this, options));148}149return undefined;150}151152override getControl(): NotebookEditorWidget | undefined {153return this._widget.value;154}155156override setVisible(visible: boolean): void {157super.setVisible(visible);158if (!visible) {159this._widget.value?.onWillHide();160}161}162163protected override setEditorVisible(visible: boolean): void {164super.setEditorVisible(visible);165this._groupListener.clear();166this._groupListener.add(this.group.onWillCloseEditor(e => this._saveEditorViewState(e.editor)));167this._groupListener.add(this.group.onDidModelChange(() => {168if (this._editorGroupService.activeGroup !== this.group) {169this._widget?.value?.updateEditorFocus();170}171}));172173if (!visible) {174this._saveEditorViewState(this.input);175if (this.input && this._widget.value) {176// the widget is not transfered to other editor inputs177this._widget.value.onWillHide();178}179}180}181182override focus() {183super.focus();184this._widget.value?.focus();185}186187override hasFocus(): boolean {188const value = this._widget.value;189if (!value) {190return false;191}192193return !!value && (DOM.isAncestorOfActiveElement(value.getDomNode() || DOM.isAncestorOfActiveElement(value.getOverflowContainerDomNode())));194}195196override async setInput(input: NotebookEditorInput, options: INotebookEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken, noRetry?: boolean): Promise<void> {197try {198let perfMarksCaptured = false;199const fileOpenMonitor = timeout(10000);200fileOpenMonitor.then(() => {201perfMarksCaptured = true;202this._handlePerfMark(perf, input);203});204205const perf = new NotebookPerfMarks();206perf.mark('startTime');207208this._inputListener.value = input.onDidChangeCapabilities(() => this._onDidChangeInputCapabilities(input));209210this._widgetDisposableStore.clear();211212// there currently is a widget which we still own so213// we need to hide it before getting a new widget214this._widget.value?.onWillHide();215216this._widget = <IBorrowValue<NotebookEditorWidget>>this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group.id, input, undefined, this._pagePosition?.dimension, this.window);217218if (this._rootElement && this._widget.value!.getDomNode()) {219this._rootElement.setAttribute('aria-flowto', this._widget.value!.getDomNode().id || '');220DOM.setParentFlowTo(this._widget.value!.getDomNode(), this._rootElement);221}222223this._widgetDisposableStore.add(this._widget.value!.onDidChangeModel(() => this._onDidChangeModel.fire()));224this._widgetDisposableStore.add(this._widget.value!.onDidChangeActiveCell(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.USER })));225226if (this._pagePosition) {227this._widget.value!.layout(this._pagePosition.dimension, this._rootElement, this._pagePosition.position);228}229230// only now `setInput` and yield/await. this is AFTER the actual widget is ready. This is very important231// so that others synchronously receive a notebook editor with the correct widget being set232await super.setInput(input, options, context, token);233const model = await input.resolve(options, perf);234perf.mark('inputLoaded');235236// Check for cancellation237if (token.isCancellationRequested) {238return undefined;239}240241// The widget has been taken away again. This can happen when the tab has been closed while242// loading was in progress, in particular when open the same resource as different view type.243// When this happen, retry once244if (!this._widget.value) {245if (noRetry) {246return undefined;247}248return this.setInput(input, options, context, token, true);249}250251if (model === null) {252const knownProvider = this._notebookService.getViewTypeProvider(input.viewType);253254if (!knownProvider) {255throw new Error(localize('fail.noEditor', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType));256}257258await this._extensionsWorkbenchService.whenInitialized;259const extensionInfo = this._extensionsWorkbenchService.local.find(e => e.identifier.id === knownProvider);260261throw createEditorOpenError(new Error(localize('fail.noEditor.extensionMissing', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType)), [262toAction({263id: 'workbench.notebook.action.installOrEnableMissing', label:264extensionInfo265? localize('notebookOpenEnableMissingViewType', "Enable extension for '{0}'", input.viewType)266: localize('notebookOpenInstallMissingViewType', "Install extension for '{0}'", input.viewType)267, run: async () => {268const d = this._notebookService.onAddViewType(viewType => {269if (viewType === input.viewType) {270// serializer is registered, try to open again271this._editorService.openEditor({ resource: input.resource });272d.dispose();273}274});275const extensionInfo = this._extensionsWorkbenchService.local.find(e => e.identifier.id === knownProvider);276277try {278if (extensionInfo) {279await this._extensionsWorkbenchService.setEnablement(extensionInfo, extensionInfo.enablementState === EnablementState.DisabledWorkspace ? EnablementState.EnabledWorkspace : EnablementState.EnabledGlobally);280} else {281await this._instantiationService.createInstance(InstallRecommendedExtensionAction, knownProvider).run();282}283} catch (ex) {284this.logService.error(`Failed to install or enable extension ${knownProvider}`, ex);285d.dispose();286}287}288}),289toAction({290id: 'workbench.notebook.action.openAsText', label: localize('notebookOpenAsText', "Open As Text"), run: async () => {291const backup = await this._workingCopyBackupService.resolve({ resource: input.resource, typeId: NotebookWorkingCopyTypeIdentifier.create(input.viewType) });292if (backup) {293// with a backup present, we must resort to opening the backup contents294// as untitled text file to not show the wrong data to the user295const contents = await streamToBuffer(backup.value);296this._editorService.openEditor({ resource: undefined, contents: contents.toString() });297} else {298// without a backup present, we can open the original resource299this._editorService.openEditor({ resource: input.resource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id, pinned: true } });300}301}302})303], { allowDialog: true });304305}306307this._widgetDisposableStore.add(model.notebook.onDidChangeContent(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT })));308309const viewState = options?.viewState ?? this._loadNotebookEditorViewState(input);310311// We might be moving the notebook widget between groups, and these services are tied to the group312this._widget.value.setParentContextKeyService(this._contextKeyService);313this._widget.value.setEditorProgressService(this._editorProgressService);314315await this._widget.value.setModel(model.notebook, viewState, perf);316const isReadOnly = !!input.isReadonly();317await this._widget.value.setOptions({ ...options, isReadOnly });318this._widgetDisposableStore.add(this._widget.value.onDidFocusWidget(() => this._onDidFocusWidget.fire()));319this._widgetDisposableStore.add(this._widget.value.onDidBlurWidget(() => this._onDidBlurWidget.fire()));320321this._widgetDisposableStore.add(this._editorGroupService.createEditorDropTarget(this._widget.value.getDomNode(), {322containsGroup: (group) => this.group.id === group.id323}));324325this._widgetDisposableStore.add(this._widget.value.onDidScroll(() => { this._onDidChangeScroll.fire(); }));326327perf.mark('editorLoaded');328329fileOpenMonitor.cancel();330if (perfMarksCaptured) {331return;332}333334this._handlePerfMark(perf, input, model.notebook);335this._onDidChangeControl.fire();336} catch (e) {337this.logService.warn('NotebookEditorWidget#setInput failed', e);338if (isEditorOpenError(e)) {339throw e;340}341342// Handle case where a file is too large to open without confirmation343if ((<FileOperationError>e).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) {344let message: string;345if (e instanceof TooLargeFileOperationError) {346message = localize('notebookTooLargeForHeapErrorWithSize', "The notebook is not displayed in the notebook editor because it is very large ({0}).", ByteSize.formatSize(e.size));347} else {348message = localize('notebookTooLargeForHeapErrorWithoutSize', "The notebook is not displayed in the notebook editor because it is very large.");349}350351throw createTooLargeFileError(this.group, input, options, message, this._preferencesService);352}353354const error = createEditorOpenError(e instanceof Error ? e : new Error((e ? e.message : '')), [355toAction({356id: 'workbench.notebook.action.openInTextEditor', label: localize('notebookOpenInTextEditor', "Open in Text Editor"), run: async () => {357const activeEditorPane = this._editorService.activeEditorPane;358if (!activeEditorPane) {359return;360}361362const activeEditorResource = EditorResourceAccessor.getCanonicalUri(activeEditorPane.input);363if (!activeEditorResource) {364return;365}366367if (activeEditorResource.toString() === input.resource?.toString()) {368// Replace the current editor with the text editor369return this._editorService.openEditor({370resource: activeEditorResource,371options: {372override: DEFAULT_EDITOR_ASSOCIATION.id,373pinned: true // new file gets pinned by default374}375});376}377378return;379}380})381], { allowDialog: true });382383throw error;384}385}386387private _handlePerfMark(perf: NotebookPerfMarks, input: NotebookEditorInput, notebook?: NotebookTextModel) {388const perfMarks = perf.value;389390type WorkbenchNotebookOpenClassification = {391owner: 'rebornix';392comment: 'The notebook file open metrics. Used to get a better understanding of the performance of notebook file opening';393scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the notebook resource' };394ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the notebook resource' };395viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The view type of the notebook editor' };396extensionActivated: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension activation time for the resource opening' };397inputLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Editor Input loading time for the resource opening' };398webviewCommLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Webview initialization time for the resource opening' };399customMarkdownLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Custom markdown loading time for the resource opening' };400editorLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Overall editor loading time for the resource opening' };401codeCellCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Total number of code cell' };402mdCellCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Total number of markdown cell' };403outputCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Total number of cell outputs' };404outputBytes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Total number of bytes for all outputs' };405codeLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Length of text in all code cells' };406markdownLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Length of text in all markdown cells' };407notebookStatsLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Time for generating the notebook level information for telemetry' };408};409410type WorkbenchNotebookOpenEvent = {411scheme: string;412ext: string;413viewType: string;414extensionActivated: number;415inputLoaded: number;416webviewCommLoaded: number;417customMarkdownLoaded: number | undefined;418editorLoaded: number;419codeCellCount: number | undefined;420mdCellCount: number | undefined;421outputCount: number | undefined;422outputBytes: number | undefined;423codeLength: number | undefined;424markdownLength: number | undefined;425notebookStatsLoaded: number | undefined;426};427428const startTime = perfMarks['startTime'];429const extensionActivated = perfMarks['extensionActivated'];430const inputLoaded = perfMarks['inputLoaded'];431const webviewCommLoaded = perfMarks['webviewCommLoaded'];432const customMarkdownLoaded = perfMarks['customMarkdownLoaded'];433const editorLoaded = perfMarks['editorLoaded'];434435let extensionActivationTimespan = -1;436let inputLoadingTimespan = -1;437let webviewCommLoadingTimespan = -1;438let customMarkdownLoadingTimespan = -1;439let editorLoadingTimespan = -1;440441if (startTime !== undefined && extensionActivated !== undefined) {442extensionActivationTimespan = extensionActivated - startTime;443444if (inputLoaded !== undefined) {445inputLoadingTimespan = inputLoaded - extensionActivated;446}447448if (webviewCommLoaded !== undefined) {449webviewCommLoadingTimespan = webviewCommLoaded - extensionActivated;450451}452453if (customMarkdownLoaded !== undefined) {454customMarkdownLoadingTimespan = customMarkdownLoaded - startTime;455}456457if (editorLoaded !== undefined) {458editorLoadingTimespan = editorLoaded - startTime;459}460}461462// Notebook information463let codeCellCount: number | undefined = undefined;464let mdCellCount: number | undefined = undefined;465let outputCount: number | undefined = undefined;466let outputBytes: number | undefined = undefined;467let codeLength: number | undefined = undefined;468let markdownLength: number | undefined = undefined;469let notebookStatsLoaded: number | undefined = undefined;470if (notebook) {471const stopWatch = new StopWatch();472for (const cell of notebook.cells) {473if (cell.cellKind === CellKind.Code) {474codeCellCount = (codeCellCount || 0) + 1;475codeLength = (codeLength || 0) + cell.getTextLength();476outputCount = (outputCount || 0) + cell.outputs.length;477outputBytes = (outputBytes || 0) + cell.outputs.reduce((prev, cur) => prev + cur.outputs.reduce((size, item) => size + item.data.byteLength, 0), 0);478} else {479mdCellCount = (mdCellCount || 0) + 1;480markdownLength = (codeLength || 0) + cell.getTextLength();481}482}483notebookStatsLoaded = stopWatch.elapsed();484}485486this.logService.trace(`[NotebookEditor] open notebook perf ${notebook?.uri.toString() ?? ''} - extensionActivation: ${extensionActivationTimespan}, inputLoad: ${inputLoadingTimespan}, webviewComm: ${webviewCommLoadingTimespan}, customMarkdown: ${customMarkdownLoadingTimespan}, editorLoad: ${editorLoadingTimespan}`);487488this.telemetryService.publicLog2<WorkbenchNotebookOpenEvent, WorkbenchNotebookOpenClassification>('notebook/editorOpenPerf', {489scheme: input.resource.scheme,490ext: extname(input.resource),491viewType: input.viewType,492extensionActivated: extensionActivationTimespan,493inputLoaded: inputLoadingTimespan,494webviewCommLoaded: webviewCommLoadingTimespan,495customMarkdownLoaded: customMarkdownLoadingTimespan,496editorLoaded: editorLoadingTimespan,497codeCellCount,498mdCellCount,499outputCount,500outputBytes,501codeLength,502markdownLength,503notebookStatsLoaded504});505}506507override clearInput(): void {508this._inputListener.clear();509510if (this._widget.value) {511this._saveEditorViewState(this.input);512this._widget.value.onWillHide();513}514super.clearInput();515}516517override setOptions(options: INotebookEditorOptions | undefined): void {518this._widget.value?.setOptions(options);519super.setOptions(options);520}521522protected override saveState(): void {523this._saveEditorViewState(this.input);524super.saveState();525}526527override getViewState(): INotebookEditorViewState | undefined {528const input = this.input;529if (!(input instanceof NotebookEditorInput)) {530return undefined;531}532533this._saveEditorViewState(input);534return this._loadNotebookEditorViewState(input);535}536537getSelection(): IEditorPaneSelection | undefined {538if (this._widget.value) {539const activeCell = this._widget.value.getActiveCell();540if (activeCell) {541const cellUri = activeCell.uri;542return new NotebookEditorSelection(cellUri, activeCell.getSelections());543}544}545546return undefined;547}548549getScrollPosition(): IEditorPaneScrollPosition {550const widget = this.getControl();551if (!widget) {552throw new Error('Notebook widget has not yet been initialized');553}554555return {556scrollTop: widget.scrollTop,557scrollLeft: 0,558};559}560561setScrollPosition(scrollPosition: IEditorPaneScrollPosition): void {562const editor = this.getControl();563if (!editor) {564throw new Error('Control has not yet been initialized');565}566567editor.setScrollTop(scrollPosition.scrollTop);568}569570private _saveEditorViewState(input: EditorInput | undefined): void {571if (this._widget.value && input instanceof NotebookEditorInput) {572if (this._widget.value.isDisposed) {573return;574}575576const state = this._widget.value.getEditorViewState();577this._editorMemento.saveEditorState(this.group, input.resource, state);578}579}580581private _loadNotebookEditorViewState(input: NotebookEditorInput): INotebookEditorViewState | undefined {582const result = this._editorMemento.loadEditorState(this.group, input.resource);583if (result) {584return result;585}586// when we don't have a view state for the group/input-tuple then we try to use an existing587// editor for the same resource.588for (const group of this._editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {589if (group.activeEditorPane !== this && group.activeEditorPane instanceof NotebookEditor && group.activeEditor?.matches(input)) {590return group.activeEditorPane._widget.value?.getEditorViewState();591}592}593return;594}595596layout(dimension: DOM.Dimension, position: DOM.IDomPosition): void {597this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600);598this._rootElement.classList.toggle('narrow-width', dimension.width < 600);599this._pagePosition = { dimension, position };600601if (!this._widget.value || !(this.input instanceof NotebookEditorInput)) {602return;603}604605if (this.input.resource.toString() !== this.textModel?.uri.toString() && this._widget.value?.hasModel()) {606// input and widget mismatch607// this happens when608// 1. open document A, pin the document609// 2. open document B610// 3. close document B611// 4. a layout is triggered612return;613}614615if (this.isVisible()) {616this._widget.value.layout(dimension, this._rootElement, position);617}618}619620//#endregion621}622623class NotebookEditorSelection implements IEditorPaneSelection {624625constructor(626private readonly cellUri: URI,627private readonly selections: Selection[]628) { }629630compare(other: IEditorPaneSelection): EditorPaneSelectionCompareResult {631if (!(other instanceof NotebookEditorSelection)) {632return EditorPaneSelectionCompareResult.DIFFERENT;633}634635if (isEqual(this.cellUri, other.cellUri)) {636return EditorPaneSelectionCompareResult.IDENTICAL;637}638639return EditorPaneSelectionCompareResult.DIFFERENT;640}641642restore(options: IEditorOptions): INotebookEditorOptions {643const notebookOptions: INotebookEditorOptions = {644cellOptions: {645resource: this.cellUri,646options: {647selection: this.selections[0]648}649}650};651652Object.assign(notebookOptions, options);653654return notebookOptions;655}656657log(): string {658return this.cellUri.fragment;659}660}661662export function isNotebookContainingCellEditor(editor: IEditorPane | undefined, codeEditor: ICodeEditor): boolean {663if (editor?.getId() === NotebookEditor.ID) {664const notebookWidget = editor.getControl() as NotebookEditorWidget;665if (notebookWidget) {666for (const [_, editor] of notebookWidget.codeEditors) {667if (editor === codeEditor) {668return true;669}670}671}672}673return false;674}675676677