From 3088c73e2bff00595cb6e357a9f7d62219aea551 Mon Sep 17 00:00:00 2001 From: Maddy <12754347+MaddyDev@users.noreply.github.com> Date: Mon, 1 Jun 2020 15:47:56 -0700 Subject: [PATCH] Feat/notebooks viewlet (#10170) * clean up unsavedBooks to providedBooks * added notebooks viewley contribution * added notebookExporerAction context * temp shortcut key B * remove commenred code * changes with master merge * fix comments * initial tests * fix casing and description * merged master and resolved errors * remove extension point & add custom view container * merge latest from master * remove unused files * move book images to common * remove notebookExplorer contrib & move to notebook * build fix * remove explorer specific sryles from common * vscode convention to define container actions * rename notebooks/title --- extensions/notebook/package.json | 47 +++--- extensions/notebook/package.nls.json | 2 +- .../notebook/resources/dark/JupyterBook_5.svg | 7 - extensions/notebook/src/book/bookTreeItem.ts | 2 +- extensions/notebook/src/book/bookTreeView.ts | 2 +- extensions/notebook/src/extension.ts | 19 ++- src/sql/media/icons/book.svg | 7 + .../sql/media/icons/book_image.svg | 0 src/sql/media/icons/book_inverse.svg | 11 ++ src/sql/media/icons/common-icons.css | 7 + .../browser/media/notebook.contribution.css | 14 ++ .../notebook/browser/notebook.contribution.ts | 16 ++ .../notebookExplorerViewlet.ts | 149 ++++++++++++++++++ .../browser/notebookExplorerViewlet.test.ts | 94 +++++++++++ src/vs/platform/actions/common/actions.ts | 1 + .../api/browser/viewsExtensionPoint.ts | 9 ++ .../api/common/menusExtensionPoint.ts | 1 + src/vs/workbench/browser/layout.ts | 4 + 18 files changed, 349 insertions(+), 43 deletions(-) delete mode 100644 extensions/notebook/resources/dark/JupyterBook_5.svg create mode 100644 src/sql/media/icons/book.svg rename extensions/notebook/resources/dark/JupyterBook_2.svg => src/sql/media/icons/book_image.svg (100%) create mode 100644 src/sql/media/icons/book_inverse.svg create mode 100644 src/sql/workbench/contrib/notebook/browser/media/notebook.contribution.css create mode 100644 src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet.ts create mode 100644 src/sql/workbench/contrib/notebook/test/browser/notebookExplorerViewlet.test.ts diff --git a/extensions/notebook/package.json b/extensions/notebook/package.json index 557f234e6d..97235d9a63 100644 --- a/extensions/notebook/package.json +++ b/extensions/notebook/package.json @@ -339,6 +339,12 @@ "when": "false" } ], + "notebooks/title": [ + { + "command": "notebook.command.createBook", + "group": "secondary" + } + ], "touchBar": [ { "command": "notebook.command.runactivecell", @@ -376,12 +382,12 @@ }, { "command": "notebook.command.searchUntitledBook", - "when": "view == unsavedBookTreeView && viewItem == unsavedBook && unsavedBooks", + "when": "view == providedBooksView && viewItem == providedBook && providedBooks", "group": "inline" }, { "command": "notebook.command.saveBook", - "when": "view == unsavedBookTreeView && viewItem == unsavedBook && unsavedBooks", + "when": "view == providedBooksView && viewItem == providedBook && providedBooks", "group": "inline" }, { @@ -400,8 +406,8 @@ "group": "navigation" }, { - "command": "notebook.command.createBook", - "when": "view == bookTreeView" + "command": "books.sqlserver2019", + "when": "view == providedBooksView" }, { "command": "notebook.command.openNotebookFolder", @@ -416,6 +422,18 @@ } ] }, + "views": { + "notebooks": [ + { + "id": "bookTreeView", + "name": "%title.SavedBooks%" + }, + { + "id": "providedBooksView", + "name": "%title.ProvidedBooks%" + } + ] + }, "keybindings": [ { "command": "notebook.command.runactivecell", @@ -507,27 +525,6 @@ "connectionProviderIds": [] } ] - }, - "viewsContainers": { - "activitybar": [ - { - "id": "books-explorer", - "title": "Notebooks", - "icon": "resources/dark/JupyterBook_2.svg" - } - ] - }, - "views": { - "books-explorer": [ - { - "id": "bookTreeView", - "name": "%title.SavedBooks%" - }, - { - "id": "unsavedBookTreeView", - "name": "%title.UnsavedBooks%" - } - ] } }, "dependencies": { diff --git a/extensions/notebook/package.nls.json b/extensions/notebook/package.nls.json index 9c605670fc..ecc0747485 100644 --- a/extensions/notebook/package.nls.json +++ b/extensions/notebook/package.nls.json @@ -34,7 +34,7 @@ "title.trustBook": "Trust Book", "title.searchJupyterBook": "Search Book", "title.SavedBooks": "Notebooks", - "title.UnsavedBooks": "Provided Books", + "title.ProvidedBooks": "Provided Books", "title.PreviewLocalizedBook": "Get localized SQL Server 2019 guide", "title.openJupyterBook": "Open Jupyter Book", "title.closeJupyterBook": "Close Jupyter Book", diff --git a/extensions/notebook/resources/dark/JupyterBook_5.svg b/extensions/notebook/resources/dark/JupyterBook_5.svg deleted file mode 100644 index 7f381926c6..0000000000 --- a/extensions/notebook/resources/dark/JupyterBook_5.svg +++ /dev/null @@ -1,7 +0,0 @@ - - ADS_jupyterBook - - - - - diff --git a/extensions/notebook/src/book/bookTreeItem.ts b/extensions/notebook/src/book/bookTreeItem.ts index b52c0bac9d..7971ad4dbe 100644 --- a/extensions/notebook/src/book/bookTreeItem.ts +++ b/extensions/notebook/src/book/bookTreeItem.ts @@ -41,7 +41,7 @@ export class BookTreeItem extends vscode.TreeItem { this.collapsibleState = book.treeItemCollapsibleState; this._sections = book.page; if (book.isUntitled) { - this.contextValue = 'unsavedBook'; + this.contextValue = 'providedBook'; } else { this.contextValue = 'savedBook'; } diff --git a/extensions/notebook/src/book/bookTreeView.ts b/extensions/notebook/src/book/bookTreeView.ts index 948d516150..c3de56cffd 100644 --- a/extensions/notebook/src/book/bookTreeView.ts +++ b/extensions/notebook/src/book/bookTreeView.ts @@ -54,7 +54,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider { - await vscode.commands.executeCommand('setContext', 'unsavedBooks', this._openAsUntitled); + await vscode.commands.executeCommand('setContext', 'providedBooks', this._openAsUntitled); await Promise.all(workspaceFolders.map(async (workspaceFolder) => { try { await this.loadNotebooksInFolder(workspaceFolder.uri.fsPath); diff --git a/extensions/notebook/src/extension.ts b/extensions/notebook/src/extension.ts index 45877940d5..fd7511a3ac 100644 --- a/extensions/notebook/src/extension.ts +++ b/extensions/notebook/src/extension.ts @@ -24,21 +24,21 @@ const JUPYTER_NOTEBOOK_PROVIDER = 'jupyter'; const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', "This sample code loads the file into a data frame and shows the first 10 results."); const noNotebookVisible = localize('noNotebookVisible', "No notebook editor is active"); const BOOKS_VIEWID = 'bookTreeView'; -const READONLY_BOOKS_VIEWID = 'unsavedBookTreeView'; +const PROVIDED_BOOKS_VIEWID = 'providedBooksView'; let controller: JupyterController; type ChooseCellType = { label: string, id: CellType }; export async function activate(extensionContext: vscode.ExtensionContext): Promise { const createBookPath: string = path.posix.join(extensionContext.extensionPath, 'resources', 'notebooks', 'JupyterBooksCreate.ipynb'); - extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openBook', (bookPath: string, openAsUntitled: boolean, urlToOpen?: string) => openAsUntitled ? untitledBookTreeViewProvider.openBook(bookPath, urlToOpen, true) : bookTreeViewProvider.openBook(bookPath, urlToOpen, true))); + extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openBook', (bookPath: string, openAsUntitled: boolean, urlToOpen?: string) => openAsUntitled ? providedBookTreeViewProvider.openBook(bookPath, urlToOpen, true) : bookTreeViewProvider.openBook(bookPath, urlToOpen, true))); extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource))); - extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openUntitledNotebook', (resource) => untitledBookTreeViewProvider.openNotebookAsUntitled(resource))); + extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openUntitledNotebook', (resource) => providedBookTreeViewProvider.openNotebookAsUntitled(resource))); extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openMarkdown', (resource) => bookTreeViewProvider.openMarkdown(resource))); extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openExternalLink', (resource) => bookTreeViewProvider.openExternalLink(resource))); - extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.saveBook', () => untitledBookTreeViewProvider.saveJupyterBooks())); + extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.saveBook', () => providedBookTreeViewProvider.saveJupyterBooks())); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.trustBook', (resource) => bookTreeViewProvider.trustBook(resource))); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchBook', (item) => bookTreeViewProvider.searchJupyterBooks(item))); - extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchUntitledBook', () => untitledBookTreeViewProvider.searchJupyterBooks())); + extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchUntitledBook', () => providedBookTreeViewProvider.searchJupyterBooks())); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.openBook', () => bookTreeViewProvider.openNewBook())); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.closeBook', (book: any) => bookTreeViewProvider.closeBook(book))); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.closeNotebook', (book: any) => bookTreeViewProvider.closeBook(book))); @@ -114,7 +114,7 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi })); extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.revealInBooksViewlet', (uri: vscode.Uri, shouldReveal: boolean) => bookTreeViewProvider.revealActiveDocumentInViewlet(uri, shouldReveal))); - extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.revealInUntitledBooksViewlet', (uri: vscode.Uri, shouldReveal: boolean) => untitledBookTreeViewProvider.revealActiveDocumentInViewlet(uri, shouldReveal))); + extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.revealInUntitledBooksViewlet', (uri: vscode.Uri, shouldReveal: boolean) => providedBookTreeViewProvider.revealActiveDocumentInViewlet(uri, shouldReveal))); let appContext = new AppContext(extensionContext, new ApiWrapper()); controller = new JupyterController(appContext); @@ -126,9 +126,12 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi let workspaceFolders = vscode.workspace.workspaceFolders?.slice() ?? []; const bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, workspaceFolders, extensionContext, false, BOOKS_VIEWID); await bookTreeViewProvider.initialized; - const untitledBookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], extensionContext, true, READONLY_BOOKS_VIEWID); - await untitledBookTreeViewProvider.initialized; + const providedBookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], extensionContext, true, PROVIDED_BOOKS_VIEWID); + await providedBookTreeViewProvider.initialized; + + extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider(BOOKS_VIEWID, bookTreeViewProvider)); + extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider(PROVIDED_BOOKS_VIEWID, providedBookTreeViewProvider)); return { getJupyterController() { return controller; diff --git a/src/sql/media/icons/book.svg b/src/sql/media/icons/book.svg new file mode 100644 index 0000000000..bae52cfcc8 --- /dev/null +++ b/src/sql/media/icons/book.svg @@ -0,0 +1,7 @@ + + Artboard 10 + + + + + diff --git a/extensions/notebook/resources/dark/JupyterBook_2.svg b/src/sql/media/icons/book_image.svg similarity index 100% rename from extensions/notebook/resources/dark/JupyterBook_2.svg rename to src/sql/media/icons/book_image.svg diff --git a/src/sql/media/icons/book_inverse.svg b/src/sql/media/icons/book_inverse.svg new file mode 100644 index 0000000000..464b3d228b --- /dev/null +++ b/src/sql/media/icons/book_inverse.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/sql/media/icons/common-icons.css b/src/sql/media/icons/common-icons.css index 11023dda22..803edf5b19 100644 --- a/src/sql/media/icons/common-icons.css +++ b/src/sql/media/icons/common-icons.css @@ -545,6 +545,13 @@ Includes non-masked style declarations. */ background-image: url("database_colored.svg"); } +.book.codicon { + + -webkit-mask-image: url("book_image.svg"); + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: 50% 50%; +} + .small { width: 16px; height: 16px; diff --git a/src/sql/workbench/contrib/notebook/browser/media/notebook.contribution.css b/src/sql/workbench/contrib/notebook/browser/media/notebook.contribution.css new file mode 100644 index 0000000000..0808ab3a26 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/media/notebook.contribution.css @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Activity Bar */ +.monaco-workbench .activitybar .monaco-action-bar .action-label.book { + background-color: rgba(255, 255, 255, 0.4); +} + +/* Activity Bar */ +.monaco-workbench .activitybar .monaco-action-bar .checked .action-label.book { + background-color: rgb(255, 255, 255); +} diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts index 41300ed99f..af34a86d4d 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -46,6 +46,9 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { NotebookThemingContribution } from 'sql/workbench/contrib/notebook/browser/notebookThemingContribution'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode'; +import { NotebookExplorerViewletViewsContribution, OpenNotebookExplorerViewletAction } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet'; +import 'vs/css!./media/notebook.contribution'; + Registry.as(EditorInputFactoryExtensions.EditorInputFactories) .registerEditorInputFactory(FileNotebookInput.ID, FileNoteBookEditorInputFactory); @@ -348,3 +351,16 @@ registerComponentType({ selector: MimeRendererComponent.SELECTOR }); registerCellComponent(TextCellComponent); + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(NotebookExplorerViewletViewsContribution, LifecyclePhase.Starting); +const registry = Registry.as(WorkbenchActionsExtensions.WorkbenchActions); +registry.registerWorkbenchAction( + SyncActionDescriptor.create( + OpenNotebookExplorerViewletAction, + OpenNotebookExplorerViewletAction.ID, + OpenNotebookExplorerViewletAction.LABEL, + { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_B }), + 'View: Show Notebook Explorer', + localize('notebookExplorer.view', "View") +); diff --git a/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet.ts b/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet.ts new file mode 100644 index 0000000000..6073109d23 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet.ts @@ -0,0 +1,149 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IAction } from 'vs/base/common/actions'; +import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; + +export const VIEWLET_ID = 'workbench.view.notebooks'; + +// Viewlet Action +export class OpenNotebookExplorerViewletAction extends ShowViewletAction { + public static ID = VIEWLET_ID; + public static LABEL = localize('showNotebookExplorer', "Show Notebooks"); + + constructor( + id: string, + label: string, + @IViewletService viewletService: IViewletService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService + ) { + super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); + } +} + +export class NotebookExplorerViewletViewsContribution implements IWorkbenchContribution { + + constructor() { + this.registerViews(); + } + + private registerViews(): void { + let viewDescriptors = []; + Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews(viewDescriptors, NOTEBOOK_VIEW_CONTAINER); + } +} + +export class NotebookExplorerViewlet extends Viewlet { + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService protected storageService: IStorageService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IExtensionService protected extensionService: IExtensionService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, + @IConfigurationService protected configurationService: IConfigurationService + ) { + super(VIEWLET_ID, instantiationService.createInstance(NotebookExplorerViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); + } +} + +export class NotebookExplorerViewPaneContainer extends ViewPaneContainer { + private root: HTMLElement; + + private notebookSourcesBox: HTMLElement; + + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IConfigurationService configurationService: IConfigurationService, + @IMenuService private menuService: IMenuService, + @IContextKeyService private contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService + ) { + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + } + + create(parent: HTMLElement): void { + addClass(parent, 'notebookExplorer-viewlet'); + this.root = parent; + + this.notebookSourcesBox = append(this.root, $('.notebookSources')); + + return super.create(this.notebookSourcesBox); + } + + public updateStyles(): void { + super.updateStyles(); + } + + focus(): void { + } + + layout(dimension: Dimension): void { + toggleClass(this.root, 'narrow', dimension.width <= 300); + super.layout(new Dimension(dimension.width, dimension.height)); + } + + getOptimalWidth(): number { + return 400; + } + + getSecondaryActions(): IAction[] { + let menu = this.menuService.createMenu(MenuId.NotebookTitle, this.contextKeyService); + let actions = []; + menu.getActions({}).forEach(group => { + if (group[0] === 'secondary') { + actions.push(...group[1]); + } + }); + menu.dispose(); + return actions; + } + + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { + let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewPane; + this._register(viewletPanel); + return viewletPanel; + } +} + +export const NOTEBOOK_VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ + id: VIEWLET_ID, + name: localize('notebookExplorer.name', "Notebooks"), + ctorDescriptor: new SyncDescriptor(NotebookExplorerViewPaneContainer), + icon: 'book', + order: 6, + storageId: `${VIEWLET_ID}.state` +}, ViewContainerLocation.Sidebar); diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookExplorerViewlet.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookExplorerViewlet.test.ts new file mode 100644 index 0000000000..0c5f30cd85 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookExplorerViewlet.test.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as Platform from 'vs/platform/registry/common/platform'; +import { ViewletDescriptor, Extensions, ViewletRegistry, Viewlet } from 'vs/workbench/browser/viewlet'; +import * as Types from 'vs/base/common/types'; +import { workbenchInstantiationService } from 'sql/workbench/test/workbenchTestServices'; +import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry } from 'vs/workbench/common/views'; +import { NotebookExplorerViewPaneContainer, NOTEBOOK_VIEW_CONTAINER } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; + +suite('Notebook Explorer Views', () => { + + class NotebookExplorerTestViewlet extends Viewlet { + + constructor() { + const instantiationService = workbenchInstantiationService(); + super('notebookExplorer', instantiationService.createInstance(NotebookExplorerViewPaneContainer), undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + } + + public layout(dimension: any): void { + throw new Error('Method not implemented.'); + } + + } + + test('ViewDescriptor API', function () { + let d = ViewletDescriptor.create(NotebookExplorerTestViewlet, 'id', 'name', 'class', 1); + assert.strictEqual(d.id, 'id'); + assert.strictEqual(d.name, 'name'); + assert.strictEqual(d.cssClass, 'class'); + assert.strictEqual(d.order, 1); + }); + + test('Editor Aware ViewletDescriptor API', function () { + let d = ViewletDescriptor.create(NotebookExplorerTestViewlet, 'id', 'name', 'class', 5); + assert.strictEqual(d.id, 'id'); + assert.strictEqual(d.name, 'name'); + + d = ViewletDescriptor.create(NotebookExplorerTestViewlet, 'id', 'name', 'class', 5); + assert.strictEqual(d.id, 'id'); + assert.strictEqual(d.name, 'name'); + }); + + test('NotebookExplorer Views registration', function () { + assert(Types.isFunction(Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews)); + assert(Types.isFunction(Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).getViews)); + assert(Types.isFunction(Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).getView)); + + Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([], NOTEBOOK_VIEW_CONTAINER); + + let oldcount = Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).getViews(NOTEBOOK_VIEW_CONTAINER).length; + let d: IViewDescriptor = { id: 'notebookView-test-1', name: 'Notebooks', ctorDescriptor: new SyncDescriptor(NotebookExplorerViewPaneContainer) }; + Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([d], NOTEBOOK_VIEW_CONTAINER); + let retrieved = Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).getView('notebookView-test-1'); + assert(d === retrieved, 'Could not register view :' + d.id + 'Retrieved: ' + retrieved); + let newCount = Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).getViews(NOTEBOOK_VIEW_CONTAINER).length; + assert.equal(oldcount + 1, newCount, 'View registration failed'); + + + }); + + test('NotebookExplorer Views should not register duplicate views', function () { + let d: IViewDescriptor = { id: 'notebookView-test-1', name: 'Notebooks', ctorDescriptor: new SyncDescriptor(NotebookExplorerViewPaneContainer) }; + assert.throws(() => Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([d], NOTEBOOK_VIEW_CONTAINER)); + }); + + test('NotebookExplorer Views deregistration', function () { + assert(Types.isFunction(Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).deregisterViews)); + assert(Types.isFunction(Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).getViews)); + + let d: IViewDescriptor = { id: 'notebookView-test-1', name: 'Notebooks', ctorDescriptor: new SyncDescriptor(NotebookExplorerViewPaneContainer) }; + assert.doesNotThrow(() => Platform.Registry.as(ViewContainerExtensions.ViewsRegistry).deregisterViews([d], NOTEBOOK_VIEW_CONTAINER)); + + }); + + test('NotebookExplorer Viewlet extension point should not register duplicate viewlets', function () { + let v1 = ViewletDescriptor.create(NotebookExplorerTestViewlet, 'notebookExplorer-test-id', 'name'); + Platform.Registry.as(Extensions.Viewlets).registerViewlet(v1); + let oldCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length; + + let v1Duplicate = ViewletDescriptor.create(NotebookExplorerTestViewlet, 'notebookExplorer-test-id', 'name'); + // Shouldn't register the duplicate. + Platform.Registry.as(Extensions.Viewlets).registerViewlet(v1Duplicate); + + let newCount = Platform.Registry.as(Extensions.Viewlets).getViewlets().length; + assert.equal(oldCount, newCount, 'Duplicate registration of views.'); + + }); + +}); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0847dbbbb8..2fe9813afb 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -126,6 +126,7 @@ export class MenuId { static readonly DataExplorerAction = new MenuId('DataExplorerAction'); // {{SQL CARBON EDIT}} static readonly ExplorerWidgetContext = new MenuId('ExplorerWidgetContext'); // {{SQL CARBON EDIT}} static readonly DashboardToolbar = new MenuId('DashboardToolbar'); // {{SQL CARBON EDIT}} + static readonly NotebookTitle = new MenuId('NotebookTitle'); // {{SQL CARBON EDIT}} static readonly TimelineItemContext = new MenuId('TimelineItemContext'); static readonly TimelineTitle = new MenuId('TimelineTitle'); static readonly TimelineTitleContext = new MenuId('TimelineTitleContext'); diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index f5cf39b33c..4a80aa8900 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -20,6 +20,7 @@ import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files' import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm'; import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/common/remote.contribution'; +import { VIEWLET_ID as NOTEBOOK } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet'; // {{SQL CARBON EDIT}} import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet'; @@ -164,6 +165,13 @@ const viewsContribution: IJSONSchema = { type: 'array', items: remoteViewDescriptor, default: [] + }, + // {{SQL CARBON EDIT}} + 'notebooks': { + description: localize('views.notebooks', "Contributes views to Notebooks container in the Activity bar."), + type: 'array', + items: viewDescriptor, + default: [] } }, additionalProperties: { @@ -479,6 +487,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { case 'debug': return this.viewContainersRegistry.get(DEBUG); case 'scm': return this.viewContainersRegistry.get(SCM); case 'remote': return this.viewContainersRegistry.get(REMOTE); + case 'notebooks': return this.viewContainersRegistry.get(NOTEBOOK); // {{SQL CARBON EDIT}} default: return this.viewContainersRegistry.get(`workbench.view.extension.${value}`); } } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 9e8f5aacdb..5ff107eaf5 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -52,6 +52,7 @@ namespace schema { case 'notebook/toolbar': return MenuId.NotebookToolbar; case 'dataExplorer/context': return MenuId.DataExplorerContext; case 'dataExplorer/action': return MenuId.DataExplorerAction; + case 'notebooks/title': return MenuId.NotebookTitle; case 'comments/commentThread/title': return MenuId.CommentThreadTitle; case 'comments/commentThread/context': return MenuId.CommentThreadActions; case 'comments/comment/title': return MenuId.CommentTitle; diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 318e96ed07..1103e42cfb 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -596,6 +596,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi case 'remote': viewletId = 'workbench.view.remote'; break; + // {{SQL CARBON EDIT}} add notebook view container to views + case 'notebooks': + viewletId = 'workbench.view.notebooks'; + break; default: viewletId = `workbench.view.extension.${container.id}`; }