未验证 提交 388ea95a 编写于 作者: J Joao Moreno

Merge branch 'joao/explorer-compressed-tree'

......@@ -931,7 +931,6 @@ export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTr
readonly collapseByDefault?: boolean; // defaults to false
readonly filter?: ITreeFilter<T, TFilterData>;
readonly dnd?: ITreeDragAndDrop<T>;
readonly autoExpandSingleChildren?: boolean;
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean);
readonly additionalScrollHeight?: number;
......
......@@ -149,16 +149,6 @@ function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTr
};
}
export enum ChildrenResolutionReason {
Refresh,
Expand
}
export interface IChildrenResolutionEvent<T> {
readonly element: T | null;
readonly reason: ChildrenResolutionReason;
}
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
if (data instanceof ElementsDragAndDropData) {
const nodes = (data as ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>>).elements;
......@@ -273,7 +263,7 @@ function dfs<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, fn: (node: IAsyncDa
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable {
private readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
protected readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
private readonly root: IAsyncDataTreeNode<TInput, T>;
private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
private readonly sorter?: ITreeSorter<T>;
......@@ -282,7 +272,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<T[]>>();
private readonly identityProvider?: IIdentityProvider<T>;
protected readonly identityProvider?: IIdentityProvider<T>;
private readonly autoExpandSingleChildren: boolean;
private readonly _onDidRender = new Emitter<void>();
......@@ -323,7 +313,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
get onDidDispose(): Event<void> { return this.tree.onDidDispose; }
constructor(
private user: string,
protected user: string,
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<T, TFilterData, any>[],
......@@ -471,7 +461,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
await Event.toPromise(this._onDidRender.event);
}
await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext);
await this.refreshAndRenderNode(this.getDataNode(element), recursive, viewStateContext);
}
resort(element: TInput | T = this.root.element, recursive = true): void {
......@@ -653,18 +643,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return node;
}
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
await this.refreshNode(node, recursive, viewStateContext);
this.render(node, viewStateContext);
if (node !== this.root && this.autoExpandSingleChildren && reason === ChildrenResolutionReason.Expand) {
const treeNode = this.tree.getNode(node);
const visibleChildren = treeNode.children.filter(node => node.visible);
if (visibleChildren.length === 1) {
await this.tree.expand(visibleChildren[0].element, false);
}
}
}
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
......@@ -770,7 +751,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
if (deep) {
this.collapse(node.element.element as T);
} else {
this.refreshAndRenderNode(node.element, false, ChildrenResolutionReason.Expand)
this.refreshAndRenderNode(node.element, false)
.catch(onUnexpectedError);
}
}
......@@ -783,13 +764,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
}
const nodesToForget = new Map<T, IAsyncDataTreeNode<TInput, T>>();
const childrenTreeNodesById = new Map<string, ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>>();
const childrenTreeNodesById = new Map<string, { node: IAsyncDataTreeNode<TInput, T>, collapsed: boolean }>();
for (const child of node.children) {
nodesToForget.set(child.element as T, child);
if (this.identityProvider) {
childrenTreeNodesById.set(child.id!, this.tree.getNode(child));
const collapsed = this.tree.isCollapsed(child);
childrenTreeNodesById.set(child.id!, { node: child, collapsed });
}
}
......@@ -810,10 +792,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
}
const id = this.identityProvider.getId(element).toString();
const childNode = childrenTreeNodesById.get(id);
const result = childrenTreeNodesById.get(id);
if (childNode) {
const asyncDataTreeNode = childNode.element!;
if (result) {
const asyncDataTreeNode = result.node;
nodesToForget.delete(asyncDataTreeNode.element as T);
this.nodes.delete(asyncDataTreeNode.element as T);
......@@ -823,8 +805,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
asyncDataTreeNode.hasChildren = hasChildren;
if (recursive) {
if (childNode.collapsed) {
dfs(asyncDataTreeNode, node => node.stale = true);
if (result.collapsed) {
asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element as T)));
asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length);
asyncDataTreeNode.stale = true;
} else {
childrenToRefresh.push(asyncDataTreeNode);
}
......@@ -866,6 +850,12 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
node.children.splice(0, node.children.length, ...children);
// TODO@joao this doesn't take filter into account
if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) {
children[0].collapsedByDefault = false;
childrenToRefresh.push(children[0]);
}
return childrenToRefresh;
}
......@@ -881,6 +871,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
}
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
if (node.stale) {
return {
element: node,
collapsible: node.hasChildren,
collapsed: true
};
}
let collapsed: boolean | undefined;
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
......@@ -1023,11 +1021,17 @@ function asCompressibleObjectTreeOptions<TInput, T, TFilterData>(options?: IComp
}
export interface ICompressibleAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptions<T, TFilterData> {
readonly compressionEnabled?: boolean;
readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
}
export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate {
readonly compressionEnabled?: boolean;
}
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
protected readonly tree: CompressibleObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
constructor(
......@@ -1037,7 +1041,7 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
private compressionDelegate: ITreeCompressionDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
dataSource: IAsyncDataSource<TInput, T>,
options: IAsyncDataTreeOptions<T, TFilterData> = {}
options: ICompressibleAsyncDataTreeOptions<T, TFilterData> = {}
) {
super(user, container, virtualDelegate, renderers, dataSource, options);
}
......@@ -1062,4 +1066,36 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
...super.asTreeElement(node, viewStateContext)
};
}
updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate = {}): void {
this.tree.updateOptions(options);
}
getViewState(): IAsyncDataTreeViewState {
if (!this.identityProvider) {
throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
}
const getId = (element: T) => this.identityProvider!.getId(element).toString();
const focus = this.getFocus().map(getId);
const selection = this.getSelection().map(getId);
const expanded: string[] = [];
const root = this.tree.getCompressedTreeNode();
const queue = [root];
while (queue.length > 0) {
const node = queue.shift()!;
if (node !== root && node.collapsible && !node.collapsed) {
for (const asyncNode of node.element!.elements) {
expanded.push(getId(asyncNode.element as T));
}
}
queue.push(...node.children);
}
return { focus, selection, expanded, scrollTop: this.scrollTop };
}
}
......@@ -100,16 +100,15 @@ export function decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>): IC
function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
if (treeElement.element === element) {
return { element, children };
return { ...treeElement, children };
}
return {
...treeElement,
children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children))
};
return { ...treeElement, children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children)) };
}
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> {
readonly compressionEnabled?: boolean;
}
// Exported only for test reasons, do not use directly
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
......@@ -122,7 +121,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
private model: ObjectTreeModel<ICompressedTreeNode<T>, TFilterData>;
private nodes = new Map<T | null, ICompressedTreeNode<T>>();
private enabled: boolean = true;
private enabled: boolean;
get size(): number { return this.nodes.size; }
......@@ -132,6 +131,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
) {
this.model = new ObjectTreeModel(user, list, options);
this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
}
setChildren(
......@@ -368,6 +368,7 @@ function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwra
}
export interface ICompressibleObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<T, TFilterData> {
readonly compressionEnabled?: boolean;
readonly elementMapper?: ElementMapper<T>;
}
......@@ -493,7 +494,7 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
return this.model.resort(element, recursive);
}
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
return this.model.getNode(element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
getCompressedTreeNode(location: T | null = null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData> {
return this.model.getNode(location);
}
}
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { ISequence } from 'vs/base/common/iterator';
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
......@@ -56,7 +56,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
}
interface ICompressedTreeNodeProvider<T, TFilterData> {
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData>;
getCompressedTreeNode(location: T | null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData>;
}
export interface ICompressibleTreeRenderer<T, TFilterData = void, TTemplateData = void> extends ITreeRenderer<T, TFilterData, TTemplateData> {
......@@ -69,7 +69,7 @@ interface CompressibleTemplateData<T, TFilterData, TTemplateData> {
readonly data: TTemplateData;
}
class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
class CompressibleRenderer<T extends NonNullable<any>, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
readonly templateId: string;
readonly onDidChangeTwistieState: Event<T> | undefined;
......@@ -93,7 +93,7 @@ class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRender
}
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element);
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
if (compressedTreeNode.element.elements.length === 1) {
templateData.compressedTreeNode = undefined;
......@@ -132,6 +132,7 @@ export interface ICompressibleKeyboardNavigationLabelProvider<T> extends IKeyboa
}
export interface ICompressibleObjectTreeOptions<T, TFilterData = void> extends IObjectTreeOptions<T, TFilterData> {
readonly compressionEnabled?: boolean;
readonly elementMapper?: ElementMapper<T>;
readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
}
......@@ -144,7 +145,7 @@ function asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider: () => I
let compressedTreeNode: ITreeNode<ICompressedTreeNode<T>, TFilterData>;
try {
compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e);
compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
} catch {
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e);
}
......@@ -159,6 +160,10 @@ function asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider: () => I
};
}
export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptionsUpdate {
readonly compressionEnabled?: boolean;
}
export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> implements ICompressedTreeNodeProvider<T, TFilterData> {
protected model!: CompressibleObjectTreeModel<T, TFilterData>;
......@@ -171,7 +176,7 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
options: ICompressibleObjectTreeOptions<T, TFilterData> = {}
) {
const compressedTreeNodeProvider = () => this;
const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r));
const compressibleRenderers = renderers.map(r => new CompressibleRenderer<T, TFilterData, any>(compressedTreeNodeProvider, r));
super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options));
}
......@@ -183,15 +188,15 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
return new CompressibleObjectTreeModel(user, view, options);
}
isCompressionEnabled(): boolean {
return this.model.isCompressionEnabled();
}
updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void {
super.updateOptions(optionsUpdate);
setCompressionEnabled(enabled: boolean): void {
this.model.setCompressionEnabled(enabled);
if (typeof optionsUpdate.compressionEnabled !== 'undefined') {
this.model.setCompressionEnabled(optionsUpdate.compressionEnabled);
}
}
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
return this.model.getCompressedTreeNode(element)!;
getCompressedTreeNode(element: T | null = null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData> {
return this.model.getCompressedTreeNode(element);
}
}
......@@ -348,8 +348,6 @@ suite('CompressibleObjectTree', function () {
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
assert.equal(tree.isCompressionEnabled(), true);
tree.setChildren(null, Iterator.fromArray([
{
element: 1, children: Iterator.fromArray([{
......@@ -367,11 +365,11 @@ suite('CompressibleObjectTree', function () {
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
tree.setCompressionEnabled(false);
tree.updateOptions({ compressionEnabled: false });
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1', '11', '111', '1111', '1112', '1113']);
tree.setCompressionEnabled(true);
tree.updateOptions({ compressionEnabled: true });
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
});
......
......@@ -27,7 +27,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree';
import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree';
import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
......@@ -900,7 +900,7 @@ export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> e
compressionDelegate: ITreeCompressionDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
dataSource: IAsyncDataSource<TInput, T>,
options: IAsyncDataTreeOptions<T, TFilterData>,
options: ICompressibleAsyncDataTreeOptions<T, TFilterData>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
......
......@@ -423,7 +423,12 @@ configurationRegistry.registerConfiguration({
],
description: nls.localize('explorer.incrementalNaming', "Controls what naming strategy to use when a giving a new name to a duplicated explorer item on paste."),
default: 'simple'
}
},
'explorer.compressSingleChildFolders': {
'type': 'boolean',
'description': nls.localize('compressSingleChildFolders', "Controls whether the explorer should compress single child folders in a combined tree element. Useful for Java project folder structures, for example."),
'default': false
},
}
});
......
......@@ -24,12 +24,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations';
import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
import { TreeResourceNavigator2, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService';
import { DelayedDragHandler } from 'vs/base/browser/dnd';
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
import { ILabelService } from 'vs/platform/label/common/label';
import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, FilesFilter, FileSorter, FileDragAndDrop } from 'vs/workbench/contrib/files/browser/views/explorerViewer';
import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate } from 'vs/workbench/contrib/files/browser/views/explorerViewer';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
......@@ -50,12 +50,13 @@ import { first } from 'vs/base/common/arrays';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { dispose } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
export class ExplorerView extends ViewletPanel {
static readonly ID: string = 'workbench.explorer.fileView';
static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState';
private tree!: WorkbenchAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>;
private tree!: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>;
private filter!: FilesFilter;
private resourceContext: ResourceContextKey;
......@@ -282,8 +283,11 @@ export class ExplorerView extends ViewletPanel {
this._register(createFileIconThemableTreeContainerScope(container, this.themeService));
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), [filesRenderer],
const isCompressionEnabled = () => this.configurationService.getValue<boolean>('explorer.compressSingleChildFolders');
this.tree = this.instantiationService.createInstance<typeof WorkbenchCompressibleAsyncDataTree, WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>>(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [filesRenderer],
this.instantiationService.createInstance(ExplorerDataSource), {
compressionEnabled: isCompressionEnabled(),
accessibilityProvider: new ExplorerAccessibilityProvider(),
ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"),
identityProvider: {
......@@ -302,6 +306,13 @@ export class ExplorerView extends ViewletPanel {
}
return stat.name;
},
getCompressedNodeKeyboardNavigationLabel: (stats: ExplorerItem[]) => {
if (stats.some(stat => this.explorerService.isEditable(stat))) {
return undefined;
}
return stats.map(stat => stat.name).join('/');
}
},
multipleSelectionSupport: true,
......@@ -313,6 +324,10 @@ export class ExplorerView extends ViewletPanel {
});
this._register(this.tree);
// Bind configuration
const onDidChangeCompressionConfiguration = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('explorer.compressSingleChildFolders'));
this._register(onDidChangeCompressionConfiguration(_ => this.tree.updateOptions({ compressionEnabled: isCompressionEnabled() })));
// Bind context keys
FilesExplorerFocusedContext.bindTo(this.tree.contextKeyService);
ExplorerFocusedContext.bindTo(this.tree.contextKeyService);
......
......@@ -15,7 +15,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { IDisposable, Disposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IFileLabelOptions, IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
......@@ -28,7 +28,7 @@ import { once } from 'vs/base/common/functional';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { equals, deepClone } from 'vs/base/common/objects';
import * as path from 'vs/base/common/path';
import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { compareFileExtensions, compareFileNames } from 'vs/base/common/comparers';
import { fillResourceDataTransfers, CodeDataTransfers, extractResources, containsDragType } from 'vs/workbench/browser/dnd';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -47,6 +47,9 @@ import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/work
import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { Emitter } from 'vs/base/common/event';
import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { VSBuffer } from 'vs/base/common/buffer';
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
......@@ -117,7 +120,7 @@ export interface IFileTemplateData {
container: HTMLElement;
}
export class FilesRenderer implements ITreeRenderer<ExplorerItem, FuzzyScore, IFileTemplateData>, IDisposable {
export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, FuzzyScore, IFileTemplateData>, IDisposable {
static readonly ID = 'file';
private config: IFilesConfiguration;
......@@ -158,25 +161,7 @@ export class FilesRenderer implements ITreeRenderer<ExplorerItem, FuzzyScore, IF
// File Label
if (!editableData) {
templateData.label.element.style.display = 'flex';
const extraClasses = ['explorer-item'];
if (this.explorerService.isCut(stat)) {
extraClasses.push('cut');
}
templateData.label.setFile(stat.resource, {
hidePath: true,
fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE,
extraClasses,
fileDecorations: this.config.explorer.decorations,
matches: createMatches(node.filterData)
});
templateData.elementDisposable = templateData.label.onDidRender(() => {
try {
this.updateWidth(stat);
} catch (e) {
// noop since the element might no longer be in the tree, no update of width necessery
}
});
templateData.elementDisposable = this.renderStat(stat, stat.name, node.filterData, templateData);
}
// Input Box
......@@ -186,6 +171,38 @@ export class FilesRenderer implements ITreeRenderer<ExplorerItem, FuzzyScore, IF
}
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ExplorerItem>, FuzzyScore>, index: number, templateData: IFileTemplateData, height: number | undefined): void {
templateData.elementDisposable.dispose();
const stat = node.element.elements[node.element.elements.length - 1];
const label = node.element.elements.map(e => e.name).join('/');
templateData.elementDisposable = this.renderStat(stat, label, node.filterData, templateData);
}
private renderStat(stat: ExplorerItem, label: string, filterData: FuzzyScore | undefined, templateData: IFileTemplateData): IDisposable {
templateData.label.element.style.display = 'flex';
const extraClasses = ['explorer-item'];
if (this.explorerService.isCut(stat)) {
extraClasses.push('cut');
}
templateData.label.setResource({ resource: stat.resource, name: label }, {
fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE,
extraClasses,
fileDecorations: this.config.explorer.decorations,
matches: createMatches(filterData)
});
return templateData.label.onDidRender(() => {
try {
this.updateWidth(stat);
} catch (e) {
// noop since the element might no longer be in the tree, no update of width necessery
}
});
}
private renderInputBox(container: HTMLElement, stat: ExplorerItem, editableData: IEditableData): IDisposable {
// Use a file label only for the icon next to the input box
......@@ -839,3 +856,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}
}
}
export class ExplorerCompressionDelegate implements ITreeCompressionDelegate<ExplorerItem> {
isIncompressible(stat: ExplorerItem): boolean {
return stat.isRoot || !stat.isDirectory || stat instanceof NewExplorerItem;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册