diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css index 8936d95727e08..4a6de69c521c0 100644 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ b/src/vs/base/browser/ui/splitview/splitview.css @@ -30,6 +30,10 @@ display: flex; } +.monaco-split-view > .split-view-view > .header.hide { + display: none; +} + /* Bold font style does not go well with CJK fonts */ .monaco-split-view:lang(zh-Hans) > .split-view-view > .header, .monaco-split-view:lang(zh-Hant) > .split-view-view > .header, diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 09df1966a1559..b642b5359e497 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -117,7 +117,9 @@ const headerDefaultOpts = { export abstract class HeaderView extends View { - protected headerSize: number; + private _headerSize: number; + private _showHeader: boolean; + protected header: HTMLElement; protected body: HTMLElement; @@ -127,7 +129,8 @@ export abstract class HeaderView extends View { constructor(opts: IHeaderViewOptions) { super(opts); - this.headerSize = types.isUndefined(opts.headerSize) ? 22 : opts.headerSize; + this._headerSize = types.isUndefined(opts.headerSize) ? 22 : opts.headerSize; + this._showHeader = this._headerSize > 0; this.headerBackground = opts.headerBackground || headerDefaultOpts.headerBackground; this.headerHighContrastBorder = opts.headerHighContrastBorder; @@ -140,6 +143,10 @@ export abstract class HeaderView extends View { this.applyStyles(); } + protected get headerSize(): number { + return this._showHeader ? this._headerSize : 0; + } + protected applyStyles(): void { if (this.header) { const headerBackgroundColor = this.headerBackground ? this.headerBackground.toString() : null; @@ -162,7 +169,7 @@ export abstract class HeaderView extends View { this.header.style.height = headerSize; } - if (this.headerSize > 0) { + if (this._showHeader) { this.renderHeader(this.header); container.appendChild(this.header); } @@ -177,6 +184,28 @@ export abstract class HeaderView extends View { this.applyStyles(); } + showHeader(): boolean { + if (!this._showHeader) { + if (!this.body.parentElement.contains(this.header)) { + this.renderHeader(this.header); + this.body.parentElement.insertBefore(this.header, this.body); + } + dom.removeClass(this.header, 'hide'); + this._showHeader = true; + return true; + } + return false; + } + + hideHeader(): boolean { + if (this._showHeader) { + dom.addClass(this.header, 'hide'); + this._showHeader = false; + return true; + } + return false; + } + layout(size: number, orientation: Orientation): void { this.layoutBodyContainer(orientation); this.layoutBody(size - this.headerSize); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5e6265c2d2077..c1cad09129dd3 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -13,98 +13,81 @@ declare module 'vscode' { } export namespace window { - /** - * Create a new explorer view. - * + * Register a [TreeDataProvider](#TreeDataProvider) for the registered view `id`. * @param id View id. - * @param name View name. - * @param dataProvider A [TreeDataProvider](#TreeDataProvider). - * @return An instance of [View](#View). + * @param treeDataProvider A [TreeDataProvider](#TreeDataProvider) that provides tree data for the view */ - export function createExplorerView(id: string, name: string, dataProvider: TreeDataProvider): View; + export function registerTreeDataProvider(id: string, treeDataProvider: TreeDataProvider): Disposable; } /** - * A view to interact with nodes + * A data provider that provides tree data for a view */ - export interface View { + export interface TreeDataProvider { + /** + * An optional event to signal that an element or root has changed. + */ + onDidChange?: Event; /** - * Refresh the given nodes + * get [TreeItem](#TreeItem) representation of the `element` + * + * @param element The element for which [TreeItem](#TreeItem) representation is asked for. + * @return [TreeItem](#TreeItem) representation of the element */ - refresh(...nodes: T[]): void; + getTreeItem(element: T): TreeItem; /** - * Dispose this view + * get the children of `element` or root. + * + * @param element The element from which the provider gets children for. + * @return Children of `element` or root. */ - dispose(): void; + getChildren(element?: T): T[] | Thenable; } - /** - * A data provider for a tree view contribution. - * - * The contributed tree view will ask the corresponding provider to provide the root - * node and resolve children for each node. In addition, the provider could **optionally** - * provide the following information for each node: - * - label: A human-readable label used for rendering the node. - * - hasChildren: Whether the node has children and is expandable. - * - clickCommand: A command to execute when the node is clicked. - */ - export interface TreeDataProvider { - + export interface TreeItem { /** - * Provide the root node. This function will be called when the tree explorer is activated - * for the first time. The root node is hidden and its direct children will be displayed on the first level of - * the tree explorer. - * - * @return The root node. + * Label of the tree item */ - provideRootNode(): T | Thenable; + label: string; /** - * Resolve the children of `node`. - * - * @param node The node from which the provider resolves children. - * @return Children of `node`. + * The icon path for the tree item */ - resolveChildren?(node: T): T[] | Thenable; + iconPath?: string | Uri | { light: string | Uri; dark: string | Uri }; /** - * Provide a human-readable string that will be used for rendering the node. Default to use - * `node.toString()` if not provided. - * - * @param node The node from which the provider computes label. - * @return A human-readable label. + * The [command](#Command) which should be run when the tree item + * is open in the Source Control viewlet. */ - getLabel?(node: T): string; + command?: Command; /** - * Determine if `node` has children and is expandable. Default to `true` if not provided. - * - * @param node The node to determine if it has children and is expandable. - * @return A boolean that determines if `node` has children and is expandable. + * Context value of the tree node */ - getHasChildren?(node: T): boolean; + contextValue?: string; /** - * Provider a context key to be set for the node. This can be used to describe actions for each node. - * - * @param node The node from which the provider computes context key. - * @return A context key. + * Collapsible state of the tree item. + * Required only when item has children. */ - getContextKey?(node: T): string; + collapsibleState?: TreeItemCollapsibleState; + } + /** + * Collapsible state of the tree item + */ + export enum TreeItemCollapsibleState { /** - * Get the command to execute when `node` is clicked. - * - * Commands can be registered through [registerCommand](#commands.registerCommand). `node` will be provided - * as the first argument to the command's callback function. - * - * @param node The node that the command is associated with. - * @return The command to execute when `node` is clicked. + * Determines an item is collapsed + */ + Collapsed = 1, + /** + * Determines an item is expanded */ - getClickCommand?(node: T): Command; + Expanded = 2 } /** diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts index 5c84c61db3549..f60782ffe6317 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts @@ -19,7 +19,7 @@ import { MainThreadDiagnostics } from './mainThreadDiagnostics'; import { MainThreadDocuments } from './mainThreadDocuments'; import { MainThreadEditors } from './mainThreadEditors'; import { MainThreadErrors } from './mainThreadErrors'; -import { MainThreadExplorerView } from './mainThreadExplorerView'; +import { MainThreadTreeViews } from './mainThreadTreeViews'; import { MainThreadLanguageFeatures } from './mainThreadLanguageFeatures'; import { MainThreadLanguages } from './mainThreadLanguages'; import { MainThreadMessageService } from './mainThreadMessageService'; @@ -74,7 +74,7 @@ export class ExtHostContribution implements IWorkbenchContribution { col.define(MainContext.MainThreadDocuments).set(this.instantiationService.createInstance(MainThreadDocuments, documentsAndEditors)); col.define(MainContext.MainThreadEditors).set(this.instantiationService.createInstance(MainThreadEditors, documentsAndEditors)); col.define(MainContext.MainThreadErrors).set(create(MainThreadErrors)); - col.define(MainContext.MainThreadExplorerViews).set(create(MainThreadExplorerView)); + col.define(MainContext.MainThreadTreeViews).set(create(MainThreadTreeViews)); col.define(MainContext.MainThreadLanguageFeatures).set(create(MainThreadLanguageFeatures)); col.define(MainContext.MainThreadLanguages).set(create(MainThreadLanguages)); col.define(MainContext.MainThreadMessageService).set(create(MainThreadMessageService)); diff --git a/src/vs/workbench/api/electron-browser/mainThreadExplorerView.ts b/src/vs/workbench/api/electron-browser/mainThreadExplorerView.ts deleted file mode 100644 index 19013b27409b5..0000000000000 --- a/src/vs/workbench/api/electron-browser/mainThreadExplorerView.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { TPromise } from 'vs/base/common/winjs.base'; -import Event, { Emitter } from 'vs/base/common/event'; -import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { ExtHostContext, MainThreadExplorerViewShape, ExtHostExplorerViewShape, ITreeNode } from '../node/extHost.protocol'; -import { IMessageService, Severity } from 'vs/platform/message/common/message'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IExplorerViewsService, IExplorerViewDataProvider, IExplorerView } from 'vs/workbench/parts/explorers/common/explorer'; - -export class MainThreadExplorerView extends MainThreadExplorerViewShape { - - private _proxy: ExtHostExplorerViewShape; - private _views: Map> = new Map>(); - - constructor( - @IThreadService threadService: IThreadService, - @IExplorerViewsService private explorerViewsService: IExplorerViewsService, - @IMessageService private messageService: IMessageService, - @ICommandService private commandService: ICommandService - ) { - super(); - this._proxy = threadService.get(ExtHostContext.ExtHostExplorerView); - } - - $registerView(providerId: string, name: string): void { - const provider = new TreeExplorerNodeProvider(providerId, this._proxy, this.messageService, this.commandService); - const view = this.explorerViewsService.createView(providerId, name, provider); - this._views.set(providerId, view); - } - - $refresh(providerId: string, node: ITreeNode): void { - this._views.get(providerId).refresh(node); - } -} - -class TreeExplorerNodeProvider implements IExplorerViewDataProvider { - - readonly _onRefresh: Emitter = new Emitter(); - readonly onRefresh: Event = this._onRefresh.event; - - constructor(public readonly id: string, private _proxy: ExtHostExplorerViewShape, - private messageService: IMessageService, - private commandService: ICommandService - ) { - } - - provideRoot(): TPromise { - return this._proxy.$provideRootNode(this.id).then(rootNode => rootNode, err => this.messageService.show(Severity.Error, err)); - } - - resolveChildren(node: ITreeNode): TPromise { - return this._proxy.$resolveChildren(this.id, node).then(children => children, err => this.messageService.show(Severity.Error, err)); - } - - hasChildren(node: ITreeNode): boolean { - return node.hasChildren; - } - - getLabel(node: ITreeNode): string { - return node.label; - } - - getId(node: ITreeNode): string { - return node.id; - } - - getContextKey(node: ITreeNode): string { - return node.contextKey; - } - - select(node: ITreeNode): void { - this._proxy.$getInternalCommand(this.id, node).then(command => { - if (command) { - this.commandService.executeCommand(command.id, ...command.arguments); - } - }); - } -} diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts new file mode 100644 index 0000000000000..186c646eaceda --- /dev/null +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import Event, { Emitter } from 'vs/base/common/event'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; +import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape } from '../node/extHost.protocol'; +import { IMessageService, Severity } from 'vs/platform/message/common/message'; +import { ViewsRegistry, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState } from 'vs/workbench/parts/views/browser/views'; + +export class MainThreadTreeViews extends MainThreadTreeViewsShape { + + private _proxy: ExtHostTreeViewsShape; + + constructor( + @IThreadService threadService: IThreadService, + @IMessageService private messageService: IMessageService + ) { + super(); + this._proxy = threadService.get(ExtHostContext.ExtHostTreeViews); + } + + $registerView(treeViewId: string): void { + ViewsRegistry.registerTreeViewDataProvider(treeViewId, new TreeViewDataProvider(treeViewId, this._proxy, this.messageService)); + } + + $refresh(treeViewId: string, treeItemHandle?: number): void { + const treeViewDataProvider: TreeViewDataProvider = ViewsRegistry.getTreeViewDataProvider(treeViewId); + if (treeViewDataProvider) { + treeViewDataProvider.refresh(treeItemHandle); + } + } +} + +type TreeItemHandle = number; + +class TreeViewDataProvider implements ITreeViewDataProvider { + + private _onDidChange: Emitter = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + private childrenMap: Map = new Map(); + private itemsMap: Map = new Map(); + + constructor(private treeViewId: string, + private _proxy: ExtHostTreeViewsShape, + private messageService: IMessageService + ) { + } + + getElements(): TPromise { + return this._proxy.$getElements(this.treeViewId) + .then(elements => { + this.postGetElements(null, elements); + return elements; + }, err => this.messageService.show(Severity.Error, err)); + } + + getChildren(treeItem: ITreeItem): TPromise { + if (treeItem.children) { + return TPromise.as(treeItem.children); + } + return this._proxy.$getChildren(this.treeViewId, treeItem.handle) + .then(children => { + this.postGetElements(treeItem.handle, children); + return children; + }, err => this.messageService.show(Severity.Error, err)); + } + + refresh(treeItemHandle?: number) { + if (treeItemHandle) { + let treeItem = this.itemsMap.get(treeItemHandle); + if (treeItem) { + this._onDidChange.fire(treeItem); + } + } else { + this._onDidChange.fire(); + } + } + + private clearChildren(treeItemHandle: TreeItemHandle): void { + const children = this.childrenMap.get(treeItemHandle); + if (children) { + for (const child of children) { + this.clearChildren(child); + this.itemsMap.delete(child); + } + this.childrenMap.delete(treeItemHandle); + } + } + + private postGetElements(parent: TreeItemHandle, children: ITreeItem[]) { + this.setElements(parent, children); + } + + private setElements(parent: TreeItemHandle, children: ITreeItem[]) { + if (children && children.length) { + for (const child of children) { + this.itemsMap.set(child.handle, child); + if (child.children && child.children.length) { + this.setElements(child.handle, child.children); + } + } + if (parent) { + this.childrenMap.set(parent, children.map(child => child.handle)); + } + } + } + + private populateElementsToExpand(elements: ITreeItem[], toExpand: ITreeItem[]) { + for (const element of elements) { + if (element.collapsibleState === TreeItemCollapsibleState.Expanded) { + toExpand.push(element); + if (element.children && element.children.length) { + this.populateElementsToExpand(element.children, toExpand); + } + } + } + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index b1e56a9c7e12f..a9cf6c35ce800 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -18,7 +18,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/node/extHostDocumentSaveParticipant'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; -import { ExtHostExplorerView } from 'vs/workbench/api/node/extHostExplorerView'; +import { ExtHostTreeViews } from 'vs/workbench/api/node/extHostTreeViews'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostQuickOpen } from 'vs/workbench/api/node/extHostQuickOpen'; import { ExtHostProgress } from 'vs/workbench/api/node/extHostProgress'; @@ -111,7 +111,7 @@ export function createApiFactory( const extHostDocumentSaveParticipant = col.define(ExtHostContext.ExtHostDocumentSaveParticipant).set(new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace))); const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set(new ExtHostEditors(threadService, extHostDocumentsAndEditors)); const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set(new ExtHostCommands(threadService, extHostHeapService)); - const extHostExplorerView = col.define(ExtHostContext.ExtHostExplorerView).set(new ExtHostExplorerView(threadService, extHostCommands)); + const extHostTreeViews = col.define(ExtHostContext.ExtHostTreeViews).set(new ExtHostTreeViews(threadService, extHostCommands)); const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), initData.configuration)); const extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set(new ExtHostDiagnostics(threadService)); const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics)); @@ -369,8 +369,8 @@ export function createApiFactory( sampleFunction: proposedApiFunction(extension, () => { return extHostMessageService.showMessage(Severity.Info, 'Hello Proposed Api!', {}, []); }), - createExplorerView: proposedApiFunction(extension, (id: string, name: string, provider: vscode.TreeDataProvider): vscode.View => { - return extHostExplorerView.createExplorerView(id, name, provider); + registerTreeDataProvider: proposedApiFunction(extension, (id: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable => { + return extHostTreeViews.registerTreeDataProvider(id, treeDataProvider); }) }; @@ -533,6 +533,7 @@ export function createApiFactory( ViewColumn: extHostTypes.ViewColumn, WorkspaceEdit: extHostTypes.WorkspaceEdit, ProgressLocation: extHostTypes.ProgressLocation, + TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState, // functions FileLocationKind: extHostTypes.FileLocationKind, ApplyToKind: extHostTypes.ApplyToKind, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 1f602e9ac5f6c..df7977297c32a 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -194,17 +194,17 @@ export abstract class MainThreadEditorsShape { $getDiffInformation(id: string): TPromise { throw ni(); } } -export interface ITreeNode { - id: string; - label: string; - hasChildren: boolean; - clickCommand: string; - contextKey: string; +export interface TreeItem extends vscode.TreeItem { + handle: number; + commandId?: string; + icon?: string; + iconDark?: string; + children?: TreeItem[]; } -export abstract class MainThreadExplorerViewShape { - $registerView(id: string, name: string): void { throw ni(); } - $refresh(viewId: string, node: ITreeNode): void { throw ni(); } +export abstract class MainThreadTreeViewsShape { + $registerView(treeViewId: string): void { throw ni(); } + $refresh(treeViewId: string, treeItemHandle?: number): void { throw ni(); } } export abstract class MainThreadErrorsShape { @@ -408,11 +408,15 @@ export abstract class ExtHostDocumentsAndEditorsShape { $acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void { throw ni(); } } +export type TreeViewCommandArg = { + treeViewId: string, + treeItemHandle: number +}; -export abstract class ExtHostExplorerViewShape { - $provideRootNode(viewId: string): TPromise { throw ni(); }; - $resolveChildren(viewId: string, node: ITreeNode): TPromise { throw ni(); } - $getInternalCommand(viewId: string, node: ITreeNode): TPromise { throw ni(); } +export abstract class ExtHostTreeViewsShape { + $getElements(treeViewId: string): TPromise { throw ni(); } + $getChildren(treeViewId: string, treeItemHandle: number): TPromise { throw ni(); } + $restore(treeViewId: string, treeItems: TreeItem[]): TPromise { throw ni(); } } export abstract class ExtHostExtensionServiceShape { @@ -503,7 +507,7 @@ export const MainContext = { MainThreadDocuments: createMainId('MainThreadDocuments', MainThreadDocumentsShape), MainThreadEditors: createMainId('MainThreadEditors', MainThreadEditorsShape), MainThreadErrors: createMainId('MainThreadErrors', MainThreadErrorsShape), - MainThreadExplorerViews: createMainId('MainThreadExplorerView', MainThreadExplorerViewShape), + MainThreadTreeViews: createMainId('MainThreadTreeViews', MainThreadTreeViewsShape), MainThreadLanguageFeatures: createMainId('MainThreadLanguageFeatures', MainThreadLanguageFeaturesShape), MainThreadLanguages: createMainId('MainThreadLanguages', MainThreadLanguagesShape), MainThreadMessageService: createMainId('MainThreadMessageService', MainThreadMessageServiceShape), @@ -528,7 +532,7 @@ export const ExtHostContext = { ExtHostDocuments: createExtId('ExtHostDocuments', ExtHostDocumentsShape), ExtHostDocumentSaveParticipant: createExtId('ExtHostDocumentSaveParticipant', ExtHostDocumentSaveParticipantShape), ExtHostEditors: createExtId('ExtHostEditors', ExtHostEditorsShape), - ExtHostExplorerView: createExtId('ExtHostExplorerView', ExtHostExplorerViewShape), + ExtHostTreeViews: createExtId('ExtHostTreeViews', ExtHostTreeViewsShape), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService', ExtHostFileSystemEventServiceShape), ExtHostHeapService: createExtId('ExtHostHeapMonitor', ExtHostHeapServiceShape), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures', ExtHostLanguageFeaturesShape), diff --git a/src/vs/workbench/api/node/extHostExplorerView.ts b/src/vs/workbench/api/node/extHostExplorerView.ts deleted file mode 100644 index cb2bd09645b5f..0000000000000 --- a/src/vs/workbench/api/node/extHostExplorerView.ts +++ /dev/null @@ -1,163 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { localize } from 'vs/nls'; -import { View, TreeDataProvider } from 'vscode'; -import { defaultGenerator } from 'vs/base/common/idGenerator'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { MainContext, ExtHostExplorerViewShape, MainThreadExplorerViewShape, ITreeNode } from './extHost.protocol'; -import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; -import { asWinJsPromise } from 'vs/base/common/async'; -import * as modes from 'vs/editor/common/modes'; - -class TreeNodeImpl implements ITreeNode { - - readonly id: string; - label: string; - hasChildren: boolean; - clickCommand: string = null; - contextKey: string; - - constructor(readonly providerId: string, node: any, provider: TreeDataProvider) { - this.id = defaultGenerator.nextId(); - this.label = provider.getLabel ? provider.getLabel(node) : node.toString(); - this.hasChildren = provider.getHasChildren ? provider.getHasChildren(node) : true; - this.contextKey = provider.getContextKey ? provider.getContextKey(node) : null; - if (provider.getClickCommand) { - const command = provider.getClickCommand(node); - if (command) { - this.clickCommand = command.command; - } - } - } -} - -export class ExtHostExplorerView extends ExtHostExplorerViewShape { - private _proxy: MainThreadExplorerViewShape; - - private _extNodeProviders: { [providerId: string]: TreeDataProvider }; - private _extViews: Map> = new Map>(); - private _extNodeMaps: { [providerId: string]: { [id: string]: ITreeNode } }; - private _mainNodesMap: Map>; - private _childrenNodesMap: Map>; - - constructor( - threadService: IThreadService, - private commands: ExtHostCommands - ) { - super(); - - this._proxy = threadService.get(MainContext.MainThreadExplorerViews); - - this._extNodeProviders = Object.create(null); - this._extNodeMaps = Object.create(null); - this._mainNodesMap = new Map>(); - this._childrenNodesMap = new Map>(); - - commands.registerArgumentProcessor({ - processArgument: arg => { - if (arg && arg.providerId && arg.id) { - const extNodeMap = this._extNodeMaps[arg.providerId]; - return extNodeMap[arg.id]; - } - return arg; - } - }); - } - - createExplorerView(viewId: string, viewName: string, provider: TreeDataProvider): View { - this._proxy.$registerView(viewId, viewName); - this._extNodeProviders[viewId] = provider; - this._mainNodesMap.set(viewId, new Map()); - this._childrenNodesMap.set(viewId, new Map()); - - const treeView: View = { - refresh: (node: T) => { - const mainThreadNode = this._mainNodesMap.get(viewId).get(node); - this._proxy.$refresh(viewId, mainThreadNode); - }, - dispose: () => { - delete this._extNodeProviders[viewId]; - delete this._extNodeProviders[viewId]; - this._mainNodesMap.delete(viewId); - this._childrenNodesMap.delete(viewId); - this._extViews.delete(viewId); - } - }; - this._extViews.set(viewId, treeView); - return treeView; - } - - $provideRootNode(providerId: string): TPromise { - const provider = this._extNodeProviders[providerId]; - if (!provider) { - const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId); - return TPromise.wrapError(errMessage); - } - - return asWinJsPromise(() => provider.provideRootNode()).then(extRootNode => { - const extNodeMap: { [id: string]: ITreeNode } = Object.create(null); - const internalRootNode = new TreeNodeImpl(providerId, extRootNode, provider); - - extNodeMap[internalRootNode.id] = extRootNode; - this._extNodeMaps[providerId] = extNodeMap; - - this._mainNodesMap.get(providerId).set(extRootNode, internalRootNode); - - return internalRootNode; - }, err => { - const errMessage = localize('treeExplorer.failedToProvideRootNode', 'TreeExplorerNodeProvider \'{0}\' failed to provide root node.', providerId); - return TPromise.wrapError(errMessage); - }); - } - - $resolveChildren(providerId: string, mainThreadNode: ITreeNode): TPromise { - const provider = this._extNodeProviders[providerId]; - if (!provider) { - const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId); - return TPromise.wrapError(errMessage); - } - - const extNodeMap = this._extNodeMaps[providerId]; - const extNode = extNodeMap[mainThreadNode.id]; - - const currentChildren = this._childrenNodesMap.get(providerId).get(extNode); - if (currentChildren) { - for (const child of currentChildren) { - this._mainNodesMap.get(providerId).delete(child); - } - } - - return asWinJsPromise(() => provider.resolveChildren(extNode)).then(children => { - return children.map(extChild => { - const internalChild = new TreeNodeImpl(providerId, extChild, provider); - extNodeMap[internalChild.id] = extChild; - this._mainNodesMap.get(providerId).set(extChild, internalChild); - return internalChild; - }); - }); - } - - // Convert the command on the ExtHost side so we can pass the original externalNode to the registered handler - $getInternalCommand(providerId: string, mainThreadNode: ITreeNode): TPromise { - const commandConverter = this.commands.converter; - - if (mainThreadNode.clickCommand) { - const extNode = this._extNodeMaps[providerId][mainThreadNode.id]; - - const internalCommand = commandConverter.toInternal({ - title: '', - command: mainThreadNode.clickCommand, - arguments: [extNode] - }); - - return TPromise.wrap(internalCommand); - } - - return TPromise.as(null); - } -} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts new file mode 100644 index 0000000000000..bbfde013dddf3 --- /dev/null +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { localize } from 'vs/nls'; +import * as vscode from 'vscode'; +import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; +import { MainContext, ExtHostTreeViewsShape, MainThreadTreeViewsShape, TreeItem, TreeViewCommandArg } from './extHost.protocol'; +import { TreeItemCollapsibleState } from './extHostTypes'; +import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; +import { asWinJsPromise } from 'vs/base/common/async'; +import * as modes from 'vs/editor/common/modes'; + +type TreeItemHandle = number; + +export class ExtHostTreeViews extends ExtHostTreeViewsShape { + + private treeViews: Map> = new Map>(); + private _proxy: MainThreadTreeViewsShape; + + constructor( + threadService: IThreadService, + private commands: ExtHostCommands + ) { + super(); + this._proxy = threadService.get(MainContext.MainThreadTreeViews); + commands.registerArgumentProcessor({ + processArgument: arg => { + if (arg && arg.treeViewId && arg.treeItemHandle) { + return this.convertArgument(arg); + } + return arg; + } + }); + } + + registerTreeDataProvider(id: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { + const treeView = new ExtHostTreeView(id, treeDataProvider, this._proxy); + this.treeViews.set(id, treeView); + return { + dispose: () => { + this.treeViews.delete(id); + treeView.dispose(); + } + }; + } + + $getElements(treeViewId: string): TPromise { + const treeView = this.treeViews.get(treeViewId); + if (!treeView) { + return TPromise.wrapError(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)); + } + return treeView.getTreeItems(); + } + + $getChildren(treeViewId: string, treeItemHandle?: number): TPromise { + const treeView = this.treeViews.get(treeViewId); + if (!treeView) { + return TPromise.wrapError(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)); + } + return treeView.getChildren(treeItemHandle); + } + + private convertArgument(arg: TreeViewCommandArg): any { + const treeView = this.treeViews.get(arg.treeViewId); + if (!treeView) { + return TPromise.wrapError(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', arg.treeViewId)); + } + return treeView.getExtensionElement(arg.treeItemHandle); + } +} + +class ExtHostTreeView extends Disposable { + + private _itemHandlePool = 0; + + private extElementsMap: Map = new Map(); + private itemHandlesMap: Map = new Map(); + private extChildrenElementsMap: Map = new Map(); + + constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider, private proxy: MainThreadTreeViewsShape) { + super(); + this.proxy.$registerView(viewId); + this._register(dataProvider.onDidChange(element => this._refresh(element))); + } + + getTreeItems(): TPromise { + this.extChildrenElementsMap.clear(); + this.extElementsMap.clear(); + this.itemHandlesMap.clear(); + + return asWinJsPromise(() => this.dataProvider.getChildren()) + .then(elements => this.processAndMapElements(elements)); + } + + getChildren(treeItemHandle: TreeItemHandle): TPromise { + let extElement = this.getExtensionElement(treeItemHandle); + if (extElement) { + this.clearChildren(extElement); + } else { + return TPromise.wrapError(localize('treeItem.notFound', 'No tree item with id \'{0}\' found.', treeItemHandle)); + } + + return asWinJsPromise(() => this.dataProvider.getChildren(extElement)) + .then(childrenElements => this.processAndMapElements(childrenElements)); + } + + getExtensionElement(treeItemHandle: TreeItemHandle): T { + return this.extElementsMap.get(treeItemHandle); + } + + private _refresh(element: T): void { + if (element) { + const itemHandle = this.itemHandlesMap.get(element); + if (itemHandle) { + this.proxy.$refresh(this.viewId, itemHandle); + } + } else { + this.proxy.$refresh(this.viewId); + } + } + + private processAndMapElements(elements: T[]): TPromise { + const treeItemsPromises: TPromise[] = []; + for (const element of elements) { + if (this.extChildrenElementsMap.has(element)) { + return TPromise.wrapError(localize('treeView.duplicateElement', 'Element {0} is already registered', element)); + } + const treeItem = this.massageTreeItem(this.dataProvider.getTreeItem(element)); + this.itemHandlesMap.set(element, treeItem.handle); + this.extElementsMap.set(treeItem.handle, element); + if (treeItem.collapsibleState === TreeItemCollapsibleState.Expanded) { + treeItemsPromises.push(this.getChildren(treeItem.handle).then(children => { + treeItem.children = children; + return treeItem; + })); + } else { + treeItemsPromises.push(TPromise.as(treeItem)); + } + } + return TPromise.join(treeItemsPromises); + } + + private massageTreeItem(extensionTreeItem: vscode.TreeItem): TreeItem { + return { + handle: ++this._itemHandlePool, + label: extensionTreeItem.label, + commandId: extensionTreeItem.command ? extensionTreeItem.command.command : void 0, + contextValue: extensionTreeItem.contextValue, + icon: this.getLightIconPath(extensionTreeItem), + iconDark: this.getDarkIconPath(extensionTreeItem), + collapsibleState: extensionTreeItem.collapsibleState, + }; + } + + private getLightIconPath(extensionTreeItem: vscode.TreeItem) { + if (extensionTreeItem.iconPath) { + if (typeof extensionTreeItem.iconPath === 'string' || extensionTreeItem.iconPath instanceof URI) { + return this.getIconPath(extensionTreeItem.iconPath); + } + return this.getIconPath(extensionTreeItem.iconPath['light']); + } + return void 0; + } + + private getDarkIconPath(extensionTreeItem: vscode.TreeItem) { + if (extensionTreeItem.iconPath && extensionTreeItem.iconPath['dark']) { + return this.getIconPath(extensionTreeItem.iconPath['dark']); + } + return void 0; + } + + private getIconPath(iconPath: string | URI): string { + if (iconPath instanceof URI) { + return iconPath.toString(); + } + return URI.file(iconPath).toString(); + } + + private clearChildren(extElement: T): void { + const children = this.extChildrenElementsMap.get(extElement); + if (children) { + for (const child of children) { + this.clearElement(child); + } + this.extChildrenElementsMap.delete(extElement); + } + } + + private clearElement(extElement: T): void { + this.clearChildren(extElement); + + const treeItemhandle = this.itemHandlesMap.get(extElement); + this.itemHandlesMap.delete(extElement); + if (treeItemhandle) { + this.extElementsMap.delete(treeItemhandle); + } + } + + dispose() { + this.extElementsMap.clear(); + this.itemHandlesMap.clear(); + this.extChildrenElementsMap.clear(); + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index e52b04efaa450..1c10cc2625192 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1265,3 +1265,8 @@ export enum ProgressLocation { SourceControl = 1, Window = 10, } + +export enum TreeItemCollapsibleState { + Collapsed = 1, + Expanded = 2 +} diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index b6b84f4b4c8f2..01ef125d23811 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -295,6 +295,8 @@ export interface IViewletView extends IView, IThemable { getActions(): IAction[]; getSecondaryActions(): IAction[]; getActionItem(action: IAction): IActionItem; + showHeader(): boolean; + hideHeader(): boolean; shutdown(): void; focusBody(): void; isExpanded(): boolean; @@ -319,20 +321,26 @@ export abstract class AdaptiveCollapsibleViewletView extends FixedCollapsibleVie initialBodySize: number, collapsed: boolean, private viewName: string, - private keybindingService: IKeybindingService, + protected keybindingService: IKeybindingService, protected contextMenuService: IContextMenuService ) { super({ expandedBodySize: initialBodySize, - headerSize: 22, initialState: collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED, - ariaHeaderLabel: viewName + ariaHeaderLabel: viewName, + headerSize: 22, }); this.actionRunner = actionRunner; this.toDispose = []; } + protected changeState(state: CollapsibleState): void { + updateTreeVisibility(this.tree, state === CollapsibleState.EXPANDED); + + super.changeState(state); + } + public create(): TPromise { return TPromise.as(null); } @@ -347,7 +355,7 @@ export abstract class AdaptiveCollapsibleViewletView extends FixedCollapsibleVie getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id) }); this.toolBar.actionRunner = this.actionRunner; - this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); + this.updateActions(); // Expand on drag over this.dragHandler = new DelayedDragHandler(container, () => { @@ -357,10 +365,8 @@ export abstract class AdaptiveCollapsibleViewletView extends FixedCollapsibleVie }); } - protected changeState(state: CollapsibleState): void { - updateTreeVisibility(this.tree, state === CollapsibleState.EXPANDED); - - super.changeState(state); + protected updateActions(): void { + this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); } protected renderViewTree(container: HTMLElement): HTMLElement { @@ -446,7 +452,7 @@ export abstract class CollapsibleViewletView extends CollapsibleView implements headerSize?: number ) { super({ - minimumSize: 2 * 22, + minimumSize: 5 * 22, initialState: collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED, ariaHeaderLabel: viewName, headerSize @@ -476,7 +482,7 @@ export abstract class CollapsibleViewletView extends CollapsibleView implements getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id) }); this.toolBar.actionRunner = this.actionRunner; - this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); + this.updateActions(); // Expand on drag over this.dragHandler = new DelayedDragHandler(container, () => { @@ -486,6 +492,10 @@ export abstract class CollapsibleViewletView extends CollapsibleView implements }); } + protected updateActions(): void { + this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); + } + protected renderViewTree(container: HTMLElement): HTMLElement { return renderViewTree(container); } diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index 07b48d526c982..ae9d791d648f3 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -18,6 +18,9 @@ import 'vs/editor/browser/editor.all'; // Menus/Actions import 'vs/platform/actions/electron-browser/menusExtensionPoint'; +// Views +import 'vs/workbench/parts/views/browser/viewsExtensionPoint'; + // Workbench import 'vs/workbench/browser/actions/toggleActivityBarVisibility'; import 'vs/workbench/browser/actions/toggleStatusbarVisibility'; @@ -64,8 +67,6 @@ import 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; // ca import 'vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution'; -import 'vs/workbench/parts/explorers/browser/explorer.contribution'; - import 'vs/workbench/parts/output/browser/output.contribution'; import 'vs/workbench/parts/output/browser/outputPanel'; // can be packaged separately diff --git a/src/vs/workbench/parts/explorers/browser/explorer.contribution.ts b/src/vs/workbench/parts/explorers/browser/explorer.contribution.ts deleted file mode 100644 index 2c3a9eecbf06e..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/explorer.contribution.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IExplorerViewsService } from 'vs/workbench/parts/explorers/common/explorer'; -import { ExplorerViewsService } from 'vs/workbench/parts/explorers/browser/explorerView'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -registerSingleton(IExplorerViewsService, ExplorerViewsService); \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/media/Refresh.svg b/src/vs/workbench/parts/explorers/browser/media/Refresh.svg deleted file mode 100644 index e0345748192ee..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/media/Refresh.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/media/Refresh_inverse.svg b/src/vs/workbench/parts/explorers/browser/media/Refresh_inverse.svg deleted file mode 100644 index d79fdaa4e8e4d..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/media/Refresh_inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorer.contribution.ts b/src/vs/workbench/parts/explorers/browser/treeExplorer.contribution.ts deleted file mode 100644 index c55073b102774..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/treeExplorer.contribution.ts +++ /dev/null @@ -1,120 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import 'vs/css!./media/treeExplorer.contribution'; - -import { localize } from 'vs/nls'; -import { join } from 'vs/base/common/paths'; -import { createCSSRule } from 'vs/base/browser/dom'; -import { Registry } from 'vs/platform/platform'; -import { ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; -import { TreeExplorerService } from 'vs/workbench/parts/explorers/browser/treeExplorerService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ToggleViewletAction } from 'vs/workbench/browser/viewlet'; -import { ITreeExplorer } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { toViewletId, toViewletCSSClass, isValidViewletId } from 'vs/workbench/parts/explorers/common/treeExplorer'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; - -registerSingleton(ITreeExplorerService, TreeExplorerService); - -const viewSchema: IJSONSchema = { - description: localize('vscode.extension.contributes.view', 'Contributes custom view'), - type: 'object', - properties: { - id: { - description: localize('vscode.extension.contributes.view.id', 'Unique id used to identify view created through vscode.workspace.createTreeView'), - type: 'string' - }, - label: { - description: localize('vscode.extension.contributes.view.label', 'Human readable string used to render the view'), - type: 'string' - }, - icon: { - description: localize('vscode.extension.contributes.view.icon', 'Path to the view icon'), - type: 'string' - } - } -}; - -const viewsSchema: IJSONSchema = { - description: localize('vscode.extension.contributes.views', 'Contributes custom views'), - type: 'object', - items: viewSchema -}; - -export class OpenViewletAction extends ToggleViewletAction { - - constructor( - id: string, - label: string, - @IViewletService viewletService: IViewletService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService - ) { - super(id, label, id, viewletService, editorService); - } -} - -export class ExtensionExplorersContribtion implements IWorkbenchContribution { - - constructor() { - this.init(); - } - - public getId(): string { - return 'vs.extension.view'; - } - - private init() { - ExtensionsRegistry.registerExtensionPoint('views', [], viewsSchema).setHandler(extensions => { - for (let extension of extensions) { - for (const { id, label, icon } of extension.value) { - if (!isValidViewletId(id)) { - console.warn(`Tree view extension '${label}' has invalid id and failed to activate.`); - continue; - } - - const viewletId = toViewletId(id); - const viewletCSSClass = toViewletCSSClass(id); - - // Generate CSS to show the icon in the activity bar - if (icon) { - const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${viewletCSSClass}`; - const iconPath = join(extension.description.extensionFolderPath, icon); - - createCSSRule(iconClass, `-webkit-mask: url('${iconPath}') no-repeat 50% 50%`); - } - - // Register action to open the viewlet - const registry = Registry.as(ActionExtensions.WorkbenchActions); - registry.registerWorkbenchAction( - new SyncActionDescriptor(OpenViewletAction, viewletId, localize('showViewlet', "Show {0}", label)), - 'View: Show {0}', - localize('view', "View") - ); - - // Register as viewlet - Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( - 'vs/workbench/parts/explorers/browser/treeExplorerViewlet', - 'TreeExplorerViewlet', - viewletId, - label, - viewletCSSClass, - -1, - extension.description.id - )); - } - } - }); - } -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExtensionExplorersContribtion); \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerActions.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerActions.ts deleted file mode 100644 index 04103edf29f32..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/treeExplorerActions.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { TPromise } from 'vs/base/common/winjs.base'; -import * as nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import { TreeExplorerView } from 'vs/workbench/parts/explorers/browser/views/treeExplorerView'; -import { toViewletActionId } from 'vs/workbench/parts/explorers/common/treeExplorer'; - -export class RefreshViewExplorerAction extends Action { - - constructor(view: TreeExplorerView) { - super(toViewletActionId('refresh'), nls.localize('refresh', "Refresh"), 'extensionViewlet-action toggle', true, () => { - view.updateInput(); - return TPromise.as(null); - }); - } -} diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerMenus.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerMenus.ts deleted file mode 100644 index 4fa05c578f7c2..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/treeExplorerMenus.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import Event, { Emitter } from 'vs/base/common/event'; -import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { IAction } from 'vs/base/common/actions'; -import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; -import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; - - -export class TreeExplorerMenus implements IDisposable { - - private disposables: IDisposable[] = []; - - private activeProviderId: string; - private titleDisposable: IDisposable = EmptyDisposable; - private titleActions: IAction[] = []; - private titleSecondaryActions: IAction[] = []; - - private _onDidChangeTitle = new Emitter(); - get onDidChangeTitle(): Event { return this._onDidChangeTitle.event; } - - constructor( - @IContextKeyService private contextKeyService: IContextKeyService, - @ITreeExplorerService private treeExplorerService: ITreeExplorerService, - @IMenuService private menuService: IMenuService - ) { - this.setActiveProvider(this.treeExplorerService.activeProvider); - this.treeExplorerService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); - } - - private setActiveProvider(activeProvider: string | undefined): void { - if (this.titleDisposable) { - this.titleDisposable.dispose(); - this.titleDisposable = EmptyDisposable; - } - - if (!activeProvider) { - return; - } - - this.activeProviderId = activeProvider; - - const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, this.contextKeyService); - const updateActions = () => { - this.titleActions = []; - this.titleSecondaryActions = []; - fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }); - this._onDidChangeTitle.fire(); - }; - - const listener = titleMenu.onDidChange(updateActions); - updateActions(); - - this.titleDisposable = toDisposable(() => { - listener.dispose(); - titleMenu.dispose(); - this.titleActions = []; - this.titleSecondaryActions = []; - }); - } - - getTitleActions(): IAction[] { - return this.titleActions; - } - - getTitleSecondaryActions(): IAction[] { - return this.titleSecondaryActions; - } - - getResourceContextActions(): IAction[] { - return this.getActions(MenuId.ViewResource).secondary; - } - - private getActions(menuId: MenuId): { primary: IAction[]; secondary: IAction[]; } { - if (!this.activeProviderId) { - return { primary: [], secondary: [] }; - } - - const menu = this.menuService.createMenu(menuId, this.contextKeyService); - const primary = []; - const secondary = []; - const result = { primary, secondary }; - fillInActions(menu, { shouldForwardArgs: true }, result, g => g === 'inline'); - - menu.dispose(); - - return result; - } - - dispose(): void { - this.disposables = dispose(this.disposables); - } -} diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts deleted file mode 100644 index 649fa221e2a49..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/treeExplorerService.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import Event, { Emitter } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { IMessageService, Severity } from 'vs/platform/message/common/message'; -import { InternalTreeNode, InternalTreeNodeProvider } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; -import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; - -export class TreeExplorerService implements ITreeExplorerService { - public _serviceBrand: any; - - private _activeProvider: string; - private activeProviderContextKey: IContextKey; - - private _onDidChangeProvider = new Emitter(); - get onDidChangeProvider(): Event { return this._onDidChangeProvider.event; } - - private _onTreeExplorerNodeProviderRegistered = new Emitter(); - public get onTreeExplorerNodeProviderRegistered(): Event { return this._onTreeExplorerNodeProviderRegistered.event; }; - - private _treeExplorerNodeProviders: { [providerId: string]: InternalTreeNodeProvider }; - - constructor( - @IContextKeyService private contextKeyService: IContextKeyService, - @IMessageService private messageService: IMessageService - ) { - this._treeExplorerNodeProviders = Object.create(null); - this.activeProviderContextKey = this.contextKeyService.createKey('view', void 0); - } - - get activeProvider(): string { - return this._activeProvider; - } - - set activeProvider(provider: string) { - if (!provider) { - throw new Error('invalid provider'); - } - - this._activeProvider = provider; - this.activeProviderContextKey.set(provider ? provider : void 0); - - this._onDidChangeProvider.fire(provider); - } - - public registerTreeExplorerNodeProvider(providerId: string, provider: InternalTreeNodeProvider): void { - this._treeExplorerNodeProviders[providerId] = provider; - this._onTreeExplorerNodeProviderRegistered.fire(providerId); - } - - public hasProvider(providerId: string): boolean { - return !!this._treeExplorerNodeProviders[providerId]; - } - - public provideRootNode(providerId: string): TPromise { - const provider = this.getProvider(providerId); - return TPromise.wrap(provider.provideRootNode()); - } - - public resolveChildren(providerId: string, node: InternalTreeNode): TPromise { - const provider = this.getProvider(providerId); - return TPromise.wrap(provider.resolveChildren(node)); - } - - public executeCommand(providerId: string, node: InternalTreeNode): TPromise { - const provider = this.getProvider(providerId); - return TPromise.wrap(provider.executeCommand(node)); - } - - public getProvider(providerId: string): InternalTreeNodeProvider { - const provider = this._treeExplorerNodeProviders[providerId]; - - if (!provider) { - this.messageService.show(Severity.Error, localize('treeExplorer.noMatchingProviderId', 'No TreeExplorerNodeProvider with id {providerId} registered.')); - } - - return provider; - } -} diff --git a/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts b/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts deleted file mode 100644 index 51a384bf51c22..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/treeExplorerViewlet.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { TPromise } from 'vs/base/common/winjs.base'; -import { Builder, Dimension } from 'vs/base/browser/builder'; -import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; -import { IAction } from 'vs/base/common/actions'; -import { Viewlet } from 'vs/workbench/browser/viewlet'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeExplorerView } from 'vs/workbench/parts/explorers/browser/views/treeExplorerView'; -import { TreeExplorerViewletState } from 'vs/workbench/parts/explorers/browser/views/treeExplorerViewer'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachHeaderViewStyler } from 'vs/platform/theme/common/styler'; - -export class TreeExplorerViewlet extends Viewlet { - - private viewletContainer: Builder; - private view: TreeExplorerView; - - private viewletState: TreeExplorerViewletState; - private viewletId: string; - private treeNodeProviderId: string; - - constructor( - viewletId: string, - @ITelemetryService telemetryService: ITelemetryService, - @IInstantiationService private instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService - ) { - super(viewletId, telemetryService, themeService); - - this.viewletState = new TreeExplorerViewletState(); - this.viewletId = viewletId; - - const tokens = viewletId.split('.'); - this.treeNodeProviderId = tokens[tokens.length - 1]; - } - - public getId(): string { - return this.viewletId; - } - - public create(parent: Builder): TPromise { - super.create(parent); - - this.viewletContainer = parent.div(); - this.addTreeView(); - - return TPromise.as(null); - } - - public layout(dimension: Dimension): void { - this.view.layout(dimension.height, Orientation.VERTICAL); - } - - public setVisible(visible: boolean): TPromise { - return super.setVisible(visible).then(() => { - this.view.setVisible(visible).done(); - }); - } - - public getActions(): IAction[] { - return this.view.getActions(); - } - - private addTreeView(): void { - const headerSize = 0; // Hide header (root node) by default - - this.view = this.instantiationService.createInstance(TreeExplorerView, this.viewletState, this.treeNodeProviderId, this.getActionRunner(), headerSize); - attachHeaderViewStyler(this.view, this.themeService, { noContrastBorder: true }); - this.view.render(this.viewletContainer.getHTMLElement(), Orientation.VERTICAL); - } - - public focus(): void { - super.focus(); - - if (this.view) { - this.view.focusBody(); - } - } - - public shutdown(): void { - if (this.view) { - this.view.shutdown(); - } - - super.shutdown(); - } - - public dispose(): void { - if (this.view) { - this.view = null; - this.view.dispose(); - } - - super.dispose(); - } -} diff --git a/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts b/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts deleted file mode 100644 index 332041abe8473..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/views/treeExplorerView.ts +++ /dev/null @@ -1,139 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import nls = require('vs/nls'); -import { TPromise } from 'vs/base/common/winjs.base'; -import * as DOM from 'vs/base/browser/dom'; -import { Builder, $ } from 'vs/base/browser/builder'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { CollapsibleViewletView } from 'vs/workbench/browser/viewlet'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IAction, IActionRunner, IActionItem } from 'vs/base/common/actions'; -import { IMessageService } from 'vs/platform/message/common/message'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IListService } from 'vs/platform/list/browser/listService'; -import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; -import { ITree } from 'vs/base/parts/tree/browser/tree'; -import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; -import { TreeExplorerViewletState, TreeDataSource, TreeRenderer, TreeController } from 'vs/workbench/parts/explorers/browser/views/treeExplorerViewer'; -import { TreeExplorerMenus } from 'vs/workbench/parts/explorers/browser/treeExplorerMenus'; -import { RefreshViewExplorerAction } from 'vs/workbench/parts/explorers/browser/treeExplorerActions'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { createActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; - -export class TreeExplorerView extends CollapsibleViewletView { - - private providerDisposables: IDisposable[]; - private menus: TreeExplorerMenus; - - constructor( - private viewletState: TreeExplorerViewletState, - private treeNodeProviderId: string, - actionRunner: IActionRunner, - headerSize: number, - @IMessageService messageService: IMessageService, - @IKeybindingService keybindingService: IKeybindingService, - @IContextMenuService contextMenuService: IContextMenuService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IInstantiationService private instantiationService: IInstantiationService, - @ITreeExplorerService private treeExplorerService: ITreeExplorerService, - @IListService private listService: IListService, - @IThemeService private themeService: IThemeService - ) { - super(actionRunner, false, nls.localize('treeExplorerViewlet.tree', "Tree Explorer Section"), messageService, keybindingService, contextMenuService, headerSize); - this.treeExplorerService.activeProvider = treeNodeProviderId; - this.menus = this.instantiationService.createInstance(TreeExplorerMenus); - this.create(); - } - - public renderBody(container: HTMLElement): void { - this.treeContainer = super.renderViewTree(container); - DOM.addClass(this.treeContainer, 'tree-explorer-viewlet-tree-view'); - - this.tree = this.createViewer($(this.treeContainer)); - } - - public createViewer(container: Builder): ITree { - const dataSource = this.instantiationService.createInstance(TreeDataSource, this.treeNodeProviderId); - const renderer = this.instantiationService.createInstance(TreeRenderer, this.viewletState, this.actionRunner, container.getHTMLElement()); - const controller = this.instantiationService.createInstance(TreeController, this.treeNodeProviderId, this.menus); - - const tree = new Tree(container.getHTMLElement(), { - dataSource, - renderer, - controller - }, { - keyboardSupport: false - }); - - this.toDispose.push(attachListStyler(tree, this.themeService)); - this.toDispose.push(this.listService.register(tree)); - - return tree; - } - - getActions(): IAction[] { - return [...this.menus.getTitleActions(), new RefreshViewExplorerAction(this)]; - } - - getSecondaryActions(): IAction[] { - return this.menus.getTitleSecondaryActions(); - } - - getActionItem(action: IAction): IActionItem { - return createActionItem(action, this.keybindingService, this.messageService); - } - - public create(): TPromise { - return this.updateInput(); - } - - public setVisible(visible: boolean): TPromise { - return super.setVisible(visible); - } - - public updateInput(): TPromise { - if (this.treeExplorerService.hasProvider(this.treeNodeProviderId)) { - return this.updateProvider(); - } - // Provider registration happens independently of the reading of extension's contribution, - // which constructs the viewlet, so it's possible the viewlet is constructed before a provider - // is registered. - // This renders the viewlet first and wait for a corresponding provider is registered. - else { - this.treeExplorerService.onTreeExplorerNodeProviderRegistered(providerId => { - if (this.treeNodeProviderId === providerId) { - return this.updateProvider(); - } - return undefined; - }); - - return TPromise.as(null); - } - } - - public getOptimalWidth(): number { - const parentNode = this.tree.getHTMLElement(); - const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a')); - - return DOM.getLargestChildWidth(parentNode, childNodes); - } - - private updateProvider(): TPromise { - if (this.providerDisposables) { - dispose(this.providerDisposables); - } - - const provider = this.treeExplorerService.getProvider(this.treeNodeProviderId); - provider.onRefresh(node => this.tree.refresh(node)); - return this.treeExplorerService.provideRootNode(this.treeNodeProviderId).then(tree => { - this.tree.setInput(tree); - }); - } -} diff --git a/src/vs/workbench/parts/explorers/browser/views/treeExplorerViewer.ts b/src/vs/workbench/parts/explorers/browser/views/treeExplorerViewer.ts deleted file mode 100644 index a54e64d0c77be..0000000000000 --- a/src/vs/workbench/parts/explorers/browser/views/treeExplorerViewer.ts +++ /dev/null @@ -1,208 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { TPromise } from 'vs/base/common/winjs.base'; -import { $, Builder } from 'vs/base/browser/builder'; -import { ITree, IDataSource, IRenderer, IActionProvider, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; -import { InternalTreeNode } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; -import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IActionRunner, IAction, ActionRunner } from 'vs/base/common/actions'; -import { ContributableActionProvider } from 'vs/workbench/browser/actionBarRegistry'; -import { ITreeExplorerService } from 'vs/workbench/parts/explorers/common/treeExplorerService'; -import { IProgressService } from 'vs/platform/progress/common/progress'; -import { TreeExplorerMenus } from 'vs/workbench/parts/explorers/browser/treeExplorerMenus'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { MenuItemAction } from 'vs/platform/actions/common/actions'; - -export class TreeDataSource implements IDataSource { - - constructor( - private treeNodeProviderId: string, - @ITreeExplorerService private treeExplorerService: ITreeExplorerService, - @IProgressService private progressService: IProgressService - ) { - - } - - public getId(tree: ITree, node: InternalTreeNode): string { - return node.id.toString(); - } - - public hasChildren(tree: ITree, node: InternalTreeNode): boolean { - return node.hasChildren; - } - - public getChildren(tree: ITree, node: InternalTreeNode): TPromise { - const promise = this.treeExplorerService.resolveChildren(this.treeNodeProviderId, node); - - this.progressService.showWhile(promise, 800); - - return promise; - } - - public getParent(tree: ITree, node: InternalTreeNode): TPromise { - return TPromise.as(null); - } -} - -export interface ITreeExplorerTemplateData { - label: Builder; -} - -export class TreeRenderer implements IRenderer { - - private static ITEM_HEIGHT = 22; - private static TREE_TEMPLATE_ID = 'treeExplorer'; - - constructor( - state: TreeExplorerViewletState, - actionRunner: IActionRunner - ) { - } - - public getHeight(tree: ITree, element: any): number { - return TreeRenderer.ITEM_HEIGHT; - } - - public getTemplateId(tree: ITree, element: any): string { - return TreeRenderer.TREE_TEMPLATE_ID; - } - - public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData { - const el = $(container); - const item = $('.custom-viewlet-tree-node-item'); - item.appendTo(el); - - const label = $('.custom-viewlet-tree-node-item-label').appendTo(item); - const link = $('a.plain').appendTo(label); - - return { label: link }; - } - - public renderElement(tree: ITree, node: InternalTreeNode, templateId: string, templateData: ITreeExplorerTemplateData): void { - templateData.label.text(node.label).title(node.label); - } - - public disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void { - } -} - -export class TreeController extends DefaultController { - - constructor( - private treeNodeProviderId: string, - private menus: TreeExplorerMenus, - @IContextMenuService private contextMenuService: IContextMenuService, - @ITreeExplorerService private treeExplorerService: ITreeExplorerService, - @IKeybindingService private _keybindingService: IKeybindingService - ) { - super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false }); - } - - public onLeftClick(tree: ITree, node: InternalTreeNode, event: IMouseEvent, origin: string = 'mouse'): boolean { - super.onLeftClick(tree, node, event, origin); - - if (node.clickCommand) { - this.treeExplorerService.executeCommand(this.treeNodeProviderId, node); - } - - return true; - } - - public onContextMenu(tree: ITree, node: InternalTreeNode, event: ContextMenuEvent): boolean { - tree.setFocus(node); - const actions = this.menus.getResourceContextActions(); - if (!actions.length) { - return true; - } - const anchor = { x: event.posx + 1, y: event.posy }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - - getActions: () => { - return TPromise.as(actions); - }, - - getActionItem: (action) => { - const keybinding = this._keybindingFor(action); - if (keybinding) { - return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() }); - } - return null; - }, - - getKeyBinding: (action): ResolvedKeybinding => { - return this._keybindingFor(action); - }, - - onHide: (wasCancelled?: boolean) => { - if (wasCancelled) { - tree.DOMFocus(); - } - }, - - getActionsContext: () => node, - - actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection()) - }); - - return true; - } - - private _keybindingFor(action: IAction): ResolvedKeybinding { - return this._keybindingService.lookupKeybinding(action.id); - } -} - -export interface ITreeExplorerViewletState { - actionProvider: IActionProvider; -} - -export class TreeExplorerActionProvider extends ContributableActionProvider { - private state: TreeExplorerViewletState; - - constructor(state: TreeExplorerViewletState) { - super(); - - this.state = state; - } -} - -export class TreeExplorerViewletState implements ITreeExplorerViewletState { - private _actionProvider: TreeExplorerActionProvider; - - constructor() { - this._actionProvider = new TreeExplorerActionProvider(this); - } - - public get actionProvider() { return this._actionProvider; } -} - -class MultipleSelectionActionRunner extends ActionRunner { - - constructor(private getSelectedResources: () => InternalTreeNode[]) { - super(); - } - - runAction(action: IAction, context: InternalTreeNode): TPromise { - if (action instanceof MenuItemAction) { - const selection = this.getSelectedResources(); - const filteredSelection = selection.filter(s => s !== context); - - if (selection.length === filteredSelection.length || selection.length === 1) { - return action.run(context); - } - - return action.run(context, ...filteredSelection); - } - - return super.runAction(action, context); - } -} diff --git a/src/vs/workbench/parts/explorers/common/explorer.ts b/src/vs/workbench/parts/explorers/common/explorer.ts deleted file mode 100644 index 338b63463de5a..0000000000000 --- a/src/vs/workbench/parts/explorers/common/explorer.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import Event from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { IActionRunner } from 'vs/base/common/actions'; -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; - -export const IExplorerViewsService = createDecorator('explorerViewsService'); - -export interface IExplorerViewsService { - _serviceBrand: any; - - readonly onViewCreated: Event>; - - createView(id: string, name: string, dataProvider: IExplorerViewDataProvider): IExplorerView; - getViews(): IExplorerView[]; -} - -export interface IExplorerView extends IDisposable { - refresh(element: T): void; - instantiate(actionRunner: IActionRunner, viewletSetings: any, instantiationService: IInstantiationService): any; -} - -export interface IExplorerViewDataProvider { - provideRoot(): TPromise; - resolveChildren(element: T): TPromise; - getId(element: T): string; - getLabel(element: T): string; - getContextKey(element: T): string; - hasChildren(element: T): boolean; - select(element: T): void; -} diff --git a/src/vs/workbench/parts/explorers/common/treeExplorer.ts b/src/vs/workbench/parts/explorers/common/treeExplorer.ts deleted file mode 100644 index fad6a30fd8506..0000000000000 --- a/src/vs/workbench/parts/explorers/common/treeExplorer.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -export function toViewletId(viewletId: string): string { - return `workbench.view.extension.${viewletId}`; -} - -export function toViewletActionId(viewletId: string): string { - return `workbench.action.extension.${viewletId}`; -} - -export function toViewletCSSClass(viewletId: string): string { - return `extensionViewlet-${viewletId}`; -} - -export function isValidViewletId(viewletId: string): boolean { - return /^[a-z0-9_-]+$/i.test(viewletId); // Only allow alphanumeric letters, `_` and `-`. -} diff --git a/src/vs/workbench/parts/explorers/common/treeExplorerService.ts b/src/vs/workbench/parts/explorers/common/treeExplorerService.ts deleted file mode 100644 index 6e388e29b822d..0000000000000 --- a/src/vs/workbench/parts/explorers/common/treeExplorerService.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import Event from 'vs/base/common/event'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { InternalTreeNode, InternalTreeNodeProvider } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; - -export const ITreeExplorerService = createDecorator('treeExplorerService'); - -export interface ITreeExplorerService { - _serviceBrand: any; - - onDidChangeProvider: Event; - activeProvider: string; - - onTreeExplorerNodeProviderRegistered: Event; - registerTreeExplorerNodeProvider(providerId: string, provider: InternalTreeNodeProvider): void; - hasProvider(providerId: string): boolean; - getProvider(providerId: string): InternalTreeNodeProvider; - - provideRootNode(providerId: string): TPromise; - resolveChildren(providerId: string, node: InternalTreeNode): TPromise; - executeCommand(providerId: string, node: InternalTreeNode): TPromise; -} diff --git a/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts b/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts deleted file mode 100644 index a2b197164715f..0000000000000 --- a/src/vs/workbench/parts/explorers/common/treeExplorerViewModel.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { TPromise } from 'vs/base/common/winjs.base'; -import Event from 'vs/base/common/event'; - -export interface InternalTreeNodeContent { - label: string; - hasChildren: boolean; - clickCommand: string; -} - -export interface InternalTreeNode extends InternalTreeNodeContent { - readonly id: string; - readonly providerId: string; -} - -export interface InternalTreeNodeProvider { - id: string; - provideRootNode(): Thenable; - resolveChildren(node: InternalTreeNodeContent): Thenable; - executeCommand(node: InternalTreeNodeContent): TPromise; - onRefresh?: Event; -} diff --git a/src/vs/workbench/parts/files/browser/explorerViewlet.ts b/src/vs/workbench/parts/files/browser/explorerViewlet.ts index 80e70692d7899..f4f1460ac74e0 100644 --- a/src/vs/workbench/parts/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/browser/explorerViewlet.ts @@ -32,7 +32,7 @@ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupSer import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachHeaderViewStyler } from 'vs/platform/theme/common/styler'; -import { IExplorerViewsService } from 'vs/workbench/parts/explorers/common/explorer'; +import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/parts/views/browser/views'; export class ExplorerViewlet extends Viewlet { private viewletContainer: Builder; @@ -57,14 +57,13 @@ export class ExplorerViewlet extends Viewlet { constructor( @ITelemetryService telemetryService: ITelemetryService, @IWorkspaceContextService private contextService: IWorkspaceContextService, - @IStorageService storageService: IStorageService, + @IStorageService private storageService: IStorageService, @IEditorGroupService private editorGroupService: IEditorGroupService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IExplorerViewsService private explorerViewsService: IExplorerViewsService ) { super(VIEWLET_ID, telemetryService, themeService); @@ -75,7 +74,7 @@ export class ExplorerViewlet extends Viewlet { this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config)); - this.explorerViewsService.onViewCreated(view => this.render()); + ViewsRegistry.onViewsRegistered(viewDescriptors => this.addViews(viewDescriptors.filter(viewDescriptor => ViewLocation.Explorer === viewDescriptor.location))); } public create(parent: Builder): TPromise { @@ -102,7 +101,6 @@ export class ExplorerViewlet extends Viewlet { // Open editors view should always be visible in no folder workspace. this.openEditorsVisible = !this.contextService.hasWorkspace() || config.explorer.openEditors.visible !== 0; - this.dispose(); this.views = []; this.viewletContainer.clearChildren(); @@ -113,7 +111,7 @@ export class ExplorerViewlet extends Viewlet { this.lastFocusedView = view; }); - const customViews = this.explorerViewsService.getViews(); + const customViews = ViewsRegistry.getViews(ViewLocation.Explorer); if (this.openEditorsVisible) { // Open editors view @@ -122,12 +120,14 @@ export class ExplorerViewlet extends Viewlet { } // Explorer view - const view = this.createExplorerOrEmptyView(this.views.length || customViews.length ? undefined : 0); - this.views.push(view); + this.views.push(this.createExplorerOrEmptyView()); // custom views for (const view of customViews) { - this.views.push(view.instantiate(this.getActionRunner(), this.viewletSettings, this.instantiationService)); + this.views.push(this.instantiationService.createInstance(view.ctor, view.id, { + name: view.name, + actionRunner: this.getActionRunner(), + })); } for (let i = 0; i < this.views.length; i++) { @@ -139,6 +139,10 @@ export class ExplorerViewlet extends Viewlet { this.lastFocusedView = this.explorerView; return TPromise.join(this.views.map(view => view.create())).then(() => void 0).then(() => { + if (this.views.length === 1) { + this.views[0].hideHeader(); + + } if (this.dimension) { this.layout(this.dimension); } @@ -149,15 +153,81 @@ export class ExplorerViewlet extends Viewlet { }); } + private updateOpenEditorsView(): void { + if (!this.splitView) { + return; + } + + if (this.openEditorsVisible) { + this.openEditorsView = this.instantiationService.createInstance(OpenEditorsView, this.getActionRunner(), this.viewletSettings); + this.views.unshift(this.openEditorsView); + this.splitView.addView(this.openEditorsView, undefined, 0); + this.openEditorsView.create().then(() => { + if (this.views.length === 2) { + this.views[1].showHeader(); + } + if (this.dimension) { + this.layout(this.dimension); + } + // Update title area since the title actions have changed. + this.updateTitleArea(); + }); + } else { + this.views.shift(); + this.splitView.removeView(this.openEditorsView); + this.openEditorsView.dispose(); + this.openEditorsView = null; + + if (this.views.length === 1) { + this.views[0].hideHeader(); + } + if (this.dimension) { + this.layout(this.dimension); + } + // Update title area since the title actions have changed. + this.updateTitleArea(); + } + } + + private addViews(viewDescriptors: IViewDescriptor[]): void { + if (!this.splitView || !viewDescriptors.length) { + return; + } + const views = []; + + for (const viewDescrirptor of viewDescriptors) { + const view = this.instantiationService.createInstance(viewDescrirptor.ctor, viewDescrirptor.id, { + name: viewDescrirptor.name, + actionRunner: this.getActionRunner(), + }); + views.push(view); + this.views.push(view); + attachHeaderViewStyler(view, this.themeService); + this.splitView.addView(view); + } + + TPromise.join(views.map(view => view.create())).then(() => void 0).then(() => { + this.views[0].showHeader(); + + if (this.dimension) { + this.layout(this.dimension); + } + + // Update title area since the title actions have changed. + this.updateTitleArea(); + }); + } + private onConfigurationUpdated(config: IFilesConfiguration): void { // Open editors view should always be visible in no folder workspace. const openEditorsVisible = !this.contextService.hasWorkspace() || config.explorer.openEditors.visible !== 0; if (this.openEditorsVisible !== openEditorsVisible) { - this.render(); + this.openEditorsVisible = openEditorsVisible; + this.updateOpenEditorsView(); } } - private createExplorerOrEmptyView(headerSize: number): IViewletView { + private createExplorerOrEmptyView(): IViewletView { let explorerOrEmptyView: ExplorerView | EmptyView; // With a Workspace @@ -194,7 +264,7 @@ export class ExplorerViewlet extends Viewlet { }); const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService])); - this.explorerView = explorerOrEmptyView = explorerInstantiator.createInstance(ExplorerView, this.viewletState, this.getActionRunner(), this.viewletSettings, headerSize); + this.explorerView = explorerOrEmptyView = explorerInstantiator.createInstance(ExplorerView, this.viewletState, this.getActionRunner(), this.viewletSettings, void 0); } // No workspace @@ -312,23 +382,24 @@ export class ExplorerViewlet extends Viewlet { } public dispose(): void { + + for (const view of this.views) { + view.dispose(); + } + if (this.splitView) { - this.splitView.dispose(); this.splitView = null; } if (this.explorerView) { - this.explorerView.dispose(); this.explorerView = null; } if (this.openEditorsView) { - this.openEditorsView.dispose(); this.openEditorsView = null; } if (this.emptyView) { - this.emptyView.dispose(); this.emptyView = null; } diff --git a/src/vs/workbench/parts/explorers/browser/media/treeExplorer.contribution.css b/src/vs/workbench/parts/views/browser/media/views.css similarity index 50% rename from src/vs/workbench/parts/explorers/browser/media/treeExplorer.contribution.css rename to src/vs/workbench/parts/views/browser/media/views.css index 35425359a0782..30eede64993ce 100644 --- a/src/vs/workbench/parts/explorers/browser/media/treeExplorer.contribution.css +++ b/src/vs/workbench/parts/views/browser/media/views.css @@ -3,16 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .extensionViewlet-action.toggle { - background: url('Refresh.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .extensionViewlet-action.toggle, -.hc-black .monaco-workbench .extensionViewlet-action.toggle { - background: url('Refresh_inverse.svg') center center no-repeat; +.custom-view-tree-node-item { + display: flex; + height: 22px; + line-height: 22px; } -.custom-viewlet-tree-node-item { +.custom-view-tree-node-item > .custom-view-tree-node-item-icon { + background-size: 16px; + background-position: left center; + background-repeat: no-repeat; + padding-right: 6px; + width: 16px; height: 22px; - line-height: 22px; + -webkit-font-smoothing: antialiased; } + +.custom-view-tree-node-item > .custom-view-tree-node-item-label .label { + vertical-align: middle; +} \ No newline at end of file diff --git a/src/vs/workbench/parts/explorers/browser/explorerView.ts b/src/vs/workbench/parts/views/browser/treeView.ts similarity index 59% rename from src/vs/workbench/parts/explorers/browser/explorerView.ts rename to src/vs/workbench/parts/views/browser/treeView.ts index 7b7d6f375d2d3..fc0e34b8a6239 100644 --- a/src/vs/workbench/parts/explorers/browser/explorerView.ts +++ b/src/vs/workbench/parts/views/browser/treeView.ts @@ -3,16 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/views'; import Event, { Emitter } from 'vs/base/common/event'; -import { IDisposable, Disposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IViewletView, CollapsibleViewletView } from 'vs/workbench/browser/viewlet'; -import { IExplorerViewsService, IExplorerViewDataProvider, IExplorerView } from 'vs/workbench/parts/explorers/common/explorer'; +import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { CollapsibleViewletView } from 'vs/workbench/browser/viewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TPromise } from 'vs/base/common/winjs.base'; import * as DOM from 'vs/base/browser/dom'; import { Builder, $ } from 'vs/base/browser/builder'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IAction, IActionRunner, IActionItem, ActionRunner } from 'vs/base/common/actions'; +import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions'; import { IMessageService } from 'vs/platform/message/common/message'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -21,96 +20,51 @@ import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { attachListStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ViewsRegistry, ITreeViewDataProvider, IViewOptions, ITreeItem, TreeItemCollapsibleState } from 'vs/workbench/parts/views/browser/views'; +import { IExtensionService } from 'vs/platform/extensions/common/extensions'; +import { CollapsibleState } from 'vs/base/browser/ui/splitview/splitview'; +import { ICommandService } from 'vs/platform/commands/common/commands'; -export interface IViewInstantiator { - instantiate(actionRunner: IActionRunner, viewletSetings: any, instantiationService: IInstantiationService): IViewletView; -} - -export class ExplorerViewsService implements IExplorerViewsService { - - public _serviceBrand: any; - - private explorerViews: Map> = new Map>(); - - private _onViewCreated: Emitter> = new Emitter>(); - public readonly onViewCreated: Event> = this._onViewCreated.event; - - private _onDataProviderRegistered: Emitter> = new Emitter>(); - public readonly onDataProviderRegistered: Event> = this._onDataProviderRegistered.event; - - createView(id: string, name: string, dataProvider: IExplorerViewDataProvider): IExplorerView { - const view = new ExplorerView(id, name, dataProvider); - this.explorerViews.set(id, view); - this._onViewCreated.fire(view); - return view; - } - - public getViews(): IExplorerView[] { - const views = []; - this.explorerViews.forEach(view => { - views.push(view); - }); - return views; - } -} - -class ExplorerView extends Disposable implements IExplorerView, IViewInstantiator { - - private view: TreeExplorerView; - - constructor(private id: string, private name: string, private dataProvider: IExplorerViewDataProvider) { - super(); - } - - refresh(element: T): void { - if (this.view) { - this.view.refresh(element); - } - } - - instantiate(actionRunner: IActionRunner, viewletSettings: any, instantiationService: IInstantiationService): IViewletView { - if (!this.view) { - this.view = instantiationService.createInstance(TreeExplorerView, this.id, this.name, this.dataProvider, actionRunner); - } - return this.view; - } -} - -class TreeExplorerView extends CollapsibleViewletView { +export class TreeView extends CollapsibleViewletView { private menus: Menus; private viewFocusContext: IContextKey; + private activated: boolean = false; + private treeInputPromise: TPromise; + + private dataProviderElementChangeListener: IDisposable; + private disposables: IDisposable[] = []; constructor( - private id: string, - private name: string, - private dataProvider: IExplorerViewDataProvider, - actionRunner: IActionRunner, + readonly id: string, + private options: IViewOptions, @IMessageService messageService: IMessageService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, - @IWorkspaceContextService contextService: IWorkspaceContextService, @IInstantiationService private instantiationService: IInstantiationService, @IListService private listService: IListService, @IThemeService private themeService: IThemeService, @IContextKeyService private contextKeyService: IContextKeyService, - @IExplorerViewsService private explorerViewsService: IExplorerViewsService + @IExtensionService private extensionService: IExtensionService, + @ICommandService private commandService: ICommandService ) { - super(actionRunner, false, name, messageService, keybindingService, contextMenuService); - this.menus = this.instantiationService.createInstance(Menus, this.id, this.dataProvider); + super(options.actionRunner, true, options.name, messageService, keybindingService, contextMenuService); + this.menus = this.instantiationService.createInstance(Menus, this.id); this.viewFocusContext = this.contextKeyService.createKey(this.id, void 0); + this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables); + this.themeService.onThemeChange(() => this.tree.refresh() /* soft refresh */, this, this.disposables); } public renderHeader(container: HTMLElement): void { const titleDiv = $('div.title').appendTo(container); - $('span').text(this.name).appendTo(titleDiv); + $('span').text(this.options.name).appendTo(titleDiv); super.renderHeader(container); } @@ -121,10 +75,24 @@ class TreeExplorerView extends CollapsibleViewletView { this.tree = this.createViewer($(this.treeContainer)); } + protected changeState(state: CollapsibleState): void { + super.changeState(state); + if (state === CollapsibleState.EXPANDED) { + this.triggerActivation(); + } + } + + private triggerActivation() { + if (!this.activated && this.extensionService) { + this.extensionService.activateByEvent(`onView:${this.id}`); + this.activated = true; + } + } + public createViewer(container: Builder): ITree { - const dataSource = this.instantiationService.createInstance(TreeDataSource, this.dataProvider); - const renderer = this.instantiationService.createInstance(TreeRenderer, this.dataProvider); - const controller = this.instantiationService.createInstance(TreeController, this.menus); + const dataSource = this.instantiationService.createInstance(TreeDataSource, this.id); + const renderer = this.instantiationService.createInstance(TreeRenderer); + const controller = this.instantiationService.createInstance(TreeController, this.id, this.menus); const tree = new Tree(container.getHTMLElement(), { dataSource, renderer, @@ -135,12 +103,7 @@ class TreeExplorerView extends CollapsibleViewletView { this.toDispose.push(attachListStyler(tree, this.themeService)); this.toDispose.push(this.listService.register(tree, [this.viewFocusContext])); - tree.addListener('selection', (event: any) => { - const selection = tree.getSelection()[0]; - if (selection) { - this.dataProvider.select(selection); - } - }); + tree.addListener('selection', (event: any) => this.onSelection()); return tree; } @@ -157,16 +120,41 @@ class TreeExplorerView extends CollapsibleViewletView { } public create(): TPromise { - return this.updateInput(); + return this.setInput(); } public setVisible(visible: boolean): TPromise { return super.setVisible(visible); } - public updateInput(): TPromise { - return this.dataProvider.provideRoot() - .then(root => this.tree.setInput(root)); + public setInput(): TPromise { + if (this.listenToDataProvider()) { + this.treeInputPromise = this.tree.setInput(new Root()); + return this.treeInputPromise; + } + this.treeInputPromise = new TPromise((c, e) => { + const disposable = ViewsRegistry.onTreeViewDataProviderRegistered(id => { + if (this.id === id) { + if (this.listenToDataProvider()) { + this.tree.setInput(new Root()).then(() => c(null)); + disposable.dispose(); + } + } + }); + }); + return TPromise.as(null); + } + + private listenToDataProvider(): boolean { + let dataProvider = ViewsRegistry.getTreeViewDataProvider(this.id); + if (dataProvider) { + if (this.dataProviderElementChangeListener) { + this.dataProviderElementChangeListener.dispose(); + } + this.dataProviderElementChangeListener = dataProvider.onDidChange(element => this.refresh(element)); + return true; + } + return false; } public getOptimalWidth(): number { @@ -176,42 +164,81 @@ class TreeExplorerView extends CollapsibleViewletView { return DOM.getLargestChildWidth(parentNode, childNodes); } - refresh(element: any) { + private onSelection(): void { + const selection: ITreeItem = this.tree.getSelection()[0]; + if (selection) { + if (selection.commandId) { + this.commandService.executeCommand(selection.commandId, { treeViewId: this.id, treeItemHandle: selection.handle }); + } + } + } + + private refresh(element?: ITreeItem): void { + element = element ? element : this.tree.getInput(); + element.children = null; this.tree.refresh(element); } + + dispose(): void { + dispose(this.disposables); + super.dispose(); + } +} + +class Root implements ITreeItem { + label = 'root'; + handle = -1; + collapsibleState = TreeItemCollapsibleState.Expanded; } class TreeDataSource implements IDataSource { constructor( - private dataProvider: IExplorerViewDataProvider, - @IProgressService private progressService: IProgressService, - @IExplorerViewsService private explorerViewsService: IExplorerViewsService + private id: string, + @IProgressService private progressService: IProgressService ) { } - public getId(tree: ITree, node: any): string { - return this.dataProvider.getId(node); + public getId(tree: ITree, node: ITreeItem): string { + return '' + node.handle; } - public hasChildren(tree: ITree, node: any): boolean { - return this.dataProvider.hasChildren(node); + public hasChildren(tree: ITree, node: ITreeItem): boolean { + return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded; } - public getChildren(tree: ITree, node: any): TPromise { - const promise = this.dataProvider.resolveChildren(node); + public getChildren(tree: ITree, node: ITreeItem): TPromise { + if (node.children) { + return TPromise.as(node.children); + } - this.progressService.showWhile(promise, 800); + const dataProvider = this.getDataProvider(); + if (dataProvider) { + const promise = node instanceof Root ? dataProvider.getElements() : dataProvider.getChildren(node); + this.progressService.showWhile(promise, 100); + return promise.then(children => { + node.children = children; + return children; + }); + } + return TPromise.as(null); + } - return promise; + public shouldAutoexpand(tree: ITree, node: ITreeItem): boolean { + return node.collapsibleState === TreeItemCollapsibleState.Expanded; } public getParent(tree: ITree, node: any): TPromise { return TPromise.as(null); } + + private getDataProvider(): ITreeViewDataProvider { + return ViewsRegistry.getTreeViewDataProvider(this.id); + } } interface ITreeExplorerTemplateData { + icon: Builder; label: Builder; } @@ -220,10 +247,7 @@ class TreeRenderer implements IRenderer { private static ITEM_HEIGHT = 22; private static TREE_TEMPLATE_ID = 'treeExplorer'; - constructor( - private dataProvider: IExplorerViewDataProvider, - @IExplorerViewsService private explorerViewsService: IExplorerViewsService - ) { + constructor( @IThemeService private themeService: IThemeService) { } public getHeight(tree: ITree, element: any): number { @@ -236,18 +260,29 @@ class TreeRenderer implements IRenderer { public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData { const el = $(container); - const item = $('.custom-viewlet-tree-node-item'); + const item = $('.custom-view-tree-node-item'); item.appendTo(el); - const label = $('.custom-viewlet-tree-node-item-label').appendTo(item); - const link = $('a.plain').appendTo(label); + const icon = $('.custom-view-tree-node-item-icon').appendTo(item); + const label = $('.custom-view-tree-node-item-label').appendTo(item); + const link = $('a.label').appendTo(label); - return { label: link }; + return { label: link, icon }; } - public renderElement(tree: ITree, node: any, templateId: string, templateData: ITreeExplorerTemplateData): void { - const label = this.dataProvider.getLabel(node); - templateData.label.text(label).title(label); + public renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void { + templateData.label.text(node.label).title(node.label); + + const theme = this.themeService.getTheme(); + const icon = theme.type === LIGHT ? node.icon : node.iconDark; + + if (icon) { + templateData.icon.getHTMLElement().style.backgroundImage = `url('${icon}')`; + DOM.addClass(templateData.icon.getHTMLElement(), 'custom-view-tree-node-item-icon'); + } else { + templateData.icon.getHTMLElement().style.backgroundImage = ''; + DOM.removeClass(templateData.icon.getHTMLElement(), 'custom-view-tree-node-item-icon'); + } } public disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void { @@ -257,6 +292,7 @@ class TreeRenderer implements IRenderer { class TreeController extends DefaultController { constructor( + private treeViewId: string, private menus: Menus, @IContextMenuService private contextMenuService: IContextMenuService, @IKeybindingService private _keybindingService: IKeybindingService @@ -264,7 +300,7 @@ class TreeController extends DefaultController { super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false }); } - public onContextMenu(tree: ITree, node: any, event: ContextMenuEvent): boolean { + public onContextMenu(tree: ITree, node: ITreeItem, event: ContextMenuEvent): boolean { tree.setFocus(node); const actions = this.menus.getResourceContextActions(node); if (!actions.length) { @@ -296,7 +332,7 @@ class TreeController extends DefaultController { } }, - getActionsContext: () => node, + getActionsContext: () => ({ treeViewId: this.treeViewId, treeItemHandle: node.handle }), actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection()) }); @@ -342,11 +378,9 @@ class Menus implements IDisposable { get onDidChangeTitle(): Event { return this._onDidChangeTitle.event; } constructor( - private viewId: string, - private dataProvider: IExplorerViewDataProvider, + private id: string, @IContextKeyService private contextKeyService: IContextKeyService, - @IMenuService private menuService: IMenuService, - @IExplorerViewsService private explorerViewsService: IExplorerViewsService + @IMenuService private menuService: IMenuService ) { if (this.titleDisposable) { this.titleDisposable.dispose(); @@ -354,7 +388,7 @@ class Menus implements IDisposable { } const _contextKeyService = this.contextKeyService.createScoped(); - contextKeyService.createKey('view', viewId); + contextKeyService.createKey('view', id); const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService); const updateActions = () => { @@ -367,7 +401,6 @@ class Menus implements IDisposable { const listener = titleMenu.onDidChange(updateActions); updateActions(); - this.titleDisposable = toDisposable(() => { listener.dispose(); titleMenu.dispose(); @@ -385,13 +418,13 @@ class Menus implements IDisposable { return this.titleSecondaryActions; } - getResourceContextActions(element: any): IAction[] { - return this.getActions(MenuId.ViewResource, { key: 'resource', value: this.dataProvider.getContextKey(element) }).secondary; + getResourceContextActions(element: ITreeItem): IAction[] { + return this.getActions(MenuId.ViewResource, { key: 'resource', value: element.contextValue }).secondary; } private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } { const contextKeyService = this.contextKeyService.createScoped(); - contextKeyService.createKey('view', this.viewId); + contextKeyService.createKey('view', this.id); contextKeyService.createKey(context.key, context.value); const menu = this.menuService.createMenu(menuId, contextKeyService); @@ -409,4 +442,4 @@ class Menus implements IDisposable { dispose(): void { this.disposables = dispose(this.disposables); } -} +} \ No newline at end of file diff --git a/src/vs/workbench/parts/views/browser/views.ts b/src/vs/workbench/parts/views/browser/views.ts new file mode 100644 index 0000000000000..831f09c8ecf00 --- /dev/null +++ b/src/vs/workbench/parts/views/browser/views.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TPromise } from 'vs/base/common/winjs.base'; +import Event, { Emitter } from 'vs/base/common/event'; +import { IActionRunner } from 'vs/base/common/actions'; +import { IViewletView as IView } from 'vs/workbench/browser/viewlet'; + +export class ViewLocation { + + static readonly Explorer = new ViewLocation('explorer'); + + constructor(private _id: string) { + } + + get id(): string { + return this._id; + } +} + +export enum TreeItemCollapsibleState { + Collapsed = 1, + Expanded = 2 +} + +export interface IViewOptions { + name: string; + actionRunner: IActionRunner; +} + +export interface IViewConstructorSignature { + + new (id: string, options: IViewOptions, ...services: { _serviceBrand: any; }[]): IView; + +} + +export interface IViewDescriptor { + + readonly id: string; + + readonly name: string; + + readonly location: ViewLocation; + + readonly ctor: IViewConstructorSignature; + + readonly order?: number; + +} + +export interface ITreeItem { + + handle: number; + + label: string; + + icon?: string; + + iconDark?: string; + + contextValue?: string; + + commandId?: string; + + children?: ITreeItem[]; + + collapsibleState?: TreeItemCollapsibleState; +} + +export interface ITreeViewDataProvider { + + onDidChange: Event; + + getElements(): TPromise; + + getChildren(element: ITreeItem): TPromise; + +} + +export interface IViewsRegistry { + + readonly onViewsRegistered: Event; + + readonly onTreeViewDataProviderRegistered: Event; + + registerViews(views: IViewDescriptor[]): void; + + registerTreeViewDataProvider(id: string, factory: ITreeViewDataProvider): void; + + getViews(loc: ViewLocation): IViewDescriptor[]; + + getTreeViewDataProvider(id: string): ITreeViewDataProvider; + +} + +export const ViewsRegistry: IViewsRegistry = new class { + + private _onViewsRegistered: Emitter = new Emitter(); + readonly onViewsRegistered: Event = this._onViewsRegistered.event; + + private _onTreeViewDataProviderRegistered: Emitter = new Emitter(); + readonly onTreeViewDataProviderRegistered: Event = this._onTreeViewDataProviderRegistered.event; + + private _views: Map = new Map(); + private _treeViewDataPoviders: Map = new Map(); + + registerViews(viewDescriptors: IViewDescriptor[]): void { + if (viewDescriptors.length) { + for (const viewDescriptor of viewDescriptors) { + let views = this._views.get(viewDescriptor.location); + if (!views) { + views = []; + this._views.set(viewDescriptor.location, views); + } + views.push(viewDescriptor); + } + this._onViewsRegistered.fire(viewDescriptors); + } + } + + registerTreeViewDataProvider(id: string, factory: ITreeViewDataProvider) { + if (!this.isViewRegistered(id)) { + // TODO: throw error + } + this._treeViewDataPoviders.set(id, factory); + this._onTreeViewDataProviderRegistered.fire(id); + } + + getViews(loc: ViewLocation): IViewDescriptor[] { + return this._views.get(loc) || []; + } + + getTreeViewDataProvider(id: string): ITreeViewDataProvider { + return this._treeViewDataPoviders.get(id); + } + + private isViewRegistered(id: string): boolean { + let registered = false; + this._views.forEach(views => registered = registered || views.some(view => view.id === id)); + return registered; + } +}; \ No newline at end of file diff --git a/src/vs/workbench/parts/views/browser/viewsExtensionPoint.ts b/src/vs/workbench/parts/views/browser/viewsExtensionPoint.ts new file mode 100644 index 0000000000000..da935449c647a --- /dev/null +++ b/src/vs/workbench/parts/views/browser/viewsExtensionPoint.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { localize } from 'vs/nls'; +import { forEach } from 'vs/base/common/collections'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; +import { ViewLocation, ViewsRegistry } from 'vs/workbench/parts/views/browser/views'; +import { TreeView } from 'vs/workbench/parts/views/browser/treeView'; + +namespace schema { + + // --views contribution point + + export interface IUserFriendlyViewDescriptor { + id: string; + name: string; + } + + export function parseLocation(value: string): ViewLocation { + switch (value) { + case ViewLocation.Explorer.id: return ViewLocation.Explorer; + } + return void 0; + } + + export function isValidViewDescriptors(viewDescriptors: IUserFriendlyViewDescriptor[], collector: ExtensionMessageCollector): boolean { + if (!Array.isArray(viewDescriptors)) { + collector.error(localize('requirearray', "views must be an array")); + return false; + } + + for (let descriptor of viewDescriptors) { + if (typeof descriptor.id !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'id')); + return false; + } + if (typeof descriptor.name !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'label')); + return false; + } + } + + return true; + } + + const viewDescriptor: IJSONSchema = { + type: 'object', + properties: { + id: { + description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. Use the same identifier to register a data provider through API.'), + type: 'string' + }, + name: { + description: localize('vscode.extension.contributes.view.name', 'The human-readable name of the view. Will be shown'), + type: 'string' + } + } + }; + + export const viewsContribution: IJSONSchema = { + description: localize('vscode.extension.contributes.views', "Contributes views to the editor"), + type: 'object', + properties: { + 'explorer': { + description: localize('views.explorer', "Explorer"), + type: 'array', + items: viewDescriptor + } + } + }; +} + +ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyViewDescriptor[] }>('views', [], schema.viewsContribution).setHandler(extensions => { + for (let extension of extensions) { + const { value, collector } = extension; + + forEach(value, entry => { + if (!schema.isValidViewDescriptors(entry.value, collector)) { + return; + } + + const location = schema.parseLocation(entry.key); + if (!location) { + collector.warn(localize('locationId.invalid', "`{0}` is not a valid view location", entry.key)); + return; + } + + const viewDescriptors = entry.value.map(item => ({ + id: item.id, + name: item.name, + ctor: TreeView, + location + })); + ViewsRegistry.registerViews(viewDescriptors); + }); + } +}); \ No newline at end of file