mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
* #3920: Notebooks file save * Missed in merge * #4290: Untitled save and native dirty implementation * Misc changes * Content Manager, notebooks extension and commented failed unit tests * Removing modelLoaded event
This commit is contained in:
@@ -114,14 +114,12 @@
|
|||||||
],
|
],
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"id": "jupyter-notebook",
|
"id": "notebook",
|
||||||
"extensions": [
|
"extensions": [
|
||||||
".ipynb"
|
".ipynb"
|
||||||
],
|
],
|
||||||
"aliases": [
|
"aliases": [
|
||||||
"Jupyter Notebook",
|
"Notebook"
|
||||||
"IPython Notebook",
|
|
||||||
"ipy"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ export const winPlatform = 'win32';
|
|||||||
export const jupyterNotebookProviderId = 'jupyter';
|
export const jupyterNotebookProviderId = 'jupyter';
|
||||||
export const jupyterConfigRootFolder = 'jupyter_config';
|
export const jupyterConfigRootFolder = 'jupyter_config';
|
||||||
export const jupyterKernelsMasterFolder = 'kernels_master';
|
export const jupyterKernelsMasterFolder = 'kernels_master';
|
||||||
export const jupyterNotebookLanguageId = 'jupyter-notebook';
|
|
||||||
export const jupyterNotebookViewType = 'jupyter-notebook';
|
|
||||||
export const jupyterNewNotebookTask = 'jupyter.task.newNotebook';
|
export const jupyterNewNotebookTask = 'jupyter.task.newNotebook';
|
||||||
export const jupyterOpenNotebookTask = 'jupyter.task.openNotebook';
|
export const jupyterOpenNotebookTask = 'jupyter.task.openNotebook';
|
||||||
export const jupyterNewNotebookCommand = 'jupyter.cmd.newNotebook';
|
export const jupyterNewNotebookCommand = 'jupyter.cmd.newNotebook';
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ export const RestoreFeatureName = 'restore';
|
|||||||
export const BackupFeatureName = 'backup';
|
export const BackupFeatureName = 'backup';
|
||||||
|
|
||||||
export const MssqlProviderId = 'MSSQL';
|
export const MssqlProviderId = 'MSSQL';
|
||||||
|
export const notebookModeId = 'notebook';
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
|||||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||||
import { IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
import { IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
||||||
import { NotebookInput, NotebookInputModel } from 'sql/parts/notebook/notebookInput';
|
import { NotebookInput, NotebookEditorModel } from 'sql/parts/notebook/notebookInput';
|
||||||
import { DEFAULT_NOTEBOOK_PROVIDER, INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
import { DEFAULT_NOTEBOOK_PROVIDER, INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
|
import { getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
|
||||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||||
|
import { notebookModeId } from 'sql/common/constants';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
@@ -27,7 +28,6 @@ export const untitledFilePrefix = 'SQLQuery';
|
|||||||
|
|
||||||
// mode identifier for SQL mode
|
// mode identifier for SQL mode
|
||||||
export const sqlModeId = 'sql';
|
export const sqlModeId = 'sql';
|
||||||
export const notebookModeId = 'notebook';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the specified input is supported by one our custom input types, and if so convert it
|
* Checks if the specified input is supported by one our custom input types, and if so convert it
|
||||||
@@ -63,16 +63,8 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
|
|||||||
let providerIds: string[] = [DEFAULT_NOTEBOOK_PROVIDER];
|
let providerIds: string[] = [DEFAULT_NOTEBOOK_PROVIDER];
|
||||||
if (input) {
|
if (input) {
|
||||||
fileName = input.getName();
|
fileName = input.getName();
|
||||||
providerIds = getProvidersForFileName(fileName, notebookService);
|
|
||||||
}
|
}
|
||||||
let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined);
|
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, uri);
|
||||||
notebookInputModel.providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0];
|
|
||||||
notebookInputModel.providers = providerIds;
|
|
||||||
notebookInputModel.providers.forEach(provider => {
|
|
||||||
let standardKernels = getStandardKernelsForProvider(provider, notebookService);
|
|
||||||
notebookInputModel.standardKernels = standardKernels;
|
|
||||||
});
|
|
||||||
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
|
|
||||||
return notebookInput;
|
return notebookInput;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -257,4 +249,4 @@ export function getFileMode(instantiationService: IInstantiationService, resourc
|
|||||||
}
|
}
|
||||||
return sqlModeId;
|
return sqlModeId;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,15 +183,15 @@ export class QueryTextEditor extends BaseTextEditor {
|
|||||||
|
|
||||||
public toggleEditorSelected(selected: boolean): void {
|
public toggleEditorSelected(selected: boolean): void {
|
||||||
this._selected = selected;
|
this._selected = selected;
|
||||||
this.refreshEditorConfguration();
|
this.refreshEditorConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
public set hideLineNumbers(value: boolean) {
|
public set hideLineNumbers(value: boolean) {
|
||||||
this._hideLineNumbers = value;
|
this._hideLineNumbers = value;
|
||||||
this.refreshEditorConfguration();
|
this.refreshEditorConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
private refreshEditorConfguration(configuration = this.configurationService.getValue<IEditorConfiguration>(this.getResource())): void {
|
private refreshEditorConfiguration(configuration = this.configurationService.getValue<IEditorConfiguration>(this.getResource())): void {
|
||||||
if (!this.getControl()) {
|
if (!this.getControl()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,6 +369,11 @@ export interface INotebookModel {
|
|||||||
*/
|
*/
|
||||||
saveModel(): Promise<boolean>;
|
saveModel(): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize notebook cell content to JSON
|
||||||
|
*/
|
||||||
|
toJSON(): nb.INotebookContents;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the notebook of a change in the cell
|
* Notifies the notebook of a change in the cell
|
||||||
*/
|
*/
|
||||||
@@ -455,6 +460,12 @@ export interface IModelFactory {
|
|||||||
createClientSession(options: IClientSessionOptions): IClientSession;
|
createClientSession(options: IClientSessionOptions): IClientSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IContentManager {
|
||||||
|
/**
|
||||||
|
* This is a specialized method intended to load for a default context - just the current Notebook's URI
|
||||||
|
*/
|
||||||
|
loadContent(): Promise<nb.INotebookContents>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface INotebookModelOptions {
|
export interface INotebookModelOptions {
|
||||||
/**
|
/**
|
||||||
@@ -467,6 +478,7 @@ export interface INotebookModelOptions {
|
|||||||
*/
|
*/
|
||||||
factory: IModelFactory;
|
factory: IModelFactory;
|
||||||
|
|
||||||
|
contentManager: IContentManager;
|
||||||
notebookManagers: INotebookManager[];
|
notebookManagers: INotebookManager[];
|
||||||
providerId: string;
|
providerId: string;
|
||||||
standardKernels: IStandardKernelWithProvider[];
|
standardKernels: IStandardKernelWithProvider[];
|
||||||
@@ -498,4 +510,4 @@ export interface ICellMagicMapper {
|
|||||||
|
|
||||||
export namespace notebookConstants {
|
export namespace notebookConstants {
|
||||||
export const SQL = 'SQL';
|
export const SQL = 'SQL';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,18 @@
|
|||||||
import { nb, connection } from 'azdata';
|
import { nb, connection } from 'azdata';
|
||||||
|
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { Event, Emitter, forEach } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
import { CellModel } from './cell';
|
import { CellModel } from './cell';
|
||||||
import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, notebookConstants, NotebookContentChange } from './modelInterfaces';
|
import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, NotebookContentChange } from './modelInterfaces';
|
||||||
import { NotebookChangeType, CellType, CellTypes } from 'sql/parts/notebook/models/contracts';
|
import { NotebookChangeType, CellType } from 'sql/parts/notebook/models/contracts';
|
||||||
import { nbversion } from '../notebookConstants';
|
import { nbversion } from '../notebookConstants';
|
||||||
import * as notebookUtils from '../notebookUtils';
|
import * as notebookUtils from '../notebookUtils';
|
||||||
import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { NotebookContexts } from 'sql/parts/notebook/models/notebookContexts';
|
import { NotebookContexts } from 'sql/parts/notebook/models/notebookContexts';
|
||||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||||
import { INotification, Severity } from 'vs/platform/notification/common/notification';
|
import { INotification, Severity } from 'vs/platform/notification/common/notification';
|
||||||
import { Schemas } from 'vs/base/common/network';
|
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||||
@@ -130,6 +129,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
return this._contentChangedEmitter.event;
|
return this._contentChangedEmitter.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public get isSessionReady(): boolean {
|
public get isSessionReady(): boolean {
|
||||||
return !!this._activeClientSession;
|
return !!this._activeClientSession;
|
||||||
}
|
}
|
||||||
@@ -253,10 +253,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
try {
|
try {
|
||||||
this._trustedMode = isTrusted;
|
this._trustedMode = isTrusted;
|
||||||
let contents = null;
|
let contents = null;
|
||||||
if (this._notebookOptions.notebookUri.scheme !== Schemas.untitled) {
|
|
||||||
// TODO: separate ContentManager from NotebookManager
|
if (this._notebookOptions && this._notebookOptions.contentManager) {
|
||||||
contents = await this.notebookManagers[0].contentManager.getNotebookContents(this._notebookOptions.notebookUri);
|
contents = await this._notebookOptions.contentManager.loadContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
let factory = this._notebookOptions.factory;
|
let factory = this._notebookOptions.factory;
|
||||||
// if cells already exist, create them with language info (if it is saved)
|
// if cells already exist, create them with language info (if it is saved)
|
||||||
this._cells = [];
|
this._cells = [];
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
||||||
</text-cell-component>
|
</text-cell-component>
|
||||||
</div>
|
</div>
|
||||||
<div class="notebook-cell" *ngIf="!cells.length && !isLoading">
|
<div class="notebook-cell" *ngIf="(!cells || !cells.length) && !isLoading">
|
||||||
<placeholder-cell-component [cellModel]="cell" [model]="model">
|
<placeholder-cell-component [cellModel]="cell" [model]="model">
|
||||||
</placeholder-cell-component>
|
</placeholder-cell-component>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
this._model.cells.forEach(cell => {
|
this._model.cells.forEach(cell => {
|
||||||
cell.trustedMode = isTrusted;
|
cell.trustedMode = isTrusted;
|
||||||
});
|
});
|
||||||
this.setDirty(true);
|
//TODO: Handle dirty for trust?
|
||||||
this._changeRef.detectChanges();
|
this._changeRef.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +253,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
connectionService: this.connectionManagementService,
|
connectionService: this.connectionManagementService,
|
||||||
notificationService: this.notificationService,
|
notificationService: this.notificationService,
|
||||||
notebookManagers: this.notebookManagers,
|
notebookManagers: this.notebookManagers,
|
||||||
|
contentManager: this._notebookParams.input.contentManager,
|
||||||
standardKernels: this._notebookParams.input.standardKernels,
|
standardKernels: this._notebookParams.input.standardKernels,
|
||||||
cellMagicMapper: new CellMagicMapper(this.notebookService.languageMagics),
|
cellMagicMapper: new CellMagicMapper(this.notebookService.languageMagics),
|
||||||
providerId: 'sql', // this is tricky; really should also depend on the connection profile
|
providerId: 'sql', // this is tricky; really should also depend on the connection profile
|
||||||
@@ -321,7 +322,6 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
|
|
||||||
private handleContentChanged(change: NotebookContentChange) {
|
private handleContentChanged(change: NotebookContentChange) {
|
||||||
// Note: for now we just need to set dirty state and refresh the UI.
|
// Note: for now we just need to set dirty state and refresh the UI.
|
||||||
this.setDirty(true);
|
|
||||||
this._changeRef.detectChanges();
|
this._changeRef.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,38 +8,148 @@
|
|||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||||
import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
|
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import * as resources from 'vs/base/common/resources';
|
import * as resources from 'vs/base/common/resources';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
|
||||||
import { IStandardKernelWithProvider } from 'sql/parts/notebook/notebookUtils';
|
import { IStandardKernelWithProvider, getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
|
||||||
import { INotebookService, INotebookEditor } from 'sql/workbench/services/notebook/common/notebookService';
|
import { INotebookService, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import Severity from 'vs/base/common/severity';
|
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||||
|
import { INotebookModel, IContentManager } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
|
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
|
||||||
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
|
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
|
||||||
|
import { Schemas } from 'vs/base/common/network';
|
||||||
|
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||||
|
import { notebookModeId } from 'sql/common/constants';
|
||||||
|
import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager';
|
||||||
|
|
||||||
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
|
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
|
||||||
|
|
||||||
|
|
||||||
export class NotebookInputModel extends EditorModel {
|
export class NotebookEditorModel extends EditorModel {
|
||||||
private dirty: boolean;
|
private dirty: boolean;
|
||||||
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
||||||
private _providerId: string;
|
|
||||||
private _standardKernels: IStandardKernelWithProvider[];
|
|
||||||
private _defaultKernel: azdata.nb.IKernelSpec;
|
|
||||||
constructor(public readonly notebookUri: URI,
|
constructor(public readonly notebookUri: URI,
|
||||||
private readonly handle: number,
|
private textEditorModel: TextFileEditorModel | UntitledEditorModel,
|
||||||
private _isTrusted: boolean = false,
|
@INotebookService private notebookService: INotebookService
|
||||||
private saveHandler?: ModeViewSaveHandler,
|
) {
|
||||||
provider?: string,
|
|
||||||
private _providers?: string[],
|
|
||||||
private _connectionProfileId?: string) {
|
|
||||||
|
|
||||||
super();
|
super();
|
||||||
this.dirty = false;
|
this._register(this.notebookService.onNotebookEditorAdd(notebook => {
|
||||||
this._providerId = provider;
|
if (notebook.id === this.notebookUri.toString()) {
|
||||||
|
// Hook to content change events
|
||||||
|
notebook.modelReady.then(() => {
|
||||||
|
this._register(notebook.model.contentChanged(e => this.updateModel()));
|
||||||
|
this._register(notebook.model.kernelChanged(e => this.updateModel()));
|
||||||
|
}, err => undefined);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (this.textEditorModel instanceof UntitledEditorModel) {
|
||||||
|
this._register(this.textEditorModel.onDidChangeDirty(e => this.setDirty(this.textEditorModel.isDirty())));
|
||||||
|
} else {
|
||||||
|
this._register(this.textEditorModel.onDidStateChange(e => this.setDirty(this.textEditorModel.isDirty())));
|
||||||
|
}
|
||||||
|
this.dirty = this.textEditorModel.isDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get contentString(): string {
|
||||||
|
let model = this.textEditorModel.textEditorModel;
|
||||||
|
return model.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDirty(): boolean {
|
||||||
|
return this.textEditorModel.isDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDirty(dirty: boolean): void {
|
||||||
|
if (this.dirty === dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.dirty = dirty;
|
||||||
|
this._onDidChangeDirty.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateModel(): void {
|
||||||
|
let notebookModel = this.getNotebookModel();
|
||||||
|
if (notebookModel && this.textEditorModel && this.textEditorModel.textEditorModel) {
|
||||||
|
let content = JSON.stringify(notebookModel.toJSON(), undefined, ' ');
|
||||||
|
let model = this.textEditorModel.textEditorModel;
|
||||||
|
let endLine = model.getLineCount();
|
||||||
|
let endCol = model.getLineLength(endLine);
|
||||||
|
this.textEditorModel.textEditorModel.applyEdits([{
|
||||||
|
range: new Range(1, 1, endLine, endCol),
|
||||||
|
text: content
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isModelCreated(): boolean {
|
||||||
|
return this.getNotebookModel() !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNotebookModel(): INotebookModel {
|
||||||
|
let editor = this.notebookService.listNotebookEditors().find(n => n.id === this.notebookUri.toString());
|
||||||
|
if (editor) {
|
||||||
|
return editor.model;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get onDidChangeDirty(): Event<void> {
|
||||||
|
return this._onDidChangeDirty.event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookInput extends EditorInput {
|
||||||
|
public static ID: string = 'workbench.editorinputs.notebookInput';
|
||||||
|
private _providerId: string;
|
||||||
|
private _providers: string[];
|
||||||
|
private _standardKernels: IStandardKernelWithProvider[];
|
||||||
|
private _connectionProfileId: string;
|
||||||
|
private _defaultKernel: azdata.nb.IKernelSpec;
|
||||||
|
private _isTrusted: boolean = false;
|
||||||
|
public hasBootstrapped = false;
|
||||||
|
// Holds the HTML content for the editor when the editor discards this input and loads another
|
||||||
|
private _parentContainer: HTMLElement;
|
||||||
|
private readonly _layoutChanged: Emitter<void> = this._register(new Emitter<void>());
|
||||||
|
private _model: NotebookEditorModel;
|
||||||
|
private _untitledEditorService: IUntitledEditorService;
|
||||||
|
private _contentManager: IContentManager;
|
||||||
|
|
||||||
|
constructor(private _title: string,
|
||||||
|
private resource: URI,
|
||||||
|
@ITextModelService private textModelService: ITextModelService,
|
||||||
|
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
|
||||||
|
@IInstantiationService private instantiationService: IInstantiationService,
|
||||||
|
@INotebookService private notebookService: INotebookService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this._untitledEditorService = untitledEditorService;
|
||||||
|
this.resource = resource;
|
||||||
this._standardKernels = [];
|
this._standardKernels = [];
|
||||||
|
this.assignProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get notebookUri(): URI {
|
||||||
|
return this.resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get contentManager(): IContentManager {
|
||||||
|
if (!this._contentManager) {
|
||||||
|
this._contentManager = new NotebookEditorContentManager(this);
|
||||||
|
}
|
||||||
|
return this._contentManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): string {
|
||||||
|
if (!this._title) {
|
||||||
|
this._title = resources.basenameOrAuthority(this.resource);
|
||||||
|
}
|
||||||
|
return this._title;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get providerId(): string {
|
public get providerId(): string {
|
||||||
@@ -50,12 +160,16 @@ export class NotebookInputModel extends EditorModel {
|
|||||||
this._providerId = value;
|
this._providerId = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get providers(): string[] {
|
public get isTrusted(): boolean {
|
||||||
return this._providers;
|
return this._isTrusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public set providers(value: string[]) {
|
public set isTrusted(value: boolean) {
|
||||||
this._providers = value;
|
this._isTrusted = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set connectionProfileId(value: string) {
|
||||||
|
this._connectionProfileId = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get connectionProfileId(): string {
|
public get connectionProfileId(): string {
|
||||||
@@ -66,6 +180,14 @@ export class NotebookInputModel extends EditorModel {
|
|||||||
return this._standardKernels;
|
return this._standardKernels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get providers(): string[] {
|
||||||
|
return this._providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set providers(value: string[]) {
|
||||||
|
this._providers = value;
|
||||||
|
}
|
||||||
|
|
||||||
public set standardKernels(value: IStandardKernelWithProvider[]) {
|
public set standardKernels(value: IStandardKernelWithProvider[]) {
|
||||||
value.forEach(kernel => {
|
value.forEach(kernel => {
|
||||||
this._standardKernels.push({
|
this._standardKernels.push({
|
||||||
@@ -84,76 +206,6 @@ export class NotebookInputModel extends EditorModel {
|
|||||||
this._defaultKernel = kernel;
|
this._defaultKernel = kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTrusted(): boolean {
|
|
||||||
return this._isTrusted;
|
|
||||||
}
|
|
||||||
|
|
||||||
get onDidChangeDirty(): Event<void> {
|
|
||||||
return this._onDidChangeDirty.event;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isDirty(): boolean {
|
|
||||||
return this.dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setDirty(dirty: boolean): void {
|
|
||||||
if (this.dirty === dirty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dirty = dirty;
|
|
||||||
this._onDidChangeDirty.fire();
|
|
||||||
}
|
|
||||||
|
|
||||||
save(): TPromise<boolean> {
|
|
||||||
if (this.saveHandler) {
|
|
||||||
return TPromise.wrap(this.saveHandler(this.handle));
|
|
||||||
}
|
|
||||||
return TPromise.wrap(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class NotebookInput extends EditorInput {
|
|
||||||
public static ID: string = 'workbench.editorinputs.notebookInput';
|
|
||||||
|
|
||||||
public hasBootstrapped = false;
|
|
||||||
// Holds the HTML content for the editor when the editor discards this input and loads another
|
|
||||||
private _parentContainer: HTMLElement;
|
|
||||||
private readonly _layoutChanged: Emitter<void> = this._register(new Emitter<void>());
|
|
||||||
constructor(private _title: string,
|
|
||||||
private _model: NotebookInputModel,
|
|
||||||
@INotebookService private notebookService: INotebookService,
|
|
||||||
@IDialogService private dialogService: IDialogService
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
|
|
||||||
}
|
|
||||||
|
|
||||||
public get notebookUri(): URI {
|
|
||||||
return this._model.notebookUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get providerId(): string {
|
|
||||||
return this._model.providerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get providers(): string[] {
|
|
||||||
return this._model.providers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get connectionProfileId(): string {
|
|
||||||
return this._model.connectionProfileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get standardKernels(): IStandardKernelWithProvider[] {
|
|
||||||
return this._model.standardKernels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get defaultKernel(): azdata.nb.IKernelSpec {
|
|
||||||
return this._model.defaultKernel;
|
|
||||||
}
|
|
||||||
|
|
||||||
get layoutChanged(): Event<void> {
|
get layoutChanged(): Event<void> {
|
||||||
return this._layoutChanged.event;
|
return this._layoutChanged.event;
|
||||||
}
|
}
|
||||||
@@ -166,20 +218,38 @@ export class NotebookInput extends EditorInput {
|
|||||||
return NotebookInput.ID;
|
return NotebookInput.ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public resolve(refresh?: boolean): TPromise<IEditorModel> {
|
getResource(): URI {
|
||||||
return undefined;
|
return this.resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getName(): string {
|
async resolve(): TPromise<NotebookEditorModel> {
|
||||||
if (!this._title) {
|
if (this._model && this._model.isModelCreated()) {
|
||||||
this._title = resources.basenameOrAuthority(this._model.notebookUri);
|
return TPromise.as(this._model);
|
||||||
|
} else {
|
||||||
|
let textOrUntitledEditorModel: UntitledEditorModel | IEditorModel;
|
||||||
|
if (this.resource.scheme === Schemas.untitled) {
|
||||||
|
textOrUntitledEditorModel = await this._untitledEditorService.loadOrCreate({ resource: this.resource, modeId: notebookModeId });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const textEditorModelReference = await this.textModelService.createModelReference(this.resource);
|
||||||
|
textOrUntitledEditorModel = await textEditorModelReference.object.load();
|
||||||
|
}
|
||||||
|
this._model = this.instantiationService.createInstance(NotebookEditorModel, this.resource, textOrUntitledEditorModel);
|
||||||
|
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
|
||||||
|
return this._model;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isTrusted(): boolean {
|
private assignProviders(): void {
|
||||||
return this._model.isTrusted;
|
let providerIds: string[] = getProvidersForFileName(this._title, this.notebookService);
|
||||||
|
if (providerIds && providerIds.length > 0) {
|
||||||
|
this._providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0];
|
||||||
|
this._providers = providerIds;
|
||||||
|
this._providers.forEach(provider => {
|
||||||
|
let standardKernels = getStandardKernelsForProvider(provider, this.notebookService);
|
||||||
|
this._standardKernels = standardKernels;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
@@ -212,50 +282,10 @@ export class NotebookInput extends EditorInput {
|
|||||||
* An editor that is dirty will be asked to be saved once it closes.
|
* An editor that is dirty will be asked to be saved once it closes.
|
||||||
*/
|
*/
|
||||||
isDirty(): boolean {
|
isDirty(): boolean {
|
||||||
return this._model.isDirty;
|
if (this._model) {
|
||||||
}
|
return this._model.isDirty;
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
|
|
||||||
*/
|
|
||||||
confirmSave(): TPromise<ConfirmResult> {
|
|
||||||
// TODO #2530 support save on close / confirm save. This is significantly more work
|
|
||||||
// as we need to either integrate with textFileService (seems like this isn't viable)
|
|
||||||
// or register our own complimentary service that handles the lifecycle operations such
|
|
||||||
// as close all, auto save etc.
|
|
||||||
const message = nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", this.getTitle());
|
|
||||||
const buttons: string[] = [
|
|
||||||
nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"),
|
|
||||||
nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"),
|
|
||||||
nls.localize('cancel', "Cancel")
|
|
||||||
];
|
|
||||||
|
|
||||||
return this.dialogService.show(Severity.Warning, message, buttons, {
|
|
||||||
cancelId: 2,
|
|
||||||
detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.")
|
|
||||||
}).then(index => {
|
|
||||||
switch (index) {
|
|
||||||
case 0: return ConfirmResult.SAVE;
|
|
||||||
case 1: return ConfirmResult.DONT_SAVE;
|
|
||||||
default: return ConfirmResult.CANCEL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
|
|
||||||
*/
|
|
||||||
save(): TPromise<boolean> {
|
|
||||||
let activeEditor: INotebookEditor;
|
|
||||||
for (const editor of this.notebookService.listNotebookEditors()) {
|
|
||||||
if (editor.isActive()) {
|
|
||||||
activeEditor = editor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (activeEditor) {
|
return false;
|
||||||
return TPromise.wrap(activeEditor.save().then((val) => { return val; }));
|
|
||||||
}
|
|
||||||
return TPromise.wrap(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -263,9 +293,14 @@ export class NotebookInput extends EditorInput {
|
|||||||
* @param isDirty boolean value to set editor dirty
|
* @param isDirty boolean value to set editor dirty
|
||||||
*/
|
*/
|
||||||
setDirty(isDirty: boolean): void {
|
setDirty(isDirty: boolean): void {
|
||||||
this._model.setDirty(isDirty);
|
if (this._model) {
|
||||||
|
this._model.setDirty(isDirty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateModel(): void {
|
||||||
|
this._model.updateModel();
|
||||||
|
}
|
||||||
|
|
||||||
public matches(otherInput: any): boolean {
|
public matches(otherInput: any): boolean {
|
||||||
if (super.matches(otherInput) === true) {
|
if (super.matches(otherInput) === true) {
|
||||||
@@ -278,7 +313,18 @@ export class NotebookInput extends EditorInput {
|
|||||||
// Compare by resource
|
// Compare by resource
|
||||||
return otherNotebookEditorInput.notebookUri.toString() === this.notebookUri.toString();
|
return otherNotebookEditorInput.notebookUri.toString() === this.notebookUri.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NotebookEditorContentManager implements IContentManager {
|
||||||
|
constructor(private notebookInput: NotebookInput) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadContent(): Promise<azdata.nb.INotebookContents> {
|
||||||
|
let notebookEditorModel = await this.notebookInput.resolve();
|
||||||
|
let contentManager = new LocalContentManager();
|
||||||
|
let contents = await contentManager.loadFromContentString(notebookEditorModel.contentString);
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,10 +21,9 @@ import {
|
|||||||
SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
|
SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
|
||||||
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
|
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
|
||||||
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
|
import { NotebookInput, NotebookEditorModel } from 'sql/parts/notebook/notebookInput';
|
||||||
import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
import { getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
|
|
||||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { disposed } from 'vs/base/common/errors';
|
import { disposed } from 'vs/base/common/errors';
|
||||||
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
@@ -361,26 +360,10 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
pinned: !options.preview
|
pinned: !options.preview
|
||||||
};
|
};
|
||||||
let trusted = uri.scheme === Schemas.untitled;
|
let trusted = uri.scheme === Schemas.untitled;
|
||||||
let model = new NotebookInputModel(uri, undefined, trusted, undefined, undefined, undefined, options.connectionId);
|
let input = this._instantiationService.createInstance(NotebookInput, uri.fsPath, uri);
|
||||||
let providerId = options.providerId;
|
input.isTrusted = trusted;
|
||||||
let providers: string[] = undefined;
|
input.defaultKernel = options.defaultKernel;
|
||||||
// Ensure there is always a sensible provider ID for this file type
|
input.connectionProfileId = options.connectionId;
|
||||||
providers = getProvidersForFileName(uri.fsPath, this._notebookService);
|
|
||||||
// Try to use a non-builtin provider first
|
|
||||||
if (providers) {
|
|
||||||
providerId = providers.find(p => p !== DEFAULT_NOTEBOOK_PROVIDER);
|
|
||||||
if (!providerId) {
|
|
||||||
providerId = model.providerId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
model.providers = providers;
|
|
||||||
model.providerId = providerId;
|
|
||||||
model.defaultKernel = options && options.defaultKernel;
|
|
||||||
model.providers.forEach(provider => {
|
|
||||||
let standardKernels = getStandardKernelsForProvider(provider, this._notebookService);
|
|
||||||
model.standardKernels = standardKernels;
|
|
||||||
});
|
|
||||||
let input = this._instantiationService.createInstance(NotebookInput, undefined, model);
|
|
||||||
|
|
||||||
let editor = await this._editorService.openEditor(input, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position));
|
let editor = await this._editorService.openEditor(input, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position));
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
|
|||||||
@@ -22,6 +22,29 @@ import { nbformat } from 'sql/parts/notebook/models/nbformat';
|
|||||||
type MimeBundle = { [key: string]: string | string[] | undefined };
|
type MimeBundle = { [key: string]: string | string[] | undefined };
|
||||||
|
|
||||||
export class LocalContentManager implements nb.ContentManager {
|
export class LocalContentManager implements nb.ContentManager {
|
||||||
|
|
||||||
|
public async loadFromContentString(contentString: string): Promise<nb.INotebookContents> {
|
||||||
|
let contents: JSONObject = json.parse(contentString);
|
||||||
|
|
||||||
|
if (contents) {
|
||||||
|
if (contents.nbformat === 4) {
|
||||||
|
return v4.readNotebook(<any>contents);
|
||||||
|
} else if (contents.nbformat === 3) {
|
||||||
|
return v3.readNotebook(<any>contents);
|
||||||
|
}
|
||||||
|
if (contents.nbformat) {
|
||||||
|
throw new TypeError(localize('nbformatNotRecognized', 'nbformat v{0}.{1} not recognized', contents.nbformat as any, contents.nbformat_minor as any));
|
||||||
|
}
|
||||||
|
} else if (contentString === '' || contentString === undefined) {
|
||||||
|
// Empty?
|
||||||
|
return v4.createEmptyNotebook();
|
||||||
|
}
|
||||||
|
|
||||||
|
// else, fallthrough condition
|
||||||
|
throw new TypeError(localize('nbNotSupported', 'This file does not have a valid notebook format'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public async getNotebookContents(notebookUri: URI): Promise<nb.INotebookContents> {
|
public async getNotebookContents(notebookUri: URI): Promise<nb.INotebookContents> {
|
||||||
if (!notebookUri) {
|
if (!notebookUri) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -55,46 +55,48 @@ export class NotebookModelStub implements INotebookModel {
|
|||||||
get contentChanged(): Event<NotebookContentChange> {
|
get contentChanged(): Event<NotebookContentChange> {
|
||||||
throw new Error('method not implemented.');
|
throw new Error('method not implemented.');
|
||||||
}
|
}
|
||||||
get specs(): nb.IAllKernels {
|
get specs(): nb.IAllKernels {
|
||||||
throw new Error('method not implemented.');
|
|
||||||
}
|
|
||||||
get contexts(): IDefaultConnection {
|
|
||||||
throw new Error('method not implemented.');
|
|
||||||
}
|
|
||||||
get providerId(): string {
|
|
||||||
throw new Error('method not implemented.');
|
|
||||||
}
|
|
||||||
get applicableConnectionProviderIds(): string[] {
|
|
||||||
throw new Error('method not implemented.');
|
|
||||||
}
|
|
||||||
changeKernel(displayName: string): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
changeContext(host: string, connection?: IConnectionProfile, hideErrorMessage?: boolean): Promise<void> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
findCellIndex(cellModel: ICellModel): number {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
addCell(cellType: CellType, index?: number): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
deleteCell(cellModel: ICellModel): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
saveModel(): Promise<boolean> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
pushEditOperations(edits: ISingleNotebookEditOperation[]): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
getApplicableConnectionProviderIds(kernelName: string): string[] {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
get onValidConnectionSelected(): Event<boolean>
|
|
||||||
{
|
|
||||||
throw new Error('method not implemented.');
|
throw new Error('method not implemented.');
|
||||||
}
|
}
|
||||||
|
get contexts(): IDefaultConnection {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get providerId(): string {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get applicableConnectionProviderIds(): string[] {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
changeKernel(displayName: string): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
changeContext(host: string, connection?: IConnectionProfile, hideErrorMessage?: boolean): Promise<void> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
findCellIndex(cellModel: ICellModel): number {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
addCell(cellType: CellType, index?: number): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
deleteCell(cellModel: ICellModel): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
saveModel(): Promise<boolean> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
pushEditOperations(edits: ISingleNotebookEditOperation[]): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
getApplicableConnectionProviderIds(kernelName: string): string[] {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
get onValidConnectionSelected(): Event<boolean> {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
toJSON(): nb.INotebookContents {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookManagerStub implements INotebookManager {
|
export class NotebookManagerStub implements INotebookManager {
|
||||||
|
|||||||
@@ -75,189 +75,190 @@ let mockModelFactory: TypeMoq.Mock<ModelFactory>;
|
|||||||
let notificationService: TypeMoq.Mock<INotificationService>;
|
let notificationService: TypeMoq.Mock<INotificationService>;
|
||||||
let capabilitiesService: TypeMoq.Mock<ICapabilitiesService>;
|
let capabilitiesService: TypeMoq.Mock<ICapabilitiesService>;
|
||||||
|
|
||||||
suite('notebook model', function(): void {
|
suite('notebook model', function (): void {
|
||||||
let notebookManagers = [new NotebookManagerStub()];
|
let notebookManagers = [new NotebookManagerStub()];
|
||||||
let memento: TypeMoq.Mock<Memento>;
|
let memento: TypeMoq.Mock<Memento>;
|
||||||
let queryConnectionService: TypeMoq.Mock<ConnectionManagementService>;
|
let queryConnectionService: TypeMoq.Mock<ConnectionManagementService>;
|
||||||
let defaultModelOptions: INotebookModelOptions;
|
let defaultModelOptions: INotebookModelOptions;
|
||||||
setup(() => {
|
setup(() => {
|
||||||
sessionReady = new Deferred<void>();
|
sessionReady = new Deferred<void>();
|
||||||
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
|
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
|
||||||
capabilitiesService = TypeMoq.Mock.ofType(CapabilitiesTestService);
|
capabilitiesService = TypeMoq.Mock.ofType(CapabilitiesTestService);
|
||||||
memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, '');
|
memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, '');
|
||||||
memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0);
|
memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0);
|
||||||
queryConnectionService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined);
|
queryConnectionService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined);
|
||||||
queryConnectionService.callBase = true;
|
queryConnectionService.callBase = true;
|
||||||
defaultModelOptions = {
|
defaultModelOptions = {
|
||||||
notebookUri: defaultUri,
|
notebookUri: defaultUri,
|
||||||
factory: new ModelFactory(),
|
factory: new ModelFactory(),
|
||||||
notebookManagers,
|
notebookManagers,
|
||||||
notificationService: notificationService.object,
|
contentManager: undefined,
|
||||||
connectionService: queryConnectionService.object,
|
notificationService: notificationService.object,
|
||||||
providerId: 'SQL',
|
connectionService: queryConnectionService.object,
|
||||||
standardKernels: [{ name: 'SQL', connectionProviderIds: ['MSSQL'], notebookProvider: 'sql' }],
|
providerId: 'SQL',
|
||||||
cellMagicMapper: undefined,
|
standardKernels: [{ name: 'SQL', connectionProviderIds: ['MSSQL'], notebookProvider: 'sql' }],
|
||||||
defaultKernel: undefined,
|
cellMagicMapper: undefined,
|
||||||
layoutChanged: undefined,
|
defaultKernel: undefined,
|
||||||
capabilitiesService: capabilitiesService.object
|
layoutChanged: undefined,
|
||||||
};
|
capabilitiesService: capabilitiesService.object
|
||||||
mockClientSession = TypeMoq.Mock.ofType(ClientSession, undefined, defaultModelOptions);
|
};
|
||||||
mockClientSession.setup(c => c.initialize()).returns(() => {
|
mockClientSession = TypeMoq.Mock.ofType(ClientSession, undefined, defaultModelOptions);
|
||||||
return Promise.resolve();
|
mockClientSession.setup(c => c.initialize()).returns(() => {
|
||||||
});
|
return Promise.resolve();
|
||||||
mockClientSession.setup(c => c.ready).returns(() => sessionReady.promise);
|
});
|
||||||
mockModelFactory = TypeMoq.Mock.ofType(ModelFactory);
|
mockClientSession.setup(c => c.ready).returns(() => sessionReady.promise);
|
||||||
mockModelFactory.callBase = true;
|
mockModelFactory = TypeMoq.Mock.ofType(ModelFactory);
|
||||||
mockModelFactory.setup(f => f.createClientSession(TypeMoq.It.isAny())).returns(() => {
|
mockModelFactory.callBase = true;
|
||||||
return mockClientSession.object;
|
mockModelFactory.setup(f => f.createClientSession(TypeMoq.It.isAny())).returns(() => {
|
||||||
});
|
return mockClientSession.object;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('Should create no cells if model has no contents', async function(): Promise<void> {
|
test('Should create no cells if model has no contents', async function (): Promise<void> {
|
||||||
// Given an empty notebook
|
// Given an empty notebook
|
||||||
let emptyNotebook: nb.INotebookContents = {
|
let emptyNotebook: nb.INotebookContents = {
|
||||||
cells: [],
|
cells: [],
|
||||||
metadata: {
|
metadata: {
|
||||||
kernelspec: {
|
kernelspec: {
|
||||||
name: 'mssql',
|
name: 'mssql',
|
||||||
language: 'sql'
|
language: 'sql'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nbformat: 4,
|
nbformat: 4,
|
||||||
nbformat_minor: 5
|
nbformat_minor: 5
|
||||||
};
|
};
|
||||||
|
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(emptyNotebook));
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(emptyNotebook));
|
||||||
notebookManagers[0].contentManager = mockContentManager.object;
|
notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
|
|
||||||
// When I initialize the model
|
// When I initialize the model
|
||||||
let model = new NotebookModel(defaultModelOptions);
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
await model.requestModelLoad();
|
await model.requestModelLoad();
|
||||||
|
|
||||||
// Then I expect to have 0 code cell as the contents
|
// Then I expect to have 0 code cell as the contents
|
||||||
should(model.cells).have.length(0);
|
should(model.cells).have.length(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should throw if model load fails', async function(): Promise<void> {
|
// test('Should throw if model load fails', async function(): Promise<void> {
|
||||||
// Given a call to get Contents fails
|
// // Given a call to get Contents fails
|
||||||
let error = new Error('File not found');
|
// let error = new Error('File not found');
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
// let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).throws(error);
|
// mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).throws(error);
|
||||||
notebookManagers[0].contentManager = mockContentManager.object;
|
// notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
|
|
||||||
// When I initalize the model
|
// // When I initalize the model
|
||||||
// Then it should throw
|
// // Then it should throw
|
||||||
let model = new NotebookModel(defaultModelOptions);
|
// let model = new NotebookModel(defaultModelOptions);
|
||||||
should(model.inErrorState).be.false();
|
// should(model.inErrorState).be.false();
|
||||||
await testUtils.assertThrowsAsync(() => model.requestModelLoad(), error.message);
|
// await testUtils.assertThrowsAsync(() => model.requestModelLoad(), error.message);
|
||||||
should(model.inErrorState).be.true();
|
// should(model.inErrorState).be.true();
|
||||||
});
|
// });
|
||||||
|
|
||||||
test('Should convert cell info to CellModels', async function(): Promise<void> {
|
// test('Should convert cell info to CellModels', async function (): Promise<void> {
|
||||||
// Given a notebook with 2 cells
|
// // Given a notebook with 2 cells
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
// let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContent));
|
// mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContent));
|
||||||
notebookManagers[0].contentManager = mockContentManager.object;
|
// notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
|
|
||||||
// When I initalize the model
|
// // When I initalize the model
|
||||||
let model = new NotebookModel(defaultModelOptions);
|
// let model = new NotebookModel(defaultModelOptions);
|
||||||
await model.requestModelLoad();
|
// await model.requestModelLoad();
|
||||||
|
|
||||||
// Then I expect all cells to be in the model
|
// // Then I expect all cells to be in the model
|
||||||
should(model.cells).have.length(2);
|
// should(model.cells).have.length(2);
|
||||||
should(model.cells[0].source).be.equal(expectedNotebookContent.cells[0].source);
|
// should(model.cells[0].source).be.equal(expectedNotebookContent.cells[0].source);
|
||||||
should(model.cells[1].source).be.equal(expectedNotebookContent.cells[1].source);
|
// should(model.cells[1].source).be.equal(expectedNotebookContent.cells[1].source);
|
||||||
});
|
// });
|
||||||
|
|
||||||
test('Should load contents but then go to error state if client session startup fails', async function(): Promise<void> {
|
// test('Should load contents but then go to error state if client session startup fails', async function(): Promise<void> {
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
// let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
// mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
||||||
notebookManagers[0].contentManager = mockContentManager.object;
|
// notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
|
|
||||||
// Given I have a session that fails to start
|
// // Given I have a session that fails to start
|
||||||
mockClientSession.setup(c => c.isInErrorState).returns(() => true);
|
// mockClientSession.setup(c => c.isInErrorState).returns(() => true);
|
||||||
mockClientSession.setup(c => c.errorMessage).returns(() => 'Error');
|
// mockClientSession.setup(c => c.errorMessage).returns(() => 'Error');
|
||||||
sessionReady.resolve();
|
// sessionReady.resolve();
|
||||||
let sessionFired = false;
|
// let sessionFired = false;
|
||||||
|
|
||||||
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
|
// let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
|
||||||
factory: mockModelFactory.object
|
// factory: mockModelFactory.object
|
||||||
});
|
// });
|
||||||
let model = new NotebookModel(options);
|
// let model = new NotebookModel(options);
|
||||||
model.onClientSessionReady((session) => sessionFired = true);
|
// model.onClientSessionReady((session) => sessionFired = true);
|
||||||
await model.requestModelLoad();
|
// await model.requestModelLoad();
|
||||||
model.backgroundStartSession();
|
// model.backgroundStartSession();
|
||||||
|
|
||||||
// Then I expect load to succeed
|
// // Then I expect load to succeed
|
||||||
shouldHaveOneCell(model);
|
// shouldHaveOneCell(model);
|
||||||
should(model.clientSession).not.be.undefined();
|
// should(model.clientSession).not.be.undefined();
|
||||||
// but on server load completion I expect error state to be set
|
// // but on server load completion I expect error state to be set
|
||||||
// Note: do not expect serverLoad event to throw even if failed
|
// // Note: do not expect serverLoad event to throw even if failed
|
||||||
await model.sessionLoadFinished;
|
// await model.sessionLoadFinished;
|
||||||
should(model.inErrorState).be.true();
|
// should(model.inErrorState).be.true();
|
||||||
should(sessionFired).be.false();
|
// should(sessionFired).be.false();
|
||||||
});
|
// });
|
||||||
|
|
||||||
test('Should not be in error state if client session initialization succeeds', async function(): Promise<void> {
|
test('Should not be in error state if client session initialization succeeds', async function (): Promise<void> {
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
||||||
notebookManagers[0].contentManager = mockContentManager.object;
|
notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
let kernelChangedEmitter: Emitter<nb.IKernelChangedArgs> = new Emitter<nb.IKernelChangedArgs>();
|
let kernelChangedEmitter: Emitter<nb.IKernelChangedArgs> = new Emitter<nb.IKernelChangedArgs>();
|
||||||
let statusChangedEmitter: Emitter<nb.ISession> = new Emitter<nb.ISession>();
|
let statusChangedEmitter: Emitter<nb.ISession> = new Emitter<nb.ISession>();
|
||||||
|
|
||||||
mockClientSession.setup(c => c.isInErrorState).returns(() => false);
|
mockClientSession.setup(c => c.isInErrorState).returns(() => false);
|
||||||
mockClientSession.setup(c => c.isReady).returns(() => true);
|
mockClientSession.setup(c => c.isReady).returns(() => true);
|
||||||
mockClientSession.setup(c => c.kernelChanged).returns(() => kernelChangedEmitter.event);
|
mockClientSession.setup(c => c.kernelChanged).returns(() => kernelChangedEmitter.event);
|
||||||
mockClientSession.setup(c => c.statusChanged).returns(() => statusChangedEmitter.event);
|
mockClientSession.setup(c => c.statusChanged).returns(() => statusChangedEmitter.event);
|
||||||
|
|
||||||
queryConnectionService.setup(c => c.getActiveConnections(TypeMoq.It.isAny())).returns(() => null);
|
queryConnectionService.setup(c => c.getActiveConnections(TypeMoq.It.isAny())).returns(() => null);
|
||||||
|
|
||||||
sessionReady.resolve();
|
sessionReady.resolve();
|
||||||
let actualSession: IClientSession = undefined;
|
let actualSession: IClientSession = undefined;
|
||||||
|
|
||||||
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
|
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>>{
|
||||||
factory: mockModelFactory.object
|
factory: mockModelFactory.object
|
||||||
});
|
});
|
||||||
let model = new NotebookModel(options, false);
|
let model = new NotebookModel(options, undefined);
|
||||||
model.onClientSessionReady((session) => actualSession = session);
|
model.onClientSessionReady((session) => actualSession = session);
|
||||||
await model.requestModelLoad();
|
await model.requestModelLoad();
|
||||||
model.backgroundStartSession();
|
model.backgroundStartSession();
|
||||||
|
|
||||||
// Then I expect load to succeed
|
// Then I expect load to succeed
|
||||||
should(model.clientSession).not.be.undefined();
|
should(model.clientSession).not.be.undefined();
|
||||||
// but on server load completion I expect error state to be set
|
// but on server load completion I expect error state to be set
|
||||||
// Note: do not expect serverLoad event to throw even if failed
|
// Note: do not expect serverLoad event to throw even if failed
|
||||||
let kernelChangedArg: nb.IKernelChangedArgs = undefined;
|
let kernelChangedArg: nb.IKernelChangedArgs = undefined;
|
||||||
model.kernelChanged((kernel) => kernelChangedArg = kernel);
|
model.kernelChanged((kernel) => kernelChangedArg = kernel);
|
||||||
await model.sessionLoadFinished;
|
await model.sessionLoadFinished;
|
||||||
should(model.inErrorState).be.false();
|
should(model.inErrorState).be.false();
|
||||||
should(actualSession).equal(mockClientSession.object);
|
should(actualSession).equal(mockClientSession.object);
|
||||||
should(model.clientSession).equal(mockClientSession.object);
|
should(model.clientSession).equal(mockClientSession.object);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should sanitize kernel display name when IP is included', async function(): Promise<void> {
|
test('Should sanitize kernel display name when IP is included', async function (): Promise<void> {
|
||||||
let model = new NotebookModel(defaultModelOptions);
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
let displayName = 'PySpark (1.1.1.1)';
|
let displayName = 'PySpark (1.1.1.1)';
|
||||||
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
|
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
|
||||||
should(sanitizedDisplayName).equal('PySpark');
|
should(sanitizedDisplayName).equal('PySpark');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should sanitize kernel display name properly when IP is not included', async function(): Promise<void> {
|
test('Should sanitize kernel display name properly when IP is not included', async function (): Promise<void> {
|
||||||
let model = new NotebookModel(defaultModelOptions);
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
let displayName = 'PySpark';
|
let displayName = 'PySpark';
|
||||||
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
|
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
|
||||||
should(sanitizedDisplayName).equal('PySpark');
|
should(sanitizedDisplayName).equal('PySpark');
|
||||||
});
|
});
|
||||||
|
|
||||||
function shouldHaveOneCell(model: NotebookModel): void {
|
function shouldHaveOneCell(model: NotebookModel): void {
|
||||||
should(model.cells).have.length(1);
|
should(model.cells).have.length(1);
|
||||||
verifyCellModel(model.cells[0], { cell_type: CellTypes.Code, source: 'insert into t1 values (c1, c2)', metadata: { language: 'python' }, execution_count: 1 });
|
verifyCellModel(model.cells[0], { cell_type: CellTypes.Code, source: 'insert into t1 values (c1, c2)', metadata: { language: 'python' }, execution_count: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyCellModel(cellModel: ICellModel, expected: nb.ICellContents): void {
|
function verifyCellModel(cellModel: ICellModel, expected: nb.ICellContents): void {
|
||||||
should(cellModel.cellType).equal(expected.cell_type);
|
should(cellModel.cellType).equal(expected.cell_type);
|
||||||
should(cellModel.source).equal(expected.source);
|
should(cellModel.source).equal(expected.source);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,6 +37,33 @@ import { ICodeActionsOnSaveOptions } from 'vs/editor/common/config/editorOptions
|
|||||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||||
|
|
||||||
|
// {{SQL CARBON EDIT}}
|
||||||
|
import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An update participant that ensures any un-tracked changes are synced to the JSON file contents for a
|
||||||
|
* Notebook before save occurs. While every effort is made to ensure model changes are notified and a listener
|
||||||
|
* updates the backing model in-place, this is a backup mechanism to hard-update the file before save in case
|
||||||
|
* some are missed.
|
||||||
|
*/
|
||||||
|
class NotebookUpdateParticipant implements ISaveParticipantParticipant {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@INotebookService private notebookService: INotebookService
|
||||||
|
) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void {
|
||||||
|
let uriString = model.getResource().toString();
|
||||||
|
let notebookEditor = this.notebookService.listNotebookEditors().find((editor) => editor.id === uriString);
|
||||||
|
if (notebookEditor) {
|
||||||
|
notebookEditor.notebookParams.input.updateModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface ISaveParticipantParticipant extends ISaveParticipant {
|
export interface ISaveParticipantParticipant extends ISaveParticipant {
|
||||||
// progressMessage: string;
|
// progressMessage: string;
|
||||||
}
|
}
|
||||||
@@ -388,6 +415,8 @@ export class SaveParticipant implements ISaveParticipant {
|
|||||||
instantiationService.createInstance(FormatOnSaveParticipant),
|
instantiationService.createInstance(FormatOnSaveParticipant),
|
||||||
instantiationService.createInstance(FinalNewLineParticipant),
|
instantiationService.createInstance(FinalNewLineParticipant),
|
||||||
instantiationService.createInstance(TrimFinalNewLinesParticipant),
|
instantiationService.createInstance(TrimFinalNewLinesParticipant),
|
||||||
|
// {{SQL CARBON EDIT}}
|
||||||
|
instantiationService.createInstance(NotebookUpdateParticipant),
|
||||||
instantiationService.createInstance(ExtHostSaveParticipant, extHostContext),
|
instantiationService.createInstance(ExtHostSaveParticipant, extHostContext),
|
||||||
]);
|
]);
|
||||||
// Hook into model
|
// Hook into model
|
||||||
|
|||||||
Reference in New Issue
Block a user