From 11f236ade14af3bcbd7d5f9e263130b6bb5c118a Mon Sep 17 00:00:00 2001 From: Daniel Grajeda Date: Sun, 27 Dec 2020 14:08:27 -0700 Subject: [PATCH] Notebook Views (#13465) * Add notebook editor Introduce notebook editor component to allow for separate notebook displays in order to accomodate notebook views * Localize notebook views configuration title * Refactor view mode and remove the views configuration while it is unused * Only fire view mode changed event when the value has been changed * Remove notebook views contribution --- .../notebook/browser/notebook.component.ts | 99 ++-------- .../notebook/browser/notebook.module.ts | 5 +- .../browser/notebookEditor.component.html | 9 + .../browser/notebookEditor.component.ts | 185 ++++++++++++++++++ .../notebook/browser/notebookEditor.ts | 4 +- .../workbench/contrib/notebook/test/stubs.ts | 10 +- .../browser/models/modelInterfaces.ts | 11 ++ .../notebook/browser/models/notebookModel.ts | 19 +- 8 files changed, 254 insertions(+), 88 deletions(-) create mode 100644 src/sql/workbench/contrib/notebook/browser/notebookEditor.component.html create mode 100644 src/sql/workbench/contrib/notebook/browser/notebookEditor.component.ts diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts index 896e7ca995..2504bd77dd 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { nb } from 'azdata'; -import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnDestroy, ViewChildren, QueryList } from '@angular/core'; +import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnDestroy, ViewChildren, QueryList, Input } from '@angular/core'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as themeColors from 'vs/workbench/common/theme'; @@ -22,21 +22,17 @@ import * as DOM from 'vs/base/browser/dom'; import { AngularDisposable } from 'sql/base/browser/lifecycle'; import { CellTypes, CellType, NotebookChangeType } from 'sql/workbench/services/notebook/common/contracts'; -import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; +import { ICellModel, INotebookModel, NotebookContentChange } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; -import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER, INotebookSection, INavigationProvider, ICellEditorProvider, NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService'; +import { INotebookService, INotebookParams, INotebookEditor, INotebookSection, INavigationProvider, ICellEditorProvider, NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService'; import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel'; -import { ModelFactory } from 'sql/workbench/services/notebook/browser/models/modelFactory'; -import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils'; import { Deferred } from 'sql/base/common/promise'; -import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { AddCellAction, KernelsDropdown, AttachToDropdown, TrustedAction, RunAllCellsAction, ClearAllOutputsAction, CollapseCellsAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; import { DropdownMenuActionViewItem } from 'sql/base/browser/ui/buttonMenu/buttonMenu'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; -import { CellMagicMapper } from 'sql/workbench/contrib/notebook/browser/models/cellMagicMapper'; import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { isValidBasename } from 'vs/base/common/extpath'; @@ -54,7 +50,6 @@ import { CodeCellComponent } from 'sql/workbench/contrib/notebook/browser/cellVi import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; -import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CellToolbarComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component'; @@ -73,12 +68,11 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe @ViewChildren(TextCellComponent) private textCells: QueryList; @ViewChildren(CellToolbarComponent) private cellToolbar: QueryList; - private _model: NotebookModel; + @Input() _model: NotebookModel; + protected _actionBar: Taskbar; protected isLoading: boolean; - private notebookManagers: INotebookManager[] = []; private _modelReadyDeferred = new Deferred(); - private profile: IConnectionProfile; private _trustedAction: TrustedAction; private _runAllCellsAction: RunAllCellsAction; private _providerRelatedActions: IAction[] = []; @@ -106,11 +100,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe @Inject(ICapabilitiesService) private capabilitiesService: ICapabilitiesService, @Inject(ITextFileService) private textFileService: ITextFileService, @Inject(ILogService) private readonly logService: ILogService, - @Inject(IAdsTelemetryService) private adstelemetryService: IAdsTelemetryService, @Inject(IConfigurationService) private _configurationService: IConfigurationService ) { super(); - this.updateProfile(); this.isLoading = true; this.doubleClickEditEnabled = this._configurationService.getValue('notebook.enableDoubleClickEdit'); this._register(this._configurationService.onDidChangeConfiguration(e => { @@ -121,15 +113,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe })); } - private updateProfile(): void { - this.profile = this.notebookParams ? this.notebookParams.profile : undefined; - } - ngOnInit() { this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this)); this.updateTheme(this.themeService.getColorTheme()); this.initActionBar(); this.setScrollPosition(); + this.doLoad().catch(e => onUnexpectedError(e)); this.initNavSection(); } @@ -260,11 +249,11 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe private async doLoad(): Promise { try { - await this.createModelAndLoadContents(); + await this.registerModel(); this._modelReadyDeferred.resolve(this._model); this.notebookService.addNotebookEditor(this); - await this.setNotebookManager(); - await this.loadModel(); + await this._model.onClientSessionReady; + this.detectChanges(); } catch (error) { if (error) { // Offer to create a file from the error if we have a file not found and the name is valid @@ -305,41 +294,14 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe this.detectChanges(); } - private async loadModel(): Promise { - // Wait on provider information to be available before loading kernel and other information - await this.awaitNonDefaultProvider(); - await this._model.requestModelLoad(); - this.detectChanges(); - this.setContextKeyServiceWithProviderId(this._model.providerId); - await this._model.startSession(this._model.notebookManager, undefined, true); - this.fillInActionsForCurrentContext(); - this.detectChanges(); - } + private async registerModel(): Promise { + this._register(this._model.onError((errInfo: INotification) => this.handleModelError(errInfo))); + this._register(this._model.contentChanged((change) => this.handleContentChanged(change))); + this._register(this._model.onProviderIdChange((provider) => this.handleProviderIdChanged(provider))); + this._register(this._model.kernelChanged((kernelArgs) => this.handleKernelChanged(kernelArgs))); + this._register(this._model.onCellTypeChanged(() => this.detectChanges())); + this._register(this._model.layoutChanged(() => this.detectChanges())); - private async createModelAndLoadContents(): Promise { - let model = new NotebookModel({ - factory: this.modelFactory, - notebookUri: this._notebookParams.notebookUri, - connectionService: this.connectionManagementService, - notificationService: this.notificationService, - notebookManagers: this.notebookManagers, - contentManager: this._notebookParams.input.contentManager, - cellMagicMapper: new CellMagicMapper(this.notebookService.languageMagics), - providerId: 'sql', - defaultKernel: this._notebookParams.input.defaultKernel, - layoutChanged: this._notebookParams.input.layoutChanged, - capabilitiesService: this.capabilitiesService, - editorLoadedTimestamp: this._notebookParams.input.editorOpenedTimestamp - }, this.profile, this.logService, this.notificationService, this.adstelemetryService, this.connectionManagementService, this._configurationService, this.capabilitiesService); - let trusted = await this.notebookService.isNotebookTrustCached(this._notebookParams.notebookUri, this.isDirty()); - this._register(model.onError((errInfo: INotification) => this.handleModelError(errInfo))); - this._register(model.contentChanged((change) => this.handleContentChanged(change))); - this._register(model.onProviderIdChange((provider) => this.handleProviderIdChanged(provider))); - this._register(model.kernelChanged((kernelArgs) => this.handleKernelChanged(kernelArgs))); - this._register(model.onCellTypeChanged(() => this.detectChanges())); - this._register(model.layoutChanged(() => this.detectChanges())); - this._model = this._register(model); - await this._model.loadContents(trusted); this.setLoading(false); // Check if URI fragment is present; if it is, navigate to section by default this.navigateToSectionIfURIFragmentExists(); @@ -347,28 +309,6 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe this.detectChanges(); } - private async setNotebookManager(): Promise { - let providerInfo = await this._notebookParams.providerInfo; - for (let providerId of providerInfo.providers) { - let notebookManager = await this.notebookService.getOrCreateNotebookManager(providerId, this._notebookParams.notebookUri); - this.notebookManagers.push(notebookManager); - } - } - - private async awaitNonDefaultProvider(): Promise { - // Wait on registration for now. Long-term would be good to cache and refresh - await this.notebookService.registrationComplete; - this.model.standardKernels = this._notebookParams.input.standardKernels; - // Refresh the provider if we had been using default - let providerInfo = await this._notebookParams.providerInfo; - - if (DEFAULT_NOTEBOOK_PROVIDER === providerInfo.providerId) { - let providers = notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService); - let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER); - providerInfo.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0]; - } - } - private updateToolbarComponents() { this._trustedAction.enabled = true; if (this.model.trustedMode) { @@ -376,13 +316,6 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe } } - private get modelFactory(): IModelFactory { - if (!this._notebookParams.modelFactory) { - this._notebookParams.modelFactory = new ModelFactory(this.instantiationService); - } - return this._notebookParams.modelFactory; - } - private handleModelError(notification: INotification): void { this.notificationService.notify(notification); } diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.module.ts b/src/sql/workbench/contrib/notebook/browser/notebook.module.ts index 15c4877736..d7d69a382c 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.module.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.module.ts @@ -32,6 +32,7 @@ import { ICellComponentRegistry, Extensions as OutputComponentExtensions } from import { CollapseComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/collapse.component'; import { MarkdownToolbarComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component'; import { CellToolbarComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component'; +import { NotebookEditorComponent } from 'sql/workbench/contrib/notebook/browser/notebookEditor.component'; const outputComponentRegistry = Registry.as(OutputComponentExtensions.CellComponentContributions); @@ -52,6 +53,7 @@ export const NotebookModule = (params, selector: string, instantiationService: I MarkdownToolbarComponent, PlaceholderCellComponent, NotebookComponent, + NotebookEditorComponent, ComponentHostDirective, OutputAreaComponent, OutputComponent, @@ -62,6 +64,7 @@ export const NotebookModule = (params, selector: string, instantiationService: I ], entryComponents: [ NotebookComponent, + NotebookEditorComponent, ...outputComponents ], imports: [ @@ -86,7 +89,7 @@ export const NotebookModule = (params, selector: string, instantiationService: I } ngDoBootstrap(appRef: ApplicationRef) { - const factoryWrapper: any = this._resolver.resolveComponentFactory(NotebookComponent); + const factoryWrapper: any = this._resolver.resolveComponentFactory(NotebookEditorComponent); factoryWrapper.factory.selector = this.selector; appRef.bootstrap(factoryWrapper); } diff --git a/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.html b/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.html new file mode 100644 index 0000000000..80fc2ad0dc --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.html @@ -0,0 +1,9 @@ + + + + diff --git a/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.ts b/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.ts new file mode 100644 index 0000000000..db9643f120 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.ts @@ -0,0 +1,185 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Component, Inject, ChangeDetectorRef, forwardRef } from '@angular/core'; +import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel'; +import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils'; +import { AngularDisposable } from 'sql/base/browser/lifecycle'; +import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams'; +import { INotebookParams, INotebookService, INotebookManager, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService'; +import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; +import { CellMagicMapper } from 'sql/workbench/contrib/notebook/browser/models/cellMagicMapper'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; +import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IModelFactory, ViewMode, NotebookContentChange } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; +import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ModelFactory } from 'sql/workbench/services/notebook/browser/models/modelFactory'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IAction } from 'vs/base/common/actions'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export const NOTEBOOKEDITOR_SELECTOR: string = 'notebookeditor-component'; + +@Component({ + selector: NOTEBOOKEDITOR_SELECTOR, + templateUrl: decodeURI(require.toUrl('./notebookEditor.component.html')) +}) +export class NotebookEditorComponent extends AngularDisposable { + private profile: IConnectionProfile; + private notebookManagers: INotebookManager[] = []; + private _model: NotebookModel; + + constructor( + @Inject(ILogService) private readonly logService: ILogService, + @Inject(IBootstrapParams) private _notebookParams: INotebookParams, + @Inject(INotebookService) private notebookService: INotebookService, + @Inject(ICapabilitiesService) private capabilitiesService: ICapabilitiesService, + @Inject(IContextKeyService) private contextKeyService: IContextKeyService, + @Inject(IMenuService) private menuService: IMenuService, + @Inject(INotificationService) private notificationService: INotificationService, + @Inject(IAdsTelemetryService) private adstelemetryService: IAdsTelemetryService, + @Inject(IInstantiationService) private instantiationService: IInstantiationService, + @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, + @Inject(IConfigurationService) private _configurationService: IConfigurationService, + @Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService, + ) { + super(); + this.updateProfile(); + } + ngOnInit() { + this.doLoad().catch(e => onUnexpectedError(e)); + } + + private updateProfile(): void { + this.profile = this._notebookParams ? this._notebookParams.profile : undefined; + } + + private detectChanges(): void { + if (!(this._changeRef['destroyed'])) { + this._changeRef.detectChanges(); + } + } + + private async doLoad(): Promise { + await this.createModelAndLoadContents(); + await this.setNotebookManager(); + await this.loadModel(); + } + + private async loadModel(): Promise { + // Wait on provider information to be available before loading kernel and other information + await this.awaitNonDefaultProvider(); + await this.model.requestModelLoad(); + this.detectChanges(); + this.setContextKeyServiceWithProviderId(this.model.providerId); + await this.model.startSession(this.model.notebookManager, undefined, true); + this.fillInActionsForCurrentContext(); + this.detectChanges(); + } + + private async createModelAndLoadContents(): Promise { + let model = new NotebookModel({ + factory: this.modelFactory, + notebookUri: this._notebookParams.notebookUri, + connectionService: this.connectionManagementService, + notificationService: this.notificationService, + notebookManagers: this.notebookManagers, + contentManager: this._notebookParams.input.contentManager, + cellMagicMapper: new CellMagicMapper(this.notebookService.languageMagics), + providerId: 'sql', + defaultKernel: this._notebookParams.input.defaultKernel, + layoutChanged: this._notebookParams.input.layoutChanged, + capabilitiesService: this.capabilitiesService, + editorLoadedTimestamp: this._notebookParams.input.editorOpenedTimestamp + }, this.profile, this.logService, this.notificationService, this.adstelemetryService, this.connectionManagementService, this._configurationService, this.capabilitiesService); + + let trusted = await this.notebookService.isNotebookTrustCached(this._notebookParams.notebookUri, this.isDirty()); + this._model = this._register(model); + await this.model.loadContents(trusted); + + this._register(model.viewModeChanged((mode) => this.onViewModeChanged())); + this._register(model.contentChanged((change) => this.handleContentChanged(change))); + this._register(model.onCellTypeChanged(() => this.detectChanges())); + this._register(model.layoutChanged(() => this.detectChanges())); + + this.detectChanges(); + } + + private async setNotebookManager(): Promise { + let providerInfo = await this._notebookParams.providerInfo; + for (let providerId of providerInfo.providers) { + let notebookManager = await this.notebookService.getOrCreateNotebookManager(providerId, this._notebookParams.notebookUri); + this.notebookManagers.push(notebookManager); + } + } + + private setContextKeyServiceWithProviderId(providerId: string) { + let provider = new RawContextKey('providerId', providerId); + provider.bindTo(this.contextKeyService); + } + + private async awaitNonDefaultProvider(): Promise { + // Wait on registration for now. Long-term would be good to cache and refresh + await this.notebookService.registrationComplete; + this.model.standardKernels = this._notebookParams.input.standardKernels; + // Refresh the provider if we had been using default + let providerInfo = await this._notebookParams.providerInfo; + + if (DEFAULT_NOTEBOOK_PROVIDER === providerInfo.providerId) { + let providers = notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService); + let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER); + providerInfo.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0]; + } + } + + /** + * Get all of the menu contributions that use the ID 'notebook/toolbar'. + * Then, find all groups (currently we don't leverage the contributed + * groups functionality for the notebook toolbar), and fill in the 'primary' + * array with items that don't list a group. Finally, add any actions from + * the primary array to the end of the toolbar. + */ + private fillInActionsForCurrentContext(): void { + let primary: IAction[] = []; + let secondary: IAction[] = []; + let notebookBarMenu = this.menuService.createMenu(MenuId.NotebookToolbar, this.contextKeyService); + let groups = notebookBarMenu.getActions({ arg: null, shouldForwardArgs: true }); + fillInActions(groups, { primary, secondary }, false, (group: string) => group === undefined || group === ''); + //this.addPrimaryContributedActions(primary); + } + + private get modelFactory(): IModelFactory { + if (!this._notebookParams.modelFactory) { + this._notebookParams.modelFactory = new ModelFactory(this.instantiationService); + } + return this._notebookParams.modelFactory; + } + + public get model(): NotebookModel | null { + return this._model; + } + + public get viewMode(): ViewMode { + return this.model?.viewMode; + } + + private isDirty(): boolean { + return this._notebookParams.input.isDirty(); + } + + private handleContentChanged(change: NotebookContentChange) { + // Note: for now we just need to set dirty state and refresh the UI. + this.detectChanges(); + } + + public onViewModeChanged(): void { + this.detectChanges(); + } +} diff --git a/src/sql/workbench/contrib/notebook/browser/notebookEditor.ts b/src/sql/workbench/contrib/notebook/browser/notebookEditor.ts index a08fd87804..44d6d9cd89 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookEditor.ts @@ -13,7 +13,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; import { NotebookModule } from 'sql/workbench/contrib/notebook/browser/notebook.module'; -import { NOTEBOOK_SELECTOR } from 'sql/workbench/contrib/notebook/browser/notebook.component'; +import { NOTEBOOKEDITOR_SELECTOR } from 'sql/workbench/contrib/notebook/browser/notebookEditor.component'; import { INotebookParams, INotebookService, NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ACTION_IDS, NOTEBOOK_MAX_MATCHES, IFindNotebookController, FindWidget, IConfigurationChangedEvent } from 'sql/workbench/contrib/notebook/browser/find/notebookFindWidget'; @@ -235,7 +235,7 @@ export class NotebookEditor extends EditorPane implements IFindNotebookControlle this._instantiationService.invokeFunction(bootstrapAngular, NotebookModule, this._notebookContainer, - NOTEBOOK_SELECTOR, + NOTEBOOKEDITOR_SELECTOR, params, input ); diff --git a/src/sql/workbench/contrib/notebook/test/stubs.ts b/src/sql/workbench/contrib/notebook/test/stubs.ts index fb1372a892..71bfba9d9c 100644 --- a/src/sql/workbench/contrib/notebook/test/stubs.ts +++ b/src/sql/workbench/contrib/notebook/test/stubs.ts @@ -5,7 +5,7 @@ import { nb, IConnectionProfile } from 'azdata'; import * as vsEvent from 'vs/base/common/event'; -import { INotebookModel, ICellModel, IClientSession, NotebookContentChange, ISingleNotebookEditOperation, MoveDirection } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; +import { INotebookModel, ICellModel, IClientSession, NotebookContentChange, ISingleNotebookEditOperation, MoveDirection, ViewMode } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import { INotebookFindModel } from 'sql/workbench/contrib/notebook/browser/models/notebookFindModel'; import { NotebookChangeType, CellType } from 'sql/workbench/services/notebook/common/contracts'; import { INotebookManager, INotebookService, INotebookEditor, ILanguageMagic, INotebookProvider, INavigationProvider, INotebookParams, INotebookSection, ICellEditorProvider, NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService'; @@ -94,6 +94,12 @@ export class NotebookModelStub implements INotebookModel { findCellIndex(cellModel: ICellModel): number { throw new Error('Method not implemented.'); } + get viewMode() { + throw new Error('Method not implemented.'); + } + set viewMode(mode: ViewMode) { + throw new Error('Method not implemented.'); + } addCell(cellType: CellType, index?: number): void { throw new Error('Method not implemented.'); } @@ -439,6 +445,7 @@ export class FutureStub implements nb.IFuture { export class NotebookComponentStub implements INotebookEditor { cellEditors: ICellEditorProvider[]; + viewMode: string; deltaDecorations(newDecorationRange: NotebookRange, oldDecorationRange: NotebookRange): void { throw new Error('Method not implemented.'); } @@ -651,6 +658,7 @@ export class NotebookEditorStub implements INotebookEditor { cellEditors: CellEditorProviderStub[]; modelReady: Promise; model: INotebookModel; + viewMode: string; isDirty(): boolean { throw new Error('Method not implemented.'); } diff --git a/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts b/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts index 1bb0e1e464..bc4f7fb2b4 100644 --- a/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts +++ b/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts @@ -24,6 +24,11 @@ import type { FutureInternal } from 'sql/workbench/services/notebook/browser/int import { ICellValue, ResultSetSummary } from 'sql/workbench/services/query/common/query'; import { QueryResultId } from 'sql/workbench/services/notebook/browser/models/cell'; +export enum ViewMode { + Notebook, + Views, +} + export interface ICellRange { readonly start: number; readonly end: number; @@ -334,6 +339,12 @@ export interface INotebookModel { */ providerId: string; + /** + * View mode for this model. It determines what editor mode + * will be displayed. + */ + viewMode: ViewMode; + /** * Change the current kernel from the Kernel dropdown * @param displayName kernel name (as displayed in Kernel dropdown) diff --git a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts index a7b2371b48..a2214cea74 100644 --- a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts +++ b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IClientSession, INotebookModel, INotebookModelOptions, ICellModel, NotebookContentChange, MoveDirection } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; +import { IClientSession, INotebookModel, INotebookModelOptions, ICellModel, NotebookContentChange, MoveDirection, ViewMode } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import { NotebookChangeType, CellType, CellTypes } from 'sql/workbench/services/notebook/common/contracts'; import { nbversion } from 'sql/workbench/services/notebook/common/notebookConstants'; import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils'; @@ -56,6 +56,7 @@ export class NotebookModel extends Disposable implements INotebookModel { private _contentChangedEmitter = new Emitter(); private _kernelsChangedEmitter = new Emitter(); private _kernelChangedEmitter = new Emitter(); + private _viewModeChangedEmitter = new Emitter(); private _layoutChanged = new Emitter(); private _inErrorState: boolean = false; private _activeClientSession: IClientSession | undefined; @@ -71,6 +72,7 @@ export class NotebookModel extends Disposable implements INotebookModel { private _tags: string[] | undefined; private _existingMetadata: nb.INotebookMetadata = {}; private _language: string = ''; + private _viewMode: ViewMode = ViewMode.Notebook; private _onErrorEmitter = new Emitter(); private _savedKernelInfo: nb.IKernelSpec | undefined; private _savedConnectionName: string | undefined; @@ -274,6 +276,21 @@ export class NotebookModel extends Disposable implements INotebookModel { }); } + public get viewModeChanged(): Event { + return this._viewModeChangedEmitter.event; + } + + public get viewMode() { + return this._viewMode; + } + + public set viewMode(mode: ViewMode) { + if (mode !== this._viewMode) { + this._viewMode = mode; + this._viewModeChangedEmitter.fire(mode); + } + } + /** * Indicates the server has finished loading. It may have failed to load in * which case the view will be in an error state.