diff --git a/extensions/notebook/README.md b/extensions/notebook/README.md
new file mode 100644
index 0000000000..67d15dad7d
--- /dev/null
+++ b/extensions/notebook/README.md
@@ -0,0 +1,17 @@
+# Notebook extension for Azure Data Studio
+
+Welcome to the Notebook extension for Azure Data Studio! This extension supports core notebook functionality including configuration settings, actions such as New / Open Notebook, and more.
+
+## Code of Conduct
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+
+## Privacy Statement
+
+The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement) describes the privacy statement of this software.
+
+## License
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt).
diff --git a/extensions/notebook/package.json b/extensions/notebook/package.json
new file mode 100644
index 0000000000..835624dab7
--- /dev/null
+++ b/extensions/notebook/package.json
@@ -0,0 +1,71 @@
+{
+ "name": "notebook",
+ "displayName": "%displayName%",
+ "description": "%description%",
+ "version": "0.1.0",
+ "publisher": "Microsoft",
+ "engines": {
+ "vscode": "*",
+ "sqlops": "*"
+ },
+ "main": "./out/extension",
+ "activationEvents": [
+ "*"
+ ],
+ "contributes": {
+ "configuration": {
+ "type": "object",
+ "title": "%notebook.configuration.title%",
+ "properties": {
+ "notebook.enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "%notebook.enabled.description%"
+ }
+ }
+ },
+ "commands": [
+ {
+ "command": "notebook.command.new",
+ "title": "%notebook.command.new%",
+ "icon": {
+ "dark": "resources/dark/new_notebook_inverse.svg",
+ "light": "resources/light/new_notebook.svg"
+ }
+ },
+ {
+ "command": "notebook.command.open",
+ "title": "%notebook.command.open%",
+ "icon": {
+ "dark": "resources/dark/open_notebook_inverse.svg",
+ "light": "resources/light/open_notebook.svg"
+ }
+ }
+ ],
+ "menus": {
+ "commandPalette": [
+ {
+ "command": "notebook.command.new",
+ "when": "config.notebook.enabled"
+ },
+ {
+ "command": "notebook.command.open",
+ "when": "config.notebook.enabled"
+ }
+ ]
+ },
+ "keybindings": [
+ {
+ "command": "notebook.command.new",
+ "key": "Ctrl+Shift+N",
+ "when": "config.notebook.enabled"
+ }
+ ]
+ },
+ "dependencies": {
+ "vscode-nls": "^4.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "8.0.33"
+ }
+}
diff --git a/extensions/notebook/package.nls.json b/extensions/notebook/package.nls.json
new file mode 100644
index 0000000000..5113ee3aab
--- /dev/null
+++ b/extensions/notebook/package.nls.json
@@ -0,0 +1,8 @@
+{
+ "displayName": "Notebook Core Extensions",
+ "description": "Defines the Data-procotol based Notebook contribution and many Notebook commands and contributions.",
+ "notebook.configuration.title": "Notebook configuration",
+ "notebook.enabled.description": "Enable viewing notebook files using built-in notebook editor.",
+ "notebook.command.new": "New Notebook",
+ "notebook.command.open": "Open Notebook"
+}
\ No newline at end of file
diff --git a/extensions/notebook/resources/dark/new_notebook_inverse.svg b/extensions/notebook/resources/dark/new_notebook_inverse.svg
new file mode 100755
index 0000000000..e0072afee1
--- /dev/null
+++ b/extensions/notebook/resources/dark/new_notebook_inverse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/notebook/resources/dark/open_notebook_inverse.svg b/extensions/notebook/resources/dark/open_notebook_inverse.svg
new file mode 100755
index 0000000000..a95750c49f
--- /dev/null
+++ b/extensions/notebook/resources/dark/open_notebook_inverse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/notebook/resources/light/new_notebook.svg b/extensions/notebook/resources/light/new_notebook.svg
new file mode 100755
index 0000000000..9618487568
--- /dev/null
+++ b/extensions/notebook/resources/light/new_notebook.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/notebook/resources/light/open_notebook.svg b/extensions/notebook/resources/light/open_notebook.svg
new file mode 100755
index 0000000000..0041ae9b21
--- /dev/null
+++ b/extensions/notebook/resources/light/open_notebook.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/notebook/src/extension.ts b/extensions/notebook/src/extension.ts
new file mode 100644
index 0000000000..7ff0c3e292
--- /dev/null
+++ b/extensions/notebook/src/extension.ts
@@ -0,0 +1,50 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+'use strict';
+
+import * as vscode from 'vscode';
+import * as sqlops from 'sqlops';
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+let counter = 0;
+
+export function activate(extensionContext: vscode.ExtensionContext) {
+ extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.new', () => {
+ let title = `Untitled-${counter++}`;
+ let untitledUri = vscode.Uri.parse(`untitled:${title}`);
+ sqlops.nb.showNotebookDocument(untitledUri).then(success => {
+
+ }, (err: Error) => {
+ vscode.window.showErrorMessage(err.message);
+ });
+ }));
+ extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.open', () => {
+ openNotebook();
+ }));
+
+}
+
+async function openNotebook(): Promise {
+ try {
+ let filter = {};
+ // TODO support querying valid notebook file types
+ filter[localize('notebookFiles', 'Notebooks')] = ['ipynb'];
+ let file = await vscode.window.showOpenDialog({
+ filters: filter
+ });
+ if (file) {
+ let doc = await vscode.workspace.openTextDocument(file[0]);
+ vscode.window.showTextDocument(doc);
+ }
+ } catch (err) {
+ vscode.window.showErrorMessage(err);
+ }
+}
+
+// this method is called when your extension is deactivated
+export function deactivate() {
+}
diff --git a/extensions/notebook/src/typings/refs.d.ts b/extensions/notebook/src/typings/refs.d.ts
new file mode 100644
index 0000000000..ee283e0c24
--- /dev/null
+++ b/extensions/notebook/src/typings/refs.d.ts
@@ -0,0 +1,9 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+///
+///
+///
+///
diff --git a/extensions/notebook/tsconfig.json b/extensions/notebook/tsconfig.json
new file mode 100644
index 0000000000..b341a65dab
--- /dev/null
+++ b/extensions/notebook/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compileOnSave": true,
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "outDir": "./out",
+ "lib": [
+ "es6", "es2015.promise"
+ ],
+ "typeRoots": [
+ "./node_modules/@types"
+ ],
+ "sourceMap": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "moduleResolution": "node",
+ "declaration": true
+ },
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/extensions/notebook/yarn.lock b/extensions/notebook/yarn.lock
new file mode 100644
index 0000000000..6767cb8d8c
--- /dev/null
+++ b/extensions/notebook/yarn.lock
@@ -0,0 +1,13 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@types/node@8.0.33":
+ version "8.0.33"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.33.tgz#1126e94374014e54478092830704f6ea89df04cd"
+ integrity sha512-vmCdO8Bm1ExT+FWfC9sd9r4jwqM7o97gGy2WBshkkXbf/2nLAJQUrZfIhw27yVOtLUev6kSZc4cav/46KbDd8A==
+
+vscode-nls@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
+ integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==
diff --git a/src/sql/parts/common/customInputConverter.ts b/src/sql/parts/common/customInputConverter.ts
index 70b74c766b..b9f22db380 100644
--- a/src/sql/parts/common/customInputConverter.ts
+++ b/src/sql/parts/common/customInputConverter.ts
@@ -19,6 +19,7 @@ import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
import { DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
+import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
const fs = require('fs');
@@ -183,17 +184,6 @@ function getNotebookFileExtensions() {
return notebookRegistry.getSupportedFileExtensions();
}
-function getProviderForFileName(fileName: string) {
- let fileExt = path.extname(fileName);
- if (fileExt && fileExt.startsWith('.')) {
- fileExt = fileExt.slice(1,fileExt.length);
- let notebookRegistry = Registry.as(Extensions.NotebookProviderContribution);
- return notebookRegistry.getProviderForFileType(fileExt);
- }
- return DEFAULT_NOTEBOOK_PROVIDER;
-}
-
-
/**
* Checks whether the given EditorInput is set to either undefined or sql mode
* @param input The EditorInput to check the mode of
diff --git a/src/sql/parts/notebook/models/cell.ts b/src/sql/parts/notebook/models/cell.ts
index 837ab67944..0be6bbd7c9 100644
--- a/src/sql/parts/notebook/models/cell.ts
+++ b/src/sql/parts/notebook/models/cell.ts
@@ -35,7 +35,7 @@ export class CellModel implements ICellModel {
private _active: boolean;
private _cellUri: URI;
- constructor(private factory: IModelFactory, cellData?: nb.ICell, private _options?: ICellModelOptions) {
+ constructor(private factory: IModelFactory, cellData?: nb.ICellContents, private _options?: ICellModelOptions) {
this.id = `${modelId++}`;
CellModel.CreateLanguageMappings();
// Do nothing for now
@@ -263,8 +263,8 @@ export class CellModel implements ICellModel {
return transient['display_id'] as string;
}
- public toJSON(): nb.ICell {
- let cellJson: Partial = {
+ public toJSON(): nb.ICellContents {
+ let cellJson: Partial = {
cell_type: this._cellType,
source: this._source,
metadata: {
@@ -275,10 +275,10 @@ export class CellModel implements ICellModel {
cellJson.outputs = this._outputs;
cellJson.execution_count = 1; // TODO: keep track of actual execution count
}
- return cellJson as nb.ICell;
+ return cellJson as nb.ICellContents;
}
- public fromJSON(cell: nb.ICell): void {
+ public fromJSON(cell: nb.ICellContents): void {
if (!cell) {
return;
}
diff --git a/src/sql/parts/notebook/models/modelFactory.ts b/src/sql/parts/notebook/models/modelFactory.ts
index 37dfca2639..d224b6fa7e 100644
--- a/src/sql/parts/notebook/models/modelFactory.ts
+++ b/src/sql/parts/notebook/models/modelFactory.ts
@@ -13,7 +13,7 @@ import { ClientSession } from './clientSession';
export class ModelFactory implements IModelFactory {
- public createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel {
+ public createCell(cell: nb.ICellContents, options: ICellModelOptions): ICellModel {
return new CellModel(this, cell, options);
}
diff --git a/src/sql/parts/notebook/models/modelInterfaces.ts b/src/sql/parts/notebook/models/modelInterfaces.ts
index 1af66e4b48..b3e79c4641 100644
--- a/src/sql/parts/notebook/models/modelInterfaces.ts
+++ b/src/sql/parts/notebook/models/modelInterfaces.ts
@@ -348,7 +348,7 @@ export interface ICellModel {
readonly onOutputsChanged: Event>;
setFuture(future: FutureInternal): void;
equals(cellModel: ICellModel): boolean;
- toJSON(): nb.ICell;
+ toJSON(): nb.ICellContents;
}
export interface FutureInternal extends nb.IFuture {
@@ -357,7 +357,7 @@ export interface FutureInternal extends nb.IFuture {
export interface IModelFactory {
- createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel;
+ createCell(cell: nb.ICellContents, options: ICellModelOptions): ICellModel;
createClientSession(options: IClientSessionOptions): IClientSession;
}
diff --git a/src/sql/parts/notebook/models/notebookModel.ts b/src/sql/parts/notebook/models/notebookModel.ts
index b4f1ef60d0..863b8789be 100644
--- a/src/sql/parts/notebook/models/notebookModel.ts
+++ b/src/sql/parts/notebook/models/notebookModel.ts
@@ -237,7 +237,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
private createCell(cellType: CellType): ICellModel {
- let singleCell: nb.ICell = {
+ let singleCell: nb.ICellContents = {
cell_type: cellType,
source: '',
metadata: {},
@@ -389,7 +389,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
// Get default language if saved in notebook file
// Otherwise, default to python
- private getDefaultLanguageInfo(notebook: nb.INotebook): nb.ILanguageInfo {
+ private getDefaultLanguageInfo(notebook: nb.INotebookContents): nb.ILanguageInfo {
return notebook!.metadata!.language_info || {
name: 'python',
version: '',
@@ -398,7 +398,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
// Get default kernel info if saved in notebook file
- private getSavedKernelInfo(notebook: nb.INotebook): nb.IKernelInfo {
+ private getSavedKernelInfo(notebook: nb.INotebookContents): nb.IKernelInfo {
return notebook!.metadata!.kernelspec;
}
@@ -490,8 +490,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
/**
* Serialize the model to JSON.
*/
- toJSON(): nb.INotebook {
- let cells: nb.ICell[] = this.cells.map(c => c.toJSON());
+ toJSON(): nb.INotebookContents {
+ let cells: nb.ICellContents[] = this.cells.map(c => c.toJSON());
let metadata = Object.create(null) as nb.INotebookMetadata;
// TODO update language and kernel when these change
metadata.kernelspec = this._savedKernelInfo;
diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts
index add5631e96..44890a16d1 100644
--- a/src/sql/parts/notebook/notebook.component.ts
+++ b/src/sql/parts/notebook/notebook.component.ts
@@ -7,7 +7,7 @@ import './notebookStyles';
import { nb } from 'sqlops';
-import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild } from '@angular/core';
+import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnDestroy } from '@angular/core';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
@@ -20,7 +20,7 @@ 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 { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
-import { INotebookService, INotebookParams, INotebookManager } from 'sql/services/notebook/notebookService';
+import { INotebookService, INotebookParams, INotebookManager, INotebookEditor } from 'sql/services/notebook/notebookService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { NotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
@@ -48,7 +48,7 @@ export const NOTEBOOK_SELECTOR: string = 'notebook-component';
selector: NOTEBOOK_SELECTOR,
templateUrl: decodeURI(require.toUrl('./notebook.component.html'))
})
-export class NotebookComponent extends AngularDisposable implements OnInit {
+export class NotebookComponent extends AngularDisposable implements OnInit, OnDestroy, INotebookEditor {
@ViewChild('toolbar', { read: ElementRef }) private toolbar: ElementRef;
private _model: NotebookModel;
private _isInErrorState: boolean = false;
@@ -73,7 +73,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
@Inject(IEditorService) private editorService: IEditorService,
@Inject(INotificationService) private notificationService: INotificationService,
@Inject(INotebookService) private notebookService: INotebookService,
- @Inject(IBootstrapParams) private notebookParams: INotebookParams,
+ @Inject(IBootstrapParams) private _notebookParams: INotebookParams,
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@@ -108,10 +108,17 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
+ this.notebookService.addNotebookEditor(this);
this.initActionBar();
this.doLoad();
}
+ ngOnDestroy() {
+ if (this.notebookService) {
+ this.notebookService.removeNotebookEditor(this);
+ }
+ }
+
public get model(): NotebookModel {
return this._model;
}
@@ -201,16 +208,16 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
}
private async loadModel(): Promise {
- this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this.notebookParams.providerId, this.notebookParams.notebookUri);
+ this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this._notebookParams.providerId, this._notebookParams.notebookUri);
let model = new NotebookModel({
factory: this.modelFactory,
- notebookUri: this.notebookParams.notebookUri,
+ notebookUri: this._notebookParams.notebookUri,
connectionService: this.connectionManagementService,
notificationService: this.notificationService,
notebookManager: this.notebookManager
}, false, this.profile);
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
- await model.requestModelLoad(this.notebookParams.isTrusted);
+ await model.requestModelLoad(this._notebookParams.isTrusted);
model.contentChanged((change) => this.handleContentChanged(change));
this._model = model;
this.updateToolbarComponents(this._model.trustedMode);
@@ -231,10 +238,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
}
private get modelFactory(): IModelFactory {
- if (!this.notebookParams.modelFactory) {
- this.notebookParams.modelFactory = new ModelFactory();
+ if (!this._notebookParams.modelFactory) {
+ this._notebookParams.modelFactory = new ModelFactory();
}
- return this.notebookParams.modelFactory;
+ return this._notebookParams.modelFactory;
}
private handleModelError(notification: INotification): void {
this.notificationService.notify(notification);
@@ -337,4 +344,24 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
return undefined;
}
+ public get notebookParams(): INotebookParams {
+ return this._notebookParams;
+ }
+
+ public get id(): string {
+ return this._notebookParams.notebookUri.toString();
+ }
+
+ isActive(): boolean {
+ return this.editorService.activeEditor === this.notebookParams.input;
+ }
+
+ isVisible(): boolean {
+ let notebookEditor = this.notebookParams.input;
+ return this.editorService.visibleEditors.some(e => e === notebookEditor);
+ }
+
+ isDirty(): boolean {
+ return this.notebookParams.input.isDirty();
+ }
}
diff --git a/src/sql/parts/notebook/notebook.contribution.ts b/src/sql/parts/notebook/notebook.contribution.ts
index c7c1d64f02..66d6e19017 100644
--- a/src/sql/parts/notebook/notebook.contribution.ts
+++ b/src/sql/parts/notebook/notebook.contribution.ts
@@ -4,60 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
-import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
-import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
-import { Action } from 'vs/base/common/actions';
-import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { TPromise } from 'vs/base/common/winjs.base';
-import { Schemas } from 'vs/base/common/network';
-import URI from 'vs/base/common/uri';
-import { localize } from 'vs/nls';
-import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { NotebookInput, NotebookInputModel, notebooksEnabledCondition } from 'sql/parts/notebook/notebookInput';
+import { NotebookInput } from 'sql/parts/notebook/notebookInput';
import { NotebookEditor } from 'sql/parts/notebook/notebookEditor';
-import { CommandsRegistry } from 'vs/platform/commands/common/commands';
-import { INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
-
-const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
-const Extensions = {
- NotebookProviderContribution: 'notebook.providers'
-};
-
-let counter = 0;
-
-/**
- * todo: Will remove this code.
- * This is the entry point to open the new Notebook
- */
-export class NewNotebookAction extends Action {
-
- public static ID = 'workbench.action.newnotebook';
- public static LABEL = localize('workbench.action.newnotebook.description', 'New Notebook');
-
- constructor(
- id: string,
- label: string,
- @IEditorService private _editorService: IEditorService,
- @IInstantiationService private _instantiationService: IInstantiationService
- ) {
- super(id, label);
- }
-
- public run(): TPromise {
- let title = `Untitled-${counter++}`;
- let untitledUri = URI.from({ scheme: Schemas.untitled, path: title });
- let model = new NotebookInputModel(untitledUri, undefined, false, undefined);
- if(!model.providerId)
- {
- let notebookRegistry = Registry.as(Extensions.NotebookProviderContribution);
- model.providerId = notebookRegistry.getProviderForFileType(DEFAULT_NOTEBOOK_FILETYPE);
- }
- let input = this._instantiationService.createInstance(NotebookInput, title, model);
- return this._editorService.openEditor(input, { pinned: true }).then(() => undefined);
- }
-}
// Model View editor registration
const viewModelEditorDescriptor = new EditorDescriptor(
@@ -68,31 +18,3 @@ const viewModelEditorDescriptor = new EditorDescriptor(
Registry.as(EditorExtensions.Editors)
.registerEditor(viewModelEditorDescriptor, [new SyncDescriptor(NotebookInput)]);
-
-// Feature flag for built-in Notebooks. Will be removed in the future.
-const configurationRegistry = Registry.as(ConfigExtensions.Configuration);
-configurationRegistry.registerConfiguration({
- 'id': 'notebook',
- 'title': 'Notebook',
- 'type': 'object',
- 'properties': {
- 'notebook.enabled': {
- 'type': 'boolean',
- 'default': false,
- 'description': localize('notebook.enabledDescription', 'Enable viewing notebook files using built-in notebook editor.')
- }
- }
-});
-
-// this is the entry point to open the new Notebook
-CommandsRegistry.registerCommand(NewNotebookAction.ID, serviceAccessor => {
- serviceAccessor.get(IInstantiationService).createInstance(NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL).run();
-});
-
-MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
- command: {
- id: NewNotebookAction.ID,
- title:NewNotebookAction.LABEL,
- },
- when: notebooksEnabledCondition
-});
\ No newline at end of file
diff --git a/src/sql/parts/notebook/notebookEditor.ts b/src/sql/parts/notebook/notebookEditor.ts
index 3df4253354..ba262f892c 100644
--- a/src/sql/parts/notebook/notebookEditor.ts
+++ b/src/sql/parts/notebook/notebookEditor.ts
@@ -86,6 +86,7 @@ export class NotebookEditor extends BaseEditor {
input.hasBootstrapped = true;
let params: INotebookParams = {
notebookUri: input.notebookUri,
+ input: input,
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER,
isTrusted: input.isTrusted
};
diff --git a/src/sql/parts/notebook/notebookInput.ts b/src/sql/parts/notebook/notebookInput.ts
index c65d0632e3..ec5b3f82c3 100644
--- a/src/sql/parts/notebook/notebookInput.ts
+++ b/src/sql/parts/notebook/notebookInput.ts
@@ -11,6 +11,7 @@ import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/edi
import { Emitter, Event } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
+import * as resources from 'vs/base/common/resources';
import { INotebookService } from 'sql/services/notebook/notebookService';
@@ -88,15 +89,6 @@ export class NotebookInput extends EditorInput {
) {
super();
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
- this.onDispose(() => {
- if (this.notebookService) {
- this.notebookService.handleNotebookClosed(this.notebookUri);
- }
- });
- }
-
- public get title(): string {
- return this._title;
}
public get notebookUri(): URI {
@@ -116,6 +108,10 @@ export class NotebookInput extends EditorInput {
}
public getName(): string {
+ if (!this._title) {
+ this._title = resources.basenameOrAuthority(this._model.notebookUri);
+ }
+
return this._title;
}
diff --git a/src/sql/parts/notebook/notebookUtils.ts b/src/sql/parts/notebook/notebookUtils.ts
index d250f5c4bb..9fd25beb05 100644
--- a/src/sql/parts/notebook/notebookUtils.ts
+++ b/src/sql/parts/notebook/notebookUtils.ts
@@ -5,11 +5,15 @@
'use strict';
+import * as path from 'path';
import { nb } from 'sqlops';
import * as os from 'os';
import * as pfs from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { IOutputChannel } from 'vs/workbench/parts/output/common/output';
+import { Registry } from 'vs/platform/registry/common/platform';
+import { INotebookProviderRegistry, Extensions } from 'sql/services/notebook/notebookRegistry';
+import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE } from 'sql/services/notebook/notebookService';
/**
@@ -36,3 +40,23 @@ export async function mkDir(dirPath: string, outputChannel?: IOutputChannel): Pr
await pfs.mkdirp(dirPath);
}
}
+
+export function getProviderForFileName(fileName: string): string {
+ let fileExt = path.extname(fileName);
+ let provider: string;
+ let notebookRegistry = Registry.as(Extensions.NotebookProviderContribution);
+ // First try to get provider for actual file type
+ if (fileExt && fileExt.startsWith('.')) {
+ fileExt = fileExt.slice(1,fileExt.length);
+ provider = notebookRegistry.getProviderForFileType(fileExt);
+ }
+ // Fallback to provider for default file type (assume this is a global handler)
+ if (!provider) {
+ provider = notebookRegistry.getProviderForFileType(DEFAULT_NOTEBOOK_FILETYPE);
+ }
+ // Finally if all else fails, use the built-in handler
+ if (!provider) {
+ provider = DEFAULT_NOTEBOOK_PROVIDER;
+ }
+ return provider;
+}
diff --git a/src/sql/services/notebook/localContentManager.ts b/src/sql/services/notebook/localContentManager.ts
index 520652f8b0..41ac81c79e 100644
--- a/src/sql/services/notebook/localContentManager.ts
+++ b/src/sql/services/notebook/localContentManager.ts
@@ -12,10 +12,9 @@ import * as pfs from 'vs/base/node/pfs';
import URI from 'vs/base/common/uri';
import ContentManager = nb.ContentManager;
-import INotebook = nb.INotebook;
export class LocalContentManager implements ContentManager {
- public async getNotebookContents(notebookUri: URI): Promise {
+ public async getNotebookContents(notebookUri: URI): Promise {
if (!notebookUri) {
return undefined;
}
@@ -23,10 +22,10 @@ export class LocalContentManager implements ContentManager {
let path = notebookUri.fsPath;
// Note: intentionally letting caller handle exceptions
let notebookFileBuffer = await pfs.readFile(path);
- return json.parse(notebookFileBuffer.toString());
+ return json.parse(notebookFileBuffer.toString());
}
- public async save(notebookUri: URI, notebook: INotebook): Promise {
+ public async save(notebookUri: URI, notebook: nb.INotebookContents): Promise {
// Convert to JSON with pretty-print functionality
let contents = JSON.stringify(notebook, undefined, ' ');
let path = notebookUri.fsPath;
diff --git a/src/sql/services/notebook/notebookService.ts b/src/sql/services/notebook/notebookService.ts
index 3a7c9b04e7..784ac224b1 100644
--- a/src/sql/services/notebook/notebookService.ts
+++ b/src/sql/services/notebook/notebookService.ts
@@ -6,21 +6,28 @@
'use strict';
import * as sqlops from 'sqlops';
+
+import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import URI from 'vs/base/common/uri';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
+import { NotebookInput } from 'sql/parts/notebook/notebookInput';
export const SERVICE_ID = 'notebookService';
export const INotebookService = createDecorator(SERVICE_ID);
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
+export const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
export interface INotebookService {
_serviceBrand: any;
+ onNotebookEditorAdd: Event;
+ onNotebookEditorRemove: Event;
+
/**
* Register a metadata provider
*/
@@ -40,7 +47,11 @@ export interface INotebookService {
*/
getOrCreateNotebookManager(providerId: string, uri: URI): Thenable;
- handleNotebookClosed(uri: URI): void;
+ addNotebookEditor(editor: INotebookEditor): void;
+
+ removeNotebookEditor(editor: INotebookEditor): void;
+
+ listNotebookEditors(): INotebookEditor[];
shutdown(): void;
@@ -62,8 +73,18 @@ export interface INotebookManager {
export interface INotebookParams extends IBootstrapParams {
notebookUri: URI;
+ input: NotebookInput;
providerId: string;
isTrusted: boolean;
profile?: IConnectionProfile;
modelFactory?: ModelFactory;
+}
+
+export interface INotebookEditor {
+ readonly notebookParams: INotebookParams;
+ readonly id: string;
+ isDirty(): boolean;
+ isActive(): boolean;
+ isVisible(): boolean;
+ save(): Promise;
}
\ No newline at end of file
diff --git a/src/sql/services/notebook/notebookServiceImpl.ts b/src/sql/services/notebook/notebookServiceImpl.ts
index d7c4e7ffd6..836ca7c579 100644
--- a/src/sql/services/notebook/notebookServiceImpl.ts
+++ b/src/sql/services/notebook/notebookServiceImpl.ts
@@ -10,20 +10,26 @@ import { localize } from 'vs/nls';
import URI from 'vs/base/common/uri';
import { Registry } from 'vs/platform/registry/common/platform';
-import { INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
+import {
+ INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER,
+ DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor
+} from 'sql/services/notebook/notebookService';
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
import { SessionManager } from 'sql/services/notebook/sessionManager';
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
+import { Emitter, Event } from 'vs/base/common/event';
-const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
export class NotebookService implements INotebookService {
_serviceBrand: any;
private _mimeRegistry: RenderMimeRegistry;
private _providers: Map = new Map();
private _managers: Map = new Map();
+ private _onNotebookEditorAdd = new Emitter();
+ private _onNotebookEditorRemove = new Emitter();
+ private _editors = new Map();
constructor() {
this.registerDefaultProvider();
@@ -71,8 +77,34 @@ export class NotebookService implements INotebookService {
return manager;
}
- handleNotebookClosed(notebookUri: URI): void {
+ get onNotebookEditorAdd(): Event {
+ return this._onNotebookEditorAdd.event;
+ }
+ get onNotebookEditorRemove(): Event {
+ return this._onNotebookEditorRemove.event;
+ }
+
+ addNotebookEditor(editor: INotebookEditor): void {
+ this._editors.set(editor.id, editor);
+ this._onNotebookEditorAdd.fire(editor);
+ }
+
+ removeNotebookEditor(editor: INotebookEditor): void {
+ if (this._editors.delete(editor.id)) {
+ this._onNotebookEditorRemove.fire(editor);
+ }
// Remove the manager from the tracked list, and let the notebook provider know that it should update its mappings
+ this.sendNotebookCloseToProvider(editor);
+ }
+
+ listNotebookEditors(): INotebookEditor[] {
+ let editors = [];
+ this._editors.forEach(e => editors.push(e));
+ return editors;
+ }
+
+ private sendNotebookCloseToProvider(editor: INotebookEditor) {
+ let notebookUri = editor.notebookParams.notebookUri;
let uriString = notebookUri.toString();
let manager = this._managers.get(uriString);
if (manager) {
@@ -82,7 +114,6 @@ export class NotebookService implements INotebookService {
}
}
-
// PRIVATE HELPERS /////////////////////////////////////////////////////
private doWithProvider(providerId: string, op: (provider: INotebookProvider) => Thenable): Thenable {
// Make sure the provider exists before attempting to retrieve accounts
@@ -109,8 +140,6 @@ export class NotebookService implements INotebookService {
}
return this._mimeRegistry;
}
-
-
}
export class BuiltinProvider implements INotebookProvider {
diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts
index a9eb3d71c2..db00f795a5 100644
--- a/src/sql/sqlops.proposed.d.ts
+++ b/src/sql/sqlops.proposed.d.ts
@@ -1368,6 +1368,201 @@ declare module 'sqlops' {
}
export namespace nb {
+ /**
+ * All notebook documents currently known to the system.
+ *
+ * @readonly
+ */
+ export let notebookDocuments: NotebookDocument[];
+
+ /**
+ * The currently active Notebook editor or `undefined`. The active editor is the one
+ * that currently has focus or, when none has focus, the one that has changed
+ * input most recently.
+ */
+ export let activeNotebookEditor: NotebookEditor | undefined;
+
+ /**
+ * The currently visible editors or an empty array.
+ */
+ export let visibleNotebookEditors: NotebookEditor[];
+
+ /**
+ * An event that is emitted when a [notebook document](#NotebookDocument) is opened.
+ *
+ * To add an event listener when a visible text document is opened, use the [TextEditor](#TextEditor) events in the
+ * [window](#window) namespace. Note that:
+ *
+ * - The event is emitted before the [document](#NotebookDocument) is updated in the
+ * [active notebook editor](#nb.activeNotebookEditor)
+ * - When a [notebook document](#NotebookDocument) is already open (e.g.: open in another visible notebook editor) this event is not emitted
+ *
+ */
+ export const onDidOpenNotebookDocument: vscode.Event;
+
+ /**
+ * An event that is emitted when a [notebook's](#NotebookDocument) cell contents are changed.
+ */
+ export const onDidChangeNotebookCell: vscode.Event;
+
+ /**
+ * Show the given document in a notebook editor. A [column](#ViewColumn) can be provided
+ * to control where the editor is being shown. Might change the [active editor](#nb.activeNotebookEditor).
+ *
+ * The document is denoted by an [uri](#Uri). Depending on the [scheme](#Uri.scheme) the
+ * following rules apply:
+ * `file`-scheme: Open a file on disk, will be rejected if the file does not exist or cannot be loaded.
+ * `untitled`-scheme: A new file that should be saved on disk, e.g. `untitled:c:\frodo\new.js`. The language
+ * will be derived from the file name.
+ * For all other schemes the registered notebook providers are consulted.
+ *
+ * @param document A document to be shown.
+ * @param column A view column in which the [editor](#NotebookEditor) should be shown. The default is the [active](#ViewColumn.Active), other values
+ * are adjusted to be `Min(column, columnCount + 1)`, the [active](#ViewColumn.Active)-column is not adjusted. Use [`ViewColumn.Beside`](#ViewColumn.Beside)
+ * to open the editor to the side of the currently active one.
+ * @param preserveFocus When `true` the editor will not take focus.
+ * @return A promise that resolves to a [notebook editor](#NotebookEditor).
+ */
+ export function showNotebookDocument(uri: vscode.Uri, showOptions?: NotebookShowOptions): Thenable;
+
+ export interface NotebookDocument {
+ /**
+ * The associated uri for this notebook document.
+ *
+ * *Note* that most documents use the `file`-scheme, which means they are files on disk. However, **not** all documents are
+ * saved on disk and therefore the `scheme` must be checked before trying to access the underlying file or siblings on disk.
+ *
+ */
+ readonly uri: vscode.Uri;
+
+ /**
+ * The file system path of the associated resource. Shorthand
+ * notation for [TextDocument.uri.fsPath](#TextDocument.uri). Independent of the uri scheme.
+ */
+ readonly fileName: string;
+
+ /**
+ * Is this document representing an untitled file which has never been saved yet. *Note* that
+ * this does not mean the document will be saved to disk, use [`uri.scheme`](#Uri.scheme)
+ * to figure out where a document will be [saved](#FileSystemProvider), e.g. `file`, `ftp` etc.
+ */
+ readonly isUntitled: boolean;
+
+ /**
+ * The identifier of the Notebook provider associated with this document.
+ */
+ readonly providerId: string;
+
+ /**
+ * `true` if there are unpersisted changes.
+ */
+ readonly isDirty: boolean;
+ /**
+ * `true` if the document have been closed. A closed document isn't synchronized anymore
+ * and won't be re-used when the same resource is opened again.
+ */
+ readonly isClosed: boolean;
+
+ /**
+ * All cells.
+ */
+ readonly cells: NotebookCell[];
+
+ /**
+ * Save the underlying file.
+ *
+ * @return A promise that will resolve to true when the file
+ * has been saved. If the file was not dirty or the save failed,
+ * will return false.
+ */
+ save(): Thenable;
+ }
+
+ export interface NotebookEditor {
+ /**
+ * The document associated with this editor. The document will be the same for the entire lifetime of this editor.
+ */
+ readonly document: NotebookDocument;
+ /**
+ * The column in which this editor shows. Will be `undefined` in case this
+ * isn't one of the main editors, e.g an embedded editor, or when the editor
+ * column is larger than three.
+ */
+ viewColumn?: vscode.ViewColumn;
+ }
+
+ export interface NotebookCell {
+ contents: ICellContents;
+ }
+
+ export interface NotebookShowOptions {
+ /**
+ * An optional view column in which the [editor](#NotebookEditor) should be shown.
+ * The default is the [active](#ViewColumn.Active), other values are adjusted to
+ * be `Min(column, columnCount + 1)`, the [active](#ViewColumn.Active)-column is
+ * not adjusted. Use [`ViewColumn.Beside`](#ViewColumn.Beside) to open the
+ * editor to the side of the currently active one.
+ */
+ viewColumn?: vscode.ViewColumn;
+
+ /**
+ * An optional flag that when `true` will stop the [editor](#NotebookEditor) from taking focus.
+ */
+ preserveFocus?: boolean;
+
+ /**
+ * An optional flag that controls if an [editor](#NotebookEditor)-tab will be replaced
+ * with the next editor or if it will be kept.
+ */
+ preview?: boolean;
+
+ /**
+ * An optional string indicating which notebook provider to initially use
+ */
+ providerId?: string;
+
+ /**
+ * Optional ID indicating the initial connection to use for this editor
+ */
+ connectionId?: string;
+ }
+
+ /**
+ * Represents an event describing the change in a [notebook documents's cells](#NotebookDocument.cells).
+ */
+ export interface NotebookCellChangeEvent {
+ /**
+ * The [notebook document](#NotebookDocument) for which the selections have changed.
+ */
+ notebook: NotebookDocument;
+ /**
+ * The new value for the [notebook documents's cells](#NotebookDocument.cells).
+ */
+ cell: NotebookCell[];
+ /**
+ * The [change kind](#TextEditorSelectionChangeKind) which has triggered this
+ * event. Can be `undefined`.
+ */
+ kind?: vscode.TextEditorSelectionChangeKind;
+ }
+
+ /**
+ * Register a notebook provider. The supported file types handled by this
+ * provider are defined in the `package.json:
+ * ```json
+ * {
+ * "contributes": {
+ * "notebook.providers": [{
+ * "provider": "providername",
+ * "fileExtensions": ["FILEEXT"]
+ * }]
+ * }
+ * }
+ * ```
+ * @export
+ * @param {NotebookProvider} provider
+ * @returns {vscode.Disposable}
+ */
export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable;
export interface NotebookProvider {
@@ -1431,7 +1626,7 @@ declare module 'sqlops' {
/* Reads contents from a Uri representing a local or remote notebook and returns a
* JSON object containing the cells and metadata about the notebook
*/
- getNotebookContents(notebookUri: vscode.Uri): Thenable;
+ getNotebookContents(notebookUri: vscode.Uri): Thenable;
/**
* Save a file.
@@ -1443,12 +1638,19 @@ declare module 'sqlops' {
* @returns A thenable which resolves with the file content model when the
* file is saved.
*/
- save(notebookUri: vscode.Uri, notebook: INotebook): Thenable;
+ save(notebookUri: vscode.Uri, notebook: INotebookContents): Thenable;
}
- export interface INotebook {
- readonly cells: ICell[];
+ /**
+ * Interface defining the file format contents of a notebook, usually in a serializable
+ * format. This interface does not have any methods for manipulating or interacting
+ * with a notebook object.
+ *
+ */
+ export interface INotebookContents {
+
+ readonly cells: ICellContents[];
readonly metadata: INotebookMetadata;
readonly nbformat: number;
readonly nbformat_minor: number;
@@ -1477,7 +1679,13 @@ declare module 'sqlops' {
version: string;
}
- export interface ICell {
+ /**
+ * Interface defining the file format contents of a notebook cell, usually in a serializable
+ * format. This interface does not have any methods for manipulating or interacting
+ * with a cell object.
+ *
+ */
+ export interface ICellContents {
cell_type: CellType;
source: string | string[];
metadata: {
diff --git a/src/sql/workbench/api/node/extHostNotebook.ts b/src/sql/workbench/api/node/extHostNotebook.ts
index 0ae03c6fe6..ad609c8289 100644
--- a/src/sql/workbench/api/node/extHostNotebook.ts
+++ b/src/sql/workbench/api/node/extHostNotebook.ts
@@ -15,6 +15,7 @@ import URI, { UriComponents } from 'vs/base/common/uri';
import { ExtHostNotebookShape, MainThreadNotebookShape, SqlMainContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType } from 'sql/workbench/api/common/sqlExtHostTypes';
+import { Event, Emitter } from 'vs/base/common/event';
type Adapter = sqlops.nb.NotebookProvider | sqlops.nb.NotebookManager | sqlops.nb.ISession | sqlops.nb.IKernel | sqlops.nb.IFuture;
@@ -23,6 +24,12 @@ 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) {
this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadNotebook);
@@ -63,11 +70,11 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
return this._withServerManager(managerHandle, (serverManager) => serverManager.stopServer());
}
- $getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable {
+ $getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable {
return this._withContentManager(managerHandle, (contentManager) => contentManager.getNotebookContents(URI.revive(notebookUri)));
}
- $save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable {
+ $save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebookContents): Thenable {
return this._withContentManager(managerHandle, (contentManager) => contentManager.save(URI.revive(notebookUri), notebook));
}
diff --git a/src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts
new file mode 100644
index 0000000000..2e524bcd86
--- /dev/null
+++ b/src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts
@@ -0,0 +1,281 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+import * as sqlops from 'sqlops';
+import * as vscode from 'vscode';
+
+import { Event, Emitter } from 'vs/base/common/event';
+import { readonly } from 'vs/base/common/errors';
+import { dispose, IDisposable } from 'vs/base/common/lifecycle';
+import URI from 'vs/base/common/uri';
+import { Disposable } from 'vs/workbench/api/node/extHostTypes';
+import { Schemas } from 'vs/base/common/network';
+import { TPromise } from 'vs/base/common/winjs.base';
+import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
+import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
+import { ok } from 'vs/base/common/assert';
+
+import {
+ MainThreadNotebookShape, SqlMainContext, INotebookDocumentsAndEditorsDelta,
+ ExtHostNotebookDocumentsAndEditorsShape, MainThreadNotebookDocumentsAndEditorsShape, INotebookShowOptions
+} from 'sql/workbench/api/node/sqlExtHost.protocol';
+
+
+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
+ ) {
+ // TODO add cell mapping support
+ this._cells = [];
+ }
+
+ dispose(): void {
+ // we don't really dispose documents but let
+ // extensions still read from them. some
+ // operations, live saving, will now error tho
+ ok(!this._isDisposed);
+ this._isDisposed = true;
+ this._isDirty = false;
+ }
+
+
+ get document(): sqlops.nb.NotebookDocument {
+ if (!this._document) {
+ const data = this;
+ this._document = {
+ get uri() { return data._uri; },
+ get fileName() { return data._uri.fsPath; },
+ get isUntitled() { return data._uri.scheme === Schemas.untitled; },
+ get providerId() { return data._providerId; },
+ get isClosed() { return data._isDisposed; },
+ get isDirty() { return data._isDirty; },
+ get cells() { return data._cells; },
+ save() { return data._save(); },
+ };
+ }
+ return Object.freeze(this._document);
+ }
+
+ private _save(): Thenable {
+ if (this._isDisposed) {
+ return TPromise.wrapError(new Error('Document has been closed'));
+ }
+ return this._proxy.$trySaveDocument(this._uri);
+
+ }
+}
+
+export class ExtHostNotebookEditor implements sqlops.nb.NotebookEditor, IDisposable {
+ private _disposed: boolean = false;
+
+ constructor(
+ private _proxy: MainThreadNotebookShape,
+ private _id: string,
+ private readonly _documentData: ExtHostNotebookDocumentData,
+ private _viewColumn: vscode.ViewColumn
+ ) {
+
+ }
+
+ dispose() {
+ ok(!this._disposed);
+ this._disposed = true;
+ }
+
+ get document(): sqlops.nb.NotebookDocument {
+ return this._documentData.document;
+ }
+
+ set document(value) {
+ throw readonly('document');
+ }
+
+ get viewColumn(): vscode.ViewColumn {
+ return this._viewColumn;
+ }
+
+ set viewColumn(value) {
+ throw readonly('viewColumn');
+ }
+
+
+ get id(): string {
+ return this._id;
+ }
+}
+
+export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocumentsAndEditorsShape {
+
+ private _disposables: Disposable[] = [];
+
+ private _activeEditorId: string;
+ private _proxy: MainThreadNotebookDocumentsAndEditorsShape;
+
+ 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();
+
+ readonly onDidAddDocuments: Event = this._onDidAddDocuments.event;
+ readonly onDidRemoveDocuments: Event = this._onDidRemoveDocuments.event;
+ readonly onDidChangeVisibleNotebookEditors: Event = this._onDidChangeVisibleNotebookEditors.event;
+ readonly onDidChangeActiveNotebookEditor: Event = this._onDidChangeActiveNotebookEditor.event;
+
+ constructor(
+ private readonly _mainContext: IMainContext,
+ ) {
+ if (this._mainContext) {
+ this._proxy = this._mainContext.getProxy(SqlMainContext.MainThreadNotebookDocumentsAndEditors);
+ }
+ }
+
+ dispose() {
+ this._disposables = dispose(this._disposables);
+ }
+
+ //#region Main Thread accessible methods
+ $acceptDocumentsAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void {
+
+ const removedDocuments: ExtHostNotebookDocumentData[] = [];
+ const addedDocuments: ExtHostNotebookDocumentData[] = [];
+ const removedEditors: ExtHostNotebookEditor[] = [];
+
+ if (delta.removedDocuments) {
+ for (const uriComponent of delta.removedDocuments) {
+ const uri = URI.revive(uriComponent);
+ const id = uri.toString();
+ const data = this._documents.get(id);
+ this._documents.delete(id);
+ removedDocuments.push(data);
+ }
+ }
+
+ if (delta.addedDocuments) {
+ for (const data of delta.addedDocuments) {
+ const resource = URI.revive(data.uri);
+ ok(!this._documents.has(resource.toString()), `document '${resource} already exists!'`);
+
+ const documentData = new ExtHostNotebookDocumentData(
+ this._proxy,
+ resource,
+ data.providerId,
+ data.isDirty
+ );
+ this._documents.set(resource.toString(), documentData);
+ addedDocuments.push(documentData);
+ }
+ }
+
+ if (delta.removedEditors) {
+ for (const id of delta.removedEditors) {
+ const editor = this._editors.get(id);
+ this._editors.delete(id);
+ removedEditors.push(editor);
+ }
+ }
+
+ if (delta.addedEditors) {
+ for (const data of delta.addedEditors) {
+ const resource = URI.revive(data.documentUri);
+ ok(this._documents.has(resource.toString()), `document '${resource}' does not exist`);
+ ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`);
+
+ const documentData = this._documents.get(resource.toString());
+ const editor = new ExtHostNotebookEditor(
+ this._mainContext.getProxy(SqlMainContext.MainThreadNotebook),
+ data.id,
+ documentData,
+ typeConverters.ViewColumn.to(data.editorPosition)
+ );
+ this._editors.set(data.id, editor);
+ }
+ }
+
+ if (delta.newActiveEditor !== undefined) {
+ ok(delta.newActiveEditor === null || this._editors.has(delta.newActiveEditor), `active editor '${delta.newActiveEditor}' does not exist`);
+ this._activeEditorId = delta.newActiveEditor;
+ }
+
+ dispose(removedDocuments);
+ dispose(removedEditors);
+
+ // now that the internal state is complete, fire events
+ if (delta.removedDocuments) {
+ this._onDidRemoveDocuments.fire(removedDocuments);
+ }
+ if (delta.addedDocuments) {
+ this._onDidAddDocuments.fire(addedDocuments);
+ }
+
+ if (delta.removedEditors || delta.addedEditors) {
+ this._onDidChangeVisibleNotebookEditors.fire(this.getAllEditors());
+ }
+ if (delta.newActiveEditor !== undefined) {
+ this._onDidChangeActiveNotebookEditor.fire(this.getActiveEditor());
+ }
+ }
+ //#endregion
+
+ //#region Extension accessible methods
+ showNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions): Thenable {
+ return this.doShowNotebookDocument(uri, showOptions);
+ }
+
+ private async doShowNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions): Promise {
+ let options: INotebookShowOptions = {};
+ if (showOptions) {
+ options.preserveFocus = showOptions.preserveFocus;
+ options.position = showOptions.viewColumn;
+ options.providerId = showOptions.providerId;
+ options.connectionId = showOptions.connectionId;
+ }
+ let id = await this._proxy.$tryShowNotebookDocument(uri, options);
+ let editor = this.getEditor(id);
+ if (editor) {
+ return editor;
+ } else {
+ throw new Error(`Failed to show notebook document ${uri.toString()}, should show in editor #${id}`);
+ }
+ }
+
+ getDocument(strUrl: string): ExtHostNotebookDocumentData {
+ return this._documents.get(strUrl);
+ }
+
+ getAllDocuments(): ExtHostNotebookDocumentData[] {
+ const result: ExtHostNotebookDocumentData[] = [];
+ this._documents.forEach(data => result.push(data));
+ return result;
+ }
+
+ getEditor(id: string): ExtHostNotebookEditor {
+ return this._editors.get(id);
+ }
+
+ getActiveEditor(): ExtHostNotebookEditor | undefined {
+ if (!this._activeEditorId) {
+ return undefined;
+ } else {
+ return this._editors.get(this._activeEditorId);
+ }
+ }
+
+ getAllEditors(): ExtHostNotebookEditor[] {
+ const result: ExtHostNotebookEditor[] = [];
+ this._editors.forEach(data => result.push(data));
+ return result;
+ }
+ //#endregion
+}
diff --git a/src/sql/workbench/api/node/mainThreadNotebook.ts b/src/sql/workbench/api/node/mainThreadNotebook.ts
index cff7da7c89..e45b92c438 100644
--- a/src/sql/workbench/api/node/mainThreadNotebook.ts
+++ b/src/sql/workbench/api/node/mainThreadNotebook.ts
@@ -153,11 +153,11 @@ class ContentManagerWrapper implements sqlops.nb.ContentManager {
constructor(private handle: number, private _proxy: Proxies) {
}
- getNotebookContents(notebookUri: URI): Thenable {
+ getNotebookContents(notebookUri: URI): Thenable {
return this._proxy.ext.$getNotebookContents(this.handle, notebookUri);
}
- save(path: URI, notebook: sqlops.nb.INotebook): Thenable {
+ save(path: URI, notebook: sqlops.nb.INotebookContents): Thenable {
return this._proxy.ext.$save(this.handle, path, notebook);
}
}
diff --git a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts
new file mode 100644
index 0000000000..17298df3df
--- /dev/null
+++ b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts
@@ -0,0 +1,378 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+import * as sqlops from 'sqlops';
+import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { Registry } from 'vs/platform/registry/common/platform';
+import URI, { UriComponents } from 'vs/base/common/uri';
+import { IExtHostContext } 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';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
+import { viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor';
+
+import {
+ SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
+ INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData
+} from 'sql/workbench/api/node/sqlExtHost.protocol';
+import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
+import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
+import { TPromise } from 'vs/base/common/winjs.base';
+import { INotebookProviderRegistry, Extensions } from 'sql/services/notebook/notebookRegistry';
+import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
+
+class MainThreadNotebookEditor extends Disposable {
+
+ constructor(public readonly editor: INotebookEditor) {
+ super();
+ }
+
+ public get uri(): URI {
+ return this.editor.notebookParams.notebookUri;
+ }
+
+ public get id(): string {
+ return this.editor.id;
+ }
+
+ public get isDirty(): boolean {
+ return this.editor.isDirty();
+ }
+
+ public get providerId(): string {
+ return this.editor.notebookParams.providerId;
+ }
+
+ public save(): Thenable {
+ return this.editor.save();
+ }
+
+ public matches(input: NotebookInput): boolean {
+ if (!input) {
+ return false;
+ }
+ return input === this.editor.notebookParams.input;
+ }
+}
+
+function wait(timeMs: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, timeMs));
+}
+
+
+namespace mapset {
+
+ export function setValues(set: Set): T[] {
+ // return Array.from(set);
+ let ret: T[] = [];
+ set.forEach(v => ret.push(v));
+ return ret;
+ }
+
+ export function mapValues(map: Map): T[] {
+ // return Array.from(map.values());
+ let ret: T[] = [];
+ map.forEach(v => ret.push(v));
+ return ret;
+ }
+}
+
+namespace delta {
+
+ export function ofSets(before: Set, after: Set): { removed: T[], added: T[] } {
+ const removed: T[] = [];
+ const added: T[] = [];
+ before.forEach(element => {
+ if (!after.has(element)) {
+ removed.push(element);
+ }
+ });
+ after.forEach(element => {
+ if (!before.has(element)) {
+ added.push(element);
+ }
+ });
+ return { removed, added };
+ }
+
+ export function ofMaps(before: Map, after: Map): { removed: V[], added: V[] } {
+ const removed: V[] = [];
+ const added: V[] = [];
+ before.forEach((value, index) => {
+ if (!after.has(index)) {
+ removed.push(value);
+ }
+ });
+ after.forEach((value, index) => {
+ if (!before.has(index)) {
+ added.push(value);
+ }
+ });
+ return { removed, added };
+ }
+}
+
+class NotebookEditorStateDelta {
+
+ readonly isEmpty: boolean;
+
+ constructor(
+ readonly removedEditors: INotebookEditor[],
+ readonly addedEditors: INotebookEditor[],
+ readonly oldActiveEditor: string,
+ readonly newActiveEditor: string,
+ ) {
+ this.isEmpty =
+ this.removedEditors.length === 0
+ && this.addedEditors.length === 0
+ && oldActiveEditor === newActiveEditor;
+ }
+
+ toString(): string {
+ let ret = 'NotebookEditorStateDelta\n';
+ ret += `\tRemoved Editors: [${this.removedEditors.map(e => e.id).join(', ')}]\n`;
+ ret += `\tAdded Editors: [${this.addedEditors.map(e => e.id).join(', ')}]\n`;
+ ret += `\tNew Active Editor: ${this.newActiveEditor}\n`;
+ return ret;
+ }
+}
+
+class NotebookEditorState {
+
+ static compute(before: NotebookEditorState, after: NotebookEditorState): NotebookEditorStateDelta {
+ if (!before) {
+ return new NotebookEditorStateDelta(
+ [], mapset.mapValues(after.textEditors),
+ undefined, after.activeEditor
+ );
+ }
+ const editorDelta = delta.ofMaps(before.textEditors, after.textEditors);
+ const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
+ const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
+
+ return new NotebookEditorStateDelta(
+ editorDelta.removed, editorDelta.added,
+ oldActiveEditor, newActiveEditor
+ );
+ }
+
+ constructor(
+ readonly textEditors: Map,
+ readonly activeEditor: string) { }
+}
+
+class MainThreadNotebookDocumentAndEditorStateComputer extends Disposable {
+
+ private _currentState: NotebookEditorState;
+
+ constructor(
+ private readonly _onDidChangeState: (delta: NotebookEditorStateDelta) => void,
+ @IEditorService private readonly _editorService: IEditorService,
+ @INotebookService private readonly _notebookService: INotebookService
+ ) {
+ super();
+ this._register(this._editorService.onDidActiveEditorChange(this._updateState, this));
+ this._register(this._editorService.onDidVisibleEditorsChange(this._updateState, this));
+ this._register(this._notebookService.onNotebookEditorAdd(this._onDidAddEditor, this));
+ this._register(this._notebookService.onNotebookEditorRemove(this._onDidRemoveEditor, this));
+
+ this._updateState();
+ }
+
+ private _onDidAddEditor(e: INotebookEditor): void {
+ // TODO hook to cell change and other events
+ this._updateState();
+ }
+
+ private _onDidRemoveEditor(e: INotebookEditor): void {
+ // TODO remove event listeners
+ this._updateState();
+ }
+
+ private _updateState(): void {
+ // editor
+ const editors = new Map();
+ let activeEditor: string = undefined;
+
+ for (const editor of this._notebookService.listNotebookEditors()) {
+ editors.set(editor.id, editor);
+ if (editor.isActive()) {
+ activeEditor = editor.id;
+ }
+ }
+
+ // compute new state and compare against old
+ const newState = new NotebookEditorState(editors, activeEditor);
+ const delta = NotebookEditorState.compute(this._currentState, newState);
+ if (!delta.isEmpty) {
+ this._currentState = newState;
+ this._onDidChangeState(delta);
+ }
+ }
+}
+
+@extHostNamedCustomer(SqlMainContext.MainThreadNotebookDocumentsAndEditors)
+export class MainThreadNotebookDocumentsAndEditors extends Disposable implements MainThreadNotebookDocumentsAndEditorsShape {
+ private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
+ private _notebookEditors = new Map();
+
+ constructor(
+ extHostContext: IExtHostContext,
+ @IInstantiationService private _instantiationService: IInstantiationService,
+ @IEditorService private _editorService: IEditorService,
+ @IEditorGroupsService private _editorGroupService: IEditorGroupsService
+ ) {
+ super();
+ if (extHostContext) {
+ this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostNotebookDocumentsAndEditors);
+ }
+
+ // Create a state computer that actually tracks all required changes. This is hooked to onDelta which notifies extension host
+ this._register(this._instantiationService.createInstance(MainThreadNotebookDocumentAndEditorStateComputer, delta => this._onDelta(delta)));
+ }
+
+ //#region extension host callable APIs
+ $trySaveDocument(uri: UriComponents): Thenable {
+ let uriString = URI.revive(uri).toString();
+ let editor = this._notebookEditors.get(uriString);
+ if (editor) {
+ return editor.save();
+ } else {
+ return Promise.resolve(false);
+ }
+ }
+
+ $tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): TPromise {
+ return TPromise.wrap(this.doOpenEditor(resource, options));
+ }
+ //#endregion
+
+ private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise {
+ const uri = URI.revive(resource);
+
+ const editorOptions: ITextEditorOptions = {
+ preserveFocus: options.preserveFocus,
+ pinned: options.pinned
+ };
+ let model = new NotebookInputModel(uri, undefined, false, undefined);
+ let providerId = options.providerId;
+ if(!providerId)
+ {
+ // Ensure there is always a sensible provider ID for this file type
+ providerId = getProviderForFileName(uri.fsPath);
+ }
+
+ model.providerId = providerId;
+ let input = this._instantiationService.createInstance(NotebookInput, undefined, model);
+
+ let editor = await this._editorService.openEditor(input, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position));
+ if (!editor) {
+ return undefined;
+ }
+ return this.waitOnEditor(input);
+ }
+
+ private async waitOnEditor(input: NotebookInput): Promise {
+ let id: string = undefined;
+ let attemptsLeft = 10;
+ let timeoutMs = 20;
+ while (!id && attemptsLeft > 0) {
+ id = this.findNotebookEditorIdFor(input);
+ if (!id) {
+ await wait(timeoutMs);
+ }
+ }
+ return id;
+ }
+
+ findNotebookEditorIdFor(input: NotebookInput): string {
+ let foundId: string = undefined;
+ this._notebookEditors.forEach(e => {
+ if (e.matches(input)) {
+ foundId = e.id;
+ }
+ });
+ return foundId;
+ }
+
+ getEditor(id: string): MainThreadNotebookEditor {
+ return this._notebookEditors.get(id);
+ }
+
+ private _onDelta(delta: NotebookEditorStateDelta): void {
+ let removedEditors: string[] = [];
+ let removedDocuments: URI[] = [];
+ let addedEditors: MainThreadNotebookEditor[] = [];
+
+ // added editors
+ for (const editor of delta.addedEditors) {
+ const mainThreadEditor = new MainThreadNotebookEditor(editor);
+
+ this._notebookEditors.set(editor.id, mainThreadEditor);
+ addedEditors.push(mainThreadEditor);
+ }
+
+ // removed editors
+ for (const { id } of delta.removedEditors) {
+ const mainThreadEditor = this._notebookEditors.get(id);
+ if (mainThreadEditor) {
+ removedDocuments.push(mainThreadEditor.uri);
+ mainThreadEditor.dispose();
+ this._notebookEditors.delete(id);
+ removedEditors.push(id);
+ }
+ }
+
+ let extHostDelta: INotebookDocumentsAndEditorsDelta = Object.create(null);
+ let empty = true;
+ if (delta.newActiveEditor !== undefined) {
+ empty = false;
+ extHostDelta.newActiveEditor = delta.newActiveEditor;
+ }
+ if (removedDocuments.length > 0) {
+ empty = false;
+ extHostDelta.removedDocuments = removedDocuments;
+ }
+ if (removedEditors.length > 0) {
+ empty = false;
+ extHostDelta.removedEditors = removedEditors;
+ }
+ if (delta.addedEditors.length > 0) {
+ empty = false;
+ extHostDelta.addedDocuments = [];
+ extHostDelta.addedEditors = [];
+ for (let editor of addedEditors) {
+ extHostDelta.addedEditors.push(this._toNotebookEditorAddData(editor));
+ // For now, add 1 document for each editor. In the future these may be trackable independently
+ extHostDelta.addedDocuments.push(this._toNotebookModelAddData(editor));
+ }
+ }
+
+ if (!empty) {
+ this._proxy.$acceptDocumentsAndEditorsDelta(extHostDelta);
+ }
+ }
+
+ private _toNotebookEditorAddData(editor: MainThreadNotebookEditor): INotebookEditorAddData {
+ let addData: INotebookEditorAddData = {
+ documentUri: editor.uri,
+ editorPosition: undefined,
+ id: editor.editor.id
+ };
+ return addData;
+ }
+
+ private _toNotebookModelAddData(editor: MainThreadNotebookEditor): INotebookModelAddedData {
+ let addData: INotebookModelAddedData = {
+ uri: editor.uri,
+ isDirty: editor.isDirty,
+ providerId: editor.providerId
+ };
+ return addData;
+ }
+}
diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts
index 4199340173..051de9b857 100644
--- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts
+++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts
@@ -38,6 +38,7 @@ import { ExtHostModelViewTreeViews } from 'sql/workbench/api/node/extHostModelVi
import { ExtHostQueryEditor } from 'sql/workbench/api/node/extHostQueryEditor';
import { ExtHostBackgroundTaskManagement } from './extHostBackgroundTaskManagement';
import { ExtHostNotebook } from 'sql/workbench/api/node/extHostNotebook';
+import { ExtHostNotebookDocumentsAndEditors } from 'sql/workbench/api/node/extHostNotebookDocumentsAndEditors';
export interface ISqlExtensionApiFactory {
vsCodeFactory(extension: IExtensionDescription): typeof vscode;
@@ -75,6 +76,7 @@ export function createApiFactory(
const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol, extHostModelView, extHostBackgroundTaskManagement));
const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol));
const extHostNotebook = rpcProtocol.set(SqlExtHostContext.ExtHostNotebook, new ExtHostNotebook(rpcProtocol));
+ const extHostNotebookDocumentsAndEditors = rpcProtocol.set(SqlExtHostContext.ExtHostNotebookDocumentsAndEditors, new ExtHostNotebookDocumentsAndEditors(rpcProtocol));
return {
@@ -420,6 +422,24 @@ export function createApiFactory(
};
const nb = {
+ get notebookDocuments() {
+ return extHostNotebookDocumentsAndEditors.getAllDocuments().map(doc => doc.document);
+ },
+ get activeNotebookEditor() {
+ return extHostNotebookDocumentsAndEditors.getActiveEditor();
+ },
+ get visibleNotebookEditors() {
+ return extHostNotebookDocumentsAndEditors.getAllEditors();
+ },
+ get onDidOpenNotebookDocument() {
+ return extHostNotebook.onDidOpenNotebookDocument;
+ },
+ get onDidChangeNotebookCell() {
+ return extHostNotebook.onDidChangeNotebookCell;
+ },
+ showNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions) {
+ return extHostNotebookDocumentsAndEditors.showNotebookDocument(uri, showOptions);
+ },
registerNotebookProvider(provider: sqlops.nb.NotebookProvider): vscode.Disposable {
return extHostNotebook.registerNotebookProvider(provider);
}
diff --git a/src/sql/workbench/api/node/sqlExtHost.contribution.ts b/src/sql/workbench/api/node/sqlExtHost.contribution.ts
index f8baed3939..10bdb07b32 100644
--- a/src/sql/workbench/api/node/sqlExtHost.contribution.ts
+++ b/src/sql/workbench/api/node/sqlExtHost.contribution.ts
@@ -24,6 +24,7 @@ import 'sql/workbench/api/node/mainThreadQueryEditor';
import 'sql/workbench/api/node/mainThreadModelView';
import 'sql/workbench/api/node/mainThreadModelViewDialog';
import 'sql/workbench/api/node/mainThreadNotebook';
+import 'sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors';
import 'sql/workbench/api/node/mainThreadAccountManagement';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts
index b8b99133b0..f94f58a7e2 100644
--- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts
+++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts
@@ -23,6 +23,7 @@ import {
IItemConfig, ModelComponentTypes, IComponentShape, IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails,
IModelViewWizardDetails, IModelViewWizardPageDetails, INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType, INotebookFutureDone
} from 'sql/workbench/api/common/sqlExtHostTypes';
+import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
export abstract class ExtHostAccountManagementShape {
$autoOAuthCancelled(handle: number): Thenable { throw ni(); }
@@ -572,7 +573,9 @@ export const SqlMainContext = {
MainThreadDashboard: createMainId('MainThreadDashboard'),
MainThreadModelViewDialog: createMainId('MainThreadModelViewDialog'),
MainThreadQueryEditor: createMainId('MainThreadQueryEditor'),
- MainThreadNotebook: createMainId('MainThreadNotebook')
+ MainThreadNotebook: createMainId('MainThreadNotebook'),
+ MainThreadNotebookDocumentsAndEditors: createMainId('MainThreadNotebookDocumentsAndEditors')
+
};
export const SqlExtHostContext = {
@@ -592,7 +595,8 @@ export const SqlExtHostContext = {
ExtHostDashboard: createExtId('ExtHostDashboard'),
ExtHostModelViewDialog: createExtId('ExtHostModelViewDialog'),
ExtHostQueryEditor: createExtId('ExtHostQueryEditor'),
- ExtHostNotebook: createExtId('ExtHostNotebook')
+ ExtHostNotebook: createExtId('ExtHostNotebook'),
+ ExtHostNotebookDocumentsAndEditors: createExtId('ExtHostNotebookDocumentsAndEditors')
};
export interface MainThreadDashboardShape extends IDisposable {
@@ -750,8 +754,8 @@ export interface ExtHostNotebookShape {
$doStopServer(managerHandle: number): Thenable;
// Content Manager APIs
- $getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable;
- $save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable;
+ $getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable;
+ $save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebookContents): Thenable;
// Session Manager APIs
$refreshSpecs(managerHandle: number): Thenable;
@@ -780,3 +784,39 @@ export interface MainThreadNotebookShape extends IDisposable {
$onFutureDone(futureId: number, done: INotebookFutureDone): void;
}
+export interface INotebookDocumentsAndEditorsDelta {
+ removedDocuments?: UriComponents[];
+ addedDocuments?: INotebookModelAddedData[];
+ removedEditors?: string[];
+ addedEditors?: INotebookEditorAddData[];
+ newActiveEditor?: string;
+}
+
+export interface INotebookModelAddedData {
+ uri: UriComponents;
+ providerId: string;
+ isDirty: boolean;
+}
+
+export interface INotebookEditorAddData {
+ id: string;
+ documentUri: UriComponents;
+ editorPosition: EditorViewColumn;
+}
+
+export interface INotebookShowOptions {
+ position?: EditorViewColumn;
+ preserveFocus?: boolean;
+ pinned?: boolean;
+ providerId?: string;
+ connectionId?: string;
+}
+
+export interface ExtHostNotebookDocumentsAndEditorsShape {
+ $acceptDocumentsAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void;
+}
+
+export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable {
+ $trySaveDocument(uri: UriComponents): Thenable;
+ $tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): TPromise;
+}
\ No newline at end of file
diff --git a/src/sqltest/workbench/api/mainThreadNotebook.test.ts b/src/sqltest/workbench/api/mainThreadNotebook.test.ts
index 07ba48e4a1..4a15440614 100644
--- a/src/sqltest/workbench/api/mainThreadNotebook.test.ts
+++ b/src/sqltest/workbench/api/mainThreadNotebook.test.ts
@@ -131,10 +131,10 @@ class ExtHostNotebookStub implements ExtHostNotebookShape {
$doStopServer(managerHandle: number): Thenable {
throw new Error('Method not implemented.');
}
- $getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable {
+ $getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable {
throw new Error('Method not implemented.');
}
- $save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable {
+ $save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebookContents): Thenable {
throw new Error('Method not implemented.');
}
$refreshSpecs(managerHandle: number): Thenable {