Implement Session support through the extension host (#3228)

Full plumb through of Session support. Also fixed some test issues

- Load session and get necessary information in kernels list
- Run Cell button now works as expected
- Added a ToggleAction base class which can be used for anything that switches icons. I'd still prefer to have this be dynamic and as clean as the extension classes
- Fixed account test unhandled promise rejections (caused by incorrect / invalid tests) that made it hard to see all the test run output.
This commit is contained in:
Kevin Cunnane
2018-11-16 10:35:03 -08:00
committed by GitHub
parent f3525cc555
commit 90dc788893
23 changed files with 939 additions and 212 deletions

View File

@@ -29,7 +29,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { RunCellAction, DeleteCellAction, AddCellAction } from 'sql/parts/notebook/cellViews/codeActions';
import { RunCellAction, DeleteCellAction, AddCellAction, CellContext } from 'sql/parts/notebook/cellViews/codeActions';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
import { CellTypes } from 'sql/parts/notebook/models/contracts';
@@ -97,7 +97,7 @@ export class CodeComponent extends AngularDisposable implements OnInit {
get model(): NotebookModel {
return this._model;
}
private createEditor(): void {
let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()]));
this._editor = instantiationService.createInstance(QueryTextEditor);
@@ -130,12 +130,12 @@ export class CodeComponent extends AngularDisposable implements OnInit {
}
protected initActionBar() {
let context = new CellContext(this.model, this.cellModel);
let runCellAction = this._instantiationService.createInstance(RunCellAction);
let taskbar = <HTMLElement>this.toolbarElement.nativeElement;
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
this._actionBar.context = this;
this._actionBar.context = context;
this._actionBar.setContent([
{ action: runCellAction }
]);
@@ -145,13 +145,13 @@ export class CodeComponent extends AngularDisposable implements OnInit {
this._moreActions.context = { target: moreActionsElement };
let actions: Action[] = [];
actions.push(this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false, this.notificationService));
actions.push(this._instantiationService.createInstance(AddCellAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true, this.notificationService));
actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false, this.notificationService));
actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true, this.notificationService));
actions.push(this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete'), CellTypes.Code, false, this.notificationService));
actions.push(this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false));
actions.push(this._instantiationService.createInstance(AddCellAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true));
actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false));
actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true));
actions.push(this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete')));
this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, actions, this.model, this.contextMenuService), { icon: true, label: false });
this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, actions, context), { icon: true, label: false });
}
private createUri(): URI {

View File

@@ -25,6 +25,17 @@ code-component .toolbarIconRun {
background-image: url('../media/dark/execute_cell_inverse.svg');
}
code-component .toolbarIconStop {
height: 20px;
background-image: url('../media/light/stop_cell.svg');
padding-bottom: 10px;
}
.vs-dark code-component .toolbarIconStop,
.hc-black code-component .toolbarIconStop {
background-image: url('../media/dark/stop_cell_inverse.svg');
}
/* overview ruler */
code-component .monaco-editor .decorationsOverviewRuler {
visibility: hidden;

View File

@@ -1,7 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { nb } from 'sqlops';
import { Action } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
@@ -10,97 +12,176 @@ import { CellType } from 'sql/parts/notebook/models/contracts';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { NOTFOUND } from 'dns';
import { NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService';
import { ICellModel, FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
import { ToggleableAction } from 'sql/parts/notebook/notebookActions';
let notebookMoreActionMsg = localize('notebook.failed', "Please select active cell and try again");
export class RunCellAction extends Action {
public static ID = 'jobaction.notebookRunCell';
function hasModelAndCell(context: CellContext, notificationService: INotificationService): boolean {
if (!context || !context.model) {
return false;
}
if (context.cell === undefined) {
notificationService.notify({
severity: Severity.Error,
message: notebookMoreActionMsg
});
return false;
}
return true;
}
export class CellContext {
constructor(public model: NotebookModel, private _cell?: ICellModel) {
}
public get cell(): ICellModel {
return this._cell ? this._cell : this.model.activeCell;
}
}
abstract class CellActionBase extends Action {
constructor(id: string, label: string, icon: string, protected notificationService: INotificationService) {
super(id, label, icon);
}
public run(context: CellContext): TPromise<boolean> {
if (hasModelAndCell(context, this.notificationService)) {
return TPromise.wrap(this.runCellAction(context).then(() => true));
}
return TPromise.as(true);
}
abstract runCellAction(context: CellContext): Promise<void>;
}
export class RunCellAction extends ToggleableAction {
public static ID = 'notebook.runCell';
public static LABEL = 'Run cell';
constructor(
) {
super(RunCellAction.ID, '', 'toolbarIconRun');
this.tooltip = localize('runCell', 'Run cell');
constructor(@INotificationService private notificationService: INotificationService) {
super(RunCellAction.ID, {
shouldToggleTooltip: true,
toggleOnLabel: localize('runCell', 'Run cell'),
toggleOnClass: 'toolbarIconRun',
toggleOffLabel: localize('stopCell', 'Cancel execution'),
toggleOffClass: 'toolbarIconStop',
isOn: true
});
}
public run(context: any): TPromise<boolean> {
return new TPromise<boolean>((resolve, reject) => {
try {
resolve(true);
} catch (e) {
reject(e);
public run(context: CellContext): TPromise<boolean> {
if (hasModelAndCell(context, this.notificationService)) {
return TPromise.wrap(this.runCellAction(context).then(() => true));
}
return TPromise.as(true);
}
public async runCellAction(context: CellContext): Promise<void> {
try {
let model = context.model;
let cell = context.cell;
let kernel = await this.getOrStartKernel(model);
if (!kernel) {
return;
}
});
// If cell is currently running and user clicks the stop/cancel button, call kernel.interrupt()
// This matches the same behavior as JupyterLab
if (cell.future && cell.future.inProgress) {
cell.future.inProgress = false;
await kernel.interrupt();
} else {
// TODO update source based on editor component contents
let content = cell.source;
if (content) {
this.toggle(false);
let future = await kernel.requestExecute({
code: content,
stop_on_error: true
}, false);
cell.setFuture(future as FutureInternal);
// For now, await future completion. Later we should just track and handle cancellation based on model notifications
let reply = await future.done;
}
}
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.error(message);
} finally {
this.toggle(true);
}
}
private async getOrStartKernel(model: NotebookModel): Promise<nb.IKernel> {
let clientSession = model && model.clientSession;
if (!clientSession) {
this.notificationService.error(localize('notebookNotReady', 'The session for this notebook is not yet ready'));
return undefined;
} else if (!clientSession.isReady || clientSession.status === 'dead') {
this.notificationService.info(localize('sessionNotReady', 'The session for this notebook will start momentarily'));
await clientSession.kernelChangeCompleted;
}
if (!clientSession.kernel) {
let defaultKernel = model && model.defaultKernel && model.defaultKernel.name;
if (!defaultKernel) {
this.notificationService.error(localize('noDefaultKernel', 'No kernel is available for this notebook'));
return undefined;
}
await clientSession.changeKernel({
name: defaultKernel
});
}
return clientSession.kernel;
}
}
export class AddCellAction extends CellActionBase {
constructor(
id: string, label: string, private cellType: CellType, private isAfter: boolean,
@INotificationService notificationService: INotificationService
) {
super(id, label, undefined, notificationService);
}
runCellAction(context: CellContext): Promise<void> {
try {
let model = context.model;
let index = model.cells.findIndex((cell) => cell.id === context.cell.id);
if (index !== undefined && this.isAfter) {
index += 1;
}
model.addCell(this.cellType, index);
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}
export class AddCellAction extends Action {
constructor(
id: string, label: string, private cellType: CellType, private isAfter: boolean, private notificationService: INotificationService
export class DeleteCellAction extends CellActionBase {
constructor(id: string, label: string,
@INotificationService notificationService: INotificationService
) {
super(id, label);
super(id, label, undefined, notificationService);
}
public run(model: NotebookModel): TPromise<boolean> {
return new TPromise<boolean>((resolve, reject) => {
try {
if (!model) {
return;
}
if (model.activeCell === undefined) {
this.notificationService.notify({
severity: Severity.Error,
message: notebookMoreActionMsg
});
}
else {
let index = model.cells.findIndex((cell) => cell.id === model.activeCell.id);
if (index !== undefined && this.isAfter) {
index += 1;
}
model.addCell(this.cellType, index);
}
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
});
}
}
runCellAction(context: CellContext): Promise<void> {
try {
context.model.deleteCell(context.cell);
} catch (error) {
let message = getErrorMessage(error);
export class DeleteCellAction extends Action {
constructor(
id: string, label: string, private cellType: CellType, private isAfter: boolean, private notificationService: INotificationService
) {
super(id, label);
}
public run(model: NotebookModel): TPromise<boolean> {
return new TPromise<boolean>((resolve, reject) => {
try {
if (!model) {
return;
}
if (model.activeCell === undefined) {
this.notificationService.notify({
severity: Severity.Error,
message: notebookMoreActionMsg
});
}
else {
model.deleteCell(model.activeCell);
}
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
});
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}

View File

@@ -39,6 +39,11 @@ export class CodeCellComponent extends CellView implements OnInit {
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
if (this.cellModel) {
this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges();
});
}
}
// Todo: implement layout

View File

@@ -3,9 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./code';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Output, EventEmitter } from '@angular/core';
import { OnInit, Component, Input, Inject, forwardRef, ChangeDetectorRef } from '@angular/core';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
@@ -20,11 +19,15 @@ export class OutputAreaComponent extends AngularDisposable implements OnInit {
private readonly _minimumHeight = 30;
constructor(
@Inject(IModeService) private _modeService: IModeService
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
) {
super();
}
ngOnInit(): void {
if (this.cellModel) {
this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges();
});
}
}
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>stop_cell_inverse</title><circle class="cls-1" cx="8" cy="7.92" r="7.76"/><rect x="5" y="4.92" width="6" height="6"/></svg>

After

Width:  |  Height:  |  Size: 269 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>stop_cell</title><circle cx="8" cy="7.92" r="7.76"/><rect class="cls-1" x="5" y="4.92" width="6" height="6"/></svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -10,7 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { nb } from 'sqlops';
import { ICellModelOptions, IModelFactory } from './modelInterfaces';
import { ICellModelOptions, IModelFactory, FutureInternal } from './modelInterfaces';
import * as notebookUtils from '../notebookUtils';
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
@@ -24,7 +24,7 @@ export class CellModel implements ICellModel {
private _cellType: nb.CellType;
private _source: string;
private _language: string;
private _future: nb.IFuture;
private _future: FutureInternal;
private _outputs: nb.ICellOutput[] = [];
private _isEditMode: boolean;
private _onOutputsChanged = new Emitter<ReadonlyArray<nb.ICellOutput>>();
@@ -69,7 +69,7 @@ export class CellModel implements ICellModel {
return this._isEditMode;
}
public get future(): nb.IFuture {
public get future(): FutureInternal {
return this._future;
}
@@ -137,7 +137,7 @@ export class CellModel implements ICellModel {
* Sets the future which will be used to update the output
* area for this cell
*/
setFuture(future: nb.IFuture): void {
setFuture(future: FutureInternal): void {
if (this._future === future) {
// Nothing to do
return;

View File

@@ -299,7 +299,7 @@ export class ClientSession implements IClientSession {
public async shutdown(): Promise<void> {
// Always try to shut down session
if (this._session && this._session.id) {
this.notebookManager.sessionManager.shutdown(this._session.id);
await this.notebookManager.sessionManager.shutdown(this._session.id);
}
let serverManager = this.notebookManager.serverManager;
if (serverManager) {

View File

@@ -338,10 +338,16 @@ export interface ICellModel {
cellType: CellType;
trustedMode: boolean;
active: boolean;
readonly future: FutureInternal;
readonly outputs: ReadonlyArray<nb.ICellOutput>;
readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
setFuture(future: FutureInternal): void;
equals(cellModel: ICellModel): boolean;
toJSON(): nb.ICell;
onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
}
export interface FutureInternal extends nb.IFuture {
inProgress: boolean;
}
export interface IModelFactory {

View File

@@ -43,41 +43,82 @@ export class AddCellAction extends Action {
}
}
export class TrustedAction extends Action {
export interface IToggleableState {
baseClass?: string;
shouldToggleTooltip?: boolean;
toggleOnClass: string;
toggleOnLabel: string;
toggleOffLabel: string;
toggleOffClass: string;
isOn: boolean;
}
export abstract class ToggleableAction extends Action {
constructor(id: string, protected state: IToggleableState) {
super(id, '');
this.updateLabelAndIcon();
}
private updateLabelAndIcon() {
if (this.state.shouldToggleTooltip) {
this.tooltip = this.state.isOn ? this.state.toggleOnLabel : this.state.toggleOffLabel;
} else {
this.label = this.state.isOn ? this.state.toggleOnLabel : this.state.toggleOffLabel;
}
let classes = this.state.baseClass ? `${this.state.baseClass} ` : '';
classes += this.state.isOn ? this.state.toggleOnClass : this.state.toggleOffClass;
this.class = classes;
}
protected toggle(isOn: boolean): void {
this.state.isOn = isOn;
this.updateLabelAndIcon();
}
}
export class TrustedAction extends ToggleableAction {
// Constants
private static readonly trustLabel = localize('trustLabel', 'Trusted');
private static readonly notTrustLabel = localize('untrustLabel', 'Not Trusted');
private static readonly trustedLabel = localize('trustLabel', 'Trusted');
private static readonly notTrustedLabel = localize('untrustLabel', 'Not Trusted');
private static readonly alreadyTrustedMsg = localize('alreadyTrustedMsg', 'Notebook is already trusted.');
private static readonly trustedCssClass = 'notebook-button icon-trusted';
private static readonly notTrustedCssClass = 'notebook-button icon-notTrusted';
private static readonly baseClass = 'notebook-button';
private static readonly trustedCssClass = 'icon-trusted';
private static readonly notTrustedCssClass = 'icon-notTrusted';
// Properties
private _isTrusted: boolean = false;
public get trusted(): boolean {
return this._isTrusted;
}
public set trusted(value: boolean) {
this._isTrusted = value;
this._setClass(value ? TrustedAction.trustedCssClass : TrustedAction.notTrustedCssClass);
this._setLabel(value ? TrustedAction.trustLabel : TrustedAction.notTrustLabel);
}
constructor(
id: string,
@INotificationService private _notificationService: INotificationService
) {
super(id, TrustedAction.notTrustLabel, TrustedAction.notTrustedCssClass);
super(id, {
baseClass: TrustedAction.baseClass,
toggleOnLabel: TrustedAction.trustedLabel,
toggleOnClass: TrustedAction.trustedCssClass,
toggleOffLabel: TrustedAction.notTrustedLabel,
toggleOffClass: TrustedAction.notTrustedCssClass,
isOn: false
});
}
public get trusted(): boolean {
return this.state.isOn;
}
public set trusted(value: boolean) {
this.toggle(value);
}
public run(context: NotebookComponent): TPromise<boolean> {
let self = this;
return new TPromise<boolean>((resolve, reject) => {
try {
if (self._isTrusted) {
if (self.trusted) {
const actions: INotificationActions = { primary: [] };
self._notificationService.notify({ severity: Severity.Info, message: TrustedAction.alreadyTrustedMsg, actions });
}
else {
self.trusted = !self._isTrusted;
self.trusted = !self.trusted;
context.updateModelTrustDetails(self.trusted);
}
resolve(true);

View File

@@ -40,7 +40,7 @@ export function getData(output: nb.ICellOutput): JSONObject {
bundle['application/vnd.jupyter.stdout'] = output.text;
}
} else if (nbformat.isError(output)) {
let traceback = output.traceback.join('\n');
let traceback = output.traceback ? output.traceback.join('\n') : undefined;
bundle['application/vnd.jupyter.stderr'] =
traceback || `${output.ename}: ${output.evalue}`;
}

View File

@@ -2,8 +2,11 @@
import { nb } from 'sqlops';
import { localize } from 'vs/nls';
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
const noKernel: string = localize('noKernel', 'No Kernel');
const runNotebookDisabled = localize('runNotebookDisabled', 'Cannot run cells as no kernel has been configured');
let noKernelSpec: nb.IKernelSpec = ({
name: noKernel,
language: 'python',
@@ -130,7 +133,7 @@ class EmptyKernel implements nb.IKernel {
}
requestExecute(content: nb.IExecuteRequest, disposeOnDone?: boolean): nb.IFuture {
throw new Error('Method not implemented.');
return new EmptyFuture();
}
requestComplete(content: nb.ICompleteRequest): Thenable<nb.ICompleteReplyMsg> {
@@ -138,4 +141,72 @@ class EmptyKernel implements nb.IKernel {
return Promise.resolve(response as nb.ICompleteReplyMsg);
}
interrupt(): Thenable<void> {
return Promise.resolve(undefined);
}
}
class EmptyFuture implements FutureInternal {
get inProgress(): boolean {
return false;
}
get msg(): nb.IMessage {
return undefined;
}
get done(): Thenable<nb.IShellMessage> {
let msg: nb.IShellMessage = {
channel: 'shell',
type: 'shell',
content: runNotebookDisabled,
header: undefined,
metadata: undefined,
parent_header: undefined
};
return Promise.resolve(msg);
}
sendInputReply(content: nb.IInputReply): void {
// no-op
}
dispose() {
// No-op
}
setReplyHandler(handler: nb.MessageHandler<nb.IShellMessage>): void {
// no-op
}
setStdInHandler(handler: nb.MessageHandler<nb.IStdinMessage>): void {
// no-op
}
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
setTimeout(() => {
let msg: nb.IIOPubMessage = {
channel: 'iopub',
type: 'iopub',
header: <nb.IHeader> {
msg_id: '0',
msg_type: 'error'
},
content: <nb.IErrorResult> {
ename: localize('errorName', 'Error'),
evalue: runNotebookDisabled,
output_type: 'error'
},
metadata: undefined,
parent_header: undefined
};
handler.handle(msg);
}, 10);
}
registerMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
// no-op
}
removeMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
// no-op
}
}

View File

@@ -1698,6 +1698,20 @@ declare module 'sqlops' {
*/
requestComplete(content: ICompleteRequest): Thenable<ICompleteReplyMsg>;
/**
* Interrupt a kernel.
*
* #### Notes
* Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels).
*
* The promise is fulfilled on a valid response and rejected otherwise.
*
* It is assumed that the API call does not mutate the kernel id or name.
*
* The promise will be rejected if the kernel status is `Dead` or if the
* request fails or the response is invalid.
*/
interrupt(): Thenable<void>;
}
export interface IInfoReply {

View File

@@ -416,4 +416,39 @@ export interface INotebookManagerDetails {
handle: number;
hasContentManager: boolean;
hasServerManager: boolean;
}
export interface INotebookSessionDetails {
readonly sessionId: number;
readonly canChangeKernels: boolean;
readonly id: string;
readonly path: string;
readonly name: string;
readonly type: string;
readonly status: string;
readonly kernelDetails: INotebookKernelDetails;
}
export interface INotebookKernelDetails {
readonly kernelId: number;
readonly id: string;
readonly name: string;
readonly supportsIntellisense: boolean;
readonly info?: any;
}
export interface INotebookFutureDetails {
readonly futureId: number;
readonly msg: any;
}
export enum FutureMessageType {
Reply = 0,
StdIn = 1,
IOPub = 2
}
export interface INotebookFutureDone {
succeeded: boolean;
rejectReason: string;
}

View File

@@ -6,24 +6,25 @@
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { TPromise } from 'vs/base/common/winjs.base';
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
import { localize } from 'vs/nls';
import URI, { UriComponents } from 'vs/base/common/uri';
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';
import { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType } from 'sql/workbench/api/common/sqlExtHostTypes';
type Adapter = sqlops.nb.NotebookProvider | sqlops.nb.NotebookManager | sqlops.nb.ISession | sqlops.nb.IKernel | sqlops.nb.IFuture;
export class ExtHostNotebook implements ExtHostNotebookShape {
private static _handlePool: number = 0;
private readonly _proxy: MainThreadNotebookShape;
private _providers = new Map<number, sqlops.nb.NotebookProvider>();
private _adapters = new Map<number, Adapter>();
// Notebook URI to manager lookup.
private _managers = new Map<number, NotebookManagerAdapter>();
constructor(private _mainContext: IMainContext) {
constructor(_mainContext: IMainContext) {
this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadNotebook);
}
@@ -39,7 +40,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
}
return {
handle: adapter.managerHandle,
handle: adapter.handle,
hasContentManager: !!adapter.contentManager,
hasServerManager: !!adapter.serverManager
};
@@ -50,7 +51,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
let manager = this.findManagerForUri(uriString);
if (manager) {
manager.provider.handleNotebookClosed(uri);
this._managers.delete(manager.managerHandle);
// Note: deliberately not removing handle.
}
}
@@ -70,6 +71,124 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
return this._withContentManager(managerHandle, (contentManager) => contentManager.save(URI.revive(notebookUri), notebook));
}
$refreshSpecs(managerHandle: number): Thenable<sqlops.nb.IAllKernels> {
return this._withSessionManager(managerHandle, async (sessionManager) => {
await sessionManager.ready;
return sessionManager.specs;
});
}
$startNewSession(managerHandle: number, options: sqlops.nb.ISessionOptions): Thenable<INotebookSessionDetails> {
return this._withSessionManager(managerHandle, async (sessionManager) => {
let session = await sessionManager.startNew(options);
let sessionId = this._addNewAdapter(session);
let kernelDetails: INotebookKernelDetails = undefined;
if (session.kernel) {
kernelDetails = this.saveKernel(session.kernel);
}
let details: INotebookSessionDetails = {
sessionId: sessionId,
id: session.id,
path: session.path,
name: session.name,
type: session.type,
status: session.status,
canChangeKernels: session.canChangeKernels,
kernelDetails: kernelDetails
};
return details;
});
}
private saveKernel(kernel: sqlops.nb.IKernel): INotebookKernelDetails {
let kernelId = this._addNewAdapter(kernel);
let kernelDetails: INotebookKernelDetails = {
kernelId: kernelId,
id: kernel.id,
info: kernel.info,
name: kernel.name,
supportsIntellisense: kernel.supportsIntellisense
};
return kernelDetails;
}
$shutdownSession(managerHandle: number, sessionId: string): Thenable<void> {
return this._withSessionManager(managerHandle, async (sessionManager) => {
return sessionManager.shutdown(sessionId);
});
}
$changeKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<INotebookKernelDetails> {
let session = this._getAdapter<sqlops.nb.ISession>(sessionId);
return session.changeKernel(kernelInfo).then(kernel => this.saveKernel(kernel));
}
$getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply> {
let kernel = this._getAdapter<sqlops.nb.IKernel>(kernelId);
return kernel.ready.then(success => kernel.info);
}
$getKernelSpec(kernelId: number): Thenable<sqlops.nb.IKernelSpec> {
let kernel = this._getAdapter<sqlops.nb.IKernel>(kernelId);
return kernel.getSpec();
}
$requestComplete(kernelId: number, content: sqlops.nb.ICompleteRequest): Thenable<sqlops.nb.ICompleteReplyMsg> {
let kernel = this._getAdapter<sqlops.nb.IKernel>(kernelId);
return kernel.requestComplete(content);
}
$requestExecute(kernelId: number, content: sqlops.nb.IExecuteRequest, disposeOnDone?: boolean): Thenable<INotebookFutureDetails> {
let kernel = this._getAdapter<sqlops.nb.IKernel>(kernelId);
let future = kernel.requestExecute(content, disposeOnDone);
let futureId = this._addNewAdapter(future);
this.hookFutureDone(futureId, future);
this.hookFutureMessages(futureId, future);
return Promise.resolve({
futureId: futureId,
msg: future.msg
});
}
private hookFutureDone(futureId: number, future: sqlops.nb.IFuture): void {
future.done.then(success => {
return this._proxy.$onFutureDone(futureId, { succeeded: true, rejectReason: undefined });
}, err => {
let rejectReason: string;
if (typeof err === 'string') {
rejectReason = err;
}
else if (err instanceof Error && typeof err.message === 'string') {
rejectReason = err.message;
}
else {
rejectReason = err;
}
return this._proxy.$onFutureDone(futureId, { succeeded: false, rejectReason: rejectReason });
});
}
private hookFutureMessages(futureId: number, future: sqlops.nb.IFuture): void {
future.setReplyHandler({ handle: (msg) => this._proxy.$onFutureMessage(futureId, FutureMessageType.Reply, msg) });
future.setStdInHandler({ handle: (msg) => this._proxy.$onFutureMessage(futureId, FutureMessageType.StdIn, msg) });
future.setIOPubHandler({ handle: (msg) => this._proxy.$onFutureMessage(futureId, FutureMessageType.IOPub, msg) });
}
$interruptKernel(kernelId: number): Thenable<void> {
let kernel = this._getAdapter<sqlops.nb.IKernel>(kernelId);
return kernel.interrupt();
}
$sendInputReply(futureId: number, content: sqlops.nb.IInputReply): void {
let future = this._getAdapter<sqlops.nb.IFuture>(futureId);
return future.sendInputReply(content);
}
$disposeFuture(futureId: number): void {
let future = this._getAdapter<sqlops.nb.IFuture>(futureId);
future.dispose();
}
//#endregion
//#region APIs called by extensions
@@ -77,7 +196,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
if (!provider || !provider.providerId) {
throw new Error(localize('providerRequired', 'A NotebookProvider with valid providerId must be passed to this method'));
}
const handle = this._addNewProvider(provider);
const handle = this._addNewAdapter(provider);
this._proxy.$registerNotebookProvider(provider.providerId, handle);
return this._createDisposable(handle);
}
@@ -86,8 +205,18 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
//#region private methods
private getAdapters<A>(ctor: { new(...args: any[]): A }): A[] {
let matchingAdapters = [];
this._adapters.forEach(a => {
if (a instanceof ctor) {
matchingAdapters.push(a);
}
});
return matchingAdapters;
}
private findManagerForUri(uriString: string): NotebookManagerAdapter {
for(let manager of Array.from(this._managers.values())) {
for(let manager of this.getAdapters(NotebookManagerAdapter)) {
if (manager.uriString === uriString) {
return manager;
}
@@ -98,15 +227,14 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
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);
let adapter = new NotebookManagerAdapter(provider, manager, uriString);
adapter.handle = this._addNewAdapter(adapter);
return adapter;
}
private _createDisposable(handle: number): Disposable {
return new Disposable(() => {
this._providers.delete(handle);
this._adapters.delete(handle);
this._proxy.$unregisterNotebookProvider(handle);
});
}
@@ -116,7 +244,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
}
private _withProvider<R>(handle: number, callback: (provider: sqlops.nb.NotebookProvider) => R | PromiseLike<R>): TPromise<R> {
let provider = this._providers.get(handle);
let provider = this._adapters.get(handle) as sqlops.nb.NotebookProvider;
if (provider === undefined) {
return TPromise.wrapError<R>(new Error(localize('errNoProvider', 'no notebook provider found')));
}
@@ -124,7 +252,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
}
private _withNotebookManager<R>(handle: number, callback: (manager: NotebookManagerAdapter) => R | PromiseLike<R>): TPromise<R> {
let manager = this._managers.get(handle);
let manager = this._adapters.get(handle) as NotebookManagerAdapter;
if (manager === undefined) {
return TPromise.wrapError<R>(new Error(localize('errNoManager', 'No Manager found')));
}
@@ -161,19 +289,28 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
});
}
private _addNewProvider(adapter: sqlops.nb.NotebookProvider): number {
private _addNewAdapter(adapter: Adapter): number {
const handle = this._nextHandle();
this._providers.set(handle, adapter);
this._adapters.set(handle, adapter);
return handle;
}
private _getAdapter<T>(id: number): T {
let adapter = <T><any>this._adapters.get(id);
if (adapter === undefined) {
throw new Error('No adapter found');
}
return adapter;
}
//#endregion
}
class NotebookManagerAdapter implements sqlops.nb.NotebookManager {
public handle: number;
constructor(
public readonly provider: sqlops.nb.NotebookProvider,
public readonly managerHandle: number,
private manager: sqlops.nb.NotebookManager,
public readonly uriString: string
) {
@@ -190,5 +327,4 @@ class NotebookManagerAdapter implements sqlops.nb.NotebookManager {
public get serverManager(): sqlops.nb.ServerManager {
return this.manager.serverManager;
}
}
}

View File

@@ -13,14 +13,17 @@ 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 { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, FutureMessageType, INotebookFutureDetails, INotebookFutureDone } from 'sql/workbench/api/common/sqlExtHostTypes';
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
import { Deferred } from 'sql/base/common/promise';
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
@extHostNamedCustomer(SqlMainContext.MainThreadNotebook)
export class MainThreadNotebook extends Disposable implements MainThreadNotebookShape {
private _proxy: ExtHostNotebookShape;
private _providers = new Map<number, NotebookProviderWrapper>();
private _futures = new Map<number, FutureWrapper>();
constructor(
extHostContext: IExtHostContext,
@@ -32,9 +35,21 @@ export class MainThreadNotebook extends Disposable implements MainThreadNotebook
}
}
public addFuture(futureId: number, future: FutureWrapper): void {
this._futures.set(futureId, future);
}
public disposeFuture(futureId: number): void {
this._futures.delete(futureId);
}
//#region Extension host callable methods
public $registerNotebookProvider(providerId: string, handle: number): void {
let notebookProvider = new NotebookProviderWrapper(this._proxy, providerId, handle);
let proxy: Proxies = {
main: this,
ext: this._proxy
};
let notebookProvider = new NotebookProviderWrapper(proxy, providerId, handle);
this._providers.set(handle, notebookProvider);
this.notebookService.registerProvider(providerId, notebookProvider);
}
@@ -48,14 +63,32 @@ export class MainThreadNotebook extends Disposable implements MainThreadNotebook
}
}
//#endregion
public $onFutureMessage(futureId: number, type: FutureMessageType, payload: sqlops.nb.IMessage): void {
let future = this._futures.get(futureId);
if (future) {
future.onMessage(type, payload);
}
}
public $onFutureDone(futureId: number, done: INotebookFutureDone): void {
let future = this._futures.get(futureId);
if (future) {
future.onDone(done);
}
}
//#endregion
}
interface Proxies {
main: MainThreadNotebook;
ext: ExtHostNotebookShape;
}
class NotebookProviderWrapper extends Disposable implements INotebookProvider {
private _managers = new Map<string, NotebookManagerWrapper>();
private _notebookUriToManagerMap = new Map<string, NotebookManagerWrapper>();
constructor(private _proxy: ExtHostNotebookShape, public readonly providerId, public readonly providerHandle: number) {
constructor(private _proxy: Proxies, public readonly providerId, public readonly providerHandle: number) {
super();
}
@@ -66,19 +99,19 @@ class NotebookProviderWrapper extends Disposable implements INotebookProvider {
private async doGetNotebookManager(notebookUri: URI): Promise<INotebookManager> {
let uriString = notebookUri.toString();
let manager = this._managers.get(uriString);
let manager = this._notebookUriToManagerMap.get(uriString);
if (!manager) {
manager = new NotebookManagerWrapper(this._proxy, this.providerId, notebookUri);
await manager.initialize(this.providerHandle);
this._managers.set(uriString, manager);
this._notebookUriToManagerMap.set(uriString, manager);
}
return manager;
}
handleNotebookClosed(notebookUri: URI): void {
this._proxy.$handleNotebookClosed(notebookUri);
this._notebookUriToManagerMap.delete(notebookUri.toString());
this._proxy.ext.$handleNotebookClosed(notebookUri);
}
}
class NotebookManagerWrapper implements INotebookManager {
@@ -87,13 +120,13 @@ class NotebookManagerWrapper implements INotebookManager {
private _serverManager: sqlops.nb.ServerManager;
private managerDetails: INotebookManagerDetails;
constructor(private _proxy: ExtHostNotebookShape,
constructor(private _proxy: Proxies,
public readonly providerId,
private notebookUri: URI
) { }
public async initialize(providerHandle: number): Promise<NotebookManagerWrapper> {
this.managerDetails = await this._proxy.$getNotebookManager(providerHandle, this.notebookUri);
this.managerDetails = await this._proxy.ext.$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;
@@ -114,26 +147,25 @@ class NotebookManagerWrapper implements INotebookManager {
public get managerHandle(): number {
return this.managerDetails.handle;
}
}
class ContentManagerWrapper implements sqlops.nb.ContentManager {
constructor(private handle: number, private _proxy: ExtHostNotebookShape) {
constructor(private handle: number, private _proxy: Proxies) {
}
getNotebookContents(notebookUri: URI): Thenable<sqlops.nb.INotebook> {
return this._proxy.$getNotebookContents(this.handle, notebookUri);
return this._proxy.ext.$getNotebookContents(this.handle, notebookUri);
}
save(path: URI, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook> {
return this._proxy.$save(this.handle, path, notebook);
return this._proxy.ext.$save(this.handle, path, notebook);
}
}
class ServerManagerWrapper implements sqlops.nb.ServerManager {
private onServerStartedEmitter: Emitter<void>;
private onServerStartedEmitter = new Emitter<void>();
private _isStarted: boolean;
constructor(private handle: number, private _proxy: ExtHostNotebookShape) {
constructor(private handle: number, private _proxy: Proxies) {
this._isStarted = false;
}
@@ -150,7 +182,7 @@ class ServerManagerWrapper implements sqlops.nb.ServerManager {
}
private async doStartServer(): Promise<void> {
await this._proxy.$doStartServer(this.handle);
await this._proxy.ext.$doStartServer(this.handle);
this._isStarted = true;
this.onServerStartedEmitter.fire();
}
@@ -161,7 +193,7 @@ class ServerManagerWrapper implements sqlops.nb.ServerManager {
private async doStopServer(): Promise<void> {
try {
await this._proxy.$doStopServer(this.handle);
await this._proxy.ext.$doStopServer(this.handle);
} finally {
// Always consider this a stopping event, even if a failure occurred.
this._isStarted = false;
@@ -170,30 +202,255 @@ class ServerManagerWrapper implements sqlops.nb.ServerManager {
}
class SessionManagerWrapper implements sqlops.nb.SessionManager {
constructor(private handle: number, private _proxy: ExtHostNotebookShape) {
private readyPromise: Promise<void>;
private _isReady: boolean;
private _specs: sqlops.nb.IAllKernels;
constructor(private managerHandle: number, private _proxy: Proxies) {
this._isReady = false;
this.readyPromise = this.initializeSessionManager();
}
get isReady(): boolean {
throw new Error('Method not implemented.');
return this._isReady;
}
get ready(): Thenable<void> {
throw new Error('Method not implemented.');
return this.readyPromise;
}
get specs(): sqlops.nb.IAllKernels {
throw new Error('Method not implemented.');
get specs(): sqlops.nb.IAllKernels {
return this._specs;
}
startNew(options: sqlops.nb.ISessionOptions): Thenable<sqlops.nb.ISession> {
throw new Error('Method not implemented.');
return this.doStartNew(options);
}
private async doStartNew(options: sqlops.nb.ISessionOptions): Promise<sqlops.nb.ISession> {
let sessionDetails = await this._proxy.ext.$startNewSession(this.managerHandle, options);
return new SessionWrapper(this._proxy, sessionDetails);
}
shutdown(id: string): Thenable<void> {
throw new Error('Method not implemented.');
return this._proxy.ext.$shutdownSession(this.managerHandle, id);
}
private async initializeSessionManager(): Promise<void> {
await this.refreshSpecs();
this._isReady = true;
}
}
private async refreshSpecs(): Promise<void> {
let specs = await this._proxy.ext.$refreshSpecs(this.managerHandle);
if (specs) {
this._specs = specs;
}
}
}
class SessionWrapper implements sqlops.nb.ISession {
private _kernel: KernelWrapper;
constructor(private _proxy: Proxies, private sessionDetails: INotebookSessionDetails) {
if (sessionDetails && sessionDetails.kernelDetails) {
this._kernel = new KernelWrapper(_proxy, sessionDetails.kernelDetails);
}
}
get canChangeKernels(): boolean {
return this.sessionDetails.canChangeKernels;
}
get id(): string {
return this.sessionDetails.id;
}
get path(): string {
return this.sessionDetails.path;
}
get name(): string {
return this.sessionDetails.name;
}
get type(): string {
return this.sessionDetails.type;
}
get status(): sqlops.nb.KernelStatus {
return this.sessionDetails.status as sqlops.nb.KernelStatus;
}
get kernel(): sqlops.nb.IKernel {
return this._kernel;
}
changeKernel(kernelInfo: sqlops.nb.IKernelSpec): Thenable<sqlops.nb.IKernel> {
return this.doChangeKernel(kernelInfo);
}
private async doChangeKernel(kernelInfo: sqlops.nb.IKernelSpec): Promise<sqlops.nb.IKernel> {
let kernelDetails = await this._proxy.ext.$changeKernel(this.sessionDetails.sessionId, kernelInfo);
this._kernel = new KernelWrapper(this._proxy, kernelDetails);
return this._kernel;
}
}
class KernelWrapper implements sqlops.nb.IKernel {
private _isReady: boolean = false;
private _ready = new Deferred<void>();
private _info: sqlops.nb.IInfoReply;
constructor(private _proxy: Proxies, private kernelDetails: INotebookKernelDetails) {
this.initialize(kernelDetails);
}
private async initialize(kernelDetails: INotebookKernelDetails): Promise<void> {
try {
this._info = await this._proxy.ext.$getKernelReadyStatus(kernelDetails.kernelId);
this._isReady = true;
this._ready.resolve();
} catch (error) {
this._isReady = false;
this._ready.reject(error);
}
}
get isReady(): boolean {
return this._isReady;
}
get ready(): Thenable<void> {
return this._ready.promise;
}
get id(): string {
return this.kernelDetails.id;
}
get name(): string {
return this.kernelDetails.name;
}
get supportsIntellisense(): boolean {
return this.kernelDetails.supportsIntellisense;
}
get info(): sqlops.nb.IInfoReply {
return this._info;
}
getSpec(): Thenable<sqlops.nb.IKernelSpec> {
return this._proxy.ext.$getKernelSpec(this.kernelDetails.kernelId);
}
requestComplete(content: sqlops.nb.ICompleteRequest): Thenable<sqlops.nb.ICompleteReplyMsg> {
return this._proxy.ext.$requestComplete(this.kernelDetails.kernelId, content);
}
requestExecute(content: sqlops.nb.IExecuteRequest, disposeOnDone?: boolean): sqlops.nb.IFuture {
let future = new FutureWrapper(this._proxy);
this._proxy.ext.$requestExecute(this.kernelDetails.kernelId, content, disposeOnDone)
.then(details => {
future.setDetails(details);
// Save the future in the main thread notebook so extension can call through and reference it
this._proxy.main.addFuture(details.futureId, future);
}, error => future.setError(error));
return future;
}
interrupt(): Thenable<void> {
return this._proxy.ext.$interruptKernel(this.kernelDetails.kernelId);
}
}
class FutureWrapper implements FutureInternal {
private _futureId: number;
private _done = new Deferred<sqlops.nb.IShellMessage>();
private _messageHandlers = new Map<FutureMessageType, sqlops.nb.MessageHandler<sqlops.nb.IMessage>>();
private _msg: sqlops.nb.IMessage;
private _inProgress: boolean;
constructor(private _proxy: Proxies) {
this._inProgress = true;
}
public setDetails(details: INotebookFutureDetails): void {
this._futureId = details.futureId;
this._msg = details.msg;
}
public setError(error: Error | string): void {
this._done.reject(error);
}
public onMessage(type: FutureMessageType, payload: sqlops.nb.IMessage): void {
let handler = this._messageHandlers.get(type);
if (handler) {
try {
handler.handle(payload);
} catch (error) {
// TODO log errors from the handler
}
}
}
public onDone(done: INotebookFutureDone): void {
this._inProgress = false;
if (done.succeeded) {
this._done.resolve();
} else {
this._done.reject(new Error(done.rejectReason));
}
}
private addMessageHandler(type: FutureMessageType, handler: sqlops.nb.MessageHandler<sqlops.nb.IMessage>): void {
// Note: there can only be 1 message handler according to the Jupyter Notebook spec.
// You can use a message hook to override this / add additional side-processors
this._messageHandlers.set(type, handler);
}
//#region Public APIs
get inProgress(): boolean {
return this._inProgress;
}
set inProgress(value: boolean) {
this._inProgress = value;
}
get msg(): sqlops.nb.IMessage {
return this._msg;
}
get done(): Thenable<sqlops.nb.IShellMessage> {
return this._done.promise;
}
setReplyHandler(handler: sqlops.nb.MessageHandler<sqlops.nb.IShellMessage>): void {
this.addMessageHandler(FutureMessageType.Reply, handler);
}
setStdInHandler(handler: sqlops.nb.MessageHandler<sqlops.nb.IStdinMessage>): void {
this.addMessageHandler(FutureMessageType.StdIn, handler);
}
setIOPubHandler(handler: sqlops.nb.MessageHandler<sqlops.nb.IIOPubMessage>): void {
this.addMessageHandler(FutureMessageType.IOPub, handler);
}
sendInputReply(content: sqlops.nb.IInputReply): void {
this._proxy.ext.$sendInputReply(this._futureId, content);
}
dispose() {
this._proxy.main.disposeFuture(this._futureId);
this._proxy.ext.$disposeFuture(this._futureId);
}
registerMessageHook(hook: (msg: sqlops.nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
throw new Error('Method not implemented.');
}
removeMessageHook(hook: (msg: sqlops.nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
throw new Error('Method not implemented.');
}
//#endregion
}

View File

@@ -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, INotebookManagerDetails
IModelViewWizardDetails, IModelViewWizardPageDetails, INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType, INotebookFutureDone
} from 'sql/workbench/api/common/sqlExtHostTypes';
export abstract class ExtHostAccountManagementShape {
@@ -717,15 +717,39 @@ export interface ExtHostNotebookShape {
*/
$getNotebookManager(providerHandle: number, notebookUri: UriComponents): Thenable<INotebookManagerDetails>;
$handleNotebookClosed(notebookUri: UriComponents): void;
// Server Manager APIs
$doStartServer(managerHandle: number): Thenable<void>;
$doStopServer(managerHandle: number): Thenable<void>;
// Content Manager APIs
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebook>;
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook>;
// Session Manager APIs
$refreshSpecs(managerHandle: number): Thenable<sqlops.nb.IAllKernels>;
$startNewSession(managerHandle: number, options: sqlops.nb.ISessionOptions): Thenable<INotebookSessionDetails>;
$shutdownSession(managerHandle: number, sessionId: string): Thenable<void>;
// Session APIs
$changeKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<INotebookKernelDetails>;
// Kernel APIs
$getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply>;
$getKernelSpec(kernelId: number): Thenable<sqlops.nb.IKernelSpec>;
$requestComplete(kernelId: number, content: sqlops.nb.ICompleteRequest): Thenable<sqlops.nb.ICompleteReplyMsg>;
$requestExecute(kernelId: number, content: sqlops.nb.IExecuteRequest, disposeOnDone?: boolean): Thenable<INotebookFutureDetails>;
$interruptKernel(kernelId: number): Thenable<void>;
// Future APIs
$sendInputReply(futureId: number, content: sqlops.nb.IInputReply): void;
$disposeFuture(futureId: number): void;
}
export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookProvider(providerId: string, handle: number): void;
$unregisterNotebookProvider(handle: number): void;
$onFutureMessage(futureId: number, type: FutureMessageType, payload: sqlops.nb.IMessage): void;
$onFutureDone(futureId: number, done: INotebookFutureDone): void;
}