mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Support Notebook integration testing by adding APIs & fixing others (#3848)
- Added `runCell` API. Updated runCell button to listen to events on the model so it'll reflect run cell when called from other sources
- Plumbed through kernelspec info to the extension side so when changed, it's updated
- Fixed bug in ConnectionProfile where it didn't copy from options but instead overrode with empty wrapper functions
Here's the rough test code (it's in the sql-vnext extension and will be out in a separate PR)
```ts
it('Should connect to local notebook server with result 2', async function() {
this.timeout(60000);
let pythonNotebook = Object.assign({}, expectedNotebookContent, { metadata: { kernelspec: { name: "python3", display_name: "Python 3" }}});
let uri = writeNotebookToFile(pythonNotebook);
await ensureJupyterInstalled();
let notebook = await sqlops.nb.showNotebookDocument(uri);
should(notebook.document.cells).have.length(1);
let ran = await notebook.runCell(notebook.document.cells[0]);
should(ran).be.true('Notebook runCell failed');
let cellOutputs = notebook.document.cells[0].contents.outputs;
should(cellOutputs).have.length(1);
let result = (<sqlops.nb.IExecuteResult>cellOutputs[0]).data['text/plain'];
should(result).equal('2');
try {
// TODO support closing the editor. Right now this prompts and there's no override for this. Need to fix in core
// Close the editor using the recommended vscode API
//await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
}
catch (e) {}
});
it('Should connect to remote spark server with result 2', async function() {
this.timeout(240000);
let uri = writeNotebookToFile(expectedNotebookContent);
await ensureJupyterInstalled();
// Given a connection to a server exists
let connectionId = await connectToSparkIntegrationServer();
// When I open a Spark notebook and run the cell
let notebook = await sqlops.nb.showNotebookDocument(uri, {
connectionId: connectionId
});
should(notebook.document.cells).have.length(1);
let ran = await notebook.runCell(notebook.document.cells[0]);
should(ran).be.true('Notebook runCell failed');
// Then I expect to get the output result of 1+1, executed remotely against the Spark endpoint
let cellOutputs = notebook.document.cells[0].contents.outputs;
should(cellOutputs).have.length(4);
let sparkResult = (<sqlops.nb.IStreamResult>cellOutputs[3]).text;
should(sparkResult).equal('2');
try {
// TODO support closing the editor. Right now this prompts and there's no override for this. Need to fix in core
// Close the editor using the recommended vscode API
//await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
}
catch (e) {}
});
});
```
This commit is contained in:
@@ -61,7 +61,7 @@ export class AddCellFromContextAction extends CellActionBase {
|
|||||||
super(id, label, undefined, notificationService);
|
super(id, label, undefined, notificationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
runCellAction(context: CellContext): Promise<void> {
|
doRun(context: CellContext): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let model = context.model;
|
let model = context.model;
|
||||||
let index = model.cells.findIndex((cell) => cell.id === context.cell.id);
|
let index = model.cells.findIndex((cell) => cell.id === context.cell.id);
|
||||||
@@ -88,7 +88,7 @@ export class DeleteCellAction extends CellActionBase {
|
|||||||
super(id, label, undefined, notificationService);
|
super(id, label, undefined, notificationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
runCellAction(context: CellContext): Promise<void> {
|
doRun(context: CellContext): Promise<void> {
|
||||||
try {
|
try {
|
||||||
context.model.deleteCell(context.cell);
|
context.model.deleteCell(context.cell);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -110,7 +110,7 @@ export class ClearCellOutputAction extends CellActionBase {
|
|||||||
super(id, label, undefined, notificationService);
|
super(id, label, undefined, notificationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
runCellAction(context: CellContext): Promise<void> {
|
doRun(context: CellContext): Promise<void> {
|
||||||
try {
|
try {
|
||||||
(context.model.activeCell as CellModel).clearOutputs();
|
(context.model.activeCell as CellModel).clearOutputs();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
|
|
||||||
protected initActionBar() {
|
protected initActionBar() {
|
||||||
let context = new CellContext(this.model, this.cellModel);
|
let context = new CellContext(this.model, this.cellModel);
|
||||||
let runCellAction = this._instantiationService.createInstance(RunCellAction);
|
let runCellAction = this._instantiationService.createInstance(RunCellAction, context);
|
||||||
|
|
||||||
let taskbar = <HTMLElement>this.toolbarElement.nativeElement;
|
let taskbar = <HTMLElement>this.toolbarElement.nativeElement;
|
||||||
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
|
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import { nb } from 'sqlops';
|
|||||||
import { Action } from 'vs/base/common/actions';
|
import { Action } from 'vs/base/common/actions';
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { CellType } from 'sql/parts/notebook/models/contracts';
|
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||||
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
|
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
|
||||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
import { ICellModel, FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
import { ToggleableAction } from 'sql/parts/notebook/notebookActions';
|
import { ToggleableAction } from 'sql/parts/notebook/notebookActions';
|
||||||
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
let notebookMoreActionMsg = localize('notebook.failed', "Please select active cell and try again");
|
let notebookMoreActionMsg = localize('notebook.failed', "Please select active cell and try again");
|
||||||
|
|
||||||
@@ -48,90 +48,62 @@ export abstract class CellActionBase extends Action {
|
|||||||
|
|
||||||
public run(context: CellContext): TPromise<boolean> {
|
public run(context: CellContext): TPromise<boolean> {
|
||||||
if (hasModelAndCell(context, this.notificationService)) {
|
if (hasModelAndCell(context, this.notificationService)) {
|
||||||
return TPromise.wrap(this.runCellAction(context).then(() => true));
|
return TPromise.wrap(this.doRun(context).then(() => true));
|
||||||
}
|
}
|
||||||
return TPromise.as(true);
|
return TPromise.as(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract runCellAction(context: CellContext): Promise<void>;
|
abstract doRun(context: CellContext): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RunCellAction extends ToggleableAction {
|
export class RunCellAction extends ToggleableAction {
|
||||||
public static ID = 'notebook.runCell';
|
public static ID = 'notebook.runCell';
|
||||||
public static LABEL = 'Run cell';
|
public static LABEL = 'Run cell';
|
||||||
|
private _executionChangedDisposable: IDisposable;
|
||||||
constructor(@INotificationService private notificationService: INotificationService) {
|
private _context: CellContext;
|
||||||
|
constructor(context: CellContext, @INotificationService private notificationService: INotificationService) {
|
||||||
super(RunCellAction.ID, {
|
super(RunCellAction.ID, {
|
||||||
shouldToggleTooltip: true,
|
shouldToggleTooltip: true,
|
||||||
toggleOnLabel: localize('runCell', 'Run cell'),
|
toggleOffLabel: localize('runCell', 'Run cell'),
|
||||||
toggleOnClass: 'toolbarIconRun',
|
toggleOffClass: 'toolbarIconRun',
|
||||||
toggleOffLabel: localize('stopCell', 'Cancel execution'),
|
toggleOnLabel: localize('stopCell', 'Cancel execution'),
|
||||||
toggleOffClass: 'toolbarIconStop',
|
toggleOnClass: 'toolbarIconStop',
|
||||||
isOn: true
|
// On == running
|
||||||
|
isOn: false
|
||||||
});
|
});
|
||||||
|
this.ensureContextIsUpdated(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public run(context: CellContext): TPromise<boolean> {
|
private _handleExecutionStateChange(running: boolean): void {
|
||||||
if (hasModelAndCell(context, this.notificationService)) {
|
this.toggle(running);
|
||||||
return TPromise.wrap(this.runCellAction(context).then(() => true));
|
|
||||||
}
|
|
||||||
return TPromise.as(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async runCellAction(context: CellContext): Promise<void> {
|
public run(context?: CellContext): TPromise<boolean> {
|
||||||
try {
|
return TPromise.wrap(this.doRun(context).then(() => true));
|
||||||
let model = context.model;
|
}
|
||||||
let cell = context.cell;
|
|
||||||
let kernel = await this.getOrStartKernel(model);
|
public async doRun(context: CellContext): Promise<void> {
|
||||||
if (!kernel) {
|
this.ensureContextIsUpdated(context);
|
||||||
|
if (!this._context) {
|
||||||
|
// TODO should we error?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If cell is currently running and user clicks the stop/cancel button, call kernel.interrupt()
|
try {
|
||||||
// This matches the same behavior as JupyterLab
|
await this._context.cell.runCell(this.notificationService);
|
||||||
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) {
|
} catch (error) {
|
||||||
let message = getErrorMessage(error);
|
let message = getErrorMessage(error);
|
||||||
this.notificationService.error(message);
|
this.notificationService.error(message);
|
||||||
} finally {
|
|
||||||
this.toggle(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOrStartKernel(model: NotebookModel): Promise<nb.IKernel> {
|
private ensureContextIsUpdated(context: CellContext) {
|
||||||
let clientSession = model && model.clientSession;
|
if (context && context !== this._context) {
|
||||||
if (!clientSession) {
|
if (this._executionChangedDisposable) {
|
||||||
this.notificationService.error(localize('notebookNotReady', 'The session for this notebook is not yet ready'));
|
this._executionChangedDisposable.dispose();
|
||||||
return undefined;
|
}
|
||||||
} else if (!clientSession.isReady || clientSession.status === 'dead') {
|
this._context = context;
|
||||||
this.notificationService.info(localize('sessionNotReady', 'The session for this notebook will start momentarily'));
|
this.toggle(context.cell.isRunning);
|
||||||
await clientSession.kernelChangeCompleted;
|
this._executionChangedDisposable = this._context.cell.onExecutionStateChange(this._handleExecutionStateChange, this);
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,18 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import { nb } from 'sqlops';
|
||||||
|
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
|
||||||
import { nb } from 'sqlops';
|
|
||||||
import { ICellModelOptions, IModelFactory, FutureInternal } from './modelInterfaces';
|
import { ICellModelOptions, IModelFactory, FutureInternal } from './modelInterfaces';
|
||||||
import * as notebookUtils from '../notebookUtils';
|
import * as notebookUtils from '../notebookUtils';
|
||||||
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
|
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
|
||||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||||
|
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
let modelId = 0;
|
let modelId = 0;
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ export class CellModel implements ICellModel {
|
|||||||
private _isEditMode: boolean;
|
private _isEditMode: boolean;
|
||||||
private _onOutputsChanged = new Emitter<ReadonlyArray<nb.ICellOutput>>();
|
private _onOutputsChanged = new Emitter<ReadonlyArray<nb.ICellOutput>>();
|
||||||
private _onCellModeChanged = new Emitter<boolean>();
|
private _onCellModeChanged = new Emitter<boolean>();
|
||||||
|
private _onExecutionStateChanged = new Emitter<boolean>();
|
||||||
public id: string;
|
public id: string;
|
||||||
private _isTrusted: boolean;
|
private _isTrusted: boolean;
|
||||||
private _active: boolean;
|
private _active: boolean;
|
||||||
@@ -131,6 +134,89 @@ export class CellModel implements ICellModel {
|
|||||||
this._language = newLanguage;
|
this._language = newLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get onExecutionStateChange(): Event<boolean> {
|
||||||
|
return this._onExecutionStateChanged.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isRunning(): boolean {
|
||||||
|
return !!(this._future && this._future.inProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async runCell(notificationService?: INotificationService): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (this.cellType !== CellTypes.Code) {
|
||||||
|
// TODO should change hidden state to false if we add support
|
||||||
|
// for this property
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let kernel = await this.getOrStartKernel(notificationService);
|
||||||
|
if (!kernel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If cell is currently running and user clicks the stop/cancel button, call kernel.interrupt()
|
||||||
|
// This matches the same behavior as JupyterLab
|
||||||
|
if (this.future && this.future.inProgress) {
|
||||||
|
this.future.inProgress = false;
|
||||||
|
await kernel.interrupt();
|
||||||
|
} else {
|
||||||
|
// TODO update source based on editor component contents
|
||||||
|
let content = this.source;
|
||||||
|
if (content) {
|
||||||
|
this._onExecutionStateChanged.fire(true);
|
||||||
|
let future = await kernel.requestExecute({
|
||||||
|
code: content,
|
||||||
|
stop_on_error: true
|
||||||
|
}, false);
|
||||||
|
this.setFuture(future as FutureInternal);
|
||||||
|
// For now, await future completion. Later we should just track and handle cancellation based on model notifications
|
||||||
|
let result: nb.IExecuteReplyMsg = <nb.IExecuteReplyMsg><any> await future.done;
|
||||||
|
return result && result.content.status === 'ok' ? true : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message === 'Canceled') {
|
||||||
|
// swallow the error
|
||||||
|
}
|
||||||
|
let message = notebookUtils.getErrorMessage(error);
|
||||||
|
this.sendNotification(notificationService, Severity.Error, message);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this._onExecutionStateChanged.fire(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getOrStartKernel(notificationService: INotificationService): Promise<nb.IKernel> {
|
||||||
|
let model = this.options.notebook;
|
||||||
|
let clientSession = model && model.clientSession;
|
||||||
|
if (!clientSession) {
|
||||||
|
this.sendNotification(notificationService, Severity.Error, localize('notebookNotReady', 'The session for this notebook is not yet ready'));
|
||||||
|
return undefined;
|
||||||
|
} else if (!clientSession.isReady || clientSession.status === 'dead') {
|
||||||
|
|
||||||
|
this.sendNotification(notificationService, Severity.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.sendNotification(notificationService, Severity.Error, localize('noDefaultKernel', 'No kernel is available for this notebook'));
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
await clientSession.changeKernel({
|
||||||
|
name: defaultKernel
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return clientSession.kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendNotification(notificationService: INotificationService, severity: Severity, message: string): void {
|
||||||
|
if (notificationService) {
|
||||||
|
notificationService.notify({ severity: severity, message: message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the future which will be used to update the output
|
* Sets the future which will be used to update the output
|
||||||
* area for this cell
|
* area for this cell
|
||||||
@@ -178,6 +264,11 @@ export class CellModel implements ICellModel {
|
|||||||
// TODO #931 we should process this. There can be a payload attached which should be added to outputs.
|
// TODO #931 we should process this. There can be a payload attached which should be added to outputs.
|
||||||
// In all other cases, it is a no-op
|
// In all other cases, it is a no-op
|
||||||
let output: nb.ICellOutput = msg.content as nb.ICellOutput;
|
let output: nb.ICellOutput = msg.content as nb.ICellOutput;
|
||||||
|
|
||||||
|
if (!this._future.inProgress) {
|
||||||
|
this._future.dispose();
|
||||||
|
this._future = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleIOPub(msg: nb.IIOPubMessage): void {
|
private handleIOPub(msg: nb.IIOPubMessage): void {
|
||||||
@@ -223,9 +314,6 @@ export class CellModel implements ICellModel {
|
|||||||
this._outputs.push(this.rewriteOutputUrls(output));
|
this._outputs.push(this.rewriteOutputUrls(output));
|
||||||
this.fireOutputsChanged();
|
this.fireOutputsChanged();
|
||||||
}
|
}
|
||||||
if (!this._future.inProgress) {
|
|
||||||
this._future.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private rewriteOutputUrls(output: nb.ICellOutput): nb.ICellOutput {
|
private rewriteOutputUrls(output: nb.ICellOutput): nb.ICellOutput {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import * as notebookUtils from '../notebookUtils';
|
|||||||
import { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService';
|
import { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||||
|
|
||||||
|
type KernelChangeHandler = (kernel: nb.IKernelChangedArgs) => Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Implementation of a client session. This is a model over session operations,
|
* Implementation of a client session. This is a model over session operations,
|
||||||
* which may come from the session manager or a specific session.
|
* which may come from the session manager or a specific session.
|
||||||
@@ -41,6 +42,9 @@ export class ClientSession implements IClientSession {
|
|||||||
private _kernelPreference: IKernelPreference;
|
private _kernelPreference: IKernelPreference;
|
||||||
private _kernelDisplayName: string;
|
private _kernelDisplayName: string;
|
||||||
private _errorMessage: string;
|
private _errorMessage: string;
|
||||||
|
private _cachedKernelSpec: nb.IKernelSpec;
|
||||||
|
private _kernelChangeHandlers: KernelChangeHandler[] = [];
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
private _serverLoadFinished: Promise<void>;
|
private _serverLoadFinished: Promise<void>;
|
||||||
@@ -62,6 +66,7 @@ export class ClientSession implements IClientSession {
|
|||||||
this._serverLoadFinished = this.startServer();
|
this._serverLoadFinished = this.startServer();
|
||||||
await this._serverLoadFinished;
|
await this._serverLoadFinished;
|
||||||
await this.initializeSession();
|
await this.initializeSession();
|
||||||
|
await this.updateCachedKernelSpec();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._errorMessage = notebookUtils.getErrorMessage(err);
|
this._errorMessage = notebookUtils.getErrorMessage(err);
|
||||||
}
|
}
|
||||||
@@ -150,6 +155,12 @@ export class ClientSession implements IClientSession {
|
|||||||
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
||||||
return this._kernelChangedEmitter.event;
|
return this._kernelChangedEmitter.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onKernelChanging(changeHandler: (kernel: nb.IKernelChangedArgs) => Promise<void>): void {
|
||||||
|
if (changeHandler) {
|
||||||
|
this._kernelChangeHandlers.push(changeHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
public get statusChanged(): Event<nb.ISession> {
|
public get statusChanged(): Event<nb.ISession> {
|
||||||
return this._statusChangedEmitter.event;
|
return this._statusChangedEmitter.event;
|
||||||
}
|
}
|
||||||
@@ -204,6 +215,10 @@ export class ClientSession implements IClientSession {
|
|||||||
public get isInErrorState(): boolean {
|
public get isInErrorState(): boolean {
|
||||||
return !!this._errorMessage;
|
return !!this._errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get cachedKernelSpec(): nb.IKernelSpec {
|
||||||
|
return this._cachedKernelSpec;
|
||||||
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Not Yet Implemented
|
//#region Not Yet Implemented
|
||||||
@@ -227,15 +242,28 @@ export class ClientSession implements IClientSession {
|
|||||||
}
|
}
|
||||||
newKernel = this._session ? kernel : this._session.kernel;
|
newKernel = this._session ? kernel : this._session.kernel;
|
||||||
this._isReady = kernel.isReady;
|
this._isReady = kernel.isReady;
|
||||||
|
await this.updateCachedKernelSpec();
|
||||||
// Send resolution events to listeners
|
// Send resolution events to listeners
|
||||||
this._kernelChangeCompleted.resolve();
|
let changeArgs: nb.IKernelChangedArgs = {
|
||||||
this._kernelChangedEmitter.fire({
|
|
||||||
oldValue: oldKernel,
|
oldValue: oldKernel,
|
||||||
newValue: newKernel
|
newValue: newKernel
|
||||||
});
|
};
|
||||||
|
let changePromises = this._kernelChangeHandlers.map(handler => handler(changeArgs));
|
||||||
|
await Promise.all(changePromises);
|
||||||
|
// Wait on connection configuration to complete before resolving full kernel change
|
||||||
|
this._kernelChangeCompleted.resolve();
|
||||||
|
this._kernelChangedEmitter.fire(changeArgs);
|
||||||
return kernel;
|
return kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateCachedKernelSpec(): Promise<void> {
|
||||||
|
this._cachedKernelSpec = undefined;
|
||||||
|
let kernel = this.kernel;
|
||||||
|
if (kernel && kernel.isReady) {
|
||||||
|
this._cachedKernelSpec = await this.kernel.getSpec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to either call ChangeKernel on current session, or start a new session
|
* Helper method to either call ChangeKernel on current session, or start a new session
|
||||||
* @param options
|
* @param options
|
||||||
|
|||||||
@@ -43,5 +43,6 @@ export enum NotebookChangeType {
|
|||||||
CellDeleted,
|
CellDeleted,
|
||||||
CellSourceUpdated,
|
CellSourceUpdated,
|
||||||
CellOutputUpdated,
|
CellOutputUpdated,
|
||||||
DirtyStateChanged
|
DirtyStateChanged,
|
||||||
|
KernelChanged
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ export interface IClientSession extends IDisposable {
|
|||||||
*/
|
*/
|
||||||
readonly kernelDisplayName: string;
|
readonly kernelDisplayName: string;
|
||||||
|
|
||||||
|
readonly cachedKernelSpec: nb.IKernelSpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the ClientSession, by starting the server and
|
* Initializes the ClientSession, by starting the server and
|
||||||
* connecting to the SessionManager.
|
* connecting to the SessionManager.
|
||||||
@@ -200,7 +202,13 @@ export interface IClientSession extends IDisposable {
|
|||||||
/**
|
/**
|
||||||
* Updates the connection
|
* Updates the connection
|
||||||
*/
|
*/
|
||||||
updateConnection(connection: IConnectionProfile): void;
|
updateConnection(connection: IConnectionProfile): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports registering a handler to run during kernel change and implement any calls needed to configure
|
||||||
|
* the kernel before actions such as run should be allowed
|
||||||
|
*/
|
||||||
|
onKernelChanging(changeHandler: ((kernel: nb.IKernelChangedArgs) => Promise<void>)): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDefaultConnection {
|
export interface IDefaultConnection {
|
||||||
@@ -322,7 +330,7 @@ export interface INotebookModel {
|
|||||||
/**
|
/**
|
||||||
* Change the current context (if applicable)
|
* Change the current context (if applicable)
|
||||||
*/
|
*/
|
||||||
changeContext(host: string, connection?: IConnectionProfile): void;
|
changeContext(host: string, connection?: IConnectionProfile): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a cell's index given its model
|
* Find a cell's index given its model
|
||||||
@@ -400,7 +408,10 @@ export interface ICellModel {
|
|||||||
readonly future: FutureInternal;
|
readonly future: FutureInternal;
|
||||||
readonly outputs: ReadonlyArray<nb.ICellOutput>;
|
readonly outputs: ReadonlyArray<nb.ICellOutput>;
|
||||||
readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
|
readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
|
||||||
|
readonly onExecutionStateChange: Event<boolean>;
|
||||||
setFuture(future: FutureInternal): void;
|
setFuture(future: FutureInternal): void;
|
||||||
|
readonly isRunning: boolean;
|
||||||
|
runCell(notificationService?: INotificationService): Promise<boolean>;
|
||||||
equals(cellModel: ICellModel): boolean;
|
equals(cellModel: ICellModel): boolean;
|
||||||
toJSON(): nb.ICellContents;
|
toJSON(): nb.ICellContents;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findCellIndex(cellModel: CellModel): number {
|
public findCellIndex(cellModel: ICellModel): number {
|
||||||
return this._cells.findIndex((cell) => cell.equals(cellModel));
|
return this._cells.findIndex((cell) => cell.equals(cellModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +420,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
public changeContext(server: string, newConnection?: IConnectionProfile): void {
|
public async changeContext(server: string, newConnection?: IConnectionProfile): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (!newConnection) {
|
if (!newConnection) {
|
||||||
newConnection = this._activeContexts.otherConnections.find((connection) => connection.serverName === server);
|
newConnection = this._activeContexts.otherConnections.find((connection) => connection.serverName === server);
|
||||||
@@ -431,7 +431,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
let newConnectionProfile = new ConnectionProfile(this.notebookOptions.capabilitiesService, newConnection);
|
let newConnectionProfile = new ConnectionProfile(this.notebookOptions.capabilitiesService, newConnection);
|
||||||
this._activeConnection = newConnectionProfile;
|
this._activeConnection = newConnectionProfile;
|
||||||
this.refreshConnections(newConnectionProfile);
|
this.refreshConnections(newConnectionProfile);
|
||||||
this._activeClientSession.updateConnection(this._activeConnection);
|
await this._activeClientSession.updateConnection(this._activeConnection);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
let msg = notebookUtils.getErrorMessage(err);
|
let msg = notebookUtils.getErrorMessage(err);
|
||||||
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
|
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
|
||||||
@@ -454,7 +454,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
|
|
||||||
private loadKernelInfo(): void {
|
private loadKernelInfo(): void {
|
||||||
this._clientSessions.forEach(clientSession => {
|
this._clientSessions.forEach(clientSession => {
|
||||||
clientSession.kernelChanged(async (e) => {
|
clientSession.onKernelChanging(async (e) => {
|
||||||
await this.loadActiveContexts(e);
|
await this.loadActiveContexts(e);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -550,7 +550,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
this._activeContexts = await NotebookContexts.getContextsForKernel(this.notebookOptions.connectionService, this.getApplicableConnectionProviderIds(kernelDisplayName), kernelChangedArgs, this.connectionProfile);
|
this._activeContexts = await NotebookContexts.getContextsForKernel(this.notebookOptions.connectionService, this.getApplicableConnectionProviderIds(kernelDisplayName), kernelChangedArgs, this.connectionProfile);
|
||||||
this._contextsChangedEmitter.fire();
|
this._contextsChangedEmitter.fire();
|
||||||
if (this.contexts.defaultConnection !== undefined && this.contexts.defaultConnection.serverName !== undefined) {
|
if (this.contexts.defaultConnection !== undefined && this.contexts.defaultConnection.serverName !== undefined) {
|
||||||
this.changeContext(this.contexts.defaultConnection.serverName);
|
await this.changeContext(this.contexts.defaultConnection.serverName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||||
this.updateTheme(this.themeService.getColorTheme());
|
this.updateTheme(this.themeService.getColorTheme());
|
||||||
this.notebookService.addNotebookEditor(this);
|
|
||||||
this.initActionBar();
|
this.initActionBar();
|
||||||
this.doLoad();
|
this.doLoad();
|
||||||
}
|
}
|
||||||
@@ -135,7 +134,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get model(): NotebookModel {
|
public get model(): NotebookModel | null {
|
||||||
return this._model;
|
return this._model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,6 +221,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
this.setViewInErrorState(localize('displayFailed', 'Could not display contents: {0}', error));
|
this.setViewInErrorState(localize('displayFailed', 'Could not display contents: {0}', error));
|
||||||
this.setLoading(false);
|
this.setLoading(false);
|
||||||
this._modelReadyDeferred.reject(error);
|
this._modelReadyDeferred.reject(error);
|
||||||
|
} finally {
|
||||||
|
// Always add the editor for now to close loop, even if loading contents failed
|
||||||
|
this.notebookService.addNotebookEditor(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,4 +540,15 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
this._model.pushEditOperations(edits);
|
this._model.pushEditOperations(edits);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async runCell(cell: ICellModel): Promise<boolean> {
|
||||||
|
await this.modelReady;
|
||||||
|
let uriString = cell.cellUri.toString();
|
||||||
|
if (this._model.cells.findIndex(c => c.cellUri.toString() === uriString) > -1) {
|
||||||
|
return cell.runCell(this.notificationService);
|
||||||
|
} else {
|
||||||
|
return Promise.reject<boolean>(new Error(localize('cellNotFound', 'cell with URI {0} was not found in this model', uriString)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,7 +230,8 @@ export class AttachToDropdown extends SelectBox {
|
|||||||
this.model = model;
|
this.model = model;
|
||||||
model.contextsChanged(() => {
|
model.contextsChanged(() => {
|
||||||
if (this.model.clientSession.kernel && this.model.clientSession.kernel.name) {
|
if (this.model.clientSession.kernel && this.model.clientSession.kernel.name) {
|
||||||
let currentKernelSpec = this.model.specs.kernels.find(kernel => kernel.name === this.model.clientSession.kernel.name);
|
let nameLower = this.model.clientSession.kernel.name.toLowerCase();
|
||||||
|
let currentKernelSpec = this.model.specs.kernels.find(kernel => kernel.name && kernel.name.toLowerCase() === nameLower);
|
||||||
this.loadAttachToDropdown(this.model, currentKernelSpec.display_name);
|
this.loadAttachToDropdown(this.model, currentKernelSpec.display_name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -282,7 +283,7 @@ export class AttachToDropdown extends SelectBox {
|
|||||||
if (this.value === msgAddNewConnection) {
|
if (this.value === msgAddNewConnection) {
|
||||||
this.openConnectionDialog();
|
this.openConnectionDialog();
|
||||||
} else {
|
} else {
|
||||||
this.model.changeContext(this.value, connection);
|
this.model.changeContext(this.value, connection).then(ok => undefined, err => this._notificationService.error(getErrorMessage(err)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import * as Constants from 'sql/platform/connection/common/constants';
|
|||||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
|
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
|
||||||
|
|
||||||
|
type SettableProperty = 'serverName' | 'authenticationType' | 'databaseName' | 'password' | 'connectionName' | 'userName';
|
||||||
|
|
||||||
export class ProviderConnectionInfo extends Disposable implements sqlops.ConnectionInfo {
|
export class ProviderConnectionInfo extends Disposable implements sqlops.ConnectionInfo {
|
||||||
|
|
||||||
options: { [name: string]: any } = {};
|
options: { [name: string]: any } = {};
|
||||||
@@ -39,16 +41,30 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
|
|||||||
this.options[option.name] = value;
|
this.options[option.name] = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.serverName = model.serverName;
|
|
||||||
this.authenticationType = model.authenticationType;
|
this.updateSpecialValueType('serverName', model);
|
||||||
this.databaseName = model.databaseName;
|
this.updateSpecialValueType('authenticationType', model);
|
||||||
this.password = model.password;
|
this.updateSpecialValueType('databaseName', model);
|
||||||
this.userName = model.userName;
|
this.updateSpecialValueType('password', model);
|
||||||
this.connectionName = model.connectionName;
|
this.updateSpecialValueType('userName', model);
|
||||||
|
this.updateSpecialValueType('connectionName', model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates one of the special value types (serverName, authenticationType, etc.) if this doesn't already
|
||||||
|
* have a value in the options map.
|
||||||
|
*
|
||||||
|
* This handles the case where someone hasn't passed in a valid property bag, but doesn't cause errors when
|
||||||
|
*/
|
||||||
|
private updateSpecialValueType(typeName: SettableProperty, model: sqlops.IConnectionProfile): void {
|
||||||
|
if (!this[typeName]) {
|
||||||
|
this[typeName] = model[typeName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public get providerName(): string {
|
public get providerName(): string {
|
||||||
return this._providerName;
|
return this._providerName;
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/sql/sqlops.proposed.d.ts
vendored
32
src/sql/sqlops.proposed.d.ts
vendored
@@ -1488,6 +1488,12 @@ declare module 'sqlops' {
|
|||||||
*/
|
*/
|
||||||
readonly cells: NotebookCell[];
|
readonly cells: NotebookCell[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The spec for current kernel, if applicable. This will be undefined
|
||||||
|
* until a kernel has been started
|
||||||
|
*/
|
||||||
|
readonly kernelSpec: IKernelSpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the underlying file.
|
* Save the underlying file.
|
||||||
*
|
*
|
||||||
@@ -1558,6 +1564,15 @@ declare module 'sqlops' {
|
|||||||
* @return A promise that resolves with a value indicating if the edits could be applied.
|
* @return A promise that resolves with a value indicating if the edits could be applied.
|
||||||
*/
|
*/
|
||||||
edit(callback: (editBuilder: NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean>;
|
edit(callback: (editBuilder: NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kicks off execution of a cell. Thenable will resolve only once the full execution is completed.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param cell A cell in this notebook which should be executed
|
||||||
|
* @return A promise that resolves with a value indicating if the cell was run or not.
|
||||||
|
*/
|
||||||
|
runCell(cell: NotebookCell): Thenable<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotebookCell {
|
export interface NotebookCell {
|
||||||
@@ -1801,7 +1816,7 @@ declare module 'sqlops' {
|
|||||||
export interface ICellContents {
|
export interface ICellContents {
|
||||||
cell_type: CellType;
|
cell_type: CellType;
|
||||||
source: string | string[];
|
source: string | string[];
|
||||||
metadata: {
|
metadata?: {
|
||||||
language?: string;
|
language?: string;
|
||||||
};
|
};
|
||||||
execution_count?: number;
|
execution_count?: number;
|
||||||
@@ -2189,7 +2204,6 @@ declare module 'sqlops' {
|
|||||||
handle(message: T): void | Thenable<void>;
|
handle(message: T): void | Thenable<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Future interface for responses from the kernel.
|
* A Future interface for responses from the kernel.
|
||||||
*
|
*
|
||||||
@@ -2285,6 +2299,20 @@ declare module 'sqlops' {
|
|||||||
sendInputReply(content: IInputReply): void;
|
sendInputReply(content: IInputReply): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IExecuteReplyMsg extends IShellMessage {
|
||||||
|
content: IExecuteReply;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content of an `execute-reply` message.
|
||||||
|
*
|
||||||
|
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execution-results).
|
||||||
|
*/
|
||||||
|
export interface IExecuteReply {
|
||||||
|
status: 'ok' | 'error' | 'abort';
|
||||||
|
execution_count: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The valid channel names.
|
* The valid channel names.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -470,6 +470,7 @@ export enum FutureMessageType {
|
|||||||
export interface INotebookFutureDone {
|
export interface INotebookFutureDone {
|
||||||
succeeded: boolean;
|
succeeded: boolean;
|
||||||
rejectReason: string;
|
rejectReason: string;
|
||||||
|
message: nb.IShellMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICellRange {
|
export interface ICellRange {
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
|||||||
|
|
||||||
private hookFutureDone(futureId: number, future: sqlops.nb.IFuture): void {
|
private hookFutureDone(futureId: number, future: sqlops.nb.IFuture): void {
|
||||||
future.done.then(success => {
|
future.done.then(success => {
|
||||||
return this._proxy.$onFutureDone(futureId, { succeeded: true, rejectReason: undefined });
|
return this._proxy.$onFutureDone(futureId, { succeeded: true, message: success, rejectReason: undefined });
|
||||||
}, err => {
|
}, err => {
|
||||||
let rejectReason: string;
|
let rejectReason: string;
|
||||||
if (typeof err === 'string') {
|
if (typeof err === 'string') {
|
||||||
@@ -176,7 +176,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
|||||||
else {
|
else {
|
||||||
rejectReason = err;
|
rejectReason = err;
|
||||||
}
|
}
|
||||||
return this._proxy.$onFutureDone(futureId, { succeeded: false, rejectReason: rejectReason });
|
return this._proxy.$onFutureDone(futureId, { succeeded: false, message: undefined, rejectReason: rejectReason });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { CellRange } from 'sql/workbench/api/common/sqlExtHostTypes';
|
|||||||
export class ExtHostNotebookDocumentData implements IDisposable {
|
export class ExtHostNotebookDocumentData implements IDisposable {
|
||||||
private _document: sqlops.nb.NotebookDocument;
|
private _document: sqlops.nb.NotebookDocument;
|
||||||
private _isDisposed: boolean = false;
|
private _isDisposed: boolean = false;
|
||||||
|
private _kernelSpec: sqlops.nb.IKernelSpec;
|
||||||
|
|
||||||
constructor(private readonly _proxy: MainThreadNotebookDocumentsAndEditorsShape,
|
constructor(private readonly _proxy: MainThreadNotebookDocumentsAndEditorsShape,
|
||||||
private readonly _uri: URI,
|
private readonly _uri: URI,
|
||||||
@@ -49,6 +50,7 @@ export class ExtHostNotebookDocumentData implements IDisposable {
|
|||||||
get isClosed() { return data._isDisposed; },
|
get isClosed() { return data._isDisposed; },
|
||||||
get isDirty() { return data._isDirty; },
|
get isDirty() { return data._isDirty; },
|
||||||
get cells() { return data._cells; },
|
get cells() { return data._cells; },
|
||||||
|
get kernelSpec() { return data._kernelSpec; },
|
||||||
save() { return data._save(); },
|
save() { return data._save(); },
|
||||||
validateCellRange(range) { return data._validateRange(range); },
|
validateCellRange(range) { return data._validateRange(range); },
|
||||||
};
|
};
|
||||||
@@ -69,6 +71,7 @@ export class ExtHostNotebookDocumentData implements IDisposable {
|
|||||||
this._isDirty = data.isDirty;
|
this._isDirty = data.isDirty;
|
||||||
this._cells = data.cells;
|
this._cells = data.cells;
|
||||||
this._providerId = data.providerId;
|
this._providerId = data.providerId;
|
||||||
|
this._kernelSpec = data.kernelSpec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,11 @@ export class ExtHostNotebookEditor implements sqlops.nb.NotebookEditor, IDisposa
|
|||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
edit(callback: (editBuilder: sqlops.nb.NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean> {
|
public runCell(cell: sqlops.nb.NotebookCell): Thenable<boolean> {
|
||||||
|
return this._proxy.$runCell(this._id, cell.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public edit(callback: (editBuilder: sqlops.nb.NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean> {
|
||||||
if (this._disposed) {
|
if (this._disposed) {
|
||||||
return TPromise.wrapError<boolean>(new Error('NotebookEditor#edit not possible on closed editors'));
|
return TPromise.wrapError<boolean>(new Error('NotebookEditor#edit not possible on closed editors'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ class FutureWrapper implements FutureInternal {
|
|||||||
public onDone(done: INotebookFutureDone): void {
|
public onDone(done: INotebookFutureDone): void {
|
||||||
this._inProgress = false;
|
this._inProgress = false;
|
||||||
if (done.succeeded) {
|
if (done.succeeded) {
|
||||||
this._done.resolve();
|
this._done.resolve(done.message);
|
||||||
} else {
|
} else {
|
||||||
this._done.reject(new Error(done.rejectReason));
|
this._done.reject(new Error(done.rejectReason));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
|||||||
import { getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
|
import { getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
|
||||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { disposed } from 'vs/base/common/errors';
|
import { disposed } from 'vs/base/common/errors';
|
||||||
import { ICellModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
|
import { NotebookChangeType } from 'sql/parts/notebook/models/contracts';
|
||||||
|
|
||||||
class MainThreadNotebookEditor extends Disposable {
|
class MainThreadNotebookEditor extends Disposable {
|
||||||
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
||||||
@@ -38,6 +39,12 @@ class MainThreadNotebookEditor extends Disposable {
|
|||||||
super();
|
super();
|
||||||
editor.modelReady.then(model => {
|
editor.modelReady.then(model => {
|
||||||
this._register(model.contentChanged((e) => this._contentChangedEmitter.fire(e)));
|
this._register(model.contentChanged((e) => this._contentChangedEmitter.fire(e)));
|
||||||
|
this._register(model.kernelChanged((e) => {
|
||||||
|
let changeEvent: NotebookContentChange = {
|
||||||
|
changeType: NotebookChangeType.KernelChanged
|
||||||
|
};
|
||||||
|
this._contentChangedEmitter.fire(changeEvent);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +72,10 @@ class MainThreadNotebookEditor extends Disposable {
|
|||||||
return this.editor.cells;
|
return this.editor.cells;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get model(): INotebookModel | null {
|
||||||
|
return this.editor.model;
|
||||||
|
}
|
||||||
|
|
||||||
public save(): Thenable<boolean> {
|
public save(): Thenable<boolean> {
|
||||||
return this.editor.save();
|
return this.editor.save();
|
||||||
}
|
}
|
||||||
@@ -100,6 +111,14 @@ class MainThreadNotebookEditor extends Disposable {
|
|||||||
// }
|
// }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public runCell(cell: ICellModel): Promise<boolean> {
|
||||||
|
if (!this.editor) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.editor.runCell(cell);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function wait(timeMs: number): Promise<void> {
|
function wait(timeMs: number): Promise<void> {
|
||||||
@@ -266,7 +285,6 @@ class MainThreadNotebookDocumentAndEditorStateComputer extends Disposable {
|
|||||||
|
|
||||||
@extHostNamedCustomer(SqlMainContext.MainThreadNotebookDocumentsAndEditors)
|
@extHostNamedCustomer(SqlMainContext.MainThreadNotebookDocumentsAndEditors)
|
||||||
export class MainThreadNotebookDocumentsAndEditors extends Disposable implements MainThreadNotebookDocumentsAndEditorsShape {
|
export class MainThreadNotebookDocumentsAndEditors extends Disposable implements MainThreadNotebookDocumentsAndEditorsShape {
|
||||||
|
|
||||||
private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
|
private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
|
||||||
private _notebookEditors = new Map<string, MainThreadNotebookEditor>();
|
private _notebookEditors = new Map<string, MainThreadNotebookEditor>();
|
||||||
private _modelToDisposeMap = new Map<string, IDisposable>();
|
private _modelToDisposeMap = new Map<string, IDisposable>();
|
||||||
@@ -308,6 +326,22 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
}
|
}
|
||||||
return TPromise.as(editor.applyEdits(modelVersionId, edits, opts));
|
return TPromise.as(editor.applyEdits(modelVersionId, edits, opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$runCell(id: string, cellUri: UriComponents): TPromise<boolean, any> {
|
||||||
|
// Requires an editor and the matching cell in that editor
|
||||||
|
let editor = this.getEditor(id);
|
||||||
|
if (!editor) {
|
||||||
|
return TPromise.wrapError<boolean>(disposed(`TextEditor(${id})`));
|
||||||
|
}
|
||||||
|
let uriString = URI.revive(cellUri).toString();
|
||||||
|
let cell = editor.cells.find(c => c.cellUri.toString() === uriString);
|
||||||
|
if (!cell) {
|
||||||
|
return TPromise.wrapError<boolean>(disposed(`TextEditorCell(${uriString})`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return TPromise.wrap(editor.runCell(cell));
|
||||||
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
|
private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
|
||||||
@@ -480,11 +514,17 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
isDirty: e.isDirty,
|
isDirty: e.isDirty,
|
||||||
providerId: editor.providerId,
|
providerId: editor.providerId,
|
||||||
providers: editor.providers,
|
providers: editor.providers,
|
||||||
uri: editor.uri
|
uri: editor.uri,
|
||||||
|
kernelSpec: this.getKernelSpec(editor)
|
||||||
};
|
};
|
||||||
return changeData;
|
return changeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getKernelSpec(editor: MainThreadNotebookEditor): sqlops.nb.IKernelSpec {
|
||||||
|
let spec = editor && editor.model && editor.model.clientSession ? editor.model.clientSession.cachedKernelSpec : undefined;
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
private convertCellModelToNotebookCell(cells: ICellModel | ICellModel[]): sqlops.nb.NotebookCell[] {
|
private convertCellModelToNotebookCell(cells: ICellModel | ICellModel[]): sqlops.nb.NotebookCell[] {
|
||||||
let notebookCells: sqlops.nb.NotebookCell[] = [];
|
let notebookCells: sqlops.nb.NotebookCell[] = [];
|
||||||
if (Array.isArray(cells)) {
|
if (Array.isArray(cells)) {
|
||||||
@@ -497,8 +537,8 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
metadata: {
|
metadata: {
|
||||||
language: cell.language
|
language: cell.language
|
||||||
},
|
},
|
||||||
source: undefined
|
source: undefined,
|
||||||
|
outputs: [...cell.outputs]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -830,6 +830,7 @@ export interface INotebookModelChangedData {
|
|||||||
providers: string[];
|
providers: string[];
|
||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
cells: sqlops.nb.NotebookCell[];
|
cells: sqlops.nb.NotebookCell[];
|
||||||
|
kernelSpec: sqlops.nb.IKernelSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INotebookEditorAddData {
|
export interface INotebookEditorAddData {
|
||||||
@@ -856,6 +857,7 @@ export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable
|
|||||||
$trySaveDocument(uri: UriComponents): Thenable<boolean>;
|
$trySaveDocument(uri: UriComponents): Thenable<boolean>;
|
||||||
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): TPromise<string>;
|
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): TPromise<string>;
|
||||||
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): TPromise<boolean>;
|
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): TPromise<boolean>;
|
||||||
|
$runCell(id: string, cellUri: UriComponents): TPromise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtHostExtensionManagementShape {
|
export interface ExtHostExtensionManagementShape {
|
||||||
|
|||||||
@@ -100,9 +100,11 @@ export interface INotebookEditor {
|
|||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly cells?: ICellModel[];
|
readonly cells?: ICellModel[];
|
||||||
readonly modelReady: Promise<INotebookModel>;
|
readonly modelReady: Promise<INotebookModel>;
|
||||||
|
readonly model: INotebookModel | null;
|
||||||
isDirty(): boolean;
|
isDirty(): boolean;
|
||||||
isActive(): boolean;
|
isActive(): boolean;
|
||||||
isVisible(): boolean;
|
isVisible(): boolean;
|
||||||
save(): Promise<boolean>;
|
save(): Promise<boolean>;
|
||||||
executeEdits(edits: ISingleNotebookEditOperation[]): boolean;
|
executeEdits(edits: ISingleNotebookEditOperation[]): boolean;
|
||||||
|
runCell(cell: ICellModel): Promise<boolean>;
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ export class NotebookModelStub implements INotebookModel {
|
|||||||
changeKernel(displayName: string): void {
|
changeKernel(displayName: string): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
changeContext(host: string, connection?: IConnectionProfile): void {
|
changeContext(host: string, connection?: IConnectionProfile): Promise<void> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
findCellIndex(cellModel: ICellModel): number {
|
findCellIndex(cellModel: ICellModel): number {
|
||||||
|
|||||||
Reference in New Issue
Block a user