Skip to content

Commit 435ce2e

Browse files
committed
wip: CompressibleAsyncDataTree
1 parent 4cad4df commit 435ce2e

4 files changed

Lines changed: 138 additions & 20 deletions

File tree

src/vs/base/browser/ui/tree/asyncDataTree.ts

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
7-
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
7+
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
88
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
99
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError } from 'vs/base/browser/ui/tree/tree';
1010
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -18,6 +18,7 @@ import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors
1818
import { toggleClass } from 'vs/base/browser/dom';
1919
import { values } from 'vs/base/common/map';
2020
import { ScrollEvent } from 'vs/base/common/scrollable';
21+
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
2122

2223
interface IAsyncDataTreeNode<TInput, T> {
2324
element: TInput | T;
@@ -101,8 +102,8 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
101102
private disposables: IDisposable[] = [];
102103

103104
constructor(
104-
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
105-
private nodeMapper: NodeMapper<TInput, T, TFilterData>,
105+
protected renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
106+
protected nodeMapper: NodeMapper<TInput, T, TFilterData>,
106107
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
107108
) {
108109
this.templateId = renderer.templateId;
@@ -317,9 +318,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
317318
private readonly autoExpandSingleChildren: boolean;
318319

319320
private readonly _onDidRender = new Emitter<void>();
320-
private readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
321+
protected readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
321322

322-
private readonly nodeMapper = new NodeMapper<TInput, T, TFilterData>();
323+
protected readonly nodeMapper = new NodeMapper<TInput, T, TFilterData>();
323324

324325
protected readonly disposables: IDisposable[] = [];
325326

@@ -366,11 +367,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
366367
this.sorter = options.sorter;
367368
this.collapseByDefault = options.collapseByDefault;
368369

369-
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
370-
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event));
371-
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
372-
373-
this.tree = new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
370+
this.tree = this.createTree(user, container, delegate, renderers, options);
374371

375372
this.root = createAsyncDataTreeNode({
376373
element: undefined!,
@@ -390,6 +387,20 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
390387
this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
391388
}
392389

390+
protected createTree(
391+
user: string,
392+
container: HTMLElement,
393+
delegate: IListVirtualDelegate<T>,
394+
renderers: ITreeRenderer<T, TFilterData, any>[],
395+
options: IAsyncDataTreeOptions<T, TFilterData>
396+
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
397+
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
398+
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event));
399+
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
400+
401+
return new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
402+
}
403+
393404
updateOptions(options: IAsyncDataTreeOptionsUpdate = {}): void {
394405
this.tree.updateOptions(options);
395406
}
@@ -933,3 +944,40 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
933944
dispose(this.disposables);
934945
}
935946
}
947+
948+
class CompressibleDataTreeRenderer<TInput, T, TFilterData, TTemplateData>
949+
extends DataTreeRenderer<TInput, T, TFilterData, TTemplateData>
950+
implements ICompressibleTreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>>
951+
{
952+
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
953+
this.renderer.renderElement(this.nodeMapper.mapNode(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
954+
}
955+
}
956+
957+
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
958+
959+
constructor(
960+
user: string,
961+
container: HTMLElement,
962+
delegate: IListVirtualDelegate<T>,
963+
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
964+
dataSource: IAsyncDataSource<TInput, T>,
965+
options: IAsyncDataTreeOptions<T, TFilterData> = {}
966+
) {
967+
super(user, container, delegate, renderers, dataSource, options);
968+
}
969+
970+
protected createTree(
971+
user: string,
972+
container: HTMLElement,
973+
delegate: IListVirtualDelegate<T>,
974+
renderers: ITreeRenderer<T, TFilterData, any>[],
975+
options: IAsyncDataTreeOptions<T, TFilterData>
976+
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
977+
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
978+
const objectTreeRenderers = renderers.map(r => new CompressibleDataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event));
979+
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
980+
981+
return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
982+
}
983+
}

src/vs/platform/list/browser/listService.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import { Registry } from 'vs/platform/registry/common/platform';
2525
import { attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler';
2626
import { IThemeService } from 'vs/platform/theme/common/themeService';
2727
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
28-
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
28+
import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
2929
import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree';
30-
import { AsyncDataTree, IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
30+
import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
3131
import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree';
3232
import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree';
3333
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
@@ -681,7 +681,7 @@ export class TreeResourceNavigator2<T, TFilterData> extends Disposable {
681681
readonly onDidOpenResource: Event<IOpenEvent<T | null>> = this._onDidOpenResource.event;
682682

683683
constructor(
684-
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<any, T, TFilterData> | WorkbenchAsyncDataTree<any, T, TFilterData>,
684+
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<any, T, TFilterData> | WorkbenchAsyncDataTree<any, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<any, T, TFilterData>,
685685
options?: IResourceResultsNavigationOptions2
686686
) {
687687
super();
@@ -863,6 +863,34 @@ export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends Async
863863
}
864864
}
865865

866+
export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> extends CompressibleAsyncDataTree<TInput, T, TFilterData> {
867+
868+
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
869+
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
870+
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
871+
872+
constructor(
873+
user: string,
874+
container: HTMLElement,
875+
delegate: IListVirtualDelegate<T>,
876+
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
877+
dataSource: IAsyncDataSource<TInput, T>,
878+
options: IAsyncDataTreeOptions<T, TFilterData>,
879+
@IContextKeyService contextKeyService: IContextKeyService,
880+
@IListService listService: IListService,
881+
@IThemeService themeService: IThemeService,
882+
@IConfigurationService configurationService: IConfigurationService,
883+
@IKeybindingService keybindingService: IKeybindingService,
884+
@IAccessibilityService accessibilityService: IAccessibilityService
885+
) {
886+
const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService);
887+
super(user, container, delegate, renderers, dataSource, treeOptions);
888+
this.disposables.push(disposable);
889+
this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService);
890+
this.disposables.push(this.internals);
891+
}
892+
}
893+
866894
function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>>(
867895
container: HTMLElement,
868896
options: TOptions,
@@ -928,7 +956,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
928956
private disposables: IDisposable[] = [];
929957

930958
constructor(
931-
tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData>,
959+
tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData>,
932960
options: IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>,
933961
getAutomaticKeyboardNavigation: () => boolean | undefined,
934962
@IContextKeyService contextKeyService: IContextKeyService,

src/vs/workbench/contrib/files/browser/views/explorerView.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
2424
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
2525
import { ResourceContextKey } from 'vs/workbench/common/resources';
2626
import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations';
27-
import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
27+
import { WorkbenchCompressibleAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
2828
import { DelayedDragHandler } from 'vs/base/browser/dnd';
2929
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
3030
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
@@ -55,7 +55,7 @@ export class ExplorerView extends ViewletPanel {
5555
static readonly ID: string = 'workbench.explorer.fileView';
5656
static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState';
5757

58-
private tree!: WorkbenchAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>;
58+
private tree!: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>;
5959
private filter!: FilesFilter;
6060

6161
private resourceContext: ResourceContextKey;
@@ -274,7 +274,7 @@ export class ExplorerView extends ViewletPanel {
274274

275275
this._register(createFileIconThemableTreeContainerScope(container, this.themeService));
276276

277-
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), [filesRenderer],
277+
this.tree = this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), [filesRenderer],
278278
this.instantiationService.createInstance(ExplorerDataSource), {
279279
accessibilityProvider: new ExplorerAccessibilityProvider(),
280280
ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"),

src/vs/workbench/contrib/files/browser/views/explorerViewer.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
1515
import { IDisposable, Disposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
1616
import { KeyCode } from 'vs/base/common/keyCodes';
1717
import { IFileLabelOptions, IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
18-
import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
18+
import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
1919
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
2020
import { IThemeService } from 'vs/platform/theme/common/themeService';
2121
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
@@ -47,6 +47,8 @@ import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/work
4747
import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions';
4848
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
4949
import { Emitter } from 'vs/base/common/event';
50+
import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
51+
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
5052

5153
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
5254

@@ -116,7 +118,7 @@ export interface IFileTemplateData {
116118
container: HTMLElement;
117119
}
118120

119-
export class FilesRenderer implements ITreeRenderer<ExplorerItem, FuzzyScore, IFileTemplateData>, IDisposable {
121+
export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, FuzzyScore, IFileTemplateData>, IDisposable {
120122
static readonly ID = 'file';
121123

122124
private config: IFilesConfiguration;
@@ -267,7 +269,47 @@ export class FilesRenderer implements ITreeRenderer<ExplorerItem, FuzzyScore, IF
267269
return toDisposable(() => ignoreBlur = true);
268270
}
269271

270-
disposeElement?(element: ITreeNode<ExplorerItem, FuzzyScore>, index: number, templateData: IFileTemplateData): void {
272+
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ExplorerItem>, [number, number, number]>, index: number, templateData: IFileTemplateData, height: number | undefined): void {
273+
templateData.elementDisposable.dispose();
274+
const stat = node.element.elements[node.element.elements.length - 1];
275+
const editableData = this.explorerService.getEditableData(stat);
276+
277+
// File Label
278+
if (!editableData) {
279+
templateData.label.element.style.display = 'flex';
280+
const extraClasses = ['explorer-item'];
281+
if (this.explorerService.isCut(stat)) {
282+
extraClasses.push('cut');
283+
}
284+
templateData.label.setFile(stat.resource, {
285+
hidePath: true,
286+
fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE,
287+
extraClasses,
288+
fileDecorations: this.config.explorer.decorations,
289+
matches: createMatches(node.filterData)
290+
});
291+
292+
templateData.elementDisposable = templateData.label.onDidRender(() => {
293+
try {
294+
this.updateWidth(stat);
295+
} catch (e) {
296+
// noop since the element might no longer be in the tree, no update of width necessery
297+
}
298+
});
299+
}
300+
301+
// Input Box
302+
else {
303+
templateData.label.element.style.display = 'none';
304+
templateData.elementDisposable = this.renderInputBox(templateData.container, stat, editableData);
305+
}
306+
}
307+
308+
disposeElement?(_element: ITreeNode<ExplorerItem, FuzzyScore>, _index: number, templateData: IFileTemplateData): void {
309+
templateData.elementDisposable.dispose();
310+
}
311+
312+
disposeCompressedElements?(_node: ITreeNode<ICompressedTreeNode<ExplorerItem>, FuzzyScore>, _index: number, templateData: IFileTemplateData): void {
271313
templateData.elementDisposable.dispose();
272314
}
273315

0 commit comments

Comments
 (0)