#28974 Introduce place holder icons for nodes without icons

This commit is contained in:
Sandeep Somavarapu 2018-02-09 18:07:23 +01:00
parent 125ac04d1f
commit ea787762db
17 changed files with 498 additions and 380 deletions

View file

@ -63,8 +63,9 @@ export class Progress<T> implements IProgress<T> {
}
export enum ProgressLocation {
Scm = 1,
Extensions = 2,
Explorer = 1,
Scm = 3,
Extensions = 5,
Window = 10
}

View file

@ -9,7 +9,7 @@ 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, ICustomViewDescriptor } from 'vs/workbench/common/views';
import { CustomTreeViewPanel } from 'vs/workbench/browser/parts/views/customView';
import { CustomTreeViewPanel } from 'vs/workbench/browser/parts/views/customViewPanel';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { coalesce, } from 'vs/base/common/arrays';
@ -110,7 +110,7 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyV
when: ContextKeyExpr.deserialize(item.when),
canToggleVisibility: true,
collapsed: true,
treeItemView: true
treeView: true
};
// validate

View file

@ -11,12 +11,13 @@ import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainCo
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { ITreeViewDataProvider, ITreeItem, ICustomViewsService } from 'vs/workbench/common/views';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { assign } from 'vs/base/common/objects';
import { distinct } from 'vs/base/common/arrays';
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape {
private _proxy: ExtHostTreeViewsShape;
private _dataProviders: Map<string, TreeViewDataProvider> = new Map<string, TreeViewDataProvider>();
constructor(
extHostContext: IExtHostContext,
@ -29,15 +30,21 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
$registerTreeViewDataProvider(treeViewId: string): void {
const dataProvider = this._register(new TreeViewDataProvider(treeViewId, this._proxy, this.messageService));
this._dataProviders.set(treeViewId, dataProvider);
this.viewsService.registerTreeViewDataProvider(treeViewId, dataProvider);
}
$refresh(treeViewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): void {
const treeViewer = this.viewsService.getTreeItemViewer(treeViewId);
if (treeViewer && treeViewer.dataProvider) {
(<TreeViewDataProvider>treeViewer.dataProvider).refresh(itemsToRefresh);
const dataProvider = this._dataProviders.get(treeViewId);
if (dataProvider) {
dataProvider.refresh(itemsToRefresh);
}
}
dispose(): void {
this._dataProviders.clear();
super.dispose();
}
}
type TreeItemHandle = string;
@ -58,23 +65,13 @@ class TreeViewDataProvider implements ITreeViewDataProvider {
) {
}
getElements(): TPromise<ITreeItem[]> {
return this._proxy.$getElements(this.treeViewId)
.then(elements => {
return this.postGetElements(elements);
}, err => {
this.messageService.show(Severity.Error, err);
return [];
});
}
getChildren(treeItem: ITreeItem): TPromise<ITreeItem[]> {
if (treeItem.children) {
getChildren(treeItem?: ITreeItem): TPromise<ITreeItem[]> {
if (treeItem && treeItem.children) {
return TPromise.as(treeItem.children);
}
return this._proxy.$getChildren(this.treeViewId, treeItem.handle)
return this._proxy.$getChildren(this.treeViewId, treeItem ? treeItem.handle : void 0)
.then(children => {
return this.postGetElements(children);
return this.postGetChildren(children);
}, err => {
this.messageService.show(Severity.Error, err);
return [];
@ -108,19 +105,12 @@ class TreeViewDataProvider implements ITreeViewDataProvider {
}
}
private postGetElements(elements: ITreeItem[]): ITreeItem[] {
private postGetChildren(elements: ITreeItem[]): ITreeItem[] {
const result = [];
if (elements) {
for (const element of elements) {
const currentTreeItem = this.itemsMap.get(element.handle);
if (currentTreeItem) {
// Update the current item with new item
this.updateTreeItem(currentTreeItem, element);
} else {
this.itemsMap.set(element.handle, element);
}
// Always use the existing items
result.push(this.itemsMap.get(element.handle));
this.itemsMap.set(element.handle, element);
result.push(element);
}
}
return result;
@ -129,7 +119,10 @@ class TreeViewDataProvider implements ITreeViewDataProvider {
private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void {
treeItem.children = treeItem.children ? treeItem.children : null;
if (current) {
assign(current, treeItem);
const properties = distinct([...Object.keys(current), ...Object.keys(treeItem)]);
for (const property of properties) {
current[property] = treeItem[property];
}
}
}

View file

@ -518,8 +518,7 @@ export interface ExtHostDocumentsAndEditorsShape {
}
export interface ExtHostTreeViewsShape {
$getElements(treeViewId: string): TPromise<ITreeItem[]>;
$getChildren(treeViewId: string, treeItemHandle: string): TPromise<ITreeItem[]>;
$getChildren(treeViewId: string, treeItemHandle?: string): TPromise<ITreeItem[]>;
}
export interface ExtHostWorkspaceShape {

View file

@ -48,14 +48,6 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
};
}
$getElements(treeViewId: string): TPromise<ITreeItem[]> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
return TPromise.wrapError<ITreeItem[]>(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
}
return treeView.getChildren();
}
$getChildren(treeViewId: string, treeItemHandle?: string): TPromise<ITreeItem[]> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {

View file

@ -6,126 +6,159 @@
import 'vs/css!./media/views';
import Event, { Emitter } from 'vs/base/common/event';
import * as errors from 'vs/base/common/errors';
import { IDisposable, dispose, empty as EmptyDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
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 { $ } from 'vs/base/browser/builder';
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';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
import { fillInActions, ContextAwareMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { LIGHT } from 'vs/platform/theme/common/themeService';
import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ActionItem, ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { TreeItemCollapsibleState, ITreeItem, TreeViewItemHandleArg, ITreeItemViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, ICustomViewDescriptor } from 'vs/workbench/common/views';
import { IViewletViewOptions, IViewOptions, FileIconThemableWorkbenchTree, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { TreeItemCollapsibleState, ITreeItem, ITreeViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, IViewDescriptor, TreeViewItemHandleArg, ICustomViewDescriptor } from 'vs/workbench/common/views';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import URI from 'vs/base/common/uri';
import { basename } from 'vs/base/common/paths';
import { FileKind } from 'vs/platform/files/common/files';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IAction, ActionRunner } from 'vs/base/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { fillInActions, ContextAwareMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
import { FileKind } from 'vs/platform/files/common/files';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet';
export class CustomViewsService implements ICustomViewsService {
export class CustomViewsService extends Disposable implements ICustomViewsService {
_serviceBrand: any;
private viewers: Map<string, ITreeItemViewer> = new Map<string, ITreeItemViewer>();
private viewers: Map<string, CustomTreeViewer> = new Map<string, CustomTreeViewer>();
constructor(
@IInstantiationService private instantiationService: IInstantiationService
) {
super();
this.createViewers(ViewsRegistry.getAllViews());
this._register(ViewsRegistry.onViewsRegistered(viewDescriptors => this.createViewers(viewDescriptors)));
this._register(ViewsRegistry.onViewsDeregistered(viewDescriptors => this.removeViewers(viewDescriptors)));
}
getTreeItemViewer(id: string): ITreeItemViewer {
let viewer = this.viewers.get(id);
if (!viewer) {
viewer = this.createViewer(id);
if (viewer) {
this.viewers.set(id, viewer);
}
}
return viewer;
getTreeViewer(id: string): ITreeViewer {
return this.viewers.get(id);
}
registerTreeViewDataProvider(id: string, dataProvider: ITreeViewDataProvider): void {
const treeViewer = this.getTreeItemViewer(id);
const treeViewer = <CustomTreeViewer>this.getTreeViewer(id);
if (treeViewer) {
treeViewer.dataProvider = dataProvider;
dataProvider.onDispose(() => treeViewer.dataProvider = null);
treeViewer.setDataProvider(dataProvider);
dataProvider.onDispose(() => treeViewer.setDataProvider(null));
}
}
private createViewer(id: string): ITreeItemViewer {
const viewDeescriptor = <ICustomViewDescriptor>ViewsRegistry.getView(id);
if (viewDeescriptor && viewDeescriptor.treeItemView) {
return this.instantiationService.createInstance(TreeItemViewer, id);
private createViewers(viewDescriptors: IViewDescriptor[]): void {
for (const viewDescriptor of viewDescriptors) {
if ((<ICustomViewDescriptor>viewDescriptor).treeView) {
this.viewers.set(viewDescriptor.id, this.instantiationService.createInstance(CustomTreeViewer, viewDescriptor.id));
}
}
}
private removeViewers(viewDescriptors: IViewDescriptor[]): void {
for (const { id } of viewDescriptors) {
const viewer = this.getTreeViewer(id);
if (viewer) {
viewer.dispose();
this.viewers.delete(id);
}
}
return null;
}
}
export class TreeItemViewer extends Disposable implements ITreeItemViewer {
class Root implements ITreeItem {
label = 'root';
handle = '0';
parentHandle = null;
collapsibleState = TreeItemCollapsibleState.Expanded;
children = void 0;
}
class CustomTreeViewer extends Disposable implements ITreeViewer {
private isVisible: boolean = false;
private activated: boolean = false;
private _hasIconForParentNode = false;
private _hasIconForLeafNode = false;
private tree: ITree;
private treeInputPromise: TPromise<void>;
private _onDidIconsChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidIconsChange: Event<void> = this._onDidIconsChange.event;
private treeContainer: HTMLElement;
private tree: FileIconThemableWorkbenchTree;
private root: ITreeItem;
private elementsToRefresh: ITreeItem[] = [];
private refreshing = 0;
private _dataProvider: ITreeViewDataProvider;
private dataProviderElementChangeListener: IDisposable;
constructor(
private id: string,
@IExtensionService private extensionService: IExtensionService
@IExtensionService private extensionService: IExtensionService,
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
@ICommandService private commandService: ICommandService
) {
super();
this.root = new Root();
this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
}
get dataProvider(): ITreeViewDataProvider {
return this._dataProvider;
}
set dataProvider(dataProvider: ITreeViewDataProvider) {
this._dataProvider = dataProvider;
setDataProvider(dataProvider: ITreeViewDataProvider) {
if (this.dataProviderElementChangeListener) {
this.dataProviderElementChangeListener.dispose();
}
if (this.dataProvider) {
this.dataProviderElementChangeListener = this._register(this.dataProvider.onDidChange(element => this.refresh(element)));
this.refresh(null);
if (dataProvider) {
const customTreeView: CustomTreeViewer = this;
this._dataProvider = new class implements ITreeViewDataProvider {
onDidChange = dataProvider.onDidChange;
onDispose = dataProvider.onDispose;
getChildren(node?: ITreeItem): TPromise<any[]> {
if (node.children) {
return TPromise.as(node.children);
}
const promise = node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node);
return promise.then(children => {
node.children = children;
if (!customTreeView.refreshing) {
customTreeView.updateIconsAvailability(node);
}
return children;
});
}
};
this.dataProviderElementChangeListener = this._register(dataProvider.onDidChange(elements => this.refresh(elements)));
} else {
this._dataProvider = null;
}
this.refresh();
}
refresh(elements: ITreeItem[]): TPromise<void> {
if (this.tree) {
if (!elements) {
const root: ITreeItem = this.tree.getInput();
root.children = null; // reset children
elements = [root];
}
if (this.isVisible) {
return this.doRefresh(elements);
} else {
this.elementsToRefresh.push(...elements);
}
}
return TPromise.as(null);
get hasIconForParentNode(): boolean {
return this._hasIconForParentNode;
}
setTree(tree: ITree): void {
this.tree = tree;
this.setInput();
get hasIconForLeafNode(): boolean {
return this._hasIconForLeafNode;
}
setVisibility(isVisible: boolean): void {
@ -169,79 +202,16 @@ export class TreeItemViewer extends Disposable implements ITreeItemViewer {
// Pass Focus to Viewer
this.tree.DOMFocus();
}
}
layout(size: number) {
if (this.tree) {
this.tree.layout(size);
}
}
private activate() {
if (!this.activated) {
this.extensionService.activateByEvent(`onView:${this.id}`);
this.activated = true;
this.setInput();
}
}
private setInput(): TPromise<void> {
if (this.tree) {
if (!this.treeInputPromise) {
this.treeInputPromise = this.tree.setInput(new Root());
}
return this.treeInputPromise;
}
return TPromise.as(null);
}
private doRefresh(elements: ITreeItem[]): TPromise<void> {
return TPromise.join(elements.map(e => this.tree.refresh(e))) as TPromise;
}
}
export class CustomTreeViewPanel extends ViewsViewletPanel {
private menus: Menus;
private treeContainer: HTMLElement;
private tree: WorkbenchTree;
private treeViewer: TreeItemViewer;
constructor(
options: IViewletViewOptions,
@IMessageService private messageService: IMessageService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService private themeService: IWorkbenchThemeService,
@ICommandService private commandService: ICommandService,
@IConfigurationService configurationService: IConfigurationService,
@ICustomViewsService customViewsService: ICustomViewsService,
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService, configurationService);
this.menus = this.instantiationService.createInstance(Menus, this.id);
this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables);
this.treeViewer = <TreeItemViewer>customViewsService.getTreeItemViewer(this.id);
this.disposables.push(this.treeViewer);
this.updateTreeVisibility();
}
setVisible(visible: boolean): TPromise<void> {
return super.setVisible(visible).then(() => this.updateTreeVisibility());
}
focus(): void {
super.focus();
this.treeViewer.focus();
}
renderBody(container: HTMLElement): void {
render(container: HTMLElement): void {
this.treeContainer = DOM.append(container, DOM.$('.tree-explorer-viewlet-tree-view'));
const actionItemProvider = (action: IAction) => this.getActionItem(action);
const dataSource = this.instantiationService.createInstance(TreeDataSource, this.treeViewer);
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, this.menus, actionItemProvider);
const controller = this.instantiationService.createInstance(TreeController, this.id, this.menus);
const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuItemActionItem, action) : undefined;
const menus = this.instantiationService.createInstance(Menus, this.id);
const dataSource = this.instantiationService.createInstance(TreeDataSource, this);
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, this, menus, actionItemProvider);
const controller = this.instantiationService.createInstance(TreeController, this.id, menus);
this.tree = this.instantiationService.createInstance(FileIconThemableWorkbenchTree,
this.treeContainer,
{ dataSource, renderer, controller },
@ -249,38 +219,17 @@ export class CustomTreeViewPanel extends ViewsViewletPanel {
);
this.tree.contextKeyService.createKey<boolean>(this.id, true);
this.disposables.push(this.tree);
this.disposables.push(this.tree.onDidChangeSelection(e => this.onSelection(e, this.tree)));
this.themeService.onThemeChange(() => this.tree.refresh() /* soft refresh */, this, this.disposables);
this._register(this.tree);
this._register(this.tree.onDidChangeSelection(e => this.onSelection(e)));
this.treeViewer.setTree(this.tree);
this.tree.setInput(this.root);
}
setExpanded(expanded: boolean): void {
this.treeViewer.setVisibility(this.isVisible() && expanded);
super.setExpanded(expanded);
}
layoutBody(size: number): void {
if (this.treeContainer) {
layout(size: number) {
if (this.tree) {
this.treeContainer.style.height = size + 'px';
this.tree.layout(size);
}
this.treeViewer.layout(size);
}
getActions(): IAction[] {
return [...this.menus.getTitleActions()];
}
getSecondaryActions(): IAction[] {
return this.menus.getTitleSecondaryActions();
}
getActionItem(action: IAction): IActionItem {
if (!(action instanceof MenuItemAction)) {
return undefined;
}
return new ContextAwareMenuItemActionItem(action, this.keybindingService, this.messageService, this.contextMenuService);
}
getOptimalWidth(): number {
@ -289,46 +238,107 @@ export class CustomTreeViewPanel extends ViewsViewletPanel {
const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
return DOM.getLargestChildWidth(parentNode, childNodes);
}
return super.getOptimalWidth();
return 0;
}
private updateTreeVisibility(): void {
this.treeViewer.setVisibility(this.isVisible() && this.isExpanded());
refresh(elements?: ITreeItem[]): TPromise<void> {
if (this.tree) {
elements = elements || [this.root];
for (const element of elements) {
element.children = null; // reset children
}
if (this.isVisible) {
return this.doRefresh(elements);
} else {
this.elementsToRefresh.push(...elements);
}
}
return TPromise.as(null);
}
private onSelection({ payload }: any, tree: WorkbenchTree): void {
const selection: ITreeItem = tree.getSelection()[0];
private activate() {
if (!this.activated) {
this.extensionService.activateByEvent(`onView:${this.id}`);
this.activated = true;
}
}
private doRefresh(elements: ITreeItem[]): TPromise<void> {
if (this.tree) {
return TPromise.join(elements.map(e => {
this.refreshing++;
return this.tree.refresh(e).then(() => this.refreshing--, () => this.refreshing--);
})).then(() => this.updateIconsAvailability(this.root));
}
return TPromise.as(null);
}
private updateIconsAvailability(parent: ITreeItem): void {
if (this.activated && this.tree) {
const initialResult = parent instanceof Root ? { hasIconForParentNode: false, hasIconForLeafNode: false } : { hasIconForParentNode: this.hasIconForParentNode, hasIconForLeafNode: this.hasIconForLeafNode };
const { hasIconForParentNode, hasIconForLeafNode } = this.computeIconsAvailability(parent.children || [], initialResult);
const changed = this.hasIconForParentNode !== hasIconForParentNode || this.hasIconForLeafNode !== hasIconForLeafNode;
this._hasIconForParentNode = hasIconForParentNode;
this._hasIconForLeafNode = hasIconForLeafNode;
if (changed) {
this._onDidIconsChange.fire();
}
DOM.toggleClass(this.treeContainer, 'custom-view-align-icons-and-twisties', this.hasIconForLeafNode && !this.hasIconForParentNode);
}
}
private computeIconsAvailability(nodes: ITreeItem[], result: { hasIconForParentNode: boolean, hasIconForLeafNode: boolean }): { hasIconForParentNode: boolean, hasIconForLeafNode: boolean } {
if (!result.hasIconForLeafNode || !result.hasIconForParentNode) {
for (const node of nodes) {
if (this.hasIcon(node)) {
result.hasIconForParentNode = result.hasIconForParentNode || node.collapsibleState !== TreeItemCollapsibleState.None;
result.hasIconForLeafNode = result.hasIconForLeafNode || node.collapsibleState === TreeItemCollapsibleState.None;
}
this.computeIconsAvailability(node.children || [], result);
if (result.hasIconForLeafNode && result.hasIconForParentNode) {
return result;
}
}
}
return result;
}
private hasIcon(node: ITreeItem): boolean {
const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
if (icon) {
return true;
}
if (node.resourceUri) {
const fileIconTheme = this.themeService.getFileIconTheme();
if (node.collapsibleState !== TreeItemCollapsibleState.None) {
return fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons;
}
return fileIconTheme.hasFileIcons;
}
return false;
}
private onSelection({ payload }: any): void {
const selection: ITreeItem = this.tree.getSelection()[0];
if (selection) {
if (selection.command) {
const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent;
const isMouseEvent = payload && payload.origin === 'mouse';
const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2;
if (!isMouseEvent || tree.openOnSingleClick || isDoubleClick) {
if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) {
this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || []));
}
}
}
}
dispose(): void {
dispose(this.disposables);
super.dispose();
}
}
class Root implements ITreeItem {
label = 'root';
handle = '0';
parentHandle = null;
collapsibleState = TreeItemCollapsibleState.Expanded;
}
class TreeDataSource implements IDataSource {
constructor(
private treeItemViewer: ITreeItemViewer,
@IProgressService private progressService: IProgressService
private treeView: ITreeViewer,
@IProgressService2 private progressService: IProgressService2
) {
}
@ -337,26 +347,13 @@ class TreeDataSource implements IDataSource {
}
public hasChildren(tree: ITree, node: ITreeItem): boolean {
if (!this.treeItemViewer.dataProvider) {
return false;
}
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded;
return this.treeView.dataProvider && node.collapsibleState !== TreeItemCollapsibleState.None;
}
public getChildren(tree: ITree, node: ITreeItem): TPromise<any[]> {
if (node.children) {
return TPromise.as(node.children);
if (this.treeView.dataProvider) {
return this.progressService.withProgress({ location: ProgressLocation.Explorer }, () => this.treeView.dataProvider.getChildren(node));
}
if (this.treeItemViewer.dataProvider) {
const promise = node instanceof Root ? this.treeItemViewer.dataProvider.getElements() : this.treeItemViewer.dataProvider.getChildren(node);
this.progressService.showWhile(promise, 100);
return promise.then(children => {
node.children = children;
return children;
});
}
return TPromise.as([]);
}
@ -372,9 +369,8 @@ class TreeDataSource implements IDataSource {
interface ITreeExplorerTemplateData {
label: HTMLElement;
resourceLabel: ResourceLabel;
icon: HTMLElement;
icon: TreeItemIcon;
actionBar: ActionBar;
aligner: Aligner;
}
class TreeRenderer implements IRenderer {
@ -384,6 +380,7 @@ class TreeRenderer implements IRenderer {
constructor(
private treeViewId: string,
private treeViewer: ITreeViewer,
private menus: Menus,
private actionItemProvider: IActionItemProvider,
@IInstantiationService private instantiationService: IInstantiationService,
@ -400,18 +397,18 @@ class TreeRenderer implements IRenderer {
}
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData {
const el = DOM.append(container, DOM.$('.custom-view-tree-node-item'));
DOM.addClass(container, 'custom-view-tree-node-item');
const icon = DOM.append(el, DOM.$('.custom-view-tree-node-item-icon'));
const label = DOM.append(el, DOM.$('.custom-view-tree-node-item-label'));
const resourceLabel = this.instantiationService.createInstance(ResourceLabel, el, {});
const actionsContainer = DOM.append(el, DOM.$('.actions'));
const icon = this.instantiationService.createInstance(TreeItemIcon, container, this.treeViewer);
const label = DOM.append(container, DOM.$('.custom-view-tree-node-item-label'));
const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, {});
const actionsContainer = DOM.append(container, DOM.$('.actions'));
const actionBar = new ActionBar(actionsContainer, {
actionItemProvider: this.actionItemProvider,
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
});
return { label, resourceLabel, icon, actionBar, aligner: new Aligner(container, tree, this.themeService) };
return { label, resourceLabel, icon, actionBar };
}
public renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void {
@ -425,7 +422,6 @@ class TreeRenderer implements IRenderer {
templateData.label.textContent = '';
DOM.removeClass(templateData.label, 'custom-view-tree-node-item-label');
DOM.removeClass(templateData.resourceLabel.element, 'custom-view-tree-node-item-resourceLabel');
DOM.removeClass(templateData.icon, 'custom-view-tree-node-item-icon');
if (resource && !icon) {
templateData.resourceLabel.setLabel({ name, resource }, { fileKind: node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE });
@ -433,95 +429,64 @@ class TreeRenderer implements IRenderer {
} else {
templateData.label.textContent = name;
DOM.addClass(templateData.label, 'custom-view-tree-node-item-label');
templateData.icon.style.backgroundImage = `url('${icon}')`;
if (icon) {
DOM.addClass(templateData.icon, 'custom-view-tree-node-item-icon');
}
}
templateData.icon.treeItem = node;
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
templateData.aligner.align(node);
}
public disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void {
templateData.resourceLabel.dispose();
templateData.aligner.dispose();
templateData.icon.dispose();
}
}
class Aligner extends Disposable {
class TreeItemIcon extends Disposable {
private node: ITreeItem;
private _treeItem: ITreeItem;
private iconElement: HTMLElement;
constructor(
private container: HTMLElement,
private tree: ITree,
private themeService: IWorkbenchThemeService
container: HTMLElement,
private treeViewer: CustomTreeViewer,
@IInstantiationService instantiationService: IInstantiationService,
@IWorkbenchThemeService private themeService: IWorkbenchThemeService
) {
super();
this._register(this.themeService.onDidFileIconThemeChange(() => this.alignByTheme()));
this.iconElement = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
this._register(this.treeViewer.onDidIconsChange(() => this.render()));
}
align(treeItem: ITreeItem): void {
this.node = treeItem;
this.alignByTheme();
set treeItem(treeItem: ITreeItem) {
this._treeItem = treeItem;
this.render();
}
private alignByTheme(): void {
if (this.node) {
DOM.toggleClass(this.container, 'align-with-twisty', this.hasToAlignWithTwisty());
}
}
private hasToAlignWithTwisty(): boolean {
if (this.hasParentHasIcon()) {
return false;
}
const fileIconTheme = this.themeService.getFileIconTheme();
if (!(fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons)) {
return false;
}
if (this.node.collapsibleState !== TreeItemCollapsibleState.None) {
return false;
}
const icon = this.themeService.getTheme().type === LIGHT ? this.node.icon : this.node.iconDark;
const hasIcon = !!icon || !!this.node.resourceUri;
if (!hasIcon) {
return false;
}
const siblingsWithChildren = this.getSiblings().filter(s => s.collapsibleState !== TreeItemCollapsibleState.None);
for (const s of siblingsWithChildren) {
const icon = this.themeService.getTheme().type === LIGHT ? s.icon : s.iconDark;
if (icon) {
return false;
}
}
return true;
}
private getSiblings(): ITreeItem[] {
const parent: ITreeItem = this.tree.getNavigator(this.node).parent() || this.tree.getInput();
return parent.children;
}
private hasParentHasIcon(): boolean {
const parent = this.tree.getNavigator(this.node).parent() || this.tree.getInput();
const icon = this.themeService.getTheme().type === LIGHT ? parent.icon : parent.iconDark;
if (icon) {
return true;
}
if (parent.resourceUri) {
private render(): void {
if (this._treeItem) {
const fileIconTheme = this.themeService.getFileIconTheme();
if (fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons) {
return true;
}
const contributedIcon = this.themeService.getTheme().type === LIGHT ? this._treeItem.icon : this._treeItem.iconDark;
const hasContributedIcon = !!contributedIcon;
const hasChildren = this._treeItem.collapsibleState !== TreeItemCollapsibleState.None;
const hasResource = !!this._treeItem.resourceUri;
const isFolder = hasResource && hasChildren;
const isFile = hasResource && !hasChildren;
const hasThemeFolderIcon = isFolder && fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons;
const hasThemeFileIcon = isFile && fileIconTheme.hasFileIcons;
const hasIcon = hasContributedIcon || hasThemeFolderIcon || hasThemeFileIcon;
const hasFolderPlaceHolderIcon = hasIcon ? false : isFolder && this.treeViewer.hasIconForParentNode;
const hasFilePlaceHolderIcon = hasIcon ? false : isFile && this.treeViewer.hasIconForLeafNode;
const hasContainerPlaceHolderIcon = hasIcon || hasFolderPlaceHolderIcon ? false : hasChildren && this.treeViewer.hasIconForParentNode;
const hasLeafPlaceHolderIcon = hasIcon || hasFilePlaceHolderIcon ? false : !hasChildren && (this.treeViewer.hasIconForParentNode || this.treeViewer.hasIconForLeafNode);
this.iconElement.style.backgroundImage = hasContributedIcon ? `url('${contributedIcon}')` : '';
DOM.toggleClass(this.iconElement, 'folder-icon', hasFolderPlaceHolderIcon);
DOM.toggleClass(this.iconElement, 'file-icon', hasFilePlaceHolderIcon);
DOM.toggleClass(this.iconElement, 'placeholder-icon', hasContainerPlaceHolderIcon);
DOM.toggleClass(this.iconElement, 'custom-view-tree-node-item-icon', hasContributedIcon || hasFolderPlaceHolderIcon || hasFilePlaceHolderIcon || hasContainerPlaceHolderIcon || hasLeafPlaceHolderIcon);
}
return false;
}
}
@ -599,15 +564,7 @@ class MultipleSelectionActionRunner extends ActionRunner {
}
}
class Menus implements IDisposable {
private disposables: IDisposable[] = [];
private titleDisposable: IDisposable = EmptyDisposable;
private titleActions: IAction[] = [];
private titleSecondaryActions: IAction[] = [];
private _onDidChangeTitle = new Emitter<void>();
get onDidChangeTitle(): Event<void> { return this._onDidChangeTitle.event; }
class Menus extends Disposable implements IDisposable {
constructor(
private id: string,
@ -615,40 +572,7 @@ class Menus implements IDisposable {
@IMenuService private menuService: IMenuService,
@IContextMenuService private contextMenuService: IContextMenuService
) {
if (this.titleDisposable) {
this.titleDisposable.dispose();
this.titleDisposable = EmptyDisposable;
}
const _contextKeyService = this.contextKeyService.createScoped();
_contextKeyService.createKey('view', id);
const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService);
const updateActions = () => {
this.titleActions = [];
this.titleSecondaryActions = [];
fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }, this.contextMenuService);
this._onDidChangeTitle.fire();
};
const listener = titleMenu.onDidChange(updateActions);
updateActions();
this.titleDisposable = toDisposable(() => {
listener.dispose();
titleMenu.dispose();
_contextKeyService.dispose();
this.titleActions = [];
this.titleSecondaryActions = [];
});
}
getTitleActions(): IAction[] {
return this.titleActions;
}
getTitleSecondaryActions(): IAction[] {
return this.titleSecondaryActions;
super();
}
getResourceActions(element: ITreeItem): IAction[] {
@ -675,8 +599,4 @@ class Menus implements IDisposable {
return result;
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
}

View file

@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/views';
import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import { IAction, 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 { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { fillInActions, ContextAwareMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICustomViewsService, ITreeViewer } from 'vs/workbench/common/views';
import { IViewletViewOptions, IViewOptions, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet';
export class CustomTreeViewPanel extends ViewsViewletPanel {
private menus: Menus;
private treeViewer: ITreeViewer;
constructor(
options: IViewletViewOptions,
@IMessageService private messageService: IMessageService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService,
@ICustomViewsService customViewsService: ICustomViewsService,
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService);
this.treeViewer = customViewsService.getTreeViewer(this.id);
this.menus = this.instantiationService.createInstance(Menus, this.id);
this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables);
this.updateTreeVisibility();
}
setVisible(visible: boolean): TPromise<void> {
return super.setVisible(visible).then(() => this.updateTreeVisibility());
}
focus(): void {
super.focus();
this.treeViewer.focus();
}
renderBody(container: HTMLElement): void {
this.treeViewer.render(container);
}
setExpanded(expanded: boolean): void {
this.treeViewer.setVisibility(this.isVisible() && expanded);
super.setExpanded(expanded);
}
layoutBody(size: number): void {
this.treeViewer.layout(size);
}
getActions(): IAction[] {
return [...this.menus.getTitleActions()];
}
getSecondaryActions(): IAction[] {
return this.menus.getTitleSecondaryActions();
}
getActionItem(action: IAction): IActionItem {
return action instanceof MenuItemAction ? new ContextAwareMenuItemActionItem(action, this.keybindingService, this.messageService, this.contextMenuService) : undefined;
}
getOptimalWidth(): number {
return this.treeViewer.getOptimalWidth();
}
private updateTreeVisibility(): void {
this.treeViewer.setVisibility(this.isVisible() && this.isExpanded());
}
dispose(): void {
dispose(this.disposables);
super.dispose();
}
}
export class Menus implements IDisposable {
private disposables: IDisposable[] = [];
private titleDisposable: IDisposable = EmptyDisposable;
private titleActions: IAction[] = [];
private titleSecondaryActions: IAction[] = [];
private _onDidChangeTitle = new Emitter<void>();
get onDidChangeTitle(): Event<void> { return this._onDidChangeTitle.event; }
constructor(
id: string,
@IContextKeyService private contextKeyService: IContextKeyService,
@IMenuService private menuService: IMenuService,
@IContextMenuService private contextMenuService: IContextMenuService
) {
if (this.titleDisposable) {
this.titleDisposable.dispose();
this.titleDisposable = EmptyDisposable;
}
const _contextKeyService = this.contextKeyService.createScoped();
_contextKeyService.createKey('view', id);
const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService);
const updateActions = () => {
this.titleActions = [];
this.titleSecondaryActions = [];
fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }, this.contextMenuService);
this._onDidChangeTitle.fire();
};
const listener = titleMenu.onDidChange(updateActions);
updateActions();
this.titleDisposable = toDisposable(() => {
listener.dispose();
titleMenu.dispose();
_contextKeyService.dispose();
this.titleActions = [];
this.titleSecondaryActions = [];
});
}
getTitleActions(): IAction[] {
return this.titleActions;
}
getTitleSecondaryActions(): IAction[] {
return this.titleSecondaryActions;
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-bg{fill:#656565;} .icon-vs-fg{fill:#F0EFF1;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M4 15c-.97 0-2-.701-2-2v-10c0-1.299 1.03-2 2-2h6.061l3.939 3.556v8.444c0 .97-.701 2-2 2h-8z" id="outline"/><path class="icon-vs-bg" d="M9.641,2H3.964C3.964,2,3,2,3,3c0,0.805,0,7.442,0,10c0,1,0.965,1,0.965,1s7,0,8,0S13,13,13,13V5L9.641,2zM12,13H4V3h5v3h3V13z" id="iconBg"/><path class="icon-vs-fg" d="M4 3h5v3h3v7h-8v-10z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 682 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-bg{fill:#C5C5C5;} .icon-vs-fg{opacity:0;fill:#F0EFF1;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M4 15c-.97 0-2-.701-2-2v-10c0-1.299 1.03-2 2-2h6.061l3.939 3.556v8.444c0 .97-.701 2-2 2h-8z" id="outline"/><path class="icon-vs-bg" d="M9.641,2H3.964C3.964,2,3,2,3,3c0,0.805,0,7.442,0,10c0,1,0.965,1,0.965,1s7,0,8,0S13,13,13,13V5L9.641,2zM12,13H4V3h5v3h3V13z" id="iconBg"/><path class="icon-vs-fg" d="M4 3h5v3h3v7h-8v-10z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 692 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{fill:#F0EFF1;} .icon-folder{fill:#656565;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 .969h-7.116l-1 2h-2.884c-.97 0-2 .701-2 2v2h-1v1.196l2.323 5.804h11.677s.86-.021 1.43-.565c.344-.332.57-.817.57-1.435v-9c0-1.303-1.005-2-2-2z" id="outline"/><path class="icon-folder" d="M14,2c0,0,1,0,1,1c0,2.36,0,8.205,0,9c0,1-1,0.984-1,0.984V3H8L7,5H3v3h8l3,5H3L1,8h1c0,0,0-2,0-3s1.236-1,1-1h3.5l1-2H14z" id="iconBg"/><path class="icon-vs-fg" d="M3 7.969v-3h4l1-2h6v10l-3-5h-8z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 745 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{opacity:0;fill:#F0EFF1;} .icon-folder{fill:#C5C5C5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 .969h-7.116l-1 2h-2.884c-.97 0-2 .701-2 2v2h-1v1.196l2.323 5.804h11.677s.86-.021 1.43-.565c.344-.332.57-.817.57-1.435v-9c0-1.303-1.005-2-2-2z" id="outline"/><path class="icon-folder" d="M14,2c0,0,1,0,1,1c0,2.36,0,8.205,0,9c0,1-1,0.984-1,0.984V3H8L7,5H3v3h8l3,5H3L1,8h1c0,0,0-2,0-3s1.236-1,1-1h3.5l1-2H14z" id="iconBg"/><path class="icon-vs-fg" d="M3 7.969v-3h4l1-2h6v10l-3-5h-8z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 755 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{fill:#F0EFF1;} .icon-folder{fill:#656565;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 750 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{opacity:0;fill:#F0EFF1;} .icon-folder{fill:#C5C5C5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 760 B

View file

@ -50,9 +50,12 @@
display: none;
}
.tree-explorer-viewlet-tree-view.file-icon-themable-tree.align-icons-and-twisties .monaco-tree-row:not(.has-children) .content:not(.align-with-twisty)::before,
.tree-explorer-viewlet-tree-view.file-icon-themable-tree.hide-arrows .monaco-tree-row .content::before {
display: inline-block;
.tree-explorer-viewlet-tree-view.file-icon-themable-tree.custom-view-align-icons-and-twisties .monaco-tree-row:not(.has-children) .content::before {
display: none;
}
.tree-explorer-viewlet-tree-view.file-icon-themable-tree.align-icons-and-twisties:not(.custom-view-align-icons-and-twisties) .monaco-tree-row:not(.has-children) .content::before {
display: block;
}
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item {
@ -82,6 +85,48 @@
-webkit-font-smoothing: antialiased;
}
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.placeholder-icon {
border: 1px;
border-style:dashed;
border-radius: 1px;
margin: 5px 6px 5px 2px;
height: 12px;
width: 6px;
}
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.file-icon {
content: ' ';
background-image: url('Document_16x.svg');
}
.hs-black .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.file-icon,
.vs-dark .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.file-icon {
content: ' ';
background-image: url('Document_16x_inverse.svg');
}
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon {
content: ' ';
background-image: url('Folder_16x.svg');
}
.hs-black .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon,
.vs-dark .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon {
content: ' ';
background-image: url('Folder_16x_inverse.svg');
}
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.expanded .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon {
content: ' ';
background-image: url('FolderOpen_16x.svg');
}
.hc-black .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.expanded .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon,
.vs-dark .tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.expanded .custom-view-tree-node-item > .custom-view-tree-node-item-icon.folder-icon {
content: ' ';
background-image: url('FolderOpen_16x_inverse.svg');
}
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .actions {
display: none;
padding-right: 6px;

View file

@ -12,6 +12,7 @@ import { ITreeViewDataProvider } from 'vs/workbench/common/views';
import { localize } from 'vs/nls';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
export class ViewLocation {
@ -69,6 +70,8 @@ export interface IViewsRegistry {
getViews(loc: ViewLocation): IViewDescriptor[];
getAllViews(): IViewDescriptor[];
getView(id: string): IViewDescriptor;
}
@ -128,6 +131,12 @@ export const ViewsRegistry: IViewsRegistry = new class implements IViewsRegistry
return this._views.get(loc) || [];
}
getAllViews(): IViewDescriptor[] {
const result: IViewDescriptor[] = [];
this._views.forEach(views => result.push(...views));
return result;
}
getView(id: string): IViewDescriptor {
for (const viewLocation of this._viewLocations) {
const viewDescriptor = (this._views.get(viewLocation) || []).filter(v => v.id === id)[0];
@ -147,17 +156,26 @@ export interface IViewsViewlet extends IViewlet {
// Custom views
export interface ITreeItemViewer {
export interface ITreeViewer extends IDisposable {
dataProvider: ITreeViewDataProvider;
readonly dataProvider: ITreeViewDataProvider;
refresh(treeItems: ITreeItem[]): TPromise<void>;
refresh(treeItems?: ITreeItem[]): TPromise<void>;
setVisibility(visible: boolean): void;
focus(): void;
layout(height: number): void;
render(container: HTMLElement);
getOptimalWidth(): number;
}
export interface ICustomViewDescriptor extends IViewDescriptor {
treeItemView?: boolean;
treeView?: boolean;
}
@ -166,7 +184,7 @@ export const ICustomViewsService = createDecorator<ICustomViewsService>('customV
export interface ICustomViewsService {
_serviceBrand: any;
getTreeItemViewer(id: string): ITreeItemViewer;
getTreeViewer(id: string): ITreeViewer;
registerTreeViewDataProvider(id: string, ITreeViewDataProvider): void;
}
@ -211,7 +229,5 @@ export interface ITreeViewDataProvider {
onDispose: Event<void>;
getElements(): TPromise<ITreeItem[]>;
getChildren(element: ITreeItem): TPromise<ITreeItem[]>;
getChildren(element?: ITreeItem): TPromise<ITreeItem[]>;
}

View file

@ -73,6 +73,8 @@ export class ProgressService2 implements IProgressService2 {
switch (location) {
case ProgressLocation.Window:
return this._withWindowProgress(options, task);
case ProgressLocation.Explorer:
return this._withViewletProgress('workbench.view.explorer', task);
case ProgressLocation.Scm:
return this._withViewletProgress('workbench.view.scm', task);
case ProgressLocation.Extensions:

View file

@ -72,7 +72,7 @@ suite('ExtHostTreeView', function () {
testObject.registerTreeDataProvider('testNodeTreeProvider', aNodeTreeDataProvider());
testObject.registerTreeDataProvider('testNodeWithIdTreeProvider', aNodeWithIdTreeDataProvider());
testObject.$getElements('testNodeTreeProvider').then(elements => {
testObject.$getChildren('testNodeTreeProvider').then(elements => {
for (const element of elements) {
testObject.$getChildren('testNodeTreeProvider', element.handle);
}
@ -80,7 +80,7 @@ suite('ExtHostTreeView', function () {
});
test('construct node tree', () => {
return testObject.$getElements('testNodeTreeProvider')
return testObject.$getChildren('testNodeTreeProvider')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['0/0:a', '0/0:b']);
@ -108,7 +108,7 @@ suite('ExtHostTreeView', function () {
});
test('construct id tree', () => {
return testObject.$getElements('testNodeWithIdTreeProvider')
return testObject.$getChildren('testNodeWithIdTreeProvider')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['1/a', '1/b']);
@ -139,7 +139,7 @@ suite('ExtHostTreeView', function () {
tree['a'] = {
'a': {}
};
return testObject.$getElements('testNodeWithIdTreeProvider')
return testObject.$getChildren('testNodeWithIdTreeProvider')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['1/a', '1/b']);
@ -300,7 +300,7 @@ suite('ExtHostTreeView', function () {
onDidChangeTreeNode.fire();
return testObject.$getElements('testNodeTreeProvider')
return testObject.$getChildren('testNodeTreeProvider')
.then(elements => {
assert.deepEqual(elements.map(e => e.handle), ['0/0:a//0:b']);
});
@ -338,7 +338,7 @@ suite('ExtHostTreeView', function () {
tree['f'] = {};
tree[dupItems['adup2']] = {};
return testObject.$getElements('testNodeTreeProvider')
return testObject.$getChildren('testNodeTreeProvider')
.then(elements => {
const actuals = elements.map(e => e.handle);
assert.deepEqual(actuals, ['0/0:a', '0/0:b', '0/1:a', '0/0:d', '0/1:b', '0/0:f', '0/2:a']);