mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-29 19:59:19 +01:00
Tree: Allow preserving preexisting element collapse state (#176507)
fixes #176505
This commit is contained in:
@@ -11,7 +11,7 @@ import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOption
|
||||
import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { IAsyncDataSource, ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeElement, ITreeEvent, ITreeFilter, ITreeMouseEvent, ITreeNode, ITreeRenderer, ITreeSorter, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IAsyncDataSource, ICollapseStateChangeEvent, IObjectTreeElement, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeMouseEvent, ITreeNode, ITreeRenderer, ITreeSorter, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { CancelablePromise, createCancelablePromise, Promises, timeout } from 'vs/base/common/async';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
@@ -988,7 +988,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
this._onDidRender.fire();
|
||||
}
|
||||
|
||||
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): IObjectTreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
if (node.stale) {
|
||||
return {
|
||||
element: node,
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { IIndexTreeModelSpliceOptions, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IObjectTreeModel, IObjectTreeModelOptions, IObjectTreeModelSetChildrenOptions, ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeModel, ITreeModelSpliceEvent, ITreeNode, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ICollapseStateChangeEvent, IObjectTreeElement, ITreeModel, ITreeModelSpliceEvent, ITreeNode, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
export interface ICompressedTreeElement<T> extends IObjectTreeElement<T> {
|
||||
readonly children?: Iterable<ICompressedTreeElement<T>>;
|
||||
readonly incompressible?: boolean;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export interface ICompressedTreeNode<T> {
|
||||
readonly incompressible: boolean;
|
||||
}
|
||||
|
||||
function noCompress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
|
||||
function noCompress<T>(element: ICompressedTreeElement<T>): ICompressedTreeElement<ICompressedTreeNode<T>> {
|
||||
const elements = [element.element];
|
||||
const incompressible = element.incompressible || false;
|
||||
|
||||
@@ -35,7 +35,7 @@ function noCompress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompre
|
||||
}
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
|
||||
export function compress<T>(element: ICompressedTreeElement<T>): ICompressedTreeElement<ICompressedTreeNode<T>> {
|
||||
const elements = [element.element];
|
||||
const incompressible = element.incompressible || false;
|
||||
|
||||
@@ -65,7 +65,7 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC
|
||||
};
|
||||
}
|
||||
|
||||
function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
|
||||
function _decompress<T>(element: ICompressedTreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
|
||||
let children: Iterable<ICompressedTreeElement<T>>;
|
||||
|
||||
if (index < element.element.elements.length - 1) {
|
||||
@@ -93,7 +93,7 @@ function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0
|
||||
}
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export function decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>): ICompressedTreeElement<T> {
|
||||
export function decompress<T>(element: ICompressedTreeElement<ICompressedTreeNode<T>>): ICompressedTreeElement<T> {
|
||||
return _decompress(element, 0);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
|
||||
private _setChildren(
|
||||
node: ICompressedTreeNode<T> | null,
|
||||
children: Iterable<ITreeElement<ICompressedTreeNode<T>>>,
|
||||
children: Iterable<IObjectTreeElement<ICompressedTreeNode<T>>>,
|
||||
options: IIndexTreeModelSpliceOptions<ICompressedTreeNode<T>, TFilterData>,
|
||||
): void {
|
||||
const insertedElements = new Set<T | null>();
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from '
|
||||
import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IObjectTreeModel, ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeModel, ITreeNode, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ICollapseStateChangeEvent, IObjectTreeElement, ITreeModel, ITreeNode, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
@@ -52,7 +52,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
super(user, container, delegate, renderers, options as IObjectTreeOptions<T | null, TFilterData>);
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions<T>): void {
|
||||
setChildren(element: T | null, children: Iterable<IObjectTreeElement<T>> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions<T>): void {
|
||||
this.model.setChildren(element, children, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { IIndexTreeModelOptions, IIndexTreeModelSpliceOptions, IList, IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeModel, ITreeModelSpliceEvent, ITreeNode, ITreeSorter, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ICollapseStateChangeEvent, IObjectTreeElement, ITreeElement, ITreeModel, ITreeModelSpliceEvent, ITreeNode, ITreeSorter, ObjectTreeElementCollapseState, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
|
||||
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
|
||||
|
||||
export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> {
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> | undefined, options?: IObjectTreeModelSetChildrenOptions<T, TFilterData>): void;
|
||||
setChildren(element: T | null, children: Iterable<IObjectTreeElement<T>> | undefined, options?: IObjectTreeModelSetChildrenOptions<T, TFilterData>): void;
|
||||
resort(element?: T | null, recursive?: boolean): void;
|
||||
updateElementHeight(element: T, height: number | undefined): void;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
children: Iterable<IObjectTreeElement<T>> = Iterable.empty(),
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData> = {},
|
||||
): void {
|
||||
const location = this.getElementLocation(element);
|
||||
@@ -127,7 +127,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
);
|
||||
}
|
||||
|
||||
private preserveCollapseState(elements: Iterable<ITreeElement<T>> = Iterable.empty()): Iterable<ITreeElement<T>> {
|
||||
private preserveCollapseState(elements: Iterable<IObjectTreeElement<T>> = Iterable.empty()): Iterable<ITreeElement<T>> {
|
||||
if (this.sorter) {
|
||||
elements = [...elements].sort(this.sorter.compare.bind(this.sorter));
|
||||
}
|
||||
@@ -141,14 +141,37 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
let collapsed: boolean | undefined;
|
||||
|
||||
if (typeof treeElement.collapsed === 'undefined') {
|
||||
collapsed = undefined;
|
||||
} else if (treeElement.collapsed === ObjectTreeElementCollapseState.Collapsed || treeElement.collapsed === ObjectTreeElementCollapseState.PreserveOrCollapsed) {
|
||||
collapsed = true;
|
||||
} else if (treeElement.collapsed === ObjectTreeElementCollapseState.Expanded || treeElement.collapsed === ObjectTreeElementCollapseState.PreserveOrExpanded) {
|
||||
collapsed = false;
|
||||
} else {
|
||||
collapsed = Boolean(treeElement.collapsed);
|
||||
}
|
||||
|
||||
return {
|
||||
...treeElement,
|
||||
children: this.preserveCollapseState(treeElement.children)
|
||||
children: this.preserveCollapseState(treeElement.children),
|
||||
collapsed
|
||||
};
|
||||
}
|
||||
|
||||
const collapsible = typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : node.collapsible;
|
||||
const collapsed = typeof treeElement.collapsed !== 'undefined' ? treeElement.collapsed : node.collapsed;
|
||||
let collapsed: boolean | undefined;
|
||||
|
||||
if (typeof treeElement.collapsed === 'undefined' || treeElement.collapsed === ObjectTreeElementCollapseState.PreserveOrCollapsed || treeElement.collapsed === ObjectTreeElementCollapseState.PreserveOrExpanded) {
|
||||
collapsed = node.collapsed;
|
||||
} else if (treeElement.collapsed === ObjectTreeElementCollapseState.Collapsed) {
|
||||
collapsed = true;
|
||||
} else if (treeElement.collapsed === ObjectTreeElementCollapseState.Expanded) {
|
||||
collapsed = false;
|
||||
} else {
|
||||
collapsed = Boolean(treeElement.collapsed);
|
||||
}
|
||||
|
||||
return {
|
||||
...treeElement,
|
||||
|
||||
@@ -78,6 +78,28 @@ export interface ITreeElement<T> {
|
||||
readonly collapsed?: boolean;
|
||||
}
|
||||
|
||||
export enum ObjectTreeElementCollapseState {
|
||||
Expanded,
|
||||
Collapsed,
|
||||
|
||||
/**
|
||||
* If the element is already in the tree, preserve its current state. Else, expand it.
|
||||
*/
|
||||
PreserveOrExpanded,
|
||||
|
||||
/**
|
||||
* If the element is already in the tree, preserve its current state. Else, collapse it.
|
||||
*/
|
||||
PreserveOrCollapsed,
|
||||
}
|
||||
|
||||
export interface IObjectTreeElement<T> {
|
||||
readonly element: T;
|
||||
readonly children?: Iterable<IObjectTreeElement<T>>;
|
||||
readonly collapsible?: boolean;
|
||||
readonly collapsed?: boolean | ObjectTreeElementCollapseState;
|
||||
}
|
||||
|
||||
export interface ITreeNode<T, TFilterData = void> {
|
||||
readonly element: T;
|
||||
readonly children: ITreeNode<T, TFilterData>[];
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { ITreeFilter, ITreeNode, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeFilter, ITreeNode, ObjectTreeElementCollapseState, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
function toList<T>(arr: T[]): IList<T> {
|
||||
@@ -171,6 +171,27 @@ suite('ObjectTreeModel', function () {
|
||||
assert.deepStrictEqual(toArray(list), ['father']);
|
||||
});
|
||||
|
||||
test('collapse state can be optionally preserved with strict identity', () => {
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>('test', toList(list), { collapseByDefault: true });
|
||||
const data = [{ element: 'father', collapsed: ObjectTreeElementCollapseState.PreserveOrExpanded, children: [{ element: 'child' }] }];
|
||||
|
||||
model.setChildren(null, data);
|
||||
assert.deepStrictEqual(toArray(list), ['father', 'child']);
|
||||
|
||||
model.setCollapsed('father', true);
|
||||
assert.deepStrictEqual(toArray(list), ['father']);
|
||||
|
||||
model.setChildren(null, data);
|
||||
assert.deepStrictEqual(toArray(list), ['father']);
|
||||
|
||||
model.setCollapsed('father', false);
|
||||
assert.deepStrictEqual(toArray(list), ['father', 'child']);
|
||||
|
||||
model.setChildren(null, data);
|
||||
assert.deepStrictEqual(toArray(list), ['father', 'child']);
|
||||
});
|
||||
|
||||
test('sorter', () => {
|
||||
const compare: (a: string, b: string) => number = (a, b) => a < b ? -1 : 1;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user