mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-21 01:25:37 -05:00
Begin defining Extension-based Notebook Provider (#3172)
Implements provider contribution in the MainThreadNotebook, with matching function calls in the ExtHostNotebook class. This will allow us to proxy through notebook providers (specifically, creation of a notebook manager with required content, server managers) from an extension up through to the main process. Implemented in this PR: - Callthroughs for content and server manager APIs - Very basic unit tests covering provider & manager registration Not implemented: - Fuller unit tests on the specific callthrough methods for content & server manager. - Contribution point needed to test this (so we can actually pass through the extension's existing Notebook implementation)
This commit is contained in:
@@ -13,23 +13,61 @@ import { localize } from 'vs/nls';
|
||||
|
||||
|
||||
import { ExtHostNotebookShape, MainThreadNotebookShape, SqlMainContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { INotebookManagerDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private readonly _proxy: MainThreadNotebookShape;
|
||||
private _providers = new Map<number, sqlops.nb.NotebookProvider>();
|
||||
|
||||
// Notebook URI to manager lookup.
|
||||
private _managers = new Map<number, NotebookManagerAdapter>();
|
||||
constructor(private _mainContext: IMainContext) {
|
||||
this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadNotebook);
|
||||
}
|
||||
|
||||
//#region APIs called by main thread
|
||||
getNotebookManager(notebookUri: vscode.Uri): Thenable<number> {
|
||||
throw new Error('Not implemented');
|
||||
async $getNotebookManager(providerHandle: number, notebookUri: UriComponents): Promise<INotebookManagerDetails> {
|
||||
let uri = URI.revive(notebookUri);
|
||||
let uriString = uri.toString();
|
||||
let adapter = this.findManagerForUri(uriString);
|
||||
if (!adapter) {
|
||||
adapter = await this._withProvider(providerHandle, (provider) => {
|
||||
return this.createManager(provider, uri);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
handle: adapter.managerHandle,
|
||||
hasContentManager: !!adapter.contentManager,
|
||||
hasServerManager: !!adapter.serverManager
|
||||
};
|
||||
}
|
||||
handleNotebookClosed(notebookUri: vscode.Uri): void {
|
||||
throw new Error('Not implemented');
|
||||
$handleNotebookClosed(notebookUri: UriComponents): void {
|
||||
let uri = URI.revive(notebookUri);
|
||||
let uriString = uri.toString();
|
||||
let manager = this.findManagerForUri(uriString);
|
||||
if (manager) {
|
||||
manager.provider.handleNotebookClosed(uri);
|
||||
this._managers.delete(manager.managerHandle);
|
||||
}
|
||||
}
|
||||
|
||||
$doStartServer(managerHandle: number): Thenable<void> {
|
||||
return this._withServerManager(managerHandle, (serverManager) => serverManager.startServer());
|
||||
}
|
||||
|
||||
$doStopServer(managerHandle: number): Thenable<void> {
|
||||
return this._withServerManager(managerHandle, (serverManager) => serverManager.stopServer());
|
||||
}
|
||||
|
||||
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebook> {
|
||||
return this._withContentManager(managerHandle, (contentManager) => contentManager.getNotebookContents(URI.revive(notebookUri)));
|
||||
}
|
||||
|
||||
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook> {
|
||||
return this._withContentManager(managerHandle, (contentManager) => contentManager.save(URI.revive(notebookUri), notebook));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -47,6 +85,25 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
|
||||
|
||||
//#region private methods
|
||||
|
||||
private findManagerForUri(uriString: string): NotebookManagerAdapter {
|
||||
for(let manager of Array.from(this._managers.values())) {
|
||||
if (manager.uriString === uriString) {
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async createManager(provider: sqlops.nb.NotebookProvider, notebookUri: URI): Promise<NotebookManagerAdapter> {
|
||||
let manager = await provider.getNotebookManager(notebookUri);
|
||||
let uriString = notebookUri.toString();
|
||||
let handle = this._nextHandle();
|
||||
let adapter = new NotebookManagerAdapter(provider, handle, manager, uriString);
|
||||
this._managers.set(handle, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
private _createDisposable(handle: number): Disposable {
|
||||
return new Disposable(() => {
|
||||
this._providers.delete(handle);
|
||||
@@ -58,12 +115,50 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
return ExtHostNotebook._handlePool++;
|
||||
}
|
||||
|
||||
private _withProvider<A, R>(handle: number, ctor: { new(...args: any[]): A }, callback: (adapter: A) => TPromise<R>): TPromise<R> {
|
||||
private _withProvider<R>(handle: number, callback: (provider: sqlops.nb.NotebookProvider) => R | PromiseLike<R>): TPromise<R> {
|
||||
let provider = this._providers.get(handle);
|
||||
if (!(provider instanceof ctor)) {
|
||||
return TPromise.wrapError<R>(new Error('no adapter found'));
|
||||
if (provider === undefined) {
|
||||
return TPromise.wrapError<R>(new Error(localize('errNoProvider', 'no notebook provider found')));
|
||||
}
|
||||
return callback(<any>provider);
|
||||
return TPromise.wrap(callback(provider));
|
||||
}
|
||||
|
||||
private _withNotebookManager<R>(handle: number, callback: (manager: NotebookManagerAdapter) => R | PromiseLike<R>): TPromise<R> {
|
||||
let manager = this._managers.get(handle);
|
||||
if (manager === undefined) {
|
||||
return TPromise.wrapError<R>(new Error(localize('errNoManager', 'No Manager found')));
|
||||
}
|
||||
return TPromise.wrap(callback(manager));
|
||||
}
|
||||
|
||||
private _withServerManager<R>(handle: number, callback: (manager: sqlops.nb.ServerManager) => R | PromiseLike<R>): TPromise<R> {
|
||||
return this._withNotebookManager(handle, (notebookManager) => {
|
||||
let serverManager = notebookManager.serverManager;
|
||||
if (!serverManager) {
|
||||
return TPromise.wrapError(new Error(localize('noServerManager', 'Notebook Manager for notebook {0} does not have a server manager. Cannot perform operations on it', notebookManager.uriString)));
|
||||
}
|
||||
return callback(serverManager);
|
||||
});
|
||||
}
|
||||
|
||||
private _withContentManager<R>(handle: number, callback: (manager: sqlops.nb.ContentManager) => R | PromiseLike<R>): TPromise<R> {
|
||||
return this._withNotebookManager(handle, (notebookManager) => {
|
||||
let contentManager = notebookManager.contentManager;
|
||||
if (!contentManager) {
|
||||
return TPromise.wrapError(new Error(localize('noContentManager', 'Notebook Manager for notebook {0} does not have a content manager. Cannot perform operations on it', notebookManager.uriString)));
|
||||
}
|
||||
return callback(contentManager);
|
||||
});
|
||||
}
|
||||
|
||||
private _withSessionManager<R>(handle: number, callback: (manager: sqlops.nb.SessionManager) => R | PromiseLike<R>): TPromise<R> {
|
||||
return this._withNotebookManager(handle, (notebookManager) => {
|
||||
let sessionManager = notebookManager.sessionManager;
|
||||
if (!sessionManager) {
|
||||
return TPromise.wrapError(new Error(localize('noSessionManager', 'Notebook Manager for notebook {0} does not have a session manager. Cannot perform operations on it', notebookManager.uriString)));
|
||||
}
|
||||
return callback(sessionManager);
|
||||
});
|
||||
}
|
||||
|
||||
private _addNewProvider(adapter: sqlops.nb.NotebookProvider): number {
|
||||
@@ -74,3 +169,26 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
//#endregion
|
||||
}
|
||||
|
||||
|
||||
class NotebookManagerAdapter implements sqlops.nb.NotebookManager {
|
||||
constructor(
|
||||
public readonly provider: sqlops.nb.NotebookProvider,
|
||||
public readonly managerHandle: number,
|
||||
private manager: sqlops.nb.NotebookManager,
|
||||
public readonly uriString: string
|
||||
) {
|
||||
}
|
||||
|
||||
public get contentManager(): sqlops.nb.ContentManager {
|
||||
return this.manager.contentManager;
|
||||
}
|
||||
|
||||
public get sessionManager(): sqlops.nb.SessionManager {
|
||||
return this.manager.sessionManager;
|
||||
}
|
||||
|
||||
public get serverManager(): sqlops.nb.ServerManager {
|
||||
return this.manager.serverManager;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,14 +9,18 @@ import { SqlExtHostContext, SqlMainContext, ExtHostNotebookShape, MainThreadNote
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { INotebookService, INotebookProvider, INotebookManager } from 'sql/services/notebook/notebookService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
import { INotebookService, INotebookProvider, INotebookManager } from 'sql/services/notebook/notebookService';
|
||||
import { INotebookManagerDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadNotebook)
|
||||
export class MainThreadNotebook extends Disposable implements MainThreadNotebookShape {
|
||||
|
||||
private _proxy: ExtHostNotebookShape;
|
||||
private _registrations: { [handle: number]: NotebookProviderWrapper } = Object.create(null);
|
||||
private _providers = new Map<number, NotebookProviderWrapper>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@@ -30,17 +34,17 @@ export class MainThreadNotebook extends Disposable implements MainThreadNotebook
|
||||
|
||||
//#region Extension host callable methods
|
||||
public $registerNotebookProvider(providerId: string, handle: number): void {
|
||||
let notebookProvider = new NotebookProviderWrapper(providerId, handle);
|
||||
this._registrations[providerId] = notebookProvider;
|
||||
let notebookProvider = new NotebookProviderWrapper(this._proxy, providerId, handle);
|
||||
this._providers.set(handle, notebookProvider);
|
||||
this.notebookService.registerProvider(providerId, notebookProvider);
|
||||
}
|
||||
|
||||
public $unregisterNotebookProvider(handle: number): void {
|
||||
let registration = this._registrations[handle];
|
||||
let registration = this._providers.get(handle);
|
||||
if (registration) {
|
||||
this.notebookService.unregisterProvider(registration.providerId);
|
||||
registration.dispose();
|
||||
delete this._registrations[handle];
|
||||
this._providers.delete(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,29 +53,147 @@ export class MainThreadNotebook extends Disposable implements MainThreadNotebook
|
||||
}
|
||||
|
||||
class NotebookProviderWrapper extends Disposable implements INotebookProvider {
|
||||
private _managers = new Map<string, NotebookManagerWrapper>();
|
||||
|
||||
constructor(public readonly providerId, public readonly handle: number) {
|
||||
constructor(private _proxy: ExtHostNotebookShape, public readonly providerId, public readonly providerHandle: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
getNotebookManager(notebookUri: URI): Thenable<INotebookManager> {
|
||||
// TODO must call through to setup in the extension host
|
||||
return Promise.resolve(new NotebookManagerWrapper(this.providerId));
|
||||
return this.doGetNotebookManager(notebookUri);
|
||||
}
|
||||
|
||||
private async doGetNotebookManager(notebookUri: URI): Promise<INotebookManager> {
|
||||
let uriString = notebookUri.toString();
|
||||
let manager = this._managers.get(uriString);
|
||||
if (!manager) {
|
||||
manager = new NotebookManagerWrapper(this._proxy, this.providerId, notebookUri);
|
||||
await manager.initialize(this.providerHandle);
|
||||
this._managers.set(uriString, manager);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
handleNotebookClosed(notebookUri: URI): void {
|
||||
// TODO implement call through to extension host
|
||||
this._proxy.$handleNotebookClosed(notebookUri);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class NotebookManagerWrapper implements INotebookManager {
|
||||
constructor(public readonly providerId) {
|
||||
private _sessionManager: sqlops.nb.SessionManager;
|
||||
private _contentManager: sqlops.nb.ContentManager;
|
||||
private _serverManager: sqlops.nb.ServerManager;
|
||||
private managerDetails: INotebookManagerDetails;
|
||||
|
||||
constructor(private _proxy: ExtHostNotebookShape,
|
||||
public readonly providerId,
|
||||
private notebookUri: URI
|
||||
) { }
|
||||
|
||||
public async initialize(providerHandle: number): Promise<NotebookManagerWrapper> {
|
||||
this.managerDetails = await this._proxy.$getNotebookManager(providerHandle, this.notebookUri);
|
||||
let managerHandle = this.managerDetails.handle;
|
||||
this._contentManager = this.managerDetails.hasContentManager ? new ContentManagerWrapper(managerHandle, this._proxy) : new LocalContentManager();
|
||||
this._serverManager = this.managerDetails.hasServerManager ? new ServerManagerWrapper(managerHandle, this._proxy) : undefined;
|
||||
this._sessionManager = new SessionManagerWrapper(managerHandle, this._proxy);
|
||||
return this;
|
||||
}
|
||||
|
||||
public get sessionManager(): sqlops.nb.SessionManager {
|
||||
return this._sessionManager;
|
||||
}
|
||||
public get contentManager(): sqlops.nb.ContentManager {
|
||||
return this._contentManager;
|
||||
}
|
||||
public get serverManager(): sqlops.nb.ServerManager {
|
||||
return this._serverManager;
|
||||
}
|
||||
|
||||
public get managerHandle(): number {
|
||||
return this.managerDetails.handle;
|
||||
}
|
||||
sessionManager: sqlops.nb.SessionManager;
|
||||
contentManager: sqlops.nb.ContentManager;
|
||||
serverManager: sqlops.nb.ServerManager;
|
||||
|
||||
}
|
||||
|
||||
class ContentManagerWrapper implements sqlops.nb.ContentManager {
|
||||
|
||||
constructor(private handle: number, private _proxy: ExtHostNotebookShape) {
|
||||
}
|
||||
getNotebookContents(notebookUri: URI): Thenable<sqlops.nb.INotebook> {
|
||||
return this._proxy.$getNotebookContents(this.handle, notebookUri);
|
||||
}
|
||||
|
||||
save(path: URI, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook> {
|
||||
return this._proxy.$save(this.handle, path, notebook);
|
||||
}
|
||||
}
|
||||
|
||||
class ServerManagerWrapper implements sqlops.nb.ServerManager {
|
||||
private onServerStartedEmitter: Emitter<void>;
|
||||
private _isStarted: boolean;
|
||||
constructor(private handle: number, private _proxy: ExtHostNotebookShape) {
|
||||
this._isStarted = false;
|
||||
}
|
||||
|
||||
get isStarted(): boolean {
|
||||
return this._isStarted;
|
||||
}
|
||||
|
||||
get onServerStarted(): Event<void> {
|
||||
return this.onServerStartedEmitter.event;
|
||||
}
|
||||
|
||||
startServer(): Thenable<void> {
|
||||
return this.doStartServer();
|
||||
}
|
||||
|
||||
private async doStartServer(): Promise<void> {
|
||||
await this._proxy.$doStartServer(this.handle);
|
||||
this._isStarted = true;
|
||||
this.onServerStartedEmitter.fire();
|
||||
}
|
||||
|
||||
stopServer(): Thenable<void> {
|
||||
return this.doStopServer();
|
||||
}
|
||||
|
||||
private async doStopServer(): Promise<void> {
|
||||
try {
|
||||
await this._proxy.$doStopServer(this.handle);
|
||||
} finally {
|
||||
// Always consider this a stopping event, even if a failure occurred.
|
||||
this._isStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SessionManagerWrapper implements sqlops.nb.SessionManager {
|
||||
constructor(private handle: number, private _proxy: ExtHostNotebookShape) {
|
||||
}
|
||||
|
||||
get isReady(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
}
|
||||
|
||||
get ready(): Thenable<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
}
|
||||
get specs(): sqlops.nb.IAllKernels {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
}
|
||||
|
||||
startNew(options: sqlops.nb.ISessionOptions): Thenable<sqlops.nb.ISession> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
shutdown(id: string): Thenable<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import { ITreeComponentItem } from 'sql/workbench/common/views';
|
||||
import { ITaskHandlerDescription } from 'sql/platform/tasks/common/tasks';
|
||||
import {
|
||||
IItemConfig, ModelComponentTypes, IComponentShape, IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails,
|
||||
IModelViewWizardDetails, IModelViewWizardPageDetails
|
||||
IModelViewWizardDetails, IModelViewWizardPageDetails, INotebookManagerDetails
|
||||
} from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export abstract class ExtHostAccountManagementShape {
|
||||
@@ -711,15 +711,21 @@ export interface ExtHostNotebookShape {
|
||||
|
||||
/**
|
||||
* Looks up a notebook manager for a given notebook URI
|
||||
* @param {number} providerHandle
|
||||
* @param {vscode.Uri} notebookUri
|
||||
* @returns {Thenable<string>} handle of the manager to be used when sending
|
||||
*/
|
||||
getNotebookManager(notebookUri: vscode.Uri): Thenable<number>;
|
||||
handleNotebookClosed(notebookUri: vscode.Uri): void;
|
||||
$getNotebookManager(providerHandle: number, notebookUri: UriComponents): Thenable<INotebookManagerDetails>;
|
||||
$handleNotebookClosed(notebookUri: UriComponents): void;
|
||||
$doStartServer(managerHandle: number): Thenable<void>;
|
||||
$doStopServer(managerHandle: number): Thenable<void>;
|
||||
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebook>;
|
||||
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook>;
|
||||
|
||||
}
|
||||
|
||||
export interface MainThreadNotebookShape extends IDisposable {
|
||||
$registerNotebookProvider(providerId: string, handle: number): void;
|
||||
$unregisterNotebookProvider(handle: number): void;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user