mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
#28974 Introduce place holder icons for nodes without icons
This commit is contained in:
parent
125ac04d1f
commit
ea787762db
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
144
src/vs/workbench/browser/parts/views/customViewPanel.ts
Normal file
144
src/vs/workbench/browser/parts/views/customViewPanel.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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 |
1
src/vs/workbench/browser/parts/views/media/Document_16x_inverse.svg
Executable file
1
src/vs/workbench/browser/parts/views/media/Document_16x_inverse.svg
Executable 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 |
|
@ -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 |
1
src/vs/workbench/browser/parts/views/media/FolderOpen_16x_inverse.svg
Executable file
1
src/vs/workbench/browser/parts/views/media/FolderOpen_16x_inverse.svg
Executable 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 |
|
@ -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 |
1
src/vs/workbench/browser/parts/views/media/Folder_16x_inverse.svg
Executable file
1
src/vs/workbench/browser/parts/views/media/Folder_16x_inverse.svg
Executable 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 |
|
@ -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;
|
||||
|
|
|
@ -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[]>;
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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']);
|
||||
|
|
Loading…
Reference in a new issue