Split up NotebookProvider into separate providers for handling file serialization and cell execution. (#17176)

This commit is contained in:
Cory Rivera
2021-09-29 16:15:28 -07:00
committed by GitHub
parent dfc2635aa7
commit 14904bb671
51 changed files with 1426 additions and 971 deletions

View File

@@ -13,11 +13,10 @@ import { IStandardKernelWithProvider, getProvidersForFileName, getStandardKernel
import { INotebookService, DEFAULT_NOTEBOOK_PROVIDER, IProviderInfo } from 'sql/workbench/services/notebook/browser/notebookService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
import { INotebookModel, IContentManager, NotebookContentChange } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { INotebookModel, IContentLoader, NotebookContentChange } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { Schemas } from 'vs/base/common/network';
import { ITextFileSaveOptions, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -37,6 +36,7 @@ import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/no
import { INotebookInput } from 'sql/workbench/services/notebook/browser/interface';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager';
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
@@ -61,8 +61,8 @@ export class NotebookEditorModel extends EditorModel {
notebook.modelReady.then((model) => {
if (!this._changeEventsHookedUp) {
this._changeEventsHookedUp = true;
this._register(model.kernelChanged(e => this.updateModel(undefined, NotebookChangeType.KernelChanged)));
this._register(model.contentChanged(e => this.updateModel(e, e.changeType)));
this._register(model.kernelChanged(e => this.updateModel(undefined, NotebookChangeType.KernelChanged).catch(e => onUnexpectedError(e))));
this._register(model.contentChanged(e => this.updateModel(e, e.changeType).catch(e => onUnexpectedError(e))));
this._register(notebook.model.onActiveCellChanged((cell) => {
if (cell) {
this._notebookTextFileModel.activeCellGuid = cell.cellGuid;
@@ -120,7 +120,7 @@ export class NotebookEditorModel extends EditorModel {
this._onDidChangeDirty.fire();
}
public updateModel(contentChange?: NotebookContentChange, type?: NotebookChangeType): void {
public async updateModel(contentChange?: NotebookContentChange, type?: NotebookChangeType): Promise<void> {
// If text editor model is readonly, exit early as no changes need to occur on the model
// Note: this follows what happens in fileCommands where update/save logic is skipped for readonly text editor models
if (this.textEditorModel?.isReadonly()) {
@@ -171,14 +171,14 @@ export class NotebookEditorModel extends EditorModel {
if (editAppliedSuccessfully) {
return;
}
this.replaceEntireTextEditorModel(notebookModel, type);
await this.replaceEntireTextEditorModel(notebookModel, type);
this._lastEditFullReplacement = true;
}
}
}
public replaceEntireTextEditorModel(notebookModel: INotebookModel, type: NotebookChangeType) {
this._notebookTextFileModel.replaceEntireTextEditorModel(notebookModel, type, this.textEditorModel);
public replaceEntireTextEditorModel(notebookModel: INotebookModel, type: NotebookChangeType): Promise<void> {
return this._notebookTextFileModel.replaceEntireTextEditorModel(notebookModel, type, this.textEditorModel);
}
private sendNotebookSerializationStateChange(): void {
@@ -224,7 +224,7 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu
private readonly _layoutChanged: Emitter<void> = this._register(new Emitter<void>());
private _model: NotebookEditorModel;
private _untitledEditorModel: IUntitledTextEditorModel;
private _contentManager: IContentManager;
private _contentLoader: IContentLoader;
private _providersLoaded: Promise<void>;
private _dirtyListener: IDisposable;
private _notebookEditorOpenedTimestamp: number;
@@ -274,11 +274,12 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu
return this._notebookFindModel;
}
public get contentManager(): IContentManager {
if (!this._contentManager) {
this._contentManager = this.instantiationService.createInstance(NotebookEditorContentManager, this);
public get contentLoader(): IContentLoader {
if (!this._contentLoader) {
let contentManager = this.instantiationService.createInstance(LocalContentManager);
this._contentLoader = this.instantiationService.createInstance(NotebookEditorContentLoader, this, contentManager);
}
return this._contentManager;
return this._contentLoader;
}
public override getName(): string {
@@ -292,6 +293,10 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu
return EditorInputCapabilities.None;
}
public get providersLoaded(): Promise<void> {
return this._providersLoaded;
}
public async getProviderInfo(): Promise<IProviderInfo> {
await this._providersLoaded;
return {
@@ -313,14 +318,14 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu
}
override async save(groupId: number, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
this.updateModel();
await this.updateModel();
let input = await this.textInput.save(groupId, options);
await this.setTrustForNewEditor(input);
return input;
}
override async saveAs(group: number, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
this.updateModel();
await this.updateModel();
let input = await this.textInput.saveAs(group, options);
await this.setTrustForNewEditor(input);
return input;
@@ -438,6 +443,8 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu
let standardKernels = getStandardKernelsForProvider(provider, this.notebookService);
this._standardKernels.push(...standardKernels);
});
let serializationProvider = await this.notebookService.getOrCreateSerializationManager(this._providerId, this._resource);
this._contentLoader = this.instantiationService.createInstance(NotebookEditorContentLoader, this, serializationProvider.contentManager);
}
}
@@ -497,8 +504,8 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu
}
}
updateModel(): void {
this._model.updateModel();
public updateModel(): Promise<void> {
return this._model.updateModel();
}
public override matches(otherInput: any): boolean {
@@ -510,17 +517,14 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu
}
}
export class NotebookEditorContentManager implements IContentManager {
export class NotebookEditorContentLoader implements IContentLoader {
constructor(
private notebookInput: NotebookInput,
@IInstantiationService private readonly instantiationService: IInstantiationService) {
private contentManager: azdata.nb.ContentManager) {
}
async loadContent(): Promise<azdata.nb.INotebookContents> {
let notebookEditorModel = await this.notebookInput.resolve();
let contentManager = this.instantiationService.createInstance(LocalContentManager);
let contents = await contentManager.loadFromContentString(notebookEditorModel.contentString);
return contents;
return this.contentManager.deserializeNotebook(notebookEditorModel.contentString);
}
}

View File

@@ -200,8 +200,15 @@ export class NotebookTextFileModel {
return false;
}
public replaceEntireTextEditorModel(notebookModel: INotebookModel, type: NotebookChangeType, textEditorModel: ITextEditorModel) {
let content = JSON.stringify(notebookModel.toJSON(type), undefined, ' ');
public async replaceEntireTextEditorModel(notebookModel: INotebookModel, type: NotebookChangeType, textEditorModel: ITextEditorModel): Promise<void> {
let content: string;
let notebookContents = notebookModel.toJSON(type);
let serializer = notebookModel.serializationManager;
if (serializer) {
content = await serializer.contentManager.serializeNotebook(notebookContents);
} else {
content = JSON.stringify(notebookContents, undefined, ' ');
}
let model = textEditorModel.textEditorModel;
let endLine = model.getLineCount();
let endCol = model.getLineMaxColumn(endLine);

View File

@@ -53,7 +53,7 @@ import { ImageMimeTypes, TextCellEditModes } from 'sql/workbench/services/notebo
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
import { INotebookModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { INotebookManager } from 'sql/workbench/services/notebook/browser/notebookService';
import { IExecuteManager } from 'sql/workbench/services/notebook/browser/notebookService';
import { NotebookExplorerViewletViewsContribution } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ContributedEditorPriority, IEditorOverrideService } from 'vs/workbench/services/editor/common/editorOverrideService';
@@ -62,6 +62,7 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { ILogService } from 'vs/platform/log/common/log';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { useNewMarkdownRendererKey } from 'sql/workbench/contrib/notebook/common/notebookCommon';
import { JUPYTER_PROVIDER_ID } from 'sql/workbench/common/constants';
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories)
.registerEditorInputSerializer(FileNotebookInput.ID, FileNoteBookEditorInputSerializer);
@@ -189,10 +190,10 @@ CommandsRegistry.registerCommand({
for (let editor of editors) {
if (editor instanceof NotebookInput) {
let model: INotebookModel = editor.notebookModel;
if (model.providerId === 'jupyter' && model.clientSession.isReady) {
if (model.providerId === JUPYTER_PROVIDER_ID && model.clientSession.isReady) {
// Jupyter server needs to be restarted so that the correct Python installation is used
if (!jupyterServerRestarted && restartJupyterServer) {
let jupyterNotebookManager: INotebookManager = model.notebookManagers.find(x => x.providerId === 'jupyter');
let jupyterNotebookManager: IExecuteManager = model.executeManagers.find(x => x.providerId === JUPYTER_PROVIDER_ID);
// Shutdown all current Jupyter sessions before stopping the server
await jupyterNotebookManager.sessionManager.shutdownAll();
// Jupyter session manager needs to be disposed so that a new one is created with the new server info
@@ -222,8 +223,8 @@ CommandsRegistry.registerCommand({
for (let editor of editors) {
if (editor instanceof NotebookInput) {
let model: INotebookModel = editor.notebookModel;
if (model?.providerId === 'jupyter') {
let jupyterNotebookManager: INotebookManager = model.notebookManagers.find(x => x.providerId === 'jupyter');
if (model?.providerId === JUPYTER_PROVIDER_ID) {
let jupyterNotebookManager: IExecuteManager = model.executeManagers.find(x => x.providerId === JUPYTER_PROVIDER_ID);
await jupyterNotebookManager.sessionManager.shutdownAll();
jupyterNotebookManager.sessionManager.dispose();
await jupyterNotebookManager.serverManager.stopServer();

View File

@@ -7,7 +7,7 @@ import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/no
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 { INotebookParams, INotebookService, IExecuteManager, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER, ISerializationManager } 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';
@@ -38,7 +38,8 @@ export const NOTEBOOKEDITOR_SELECTOR: string = 'notebookeditor-component';
export class NotebookEditorComponent extends AngularDisposable {
private readonly defaultViewMode = ViewMode.Notebook;
private profile: IConnectionProfile;
private notebookManagers: INotebookManager[] = [];
private serializationManagers: ISerializationManager[] = [];
private executeManagers: IExecuteManager[] = [];
private _modelReadyDeferred = new Deferred<NotebookModel>();
public model: NotebookModel;
@@ -80,7 +81,8 @@ export class NotebookEditorComponent extends AngularDisposable {
private async doLoad(): Promise<void> {
await this.createModelAndLoadContents();
await this.setNotebookManager();
await this.setSerializationManager();
await this.setExecuteManager();
await this.loadModel();
this.setActiveView();
@@ -93,21 +95,23 @@ export class NotebookEditorComponent extends AngularDisposable {
await this.model.requestModelLoad();
this.detectChanges();
this.setContextKeyServiceWithProviderId(this.model.providerId);
await this.model.startSession(this.model.notebookManager, undefined, true);
await this.model.startSession(this.model.executeManager, undefined, true);
this.fillInActionsForCurrentContext();
this.detectChanges();
}
private async createModelAndLoadContents(): Promise<void> {
let providerInfo = await this._notebookParams.providerInfo;
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,
serializationManagers: this.serializationManagers,
executeManagers: this.executeManagers,
contentLoader: this._notebookParams.input.contentLoader,
cellMagicMapper: new CellMagicMapper(this.notebookService.languageMagics),
providerId: 'sql',
providerId: providerInfo.providerId,
defaultKernel: this._notebookParams.input.defaultKernel,
layoutChanged: this._notebookParams.input.layoutChanged,
capabilitiesService: this.capabilitiesService,
@@ -132,11 +136,19 @@ export class NotebookEditorComponent extends AngularDisposable {
this.detectChanges();
}
private async setNotebookManager(): Promise<void> {
private async setSerializationManager(): Promise<void> {
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);
let manager = await this.notebookService.getOrCreateSerializationManager(providerId, this._notebookParams.notebookUri);
this.serializationManagers.push(manager);
}
}
private async setExecuteManager(): Promise<void> {
let providerInfo = await this._notebookParams.providerInfo;
for (let providerId of providerInfo.providers) {
let manager = await this.notebookService.getOrCreateExecuteManager(providerId, this._notebookParams.notebookUri);
this.executeManagers.push(manager);
}
}