Notebook extensibility: Move New Notebook and configuration to an extension (#3382)

initial support for Notebook extensibility. Fixes #3148 , Fixes #3382.

## Design notes
The extensibility patterns are modeled after the VSCode Document and Editor APIs but need to be different since core editor concepts are different - for example Notebooks have cells, and cells have contents rather than editors which have text lines.

Most importantly, a lot of the code is based on the MainThreadDocumentsAndEditors class, with some related classes (the MainThreadDocuments, and MainThreadEditors) brought in too. Given our current limitations I felt moving to add 3 full sets of extension host API classes was overkill so am currently using one. Will see if we need to change this in the future based on what we add in the additional APIs

## Limitations
The current implementation is limited to visible editors, rather than all documents in the workspace. We are not following the `openDocument` -> `showDocument` pattern, but instead just supporting `showDocument` directly.

## Changes in this PR
- Renamed existing APIs to make clear that they were about notebook contents, not about notebook behavior
- Added new APIs for querying notebook documents and editors 
- Added new API for opening a notebook
- Moved `New Notebook` command to an extension, and added an `Open Notebook` command too
- Moved notebook feature flag to the extension

## Not covered in this PR
- Need to actually implement support for defining the provider and connection IDs for a notebook. this will be important to support New Notebook from a big data connection in Object Explorer
- Need to add APIs for adding cells, to support 
- Need to implement the metadata for getting full notebook contents. I've only implemented to key APIs needed to make this all work.
This commit is contained in:
Kevin Cunnane
2018-12-03 18:50:44 -08:00
committed by GitHub
parent cb162b16f2
commit cac8cc99e1
33 changed files with 1286 additions and 148 deletions

View File

@@ -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<number, Adapter>();
private _onDidOpenNotebook = new Emitter<sqlops.nb.NotebookDocument>();
private _onDidChangeNotebookCell = new Emitter<sqlops.nb.NotebookCellChangeEvent>();
public readonly onDidOpenNotebookDocument: Event<sqlops.nb.NotebookDocument> = this._onDidOpenNotebook.event;
public readonly onDidChangeNotebookCell: Event<sqlops.nb.NotebookCellChangeEvent> = 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<sqlops.nb.INotebook> {
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebookContents> {
return this._withContentManager(managerHandle, (contentManager) => contentManager.getNotebookContents(URI.revive(notebookUri)));
}
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook> {
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebookContents): Thenable<sqlops.nb.INotebookContents> {
return this._withContentManager(managerHandle, (contentManager) => contentManager.save(URI.revive(notebookUri), notebook));
}

View File

@@ -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<boolean> {
if (this._isDisposed) {
return TPromise.wrapError<boolean>(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<string, ExtHostNotebookEditor>();
private readonly _documents = new Map<string, ExtHostNotebookDocumentData>();
private readonly _onDidAddDocuments = new Emitter<ExtHostNotebookDocumentData[]>();
private readonly _onDidRemoveDocuments = new Emitter<ExtHostNotebookDocumentData[]>();
private readonly _onDidChangeVisibleNotebookEditors = new Emitter<ExtHostNotebookEditor[]>();
private readonly _onDidChangeActiveNotebookEditor = new Emitter<ExtHostNotebookEditor>();
readonly onDidAddDocuments: Event<ExtHostNotebookDocumentData[]> = this._onDidAddDocuments.event;
readonly onDidRemoveDocuments: Event<ExtHostNotebookDocumentData[]> = this._onDidRemoveDocuments.event;
readonly onDidChangeVisibleNotebookEditors: Event<ExtHostNotebookEditor[]> = this._onDidChangeVisibleNotebookEditors.event;
readonly onDidChangeActiveNotebookEditor: Event<ExtHostNotebookEditor> = 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<sqlops.nb.NotebookEditor> {
return this.doShowNotebookDocument(uri, showOptions);
}
private async doShowNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions): Promise<sqlops.nb.NotebookEditor> {
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
}

View File

@@ -153,11 +153,11 @@ class ContentManagerWrapper implements sqlops.nb.ContentManager {
constructor(private handle: number, private _proxy: Proxies) {
}
getNotebookContents(notebookUri: URI): Thenable<sqlops.nb.INotebook> {
getNotebookContents(notebookUri: URI): Thenable<sqlops.nb.INotebookContents> {
return this._proxy.ext.$getNotebookContents(this.handle, notebookUri);
}
save(path: URI, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook> {
save(path: URI, notebook: sqlops.nb.INotebookContents): Thenable<sqlops.nb.INotebookContents> {
return this._proxy.ext.$save(this.handle, path, notebook);
}
}

View File

@@ -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<boolean> {
return this.editor.save();
}
public matches(input: NotebookInput): boolean {
if (!input) {
return false;
}
return input === this.editor.notebookParams.input;
}
}
function wait(timeMs: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, timeMs));
}
namespace mapset {
export function setValues<T>(set: Set<T>): T[] {
// return Array.from(set);
let ret: T[] = [];
set.forEach(v => ret.push(v));
return ret;
}
export function mapValues<T>(map: Map<any, T>): T[] {
// return Array.from(map.values());
let ret: T[] = [];
map.forEach(v => ret.push(v));
return ret;
}
}
namespace delta {
export function ofSets<T>(before: Set<T>, after: Set<T>): { 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<K, V>(before: Map<K, V>, after: Map<K, V>): { 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<string, INotebookEditor>,
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<string, INotebookEditor>();
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<string, MainThreadNotebookEditor>();
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<boolean> {
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<string> {
return TPromise.wrap(this.doOpenEditor(resource, options));
}
//#endregion
private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
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<string> {
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;
}
}

View File

@@ -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);
}

View File

@@ -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';

View File

@@ -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<void> { throw ni(); }
@@ -572,7 +573,9 @@ export const SqlMainContext = {
MainThreadDashboard: createMainId<MainThreadDashboardShape>('MainThreadDashboard'),
MainThreadModelViewDialog: createMainId<MainThreadModelViewDialogShape>('MainThreadModelViewDialog'),
MainThreadQueryEditor: createMainId<MainThreadQueryEditorShape>('MainThreadQueryEditor'),
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook')
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook'),
MainThreadNotebookDocumentsAndEditors: createMainId<MainThreadNotebookDocumentsAndEditorsShape>('MainThreadNotebookDocumentsAndEditors')
};
export const SqlExtHostContext = {
@@ -592,7 +595,8 @@ export const SqlExtHostContext = {
ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard'),
ExtHostModelViewDialog: createExtId<ExtHostModelViewDialogShape>('ExtHostModelViewDialog'),
ExtHostQueryEditor: createExtId<ExtHostQueryEditorShape>('ExtHostQueryEditor'),
ExtHostNotebook: createExtId<ExtHostNotebookShape>('ExtHostNotebook')
ExtHostNotebook: createExtId<ExtHostNotebookShape>('ExtHostNotebook'),
ExtHostNotebookDocumentsAndEditors: createExtId<ExtHostNotebookDocumentsAndEditorsShape>('ExtHostNotebookDocumentsAndEditors')
};
export interface MainThreadDashboardShape extends IDisposable {
@@ -750,8 +754,8 @@ export interface ExtHostNotebookShape {
$doStopServer(managerHandle: number): Thenable<void>;
// Content Manager APIs
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebook>;
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook>;
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebookContents>;
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebookContents): Thenable<sqlops.nb.INotebookContents>;
// Session Manager APIs
$refreshSpecs(managerHandle: number): Thenable<sqlops.nb.IAllKernels>;
@@ -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<boolean>;
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): TPromise<string>;
}