diff --git a/src/sql/parts/notebook/cellViews/code.component.html b/src/sql/parts/notebook/cellViews/code.component.html index 421f9416e3..cde7270c06 100644 --- a/src/sql/parts/notebook/cellViews/code.component.html +++ b/src/sql/parts/notebook/cellViews/code.component.html @@ -5,8 +5,7 @@ *--------------------------------------------------------------------------------------------*/ -->
-
- Toolbar +
diff --git a/src/sql/parts/notebook/cellViews/code.component.ts b/src/sql/parts/notebook/cellViews/code.component.ts index 9b3c270ae7..25ecc4869d 100644 --- a/src/sql/parts/notebook/cellViews/code.component.ts +++ b/src/sql/parts/notebook/cellViews/code.component.ts @@ -23,7 +23,10 @@ import { Schemas } from 'vs/base/common/network'; import * as DOM from 'vs/base/browser/dom'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; +import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; +import { RunCellAction } from 'sql/parts/notebook/cellViews/codeActions'; export const CODE_SELECTOR: string = 'code-component'; @@ -37,6 +40,7 @@ export class CodeComponent extends AngularDisposable implements OnInit { @Input() cellModel: ICellModel; @Output() public onContentChanged = new EventEmitter(); + protected _actionBar: Taskbar; private readonly _minimumHeight = 30; private _editor: QueryTextEditor; private _editorInput: UntitledEditorInput; @@ -49,7 +53,9 @@ export class CodeComponent extends AngularDisposable implements OnInit { @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @Inject(IInstantiationService) private _instantiationService: IInstantiationService, @Inject(IModelService) private _modelService: IModelService, - @Inject(IModeService) private _modeService: IModeService + @Inject(IModeService) private _modeService: IModeService, + @Inject(IContextMenuService) private contextMenuService: IContextMenuService, + @Inject(IContextViewService) private contextViewService: IContextViewService ) { super(); } @@ -57,6 +63,7 @@ export class CodeComponent extends AngularDisposable implements OnInit { ngOnInit() { this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this)); this.updateTheme(this.themeService.getColorTheme()); + this.initActionBar(); } ngOnChanges() { @@ -102,6 +109,18 @@ export class CodeComponent extends AngularDisposable implements OnInit { this._editor.setHeightToScrollHeight(); } + protected initActionBar() { + + let runCellAction = this._instantiationService.createInstance(RunCellAction); + + let taskbar = this.toolbarElement.nativeElement; + this._actionBar = new Taskbar(taskbar, this.contextMenuService); + this._actionBar.context = this; + this._actionBar.setContent([ + { action: runCellAction } + ]); + } + private createUri(): URI { let uri = URI.from({ scheme: Schemas.untitled, path: `notebook-editor-${this.cellModel.id}` }); // Use this to set the internal (immutable) and public (shared with extension) uri properties diff --git a/src/sql/parts/notebook/cellViews/codeActions.ts b/src/sql/parts/notebook/cellViews/codeActions.ts new file mode 100644 index 0000000000..48ae4a7283 --- /dev/null +++ b/src/sql/parts/notebook/cellViews/codeActions.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Action } from 'vs/base/common/actions'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox'; +import { TaskHistoryViewlet } from 'sql/parts/taskHistory/viewlet/taskHistoryViewlet'; + +export class RunCellAction extends Action { + public static ID = 'jobaction.notebookRunCell'; + public static LABEL = 'Run cell'; + + constructor( + ) { + super(RunCellAction.ID, RunCellAction.LABEL, 'newStepIcon'); + } + + public run(context: any): TPromise { + return new TPromise((resolve, reject) => { + try { + resolve(true); + } catch (e) { + reject(e); + } + }); + } +} \ No newline at end of file diff --git a/src/sql/parts/notebook/models/clientSession.ts b/src/sql/parts/notebook/models/clientSession.ts index d363b1cc17..5b4154e4d5 100644 --- a/src/sql/parts/notebook/models/clientSession.ts +++ b/src/sql/parts/notebook/models/clientSession.ts @@ -119,14 +119,14 @@ export class ClientSession implements IClientSession { path: this.notebookUri.fsPath, kernelName: undefined }); + session.defaultKernelLoaded = false; } else { throw err; } - session.defaultKernelLoaded = false; } - this._session = session; - await this.runKernelConfigActions(kernelName); - this._statusChangedEmitter.fire(session); + this._session = session; + await this.runKernelConfigActions(kernelName); + this._statusChangedEmitter.fire(session); } private async runKernelConfigActions(kernelName: string): Promise { diff --git a/src/sql/parts/notebook/models/notebookConnection.ts b/src/sql/parts/notebook/models/notebookConnection.ts index 507ee11622..60b778669b 100644 --- a/src/sql/parts/notebook/models/notebookConnection.ts +++ b/src/sql/parts/notebook/models/notebookConnection.ts @@ -36,7 +36,7 @@ export class NotebookConnection { } public get connectionProfile(): IConnectionProfile { - return this.connectionProfile; + return this._connectionProfile; } @@ -70,11 +70,11 @@ export class NotebookConnection { } public get user(): string { - return this.connectionProfile.options[constants.userPropName]; + return this._connectionProfile.options[constants.userPropName]; } public get password(): string { - return this.connectionProfile.options[constants.passwordPropName]; + return this._connectionProfile.options[constants.passwordPropName]; } public get knoxport(): string { diff --git a/src/sql/parts/notebook/models/sparkMagicContexts.ts b/src/sql/parts/notebook/models/sparkMagicContexts.ts index 2a9f55f694..2f9854233a 100644 --- a/src/sql/parts/notebook/models/sparkMagicContexts.ts +++ b/src/sql/parts/notebook/models/sparkMagicContexts.ts @@ -148,7 +148,7 @@ export class SparkMagicContexts { } else { // Handle kernels if (savedKernelInfo && savedKernelInfo.name.toLowerCase().indexOf('spark') > -1) { - notificationService.warn(localize('sparkKernelRequiresConnection', 'Cannot use kernel {0} as no connection is active. The default kernel of Python3 will be used instead.', savedKernelInfo.display_name)); + notificationService.warn(localize('sparkKernelRequiresConnection', 'Cannot use kernel {0} as no connection is active. The default kernel of {1} will be used instead.', savedKernelInfo.display_name, defaultKernel.display_name)); } } diff --git a/src/sql/parts/notebook/notebook.component.html b/src/sql/parts/notebook/notebook.component.html index c7225fded4..49db5b2fe0 100644 --- a/src/sql/parts/notebook/notebook.component.html +++ b/src/sql/parts/notebook/notebook.component.html @@ -5,10 +5,7 @@ *--------------------------------------------------------------------------------------------*/ -->
-
-
- PlaceHolder for Toolbar -
+
diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts index 9faa7d490d..33c2f74ecc 100644 --- a/src/sql/parts/notebook/notebook.component.ts +++ b/src/sql/parts/notebook/notebook.component.ts @@ -28,6 +28,11 @@ import { ModelFactory } from 'sql/parts/notebook/models/modelFactory'; import * as notebookUtils from './notebookUtils'; import { Deferred } from 'sql/base/common/promise'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; +import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { AddCellAction, KernelsDropdown, AttachToDropdown } from 'sql/parts/notebook/notebookActions'; +import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; export const NOTEBOOK_SELECTOR: string = 'notebook-component'; @@ -41,10 +46,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit { private _model: NotebookModel; private _isInErrorState: boolean = false; private _errorMessage: string; + protected _actionBar: Taskbar; private _activeCell: ICellModel; protected isLoading: boolean; private notebookManager: INotebookManager; private _modelReadyDeferred = new Deferred(); + private _modelRegisteredDeferred = new Deferred(); private profile: IConnectionProfile; @@ -55,7 +62,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit { @Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService, @Inject(INotificationService) private notificationService: INotificationService, @Inject(INotebookService) private notebookService: INotebookService, - @Inject(IBootstrapParams) private notebookParams: INotebookParams + @Inject(IBootstrapParams) private notebookParams: INotebookParams, + @Inject(IInstantiationService) private instantiationService: IInstantiationService, + @Inject(IContextMenuService) private contextMenuService: IContextMenuService, + @Inject(IContextViewService) private contextViewService: IContextViewService ) { super(); this.profile = this.notebookParams!.profile; @@ -65,9 +75,14 @@ export class NotebookComponent extends AngularDisposable implements OnInit { ngOnInit() { this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this)); this.updateTheme(this.themeService.getColorTheme()); + this.initActionBar(); this.doLoad(); } + public get modelRegistered(): Promise { + return this._modelRegisteredDeferred.promise; + } + protected get cells(): ReadonlyArray { return this._model ? this._model.cells : []; } @@ -135,11 +150,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit { notebookManager: this.notebookManager }, false, this.profile); model.onError((errInfo: INotification) => this.handleModelError(errInfo)); - model.backgroundStartSession(); await model.requestModelLoad(this.notebookParams.isTrusted); model.contentChanged((change) => this.handleContentChanged(change)); this._model = model; this._register(model); + this._modelRegisteredDeferred.resolve(this._model); + model.backgroundStartSession(); this._changeRef.detectChanges(); } @@ -170,6 +186,38 @@ export class NotebookComponent extends AngularDisposable implements OnInit { this.notificationService.error(error); } + protected initActionBar() { + let kernelInfoText = document.createElement('div'); + kernelInfoText.className ='notebook-info-label'; + kernelInfoText.innerText = 'Kernel: '; + + let kernelsDropdown = new KernelsDropdown(this.contextViewService, this.modelRegistered); + let kernelsDropdownTemplateContainer = document.createElement('div'); + kernelsDropdownTemplateContainer.className = 'notebook-toolbar-dropdown'; + kernelsDropdown.render(kernelsDropdownTemplateContainer); + attachSelectBoxStyler(kernelsDropdown, this.themeService); + + let attachToDropdown = new AttachToDropdown(this.contextViewService); + let attachToDropdownTemplateContainer = document.createElement('div'); + attachToDropdownTemplateContainer.className = 'notebook-toolbar-dropdown'; + attachToDropdown.render(attachToDropdownTemplateContainer); + attachSelectBoxStyler(attachToDropdown, this.themeService); + + let attachToInfoText = document.createElement('div'); + attachToInfoText.className ='notebook-info-label'; + attachToInfoText.innerText = 'Attach To: '; + + let taskbar = this.toolbar.nativeElement; + this._actionBar = new Taskbar(taskbar, this.contextMenuService); + this._actionBar.context = this; + this._actionBar.setContent([ + { element: kernelInfoText }, + { element: kernelsDropdownTemplateContainer }, + { element: attachToInfoText }, + { element: attachToDropdownTemplateContainer } + ]); + } + public async save(): Promise { try { let saved = await this._model.saveModel(); diff --git a/src/sql/parts/notebook/notebook.contribution.ts b/src/sql/parts/notebook/notebook.contribution.ts index bcb6df8259..e194b0598a 100644 --- a/src/sql/parts/notebook/notebook.contribution.ts +++ b/src/sql/parts/notebook/notebook.contribution.ts @@ -10,13 +10,12 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { TPromise } from 'vs/base/common/winjs.base'; -import * as nls from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; +import URI from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; import { NotebookInput, NotebookInputModel } from 'sql/parts/notebook/notebookInput'; import { NotebookEditor } from 'sql/parts/notebook/notebookEditor'; -import URI from 'vs/base/common/uri'; - let counter = 0; @@ -27,7 +26,7 @@ let counter = 0; export class OpenNotebookAction extends Action { public static ID = 'OpenNotebookAction'; - public static LABEL = nls.localize('OpenNotebookAction', 'Open Notebook editor'); + public static LABEL = localize('OpenNotebookAction', 'Open Notebook editor'); constructor( id: string, @@ -67,4 +66,6 @@ actionRegistry.registerWorkbenchAction( OpenNotebookAction.LABEL ), OpenNotebookAction.LABEL -); \ No newline at end of file +); + + diff --git a/src/sql/parts/notebook/notebook.css b/src/sql/parts/notebook/notebook.css index ce0d058eed..7e63bd1b1a 100644 --- a/src/sql/parts/notebook/notebook.css +++ b/src/sql/parts/notebook/notebook.css @@ -12,3 +12,15 @@ border-width: 1px; border-style: solid; } + +.notebookEditor .notebook-toolbar-dropdown { + width: 150px; + padding-right: 10px; +} + +.notebookEditor .notebook-info-label { + padding-right: 5px; + text-align: center; + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/src/sql/parts/notebook/notebookActions.ts b/src/sql/parts/notebook/notebookActions.ts new file mode 100644 index 0000000000..65b1535894 --- /dev/null +++ b/src/sql/parts/notebook/notebookActions.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + import * as sqlops from 'sqlops'; + +import { Action } from 'vs/base/common/actions'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { localize } from 'vs/nls'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; + +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; +import { INotebookModel } from 'sql/parts/notebook/models/modelInterfaces'; + +const msgLoading = localize('loading', 'Loading kernels...'); +export class AddCellAction extends Action { + public static ID = 'notebook.addCell'; + public static LABEL = 'Cell'; + + constructor( + ) { + super(AddCellAction.ID, AddCellAction.LABEL, 'newStepIcon'); + } + + public run(context: any): TPromise { + return new TPromise((resolve, reject) => { + try { + resolve(true); + } catch (e) { + reject(e); + } + }); + } +} + +export class KernelsDropdown extends SelectBox { + private model: INotebookModel; + constructor(contextViewProvider: IContextViewProvider, modelRegistered: Promise + ) { + super( [msgLoading], msgLoading, contextViewProvider); + if (modelRegistered) { + modelRegistered + .then((model) => this.updateModel(model)) + .catch((err) => { + // No-op for now + }); + } + + this.onDidSelect(e => this.doChangeKernel(e.selected)); + } + + updateModel(model: INotebookModel): void { + this.model = model; + model.kernelsChanged((defaultKernel) => { + this.updateKernel(defaultKernel); + }); + if (model.clientSession) { + model.clientSession.kernelChanged((changedArgs: sqlops.nb.IKernelChangedArgs) => { + if (changedArgs.newValue) { + this.updateKernel(changedArgs.newValue); + } + }); + } + } + + // Update SelectBox values + private updateKernel(defaultKernel: sqlops.nb.IKernelSpec) { + let specs = this.model.specs; + if (specs && specs.kernels) { + let index = specs.kernels.findIndex((kernel => kernel.name === defaultKernel.name)); + this.setOptions(specs.kernels.map(kernel => kernel.display_name), index); + } + } + + public doChangeKernel(displayName: string): void { + this.model.changeKernel(displayName); + } +} + +export class AttachToDropdown extends SelectBox { + constructor(contextViewProvider: IContextViewProvider + ) { + let options: string[] = ['localhost']; + super(options, 'localhost', contextViewProvider); + } +} \ No newline at end of file diff --git a/src/sql/services/notebook/sessionManager.ts b/src/sql/services/notebook/sessionManager.ts index 5ac4dc1f0a..dda6f17735 100644 --- a/src/sql/services/notebook/sessionManager.ts +++ b/src/sql/services/notebook/sessionManager.ts @@ -1,31 +1,141 @@ 'use strict'; import { nb } from 'sqlops'; -import { Session } from 'electron'; +import { localize } from 'vs/nls'; + +const noKernel: string = localize('noKernel', 'No Kernel'); +let noKernelSpec: nb.IKernelSpec = ({ + name: noKernel, + language: 'python', + display_name: noKernel +}); export class SessionManager implements nb.SessionManager { - private _sessionManager: nb.SessionManager; - - constructor() { - - } public get isReady(): boolean { - return this._sessionManager.isReady; + return true; } public get ready(): Thenable { - return this._sessionManager.ready; + return Promise.resolve(); } + public get specs(): nb.IAllKernels { - return this._sessionManager.specs; + let allKernels: nb.IAllKernels = { + defaultKernel: noKernel, + kernels: [noKernelSpec] + }; + return allKernels; } startNew(options: nb.ISessionOptions): Thenable { - return this._sessionManager.startNew(options); + let session = new EmptySession(options); + return Promise.resolve(session); } shutdown(id: string): Thenable { - return this.shutdown(id); + return Promise.resolve(); + } +} + +class EmptySession implements nb.ISession { + private _kernel: EmptyKernel; + private _defaultKernelLoaded = false; + + public set defaultKernelLoaded(value) { + this._defaultKernelLoaded = value; } + public get defaultKernelLoaded(): boolean { + return this._defaultKernelLoaded; + } + + constructor(private options: nb.ISessionOptions) { + this._kernel = new EmptyKernel(); + } + + public get canChangeKernels(): boolean { + return true; + } + + public get id(): string { + return this.options.kernelId || ''; + } + + public get path(): string { + return this.options.path; + } + + public get name(): string { + return this.options.name || ''; + } + + public get type(): string { + return this.options.type || ''; + } + + public get status(): nb.KernelStatus { + return 'connected'; + } + + public get kernel(): nb.IKernel { + return this._kernel; + } + + changeKernel(kernelInfo: nb.IKernelSpec): Thenable { + return Promise.resolve(this.kernel); + } } + +class EmptyKernel implements nb.IKernel { + public get id(): string { + return '-1'; + } + + public get name(): string { + return noKernel; + } + + public get supportsIntellisense(): boolean { + return false; + } + + public get isReady(): boolean { + return true; + } + + public get ready(): Thenable { + return Promise.resolve(); + } + + public get info(): nb.IInfoReply { + let info: nb.IInfoReply = { + protocol_version: '', + implementation: '', + implementation_version: '', + language_info: { + name: '', + version: '', + }, + banner: '', + help_links: [{ + text: '', + url: '' + }] + }; + + return info; + } + getSpec(): Thenable { + return Promise.resolve(noKernelSpec); + } + + requestExecute(content: nb.IExecuteRequest, disposeOnDone?: boolean): nb.IFuture { + throw new Error('Method not implemented.'); + } + + requestComplete(content: nb.ICompleteRequest): Thenable { + let response: Partial = { }; + return Promise.resolve(response as nb.ICompleteReplyMsg); + } + +} \ No newline at end of file