提交 e87e0999 编写于 作者: S Sandeep Somavarapu

Implement #26948

- Initial version of custom views and tree data provider API
上级 6fd1ac9c
......@@ -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,
......
......@@ -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);
......
......@@ -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<T>(id: string, name: string, dataProvider: TreeDataProvider<T>): View<T>;
export function registerTreeDataProvider<T>(id: string, treeDataProvider: TreeDataProvider<T>): Disposable;
}
/**
* A view to interact with nodes
* A data provider that provides tree data for a view
*/
export interface View<T> {
export interface TreeDataProvider<T> {
/**
* An optional event to signal that an element or root has changed.
*/
onDidChange?: Event<T | undefined | null>;
/**
* 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<T[]>;
}
/**
* 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<T> {
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<T>;
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<T[]>;
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
}
/**
......
......@@ -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));
......
/*---------------------------------------------------------------------------------------------
* 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<string, IExplorerView<ITreeNode>> = new Map<string, IExplorerView<ITreeNode>>();
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<ITreeNode> {
readonly _onRefresh: Emitter<ITreeNode> = new Emitter<ITreeNode>();
readonly onRefresh: Event<ITreeNode> = this._onRefresh.event;
constructor(public readonly id: string, private _proxy: ExtHostExplorerViewShape,
private messageService: IMessageService,
private commandService: ICommandService
) {
}
provideRoot(): TPromise<ITreeNode> {
return this._proxy.$provideRootNode(this.id).then(rootNode => rootNode, err => this.messageService.show(Severity.Error, err));
}
resolveChildren(node: ITreeNode): TPromise<ITreeNode[]> {
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);
}
});
}
}
/*---------------------------------------------------------------------------------------------
* 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 = <TreeViewDataProvider>ViewsRegistry.getTreeViewDataProvider(treeViewId);
if (treeViewDataProvider) {
treeViewDataProvider.refresh(treeItemHandle);
}
}
}
type TreeItemHandle = number;
class TreeViewDataProvider implements ITreeViewDataProvider {
private _onDidChange: Emitter<ITreeItem | undefined | void> = new Emitter<ITreeItem | undefined | void>();
readonly onDidChange: Event<ITreeItem | undefined | void> = this._onDidChange.event;
private childrenMap: Map<TreeItemHandle, TreeItemHandle[]> = new Map<TreeItemHandle, TreeItemHandle[]>();
private itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>();
constructor(private treeViewId: string,
private _proxy: ExtHostTreeViewsShape,
private messageService: IMessageService
) {
}
getElements(): TPromise<ITreeItem[]> {
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<ITreeItem[]> {
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
......@@ -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<ExtHostDocumentSaveParticipant>(new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace)));
const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set<ExtHostEditors>(new ExtHostEditors(threadService, extHostDocumentsAndEditors));
const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set<ExtHostCommands>(new ExtHostCommands(threadService, extHostHeapService));
const extHostExplorerView = col.define(ExtHostContext.ExtHostExplorerView).set<ExtHostExplorerView>(new ExtHostExplorerView(threadService, extHostCommands));
const extHostTreeViews = col.define(ExtHostContext.ExtHostTreeViews).set<ExtHostTreeViews>(new ExtHostTreeViews(threadService, extHostCommands));
const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set<ExtHostConfiguration>(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), initData.configuration));
const extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set<ExtHostDiagnostics>(new ExtHostDiagnostics(threadService));
const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set<ExtHostLanguageFeatures>(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<any>): vscode.View<any> => {
return extHostExplorerView.createExplorerView(id, name, provider);
registerTreeDataProvider: proposedApiFunction(extension, (id: string, treeDataProvider: vscode.TreeDataProvider<any>): 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,
......
......@@ -194,17 +194,17 @@ export abstract class MainThreadEditorsShape {
$getDiffInformation(id: string): TPromise<editorCommon.ILineChange[]> { 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<ITreeNode> { throw ni(); };
$resolveChildren(viewId: string, node: ITreeNode): TPromise<ITreeNode[]> { throw ni(); }
$getInternalCommand(viewId: string, node: ITreeNode): TPromise<modes.Command> { throw ni(); }
export abstract class ExtHostTreeViewsShape {
$getElements(treeViewId: string): TPromise<TreeItem[]> { throw ni(); }
$getChildren(treeViewId: string, treeItemHandle: number): TPromise<TreeItem[]> { throw ni(); }
$restore(treeViewId: string, treeItems: TreeItem[]): TPromise<TreeItem[]> { throw ni(); }
}
export abstract class ExtHostExtensionServiceShape {
......@@ -503,7 +507,7 @@ export const MainContext = {
MainThreadDocuments: createMainId<MainThreadDocumentsShape>('MainThreadDocuments', MainThreadDocumentsShape),
MainThreadEditors: createMainId<MainThreadEditorsShape>('MainThreadEditors', MainThreadEditorsShape),
MainThreadErrors: createMainId<MainThreadErrorsShape>('MainThreadErrors', MainThreadErrorsShape),
MainThreadExplorerViews: createMainId<MainThreadExplorerViewShape>('MainThreadExplorerView', MainThreadExplorerViewShape),
MainThreadTreeViews: createMainId<MainThreadTreeViewsShape>('MainThreadTreeViews', MainThreadTreeViewsShape),
MainThreadLanguageFeatures: createMainId<MainThreadLanguageFeaturesShape>('MainThreadLanguageFeatures', MainThreadLanguageFeaturesShape),
MainThreadLanguages: createMainId<MainThreadLanguagesShape>('MainThreadLanguages', MainThreadLanguagesShape),
MainThreadMessageService: createMainId<MainThreadMessageServiceShape>('MainThreadMessageService', MainThreadMessageServiceShape),
......@@ -528,7 +532,7 @@ export const ExtHostContext = {
ExtHostDocuments: createExtId<ExtHostDocumentsShape>('ExtHostDocuments', ExtHostDocumentsShape),
ExtHostDocumentSaveParticipant: createExtId<ExtHostDocumentSaveParticipantShape>('ExtHostDocumentSaveParticipant', ExtHostDocumentSaveParticipantShape),
ExtHostEditors: createExtId<ExtHostEditorsShape>('ExtHostEditors', ExtHostEditorsShape),
ExtHostExplorerView: createExtId<ExtHostExplorerViewShape>('ExtHostExplorerView', ExtHostExplorerViewShape),
ExtHostTreeViews: createExtId<ExtHostTreeViewsShape>('ExtHostTreeViews', ExtHostTreeViewsShape),
ExtHostFileSystemEventService: createExtId<ExtHostFileSystemEventServiceShape>('ExtHostFileSystemEventService', ExtHostFileSystemEventServiceShape),
ExtHostHeapService: createExtId<ExtHostHeapServiceShape>('ExtHostHeapMonitor', ExtHostHeapServiceShape),
ExtHostLanguageFeatures: createExtId<ExtHostLanguageFeaturesShape>('ExtHostLanguageFeatures', ExtHostLanguageFeaturesShape),
......
/*---------------------------------------------------------------------------------------------
* 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<any>) {
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<any> };
private _extViews: Map<string, View<any>> = new Map<string, View<any>>();
private _extNodeMaps: { [providerId: string]: { [id: string]: ITreeNode } };
private _mainNodesMap: Map<string, Map<any, ITreeNode>>;
private _childrenNodesMap: Map<string, Map<any, any[]>>;
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<string, Map<any, ITreeNode>>();
this._childrenNodesMap = new Map<string, Map<any, any[]>>();
commands.registerArgumentProcessor({
processArgument: arg => {
if (arg && arg.providerId && arg.id) {
const extNodeMap = this._extNodeMaps[arg.providerId];
return extNodeMap[arg.id];
}
return arg;
}
});
}
createExplorerView<T>(viewId: string, viewName: string, provider: TreeDataProvider<T>): View<T> {
this._proxy.$registerView(viewId, viewName);
this._extNodeProviders[viewId] = provider;
this._mainNodesMap.set(viewId, new Map<any, ITreeNode>());
this._childrenNodesMap.set(viewId, new Map<any, any>());
const treeView: View<T> = {
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<ITreeNode> {
const provider = this._extNodeProviders[providerId];
if (!provider) {
const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId);
return TPromise.wrapError<ITreeNode>(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<ITreeNode>(errMessage);
});
}
$resolveChildren(providerId: string, mainThreadNode: ITreeNode): TPromise<ITreeNode[]> {
const provider = this._extNodeProviders[providerId];
if (!provider) {
const errMessage = localize('treeExplorer.notRegistered', 'No TreeExplorerNodeProvider with id \'{0}\' registered.', providerId);
return TPromise.wrapError<ITreeNode[]>(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<modes.Command> {
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
/*---------------------------------------------------------------------------------------------
* 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<string, ExtHostTreeView<any>> = new Map<string, ExtHostTreeView<any>>();
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<T>(id: string, treeDataProvider: vscode.TreeDataProvider<T>): vscode.Disposable {
const treeView = new ExtHostTreeView<T>(id, treeDataProvider, this._proxy);
this.treeViews.set(id, treeView);
return {
dispose: () => {
this.treeViews.delete(id);
treeView.dispose();
}
};
}
$getElements(treeViewId: string): TPromise<TreeItem[]> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
return TPromise.wrapError<TreeItem[]>(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
}
return treeView.getTreeItems();
}
$getChildren(treeViewId: string, treeItemHandle?: number): TPromise<TreeItem[]> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
return TPromise.wrapError<TreeItem[]>(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<modes.Command>(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', arg.treeViewId));
}
return treeView.getExtensionElement(arg.treeItemHandle);
}
}
class ExtHostTreeView<T> extends Disposable {
private _itemHandlePool = 0;
private extElementsMap: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
private itemHandlesMap: Map<T, TreeItemHandle> = new Map<T, TreeItemHandle>();
private extChildrenElementsMap: Map<T, T[]> = new Map<T, T[]>();
constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape) {
super();
this.proxy.$registerView(viewId);
this._register(dataProvider.onDidChange(element => this._refresh(element)));
}
getTreeItems(): TPromise<TreeItem[]> {
this.extChildrenElementsMap.clear();
this.extElementsMap.clear();
this.itemHandlesMap.clear();
return asWinJsPromise(() => this.dataProvider.getChildren())
.then(elements => this.processAndMapElements(elements));
}
getChildren(treeItemHandle: TreeItemHandle): TPromise<TreeItem[]> {
let extElement = this.getExtensionElement(treeItemHandle);
if (extElement) {
this.clearChildren(extElement);
} else {
return TPromise.wrapError<TreeItem[]>(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<TreeItem[]> {
const treeItemsPromises: TPromise<TreeItem>[] = [];
for (const element of elements) {
if (this.extChildrenElementsMap.has(element)) {
return TPromise.wrapError<TreeItem[]>(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
......@@ -1265,3 +1265,8 @@ export enum ProgressLocation {
SourceControl = 1,
Window = 10,
}
export enum TreeItemCollapsibleState {
Collapsed = 1,
Expanded = 2
}
......@@ -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<void> {
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);
}
......
......@@ -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
......
/*---------------------------------------------------------------------------------------------
* 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
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#F6F6F6"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#424242"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#2D2D30"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#C5C5C5"/></svg>
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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<ITreeExplorer[]>('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<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
new SyncActionDescriptor(OpenViewletAction, viewletId, localize('showViewlet', "Show {0}", label)),
'View: Show {0}',
localize('view', "View")
);
// Register as viewlet
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor(
'vs/workbench/parts/explorers/browser/treeExplorerViewlet',
'TreeExplorerViewlet',
viewletId,
label,
viewletCSSClass,
-1,
extension.description.id
));
}
}
});
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExtensionExplorersContribtion);
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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);
});
}
}
/*---------------------------------------------------------------------------------------------
* 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<void>();
get onDidChangeTitle(): Event<void> { 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);
}
}
/*---------------------------------------------------------------------------------------------
* 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<string | undefined>;
private _onDidChangeProvider = new Emitter<string>();
get onDidChangeProvider(): Event<string> { return this._onDidChangeProvider.event; }
private _onTreeExplorerNodeProviderRegistered = new Emitter<string>();
public get onTreeExplorerNodeProviderRegistered(): Event<string> { 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<string | undefined>('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<InternalTreeNode> {
const provider = this.getProvider(providerId);
return TPromise.wrap(provider.provideRootNode());
}
public resolveChildren(providerId: string, node: InternalTreeNode): TPromise<InternalTreeNode[]> {
const provider = this.getProvider(providerId);
return TPromise.wrap(provider.resolveChildren(node));
}
public executeCommand(providerId: string, node: InternalTreeNode): TPromise<any> {
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;
}
}
/*---------------------------------------------------------------------------------------------
* 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<void> {
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<void> {
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();
}
}
/*---------------------------------------------------------------------------------------------
* 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<void> {
return this.updateInput();
}
public setVisible(visible: boolean): TPromise<void> {
return super.setVisible(visible);
}
public updateInput(): TPromise<void> {
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<void> {
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);
});
}
}
/*---------------------------------------------------------------------------------------------
* 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<InternalTreeNode[]> {
const promise = this.treeExplorerService.resolveChildren(this.treeNodeProviderId, node);
this.progressService.showWhile(promise, 800);
return promise;
}
public getParent(tree: ITree, node: InternalTreeNode): TPromise<InternalTreeNode> {
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<any> {
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);
}
}
/*---------------------------------------------------------------------------------------------
* 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<IExplorerViewsService>('explorerViewsService');
export interface IExplorerViewsService {
_serviceBrand: any;
readonly onViewCreated: Event<IExplorerView<any>>;
createView<T>(id: string, name: string, dataProvider: IExplorerViewDataProvider<T>): IExplorerView<T>;
getViews<T>(): IExplorerView<T>[];
}
export interface IExplorerView<T> extends IDisposable {
refresh(element: T): void;
instantiate(actionRunner: IActionRunner, viewletSetings: any, instantiationService: IInstantiationService): any;
}
export interface IExplorerViewDataProvider<T> {
provideRoot(): TPromise<T>;
resolveChildren(element: T): TPromise<T[]>;
getId(element: T): string;
getLabel(element: T): string;
getContextKey(element: T): string;
hasChildren(element: T): boolean;
select(element: T): void;
}
/*---------------------------------------------------------------------------------------------
* 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 `-`.
}
/*---------------------------------------------------------------------------------------------
* 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<ITreeExplorerService>('treeExplorerService');
export interface ITreeExplorerService {
_serviceBrand: any;
onDidChangeProvider: Event<string>;
activeProvider: string;
onTreeExplorerNodeProviderRegistered: Event<String>;
registerTreeExplorerNodeProvider(providerId: string, provider: InternalTreeNodeProvider): void;
hasProvider(providerId: string): boolean;
getProvider(providerId: string): InternalTreeNodeProvider;
provideRootNode(providerId: string): TPromise<InternalTreeNode>;
resolveChildren(providerId: string, node: InternalTreeNode): TPromise<InternalTreeNode[]>;
executeCommand(providerId: string, node: InternalTreeNode): TPromise<void>;
}
/*---------------------------------------------------------------------------------------------
* 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<InternalTreeNodeContent>;
resolveChildren(node: InternalTreeNodeContent): Thenable<InternalTreeNodeContent[]>;
executeCommand(node: InternalTreeNodeContent): TPromise<any>;
onRefresh?: Event<InternalTreeNodeContent>;
}
......@@ -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<void> {
......@@ -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;
}
......
......@@ -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
......@@ -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<string, IExplorerView<any>> = new Map<string, IExplorerView<any>>();
private _onViewCreated: Emitter<IExplorerView<any>> = new Emitter<IExplorerView<any>>();
public readonly onViewCreated: Event<IExplorerView<any>> = this._onViewCreated.event;
private _onDataProviderRegistered: Emitter<IExplorerView<any>> = new Emitter<IExplorerView<any>>();
public readonly onDataProviderRegistered: Event<IExplorerView<any>> = this._onDataProviderRegistered.event;
createView(id: string, name: string, dataProvider: IExplorerViewDataProvider<any>): IExplorerView<any> {
const view = new ExplorerView(id, name, dataProvider);
this.explorerViews.set(id, view);
this._onViewCreated.fire(view);
return view;
}
public getViews(): IExplorerView<any>[] {
const views = [];
this.explorerViews.forEach(view => {
views.push(view);
});
return views;
}
}
class ExplorerView<T> extends Disposable implements IExplorerView<T>, IViewInstantiator {
private view: TreeExplorerView;
constructor(private id: string, private name: string, private dataProvider: IExplorerViewDataProvider<T>) {
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<boolean>;
private activated: boolean = false;
private treeInputPromise: TPromise<void>;
private dataProviderElementChangeListener: IDisposable;
private disposables: IDisposable[] = [];
constructor(
private id: string,
private name: string,
private dataProvider: IExplorerViewDataProvider<any>,
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<boolean>(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<void> {
return this.updateInput();
return this.setInput();
}
public setVisible(visible: boolean): TPromise<void> {
return super.setVisible(visible);
}
public updateInput(): TPromise<void> {
return this.dataProvider.provideRoot()
.then(root => this.tree.setInput(root));
public setInput(): TPromise<void> {
if (this.listenToDataProvider()) {
this.treeInputPromise = this.tree.setInput(new Root());
return this.treeInputPromise;
}
this.treeInputPromise = new TPromise<void>((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<any>,
@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<any[]> {
const promise = this.dataProvider.resolveChildren(node);
public getChildren(tree: ITree, node: ITreeItem): TPromise<any[]> {
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<any> {
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<any>,
@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<void> { return this._onDidChangeTitle.event; }
constructor(
private viewId: string,
private dataProvider: IExplorerViewDataProvider<any>,
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
/*---------------------------------------------------------------------------------------------
* 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<ITreeItem | undefined | null>;
getElements(): TPromise<ITreeItem[]>;
getChildren(element: ITreeItem): TPromise<ITreeItem[]>;
}
export interface IViewsRegistry {
readonly onViewsRegistered: Event<IViewDescriptor[]>;
readonly onTreeViewDataProviderRegistered: Event<string>;
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<IViewDescriptor[]> = new Emitter<IViewDescriptor[]>();
readonly onViewsRegistered: Event<IViewDescriptor[]> = this._onViewsRegistered.event;
private _onTreeViewDataProviderRegistered: Emitter<string> = new Emitter<string>();
readonly onTreeViewDataProviderRegistered: Event<string> = this._onTreeViewDataProviderRegistered.event;
private _views: Map<ViewLocation, IViewDescriptor[]> = new Map<ViewLocation, IViewDescriptor[]>();
private _treeViewDataPoviders: Map<string, ITreeViewDataProvider> = new Map<string, ITreeViewDataProvider>();
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<T>(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
/*---------------------------------------------------------------------------------------------
* 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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册