diff --git a/src/sql/parts/notebook/models/modelInterfaces.ts b/src/sql/parts/notebook/models/modelInterfaces.ts index a0977f0008..7d6868ede2 100644 --- a/src/sql/parts/notebook/models/modelInterfaces.ts +++ b/src/sql/parts/notebook/models/modelInterfaces.ts @@ -288,6 +288,12 @@ export interface INotebookModel { */ readonly contexts: IDefaultConnection | undefined; + /** + * Event fired on first initialization of the cells and + * on subsequent change events + */ + readonly contentChanged: Event; + /** * The trusted mode of the Notebook */ @@ -339,6 +345,29 @@ export interface INotebookModel { pushEditOperations(edits: ISingleNotebookEditOperation[]): void; } +export interface NotebookContentChange { + /** + * The type of change that occurred + */ + changeType: NotebookChangeType; + /** + * Optional cells that were changed + */ + cells?: ICellModel | ICellModel[]; + /** + * Optional index of the change, indicating the cell at which an insert or + * delete occurred + */ + cellIndex?: number; + /** + * Optional value indicating if the notebook is in a dirty or clean state after this change + * + * @type {boolean} + * @memberof NotebookContentChange + */ + isDirty?: boolean; +} + export interface ICellModelOptions { notebook: INotebookModel; isTrusted: boolean; diff --git a/src/sql/parts/notebook/models/notebookModel.ts b/src/sql/parts/notebook/models/notebookModel.ts index 44cd767091..14033436d4 100644 --- a/src/sql/parts/notebook/models/notebookModel.ts +++ b/src/sql/parts/notebook/models/notebookModel.ts @@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { CellModel } from './cell'; -import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, notebookConstants } from './modelInterfaces'; +import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, notebookConstants, NotebookContentChange } from './modelInterfaces'; import { NotebookChangeType, CellTypes, CellType } from 'sql/parts/notebook/models/contracts'; import { nbversion } from '../notebookConstants'; import * as notebookUtils from '../notebookUtils'; @@ -39,28 +39,6 @@ export class ErrorInfo { constructor(public readonly message: string, public readonly severity: MessageLevel) { } } -export interface NotebookContentChange { - /** - * What was the change that occurred? - */ - changeType: NotebookChangeType; - /** - * Optional cells that were changed - */ - cells?: ICellModel | ICellModel[]; - /** - * Optional index of the change, indicating the cell at which an insert or - * delete occurred - */ - cellIndex?: number; - /** - * Optional value indicating if the notebook is in a dirty or clean state after this change - * - * @type {boolean} - * @memberof NotebookContentChange - */ - isDirty?: boolean; -} export class NotebookModel extends Disposable implements INotebookModel { private _contextsChangedEmitter = new Emitter(); diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts index 46a492374f..068086e6c3 100644 --- a/src/sql/parts/notebook/notebook.component.ts +++ b/src/sql/parts/notebook/notebook.component.ts @@ -32,11 +32,11 @@ import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/co import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { AngularDisposable } from 'sql/base/common/lifecycle'; import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts'; -import { ICellModel, IModelFactory, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces'; +import { ICellModel, IModelFactory, notebookConstants, INotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces'; import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement'; import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService'; import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; -import { NotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel'; +import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; import { ModelFactory } from 'sql/parts/notebook/models/modelFactory'; import * as notebookUtils from 'sql/parts/notebook/notebookUtils'; import { Deferred } from 'sql/base/common/promise'; @@ -140,7 +140,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe return this._modelRegisteredDeferred.promise; } - protected get cells(): ReadonlyArray { + public get cells(): ICellModel[] { return this._model ? this._model.cells : []; } @@ -452,6 +452,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe return this._notebookParams.notebookUri.toString(); } + public get modelReady(): Promise { + return this._modelReadyDeferred.promise; + } + isActive(): boolean { return this.editorService.activeEditor === this.notebookParams.input; } diff --git a/src/sql/services/notebook/notebookService.ts b/src/sql/services/notebook/notebookService.ts index 67ea9c290b..58cc5dc44a 100644 --- a/src/sql/services/notebook/notebookService.ts +++ b/src/sql/services/notebook/notebookService.ts @@ -16,6 +16,7 @@ import { ModelFactory } from 'sql/parts/notebook/models/modelFactory'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { NotebookInput } from 'sql/parts/notebook/notebookInput'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { ICellModel, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces'; export const SERVICE_ID = 'notebookService'; export const INotebookService = createDecorator(SERVICE_ID); @@ -93,6 +94,8 @@ export interface INotebookParams extends IBootstrapParams { export interface INotebookEditor { readonly notebookParams: INotebookParams; readonly id: string; + readonly cells?: ICellModel[]; + readonly modelReady: Promise; isDirty(): boolean; isActive(): boolean; isVisible(): boolean; diff --git a/src/sql/services/notebook/notebookServiceImpl.ts b/src/sql/services/notebook/notebookServiceImpl.ts index d4ae90b376..55905963ad 100644 --- a/src/sql/services/notebook/notebookServiceImpl.ts +++ b/src/sql/services/notebook/notebookServiceImpl.ts @@ -73,6 +73,7 @@ export class NotebookService extends Disposable implements INotebookService { private _managers: Map = new Map(); private _onNotebookEditorAdd = new Emitter(); private _onNotebookEditorRemove = new Emitter(); + private _onCellChanged = new Emitter(); private _onNotebookEditorRename = new Emitter(); private _editors = new Map(); private _fileToProviders = new Map(); @@ -184,6 +185,9 @@ export class NotebookService extends Disposable implements INotebookService { get onNotebookEditorRemove(): Event { return this._onNotebookEditorRemove.event; } + get onCellChanged(): Event { + return this._onCellChanged.event; + } get onNotebookEditorRename(): Event { return this._onNotebookEditorRename.event; diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 55c7e80345..45950715af 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -1548,6 +1548,7 @@ declare module 'sqlops' { export interface NotebookCell { contents: ICellContents; + uri?: vscode.Uri; } export interface NotebookShowOptions { @@ -1593,7 +1594,7 @@ declare module 'sqlops' { /** * The new value for the [notebook documents's cells](#NotebookDocument.cells). */ - cell: NotebookCell[]; + cells: NotebookCell[]; /** * The [change kind](#TextEditorSelectionChangeKind) which has triggered this * event. Can be `undefined`. diff --git a/src/sql/workbench/api/node/extHostNotebook.ts b/src/sql/workbench/api/node/extHostNotebook.ts index ad609c8289..623c8e1c04 100644 --- a/src/sql/workbench/api/node/extHostNotebook.ts +++ b/src/sql/workbench/api/node/extHostNotebook.ts @@ -24,11 +24,6 @@ export class ExtHostNotebook implements ExtHostNotebookShape { private readonly _proxy: MainThreadNotebookShape; private _adapters = new Map(); - private _onDidOpenNotebook = new Emitter(); - private _onDidChangeNotebookCell = new Emitter(); - - public readonly onDidOpenNotebookDocument: Event = this._onDidOpenNotebook.event; - public readonly onDidChangeNotebookCell: Event = this._onDidChangeNotebookCell.event; // Notebook URI to manager lookup. constructor(_mainContext: IMainContext) { diff --git a/src/sql/workbench/api/node/extHostNotebookDocumentData.ts b/src/sql/workbench/api/node/extHostNotebookDocumentData.ts index afaf721afa..db7c9b7d6e 100644 --- a/src/sql/workbench/api/node/extHostNotebookDocumentData.ts +++ b/src/sql/workbench/api/node/extHostNotebookDocumentData.ts @@ -12,22 +12,20 @@ import { ok } from 'vs/base/common/assert'; import { Schemas } from 'vs/base/common/network'; import { TPromise } from 'vs/base/common/winjs.base'; -import { MainThreadNotebookDocumentsAndEditorsShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; +import { MainThreadNotebookDocumentsAndEditorsShape, INotebookModelChangedData } from 'sql/workbench/api/node/sqlExtHost.protocol'; import { CellRange } from 'sql/workbench/api/common/sqlExtHostTypes'; export class ExtHostNotebookDocumentData implements IDisposable { private _document: sqlops.nb.NotebookDocument; - private _cells: sqlops.nb.NotebookCell[]; private _isDisposed: boolean = false; constructor(private readonly _proxy: MainThreadNotebookDocumentsAndEditorsShape, private readonly _uri: URI, - private readonly _providerId: string, - private _isDirty: boolean + private _providerId: string, + private _isDirty: boolean, + private _cells: sqlops.nb.NotebookCell[] ) { - // TODO add cell mapping support - this._cells = []; } dispose(): void { @@ -66,6 +64,14 @@ export class ExtHostNotebookDocumentData implements IDisposable { } + public onModelChanged(data: INotebookModelChangedData) { + if (data) { + this._isDirty = data.isDirty; + this._cells = data.cells; + this._providerId = data.providerId; + } + } + // ---- range math private _validateRange(range: sqlops.nb.CellRange): sqlops.nb.CellRange { diff --git a/src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts index 3cb0841119..c71f8f33c1 100644 --- a/src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import { Event, Emitter } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { Disposable } from 'vs/workbench/api/node/extHostTypes'; import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { IMainContext } from 'vs/workbench/api/node/extHost.protocol'; @@ -17,7 +17,7 @@ import { ok } from 'vs/base/common/assert'; import { SqlMainContext, INotebookDocumentsAndEditorsDelta, ExtHostNotebookDocumentsAndEditorsShape, - MainThreadNotebookDocumentsAndEditorsShape, INotebookShowOptions + MainThreadNotebookDocumentsAndEditorsShape, INotebookShowOptions, INotebookModelChangedData } from 'sql/workbench/api/node/sqlExtHost.protocol'; import { ExtHostNotebookDocumentData } from 'sql/workbench/api/node/extHostNotebookDocumentData'; import { ExtHostNotebookEditor } from 'sql/workbench/api/node/extHostNotebookEditor'; @@ -33,15 +33,16 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume private readonly _editors = new Map(); private readonly _documents = new Map(); - private readonly _onDidAddDocuments = new Emitter(); - private readonly _onDidRemoveDocuments = new Emitter(); private readonly _onDidChangeVisibleNotebookEditors = new Emitter(); private readonly _onDidChangeActiveNotebookEditor = new Emitter(); + private _onDidOpenNotebook = new Emitter(); + private _onDidChangeNotebookCell = new Emitter(); - readonly onDidAddDocuments: Event = this._onDidAddDocuments.event; - readonly onDidRemoveDocuments: Event = this._onDidRemoveDocuments.event; readonly onDidChangeVisibleNotebookEditors: Event = this._onDidChangeVisibleNotebookEditors.event; readonly onDidChangeActiveNotebookEditor: Event = this._onDidChangeActiveNotebookEditor.event; + readonly onDidOpenNotebookDocument: Event = this._onDidOpenNotebook.event; + readonly onDidChangeNotebookCell: Event = this._onDidChangeNotebookCell.event; + constructor( private readonly _mainContext: IMainContext, @@ -81,7 +82,8 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume this._proxy, resource, data.providerId, - data.isDirty + data.isDirty, + data.cells ); this._documents.set(resource.toString(), documentData); addedDocuments.push(documentData); @@ -122,11 +124,11 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume dispose(removedEditors); // now that the internal state is complete, fire events - if (delta.removedDocuments) { - this._onDidRemoveDocuments.fire(removedDocuments); + if (removedDocuments) { + // TODO add doc close event } - if (delta.addedDocuments) { - this._onDidAddDocuments.fire(addedDocuments); + if (addedDocuments) { + addedDocuments.forEach(d => this._onDidOpenNotebook.fire(d.document)); } if (delta.removedEditors || delta.addedEditors) { @@ -136,6 +138,21 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume this._onDidChangeActiveNotebookEditor.fire(this.getActiveEditor()); } } + + $acceptModelChanged(uriComponents: UriComponents, e: INotebookModelChangedData): void { + const uri = URI.revive(uriComponents); + const strURL = uri.toString(); + let data = this._documents.get(strURL); + if (data) { + data.onModelChanged(e); + this._onDidChangeNotebookCell.fire({ + cells: data.document.cells, + notebook: data.document, + kind: undefined + }); + } + } + //#endregion //#region Extension accessible methods diff --git a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts index 8068c9a015..2e6855e21f 100644 --- a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts @@ -5,9 +5,11 @@ 'use strict'; import * as sqlops from 'sqlops'; +import * as util from 'util'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import URI, { UriComponents } from 'vs/base/common/uri'; +import { Event, Emitter } from 'vs/base/common/event'; import { IExtHostContext, IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; @@ -18,7 +20,7 @@ import { Schemas } from 'vs/base/common/network'; import { SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape, - INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData + INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData } from 'sql/workbench/api/node/sqlExtHost.protocol'; import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput'; import { INotebookService, INotebookEditor } from 'sql/services/notebook/notebookService'; @@ -26,11 +28,17 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; import { disposed } from 'vs/base/common/errors'; +import { ICellModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces'; class MainThreadNotebookEditor extends Disposable { + private _contentChangedEmitter = new Emitter(); + public readonly contentChanged: Event = this._contentChangedEmitter.event; constructor(public readonly editor: INotebookEditor) { super(); + editor.modelReady.then(model => { + this._register(model.contentChanged((e) => this._contentChangedEmitter.fire(e))); + }); } public get uri(): URI { @@ -49,6 +57,10 @@ class MainThreadNotebookEditor extends Disposable { return this.editor.notebookParams.providerId; } + public get cells(): ICellModel[] { + return this.editor.cells; + } + public save(): Thenable { return this.editor.save(); } @@ -253,7 +265,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements private _proxy: ExtHostNotebookDocumentsAndEditorsShape; private _notebookEditors = new Map(); - + private _modelToDisposeMap = new Map(); constructor( extHostContext: IExtHostContext, @IInstantiationService private _instantiationService: IInstantiationService, @@ -398,8 +410,34 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements if (!empty) { this._proxy.$acceptDocumentsAndEditorsDelta(extHostDelta); + this.processRemovedDocs(removedDocuments); + this.processAddedDocs(addedEditors); } } + processRemovedDocs(removedDocuments: URI[]): void { + if (!removedDocuments) { + return; + } + removedDocuments.forEach(removedDoc => { + let listener = this._modelToDisposeMap.get(removedDoc.toString()); + if (listener) { + listener.dispose(); + this._modelToDisposeMap.delete(removedDoc.toString()); + } + }); + } + + processAddedDocs(addedEditors: MainThreadNotebookEditor[]): any { + if (!addedEditors) { + return; + } + addedEditors.forEach(editor => { + let modelUrl = editor.uri; + this._modelToDisposeMap.set(editor.uri.toString(), editor.contentChanged((e) => { + this._proxy.$acceptModelChanged(modelUrl, this._toNotebookChangeData(e, editor)); + })); + }); + } private _toNotebookEditorAddData(editor: MainThreadNotebookEditor): INotebookEditorAddData { let addData: INotebookEditorAddData = { @@ -414,8 +452,54 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements let addData: INotebookModelAddedData = { uri: editor.uri, isDirty: editor.isDirty, - providerId: editor.providerId + providerId: editor.providerId, + cells: this.convertCellModelToNotebookCell(editor.cells) }; return addData; } + + private _toNotebookChangeData(e: NotebookContentChange, editor: MainThreadNotebookEditor): INotebookModelChangedData { + let changeData: INotebookModelChangedData = { + // Note: we just send all cells for now, not a diff + cells: this.convertCellModelToNotebookCell(editor.cells), + isDirty: e.isDirty, + providerId: editor.providerId, + uri: editor.uri + }; + return changeData; + } + + private convertCellModelToNotebookCell(cells: ICellModel | ICellModel[]): sqlops.nb.NotebookCell[] { + let notebookCells: sqlops.nb.NotebookCell[] = []; + if (Array.isArray(cells)) { + for (let cell of cells) { + notebookCells.push({ + uri: cell.cellUri, + contents: { + cell_type: cell.cellType, + execution_count: undefined, + metadata: { + language: cell.language + }, + source: undefined + + } + }); + } + } + else { + notebookCells.push({ + uri: cells.cellUri, + contents: { + cell_type: cells.cellType, + execution_count: undefined, + metadata: { + language: cells.language + }, + source: undefined + } + }); + } + return notebookCells; + } } diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index 4cf6b27a46..cef6f96d6e 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -432,10 +432,10 @@ export function createApiFactory( return extHostNotebookDocumentsAndEditors.getAllEditors(); }, get onDidOpenNotebookDocument() { - return extHostNotebook.onDidOpenNotebookDocument; + return extHostNotebookDocumentsAndEditors.onDidOpenNotebookDocument; }, get onDidChangeNotebookCell() { - return extHostNotebook.onDidChangeNotebookCell; + return extHostNotebookDocumentsAndEditors.onDidChangeNotebookCell; }, showNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions) { return extHostNotebookDocumentsAndEditors.showNotebookDocument(uri, showOptions); diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index 9e9839a8d1..23aab8334a 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -797,6 +797,14 @@ export interface INotebookModelAddedData { uri: UriComponents; providerId: string; isDirty: boolean; + cells: sqlops.nb.NotebookCell[]; +} + +export interface INotebookModelChangedData { + uri: UriComponents; + providerId: string; + isDirty: boolean; + cells: sqlops.nb.NotebookCell[]; } export interface INotebookEditorAddData { @@ -815,6 +823,7 @@ export interface INotebookShowOptions { export interface ExtHostNotebookDocumentsAndEditorsShape { $acceptDocumentsAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void; + $acceptModelChanged(strURL: UriComponents, e: INotebookModelChangedData); } export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable { diff --git a/src/sqltest/parts/notebook/common.ts b/src/sqltest/parts/notebook/common.ts index dc787c1843..8302593538 100644 --- a/src/sqltest/parts/notebook/common.ts +++ b/src/sqltest/parts/notebook/common.ts @@ -8,7 +8,7 @@ import { nb, IConnectionProfile } from 'sqlops'; import { Event, Emitter } from 'vs/base/common/event'; -import { INotebookModel, ICellModel, IClientSession, IDefaultConnection } from 'sql/parts/notebook/models/modelInterfaces'; +import { INotebookModel, ICellModel, IClientSession, IDefaultConnection, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces'; import { NotebookChangeType, CellType } from 'sql/parts/notebook/models/contracts'; import { INotebookManager } from 'sql/services/notebook/notebookService'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; @@ -44,6 +44,9 @@ export class NotebookModelStub implements INotebookModel { get contextsChanged(): Event { throw new Error('method not implemented.'); } + get contentChanged(): Event { + throw new Error('method not implemented.'); + } get specs(): nb.IAllKernels { throw new Error('method not implemented.'); }