chore(vscode): update to 1.53.2
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
This commit is contained in:
@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, IObjectTreeSetChildrenOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
@ -279,6 +279,7 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
|
||||
}
|
||||
|
||||
export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { }
|
||||
export interface IAsyncDataTreeUpdateChildrenOptions<T> extends IObjectTreeSetChildrenOptions<T> { }
|
||||
|
||||
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, Pick<IAbstractTreeOptions<T, TFilterData>, Exclude<keyof IAbstractTreeOptions<T, TFilterData>, 'collapseByDefault'>> {
|
||||
readonly collapseByDefault?: { (e: T): boolean; };
|
||||
@ -499,11 +500,11 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise<void> {
|
||||
await this._updateChildren(element, recursive, rerender);
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, options?: IAsyncDataTreeUpdateChildrenOptions<T>): Promise<void> {
|
||||
await this._updateChildren(element, recursive, rerender, undefined, options);
|
||||
}
|
||||
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>, options?: IAsyncDataTreeUpdateChildrenOptions<T>): Promise<void> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new TreeError(this.user, 'Tree input not set');
|
||||
}
|
||||
@ -514,7 +515,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
const node = this.getDataNode(element);
|
||||
await this.refreshAndRenderNode(node, recursive, viewStateContext);
|
||||
await this.refreshAndRenderNode(node, recursive, viewStateContext, options);
|
||||
|
||||
if (rerender) {
|
||||
try {
|
||||
@ -704,9 +705,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return node;
|
||||
}
|
||||
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>, options?: IAsyncDataTreeUpdateChildrenOptions<T>): Promise<void> {
|
||||
await this.refreshNode(node, recursive, viewStateContext);
|
||||
this.render(node, viewStateContext);
|
||||
this.render(node, viewStateContext, options);
|
||||
}
|
||||
|
||||
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
@ -768,8 +769,8 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
const children = await childrenPromise;
|
||||
return this.setChildren(node, children, recursive, viewStateContext);
|
||||
} catch (err) {
|
||||
if (node !== this.root) {
|
||||
this.tree.collapse(node === this.root ? null : node);
|
||||
if (node !== this.root && this.tree.hasElement(node)) {
|
||||
this.tree.collapse(node);
|
||||
}
|
||||
|
||||
if (isPromiseCanceledError(err)) {
|
||||
@ -921,9 +922,18 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return childrenToRefresh;
|
||||
}
|
||||
|
||||
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>, options?: IAsyncDataTreeUpdateChildrenOptions<T>): void {
|
||||
const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
|
||||
this.tree.setChildren(node === this.root ? null : node, children);
|
||||
const objectTreeOptions: IObjectTreeSetChildrenOptions<IAsyncDataTreeNode<TInput, T>> | undefined = options && {
|
||||
...options,
|
||||
diffIdentityProvider: options!.diffIdentityProvider && {
|
||||
getId(node: IAsyncDataTreeNode<TInput, T>): { toString(): string; } {
|
||||
return options!.diffIdentityProvider!.getId(node.element as T);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.tree.setChildren(node === this.root ? null : node, children, objectTreeOptions);
|
||||
|
||||
if (node !== this.root) {
|
||||
this.tree.setCollapsible(node, node.hasChildren);
|
||||
|
@ -6,8 +6,9 @@
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel, IObjectTreeModelSetChildrenOptions } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IIndexTreeModelSpliceOptions, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
@ -108,6 +109,12 @@ interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeM
|
||||
readonly compressionEnabled?: boolean;
|
||||
}
|
||||
|
||||
const wrapIdentityProvider = <T>(base: IIdentityProvider<T>): IIdentityProvider<ICompressedTreeNode<T>> => ({
|
||||
getId(node) {
|
||||
return node.elements.map(e => base.getId(e).toString()).join('\0');
|
||||
}
|
||||
});
|
||||
|
||||
// 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> {
|
||||
|
||||
@ -120,6 +127,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;
|
||||
private readonly identityProvider?: IIdentityProvider<ICompressedTreeNode<T>>;
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
@ -130,15 +138,21 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
) {
|
||||
this.model = new ObjectTreeModel(user, list, options);
|
||||
this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
|
||||
this.identityProvider = options.identityProvider;
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: Iterable<ICompressedTreeElement<T>> = Iterable.empty()
|
||||
children: Iterable<ICompressedTreeElement<T>> = Iterable.empty(),
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData>,
|
||||
): void {
|
||||
// Diffs must be deem, since the compression can affect nested elements.
|
||||
// @see https://github.com/microsoft/vscode/pull/114237#issuecomment-759425034
|
||||
|
||||
const diffIdentityProvider = options.diffIdentityProvider && wrapIdentityProvider(options.diffIdentityProvider);
|
||||
if (element === null) {
|
||||
const compressedChildren = Iterable.map(children, this.enabled ? compress : noCompress);
|
||||
this._setChildren(null, compressedChildren);
|
||||
this._setChildren(null, compressedChildren, { diffIdentityProvider, diffDepth: Infinity });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -159,7 +173,10 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
const parentChildren = parent.children
|
||||
.map(child => child === node ? recompressedElement : child);
|
||||
|
||||
this._setChildren(parent.element, parentChildren);
|
||||
this._setChildren(parent.element, parentChildren, {
|
||||
diffIdentityProvider,
|
||||
diffDepth: node.depth - parent.depth,
|
||||
});
|
||||
}
|
||||
|
||||
isCompressionEnabled(): boolean {
|
||||
@ -177,22 +194,29 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
const rootChildren = root.children as ITreeNode<ICompressedTreeNode<T>>[];
|
||||
const decompressedRootChildren = Iterable.map(rootChildren, decompress);
|
||||
const recompressedRootChildren = Iterable.map(decompressedRootChildren, enabled ? compress : noCompress);
|
||||
this._setChildren(null, recompressedRootChildren);
|
||||
|
||||
// it should be safe to always use deep diff mode here if an identity
|
||||
// provider is available, since we know the raw nodes are unchanged.
|
||||
this._setChildren(null, recompressedRootChildren, {
|
||||
diffIdentityProvider: this.identityProvider,
|
||||
diffDepth: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
private _setChildren(
|
||||
node: ICompressedTreeNode<T> | null,
|
||||
children: Iterable<ITreeElement<ICompressedTreeNode<T>>>
|
||||
children: Iterable<ITreeElement<ICompressedTreeNode<T>>>,
|
||||
options: IIndexTreeModelSpliceOptions<ICompressedTreeNode<T>, TFilterData>,
|
||||
): void {
|
||||
const insertedElements = new Set<T | null>();
|
||||
const _onDidCreateNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
|
||||
const onDidCreateNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
|
||||
for (const element of node.element.elements) {
|
||||
insertedElements.add(element);
|
||||
this.nodes.set(element, node.element);
|
||||
}
|
||||
};
|
||||
|
||||
const _onDidDeleteNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
|
||||
const onDidDeleteNode = (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => {
|
||||
for (const element of node.element.elements) {
|
||||
if (!insertedElements.has(element)) {
|
||||
this.nodes.delete(element);
|
||||
@ -200,7 +224,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
}
|
||||
};
|
||||
|
||||
this.model.setChildren(node, children, _onDidCreateNode, _onDidDeleteNode);
|
||||
this.model.setChildren(node, children, { ...options, onDidCreateNode, onDidDeleteNode });
|
||||
}
|
||||
|
||||
has(element: T | null): boolean {
|
||||
@ -363,16 +387,16 @@ function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilter
|
||||
function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwrapper<T>, options: ICompressibleObjectTreeModelOptions<T, TFilterData>): ICompressedObjectTreeModelOptions<T, TFilterData> {
|
||||
return {
|
||||
...options,
|
||||
sorter: options.sorter && {
|
||||
compare(node: ICompressedTreeNode<T>, otherNode: ICompressedTreeNode<T>): number {
|
||||
return options.sorter!.compare(node.elements[0], otherNode.elements[0]);
|
||||
}
|
||||
},
|
||||
identityProvider: options.identityProvider && {
|
||||
getId(node: ICompressedTreeNode<T>): { toString(): string; } {
|
||||
return options.identityProvider!.getId(compressedNodeUnwrapper(node));
|
||||
}
|
||||
},
|
||||
sorter: options.sorter && {
|
||||
compare(node: ICompressedTreeNode<T>, otherNode: ICompressedTreeNode<T>): number {
|
||||
return options.sorter!.compare(node.elements[0], otherNode.elements[0]);
|
||||
}
|
||||
},
|
||||
filter: options.filter && {
|
||||
filter(node: ICompressedTreeNode<T>, parentVisibility: TreeVisibility): TreeFilterResult<TFilterData> {
|
||||
return options.filter!.filter(compressedNodeUnwrapper(node), parentVisibility);
|
||||
@ -424,8 +448,12 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
|
||||
this.model = new CompressedObjectTreeModel(user, mapList(this.nodeMapper, list), mapOptions(compressedNodeUnwrapper, options));
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children: Iterable<ICompressedTreeElement<T>> = Iterable.empty()): void {
|
||||
this.model.setChildren(element, children);
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: Iterable<ICompressedTreeElement<T>> = Iterable.empty(),
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData> = {},
|
||||
): void {
|
||||
this.model.setChildren(element, children, options);
|
||||
}
|
||||
|
||||
isCompressionEnabled(): boolean {
|
||||
|
@ -47,13 +47,19 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
return this.input;
|
||||
}
|
||||
|
||||
setInput(input: TInput, viewState?: IDataTreeViewState): void {
|
||||
setInput(input: TInput | undefined, viewState?: IDataTreeViewState): void {
|
||||
if (viewState && !this.identityProvider) {
|
||||
throw new TreeError(this.user, 'Can\'t restore tree view state without an identity provider');
|
||||
}
|
||||
|
||||
this.input = input;
|
||||
|
||||
if (!input) {
|
||||
this.nodesByIdentity.clear();
|
||||
this.model.setChildren(null, Iterable.empty());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!viewState) {
|
||||
this._refresh(input);
|
||||
return;
|
||||
@ -155,7 +161,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
};
|
||||
}
|
||||
|
||||
this.model.setChildren((element === this.input ? null : element) as T, this.iterate(element, isCollapsed).elements, onDidCreateNode, onDidDeleteNode);
|
||||
this.model.setChildren((element === this.input ? null : element) as T, this.iterate(element, isCollapsed).elements, { onDidCreateNode, onDidDeleteNode });
|
||||
}
|
||||
|
||||
private iterate(element: TInput | T, isCollapsed?: (el: T) => boolean | undefined): { elements: Iterable<ITreeElement<T>>, size: number } {
|
||||
|
@ -3,8 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeNode, TreeVisibility, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { tail2 } from 'vs/base/common/arrays';
|
||||
import { LcsDiff } from 'vs/base/common/diff/diff';
|
||||
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
@ -41,6 +43,34 @@ export interface IIndexTreeModelOptions<T, TFilterData> {
|
||||
readonly autoExpandSingleChildren?: boolean;
|
||||
}
|
||||
|
||||
export interface IIndexTreeModelSpliceOptions<T, TFilterData> {
|
||||
/**
|
||||
* If set, child updates will recurse the given number of levels even if
|
||||
* items in the splice operation are unchanged. `Infinity` is a valid value.
|
||||
*/
|
||||
readonly diffDepth?: number;
|
||||
|
||||
/**
|
||||
* Identity provider used to optimize splice() calls in the IndexTree. If
|
||||
* this is not present, optimized splicing is not enabled.
|
||||
*
|
||||
* Warning: if this is present, calls to `setChildren()` will not replace
|
||||
* or update nodes if their identity is the same, even if the elements are
|
||||
* different. For this, you should call `rerender()`.
|
||||
*/
|
||||
readonly diffIdentityProvider?: IIdentityProvider<T>;
|
||||
|
||||
/**
|
||||
* Callback for when a node is created.
|
||||
*/
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void;
|
||||
|
||||
/**
|
||||
* Callback for when a node is deleted.
|
||||
*/
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
}
|
||||
|
||||
interface CollapsibleStateUpdate {
|
||||
readonly collapsible: boolean;
|
||||
}
|
||||
@ -110,18 +140,95 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
location: number[],
|
||||
deleteCount: number,
|
||||
toInsert: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
options: IIndexTreeModelSpliceOptions<T, TFilterData> = {},
|
||||
): void {
|
||||
if (location.length === 0) {
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
if (options.diffIdentityProvider) {
|
||||
this.spliceSmart(options.diffIdentityProvider, location, deleteCount, toInsert, options);
|
||||
} else {
|
||||
this.spliceSimple(location, deleteCount, toInsert, options);
|
||||
}
|
||||
}
|
||||
|
||||
private spliceSmart(
|
||||
identity: IIdentityProvider<T>,
|
||||
location: number[],
|
||||
deleteCount: number,
|
||||
toInsertIterable: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
options: IIndexTreeModelSpliceOptions<T, TFilterData>,
|
||||
recurseLevels = options.diffDepth ?? 0,
|
||||
) {
|
||||
const { parentNode } = this.getParentNodeWithListIndex(location);
|
||||
const toInsert = [...toInsertIterable];
|
||||
const index = location[location.length - 1];
|
||||
const diff = new LcsDiff(
|
||||
{ getElements: () => parentNode.children.map(e => identity.getId(e.element).toString()) },
|
||||
{
|
||||
getElements: () => [
|
||||
...parentNode.children.slice(0, index),
|
||||
...toInsert,
|
||||
...parentNode.children.slice(index + deleteCount),
|
||||
].map(e => identity.getId(e.element).toString())
|
||||
},
|
||||
).ComputeDiff(false);
|
||||
|
||||
// if we were given a 'best effort' diff, use default behavior
|
||||
if (diff.quitEarly) {
|
||||
return this.spliceSimple(location, deleteCount, toInsert, options);
|
||||
}
|
||||
|
||||
const locationPrefix = location.slice(0, -1);
|
||||
const recurseSplice = (fromOriginal: number, fromModified: number, count: number) => {
|
||||
if (recurseLevels > 0) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
fromOriginal--;
|
||||
fromModified--;
|
||||
this.spliceSmart(
|
||||
identity,
|
||||
[...locationPrefix, fromOriginal, 0],
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
toInsert[fromModified].children,
|
||||
options,
|
||||
recurseLevels - 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let lastStartO = Math.min(parentNode.children.length, index + deleteCount);
|
||||
let lastStartM = toInsert.length;
|
||||
for (const change of diff.changes.sort((a, b) => b.originalStart - a.originalStart)) {
|
||||
recurseSplice(lastStartO, lastStartM, lastStartO - (change.originalStart + change.originalLength));
|
||||
lastStartO = change.originalStart;
|
||||
lastStartM = change.modifiedStart - index;
|
||||
|
||||
this.spliceSimple(
|
||||
[...locationPrefix, lastStartO],
|
||||
change.originalLength,
|
||||
Iterable.slice(toInsert, lastStartM, lastStartM + change.modifiedLength),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
// at this point, startO === startM === count since any remaining prefix should match
|
||||
recurseSplice(lastStartO, lastStartM, lastStartO);
|
||||
}
|
||||
|
||||
private spliceSimple(
|
||||
location: number[],
|
||||
deleteCount: number,
|
||||
toInsert: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
{ onDidCreateNode, onDidDeleteNode }: IIndexTreeModelSpliceOptions<T, TFilterData>,
|
||||
) {
|
||||
const { parentNode, listIndex, revealed, visible } = this.getParentNodeWithListIndex(location);
|
||||
const treeListElementsToInsert: ITreeNode<T, TFilterData>[] = [];
|
||||
const nodesToInsertIterator = Iterable.map(toInsert, el => this.createTreeNode(el, parentNode, parentNode.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, revealed, treeListElementsToInsert, onDidCreateNode));
|
||||
|
||||
const lastIndex = location[location.length - 1];
|
||||
const lastHadChildren = parentNode.children.length > 0;
|
||||
|
||||
// figure out what's the visible child start index right before the
|
||||
// splice point
|
||||
@ -190,6 +297,11 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
deletedNodes.forEach(visit);
|
||||
}
|
||||
|
||||
const currentlyHasChildren = parentNode.children.length > 0;
|
||||
if (lastHadChildren !== currentlyHasChildren) {
|
||||
this.setCollapsible(location.slice(0, -1), currentlyHasChildren);
|
||||
}
|
||||
|
||||
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
|
||||
|
||||
let node: IIndexTreeNode<T, TFilterData> | undefined = parentNode;
|
||||
|
@ -7,7 +7,7 @@ import { Iterable } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { IListVirtualDelegate, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
@ -17,6 +17,25 @@ export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTree
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export interface IObjectTreeSetChildrenOptions<T> {
|
||||
|
||||
/**
|
||||
* If set, child updates will recurse the given number of levels even if
|
||||
* items in the splice operation are unchanged. `Infinity` is a valid value.
|
||||
*/
|
||||
readonly diffDepth?: number;
|
||||
|
||||
/**
|
||||
* Identity provider used to optimize splice() calls in the IndexTree. If
|
||||
* this is not present, optimized splicing is not enabled.
|
||||
*
|
||||
* Warning: if this is present, calls to `setChildren()` will not replace
|
||||
* or update nodes if their identity is the same, even if the elements are
|
||||
* different. For this, you should call `rerender()`.
|
||||
*/
|
||||
readonly diffIdentityProvider?: IIdentityProvider<T>;
|
||||
}
|
||||
|
||||
export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
|
||||
|
||||
protected model!: IObjectTreeModel<T, TFilterData>;
|
||||
@ -33,8 +52,8 @@ 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()): void {
|
||||
this.model.setChildren(element, children);
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions<T>): void {
|
||||
this.model.setChildren(element, children, options);
|
||||
}
|
||||
|
||||
rerender(element?: T): void {
|
||||
@ -189,8 +208,8 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
||||
super(user, container, delegate, compressibleRenderers, asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider, options));
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children: Iterable<ICompressedTreeElement<T>> = Iterable.empty()): void {
|
||||
this.model.setChildren(element, children);
|
||||
setChildren(element: T | null, children: Iterable<ICompressedTreeElement<T>> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions<T>): void {
|
||||
this.model.setChildren(element, children, options);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
|
@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions, IList, IIndexTreeModelSpliceOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
@ -13,11 +13,14 @@ import { mergeSort } from 'vs/base/common/arrays';
|
||||
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): void;
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> | undefined, options?: IObjectTreeModelSetChildrenOptions<T, TFilterData>): void;
|
||||
resort(element?: T | null, recursive?: boolean): void;
|
||||
updateElementHeight(element: T, height: number): void;
|
||||
}
|
||||
|
||||
export interface IObjectTreeModelSetChildrenOptions<T, TFilterData> extends IIndexTreeModelSpliceOptions<T, TFilterData> {
|
||||
}
|
||||
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
readonly identityProvider?: IIdentityProvider<T>;
|
||||
@ -63,23 +66,21 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
|
||||
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData> = {},
|
||||
): void {
|
||||
const location = this.getElementLocation(element);
|
||||
this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
|
||||
this._setChildren(location, this.preserveCollapseState(children), options);
|
||||
}
|
||||
|
||||
private _setChildren(
|
||||
location: number[],
|
||||
children: Iterable<ITreeElement<T>> = Iterable.empty(),
|
||||
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
|
||||
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
|
||||
options: IObjectTreeModelSetChildrenOptions<T, TFilterData>,
|
||||
): void {
|
||||
const insertedElements = new Set<T | null>();
|
||||
const insertedElementIds = new Set<string>();
|
||||
|
||||
const _onDidCreateNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
const onDidCreateNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
if (node.element === null) {
|
||||
return;
|
||||
}
|
||||
@ -95,12 +96,10 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
this.nodesByIdentity.set(id, tnode);
|
||||
}
|
||||
|
||||
if (onDidCreateNode) {
|
||||
onDidCreateNode(tnode);
|
||||
}
|
||||
options.onDidCreateNode?.(tnode);
|
||||
};
|
||||
|
||||
const _onDidDeleteNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
const onDidDeleteNode = (node: ITreeNode<T | null, TFilterData>) => {
|
||||
if (node.element === null) {
|
||||
return;
|
||||
}
|
||||
@ -118,17 +117,14 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
}
|
||||
}
|
||||
|
||||
if (onDidDeleteNode) {
|
||||
onDidDeleteNode(tnode);
|
||||
}
|
||||
options.onDidDeleteNode?.(tnode);
|
||||
};
|
||||
|
||||
this.model.splice(
|
||||
[...location, 0],
|
||||
Number.MAX_VALUE,
|
||||
children,
|
||||
_onDidCreateNode,
|
||||
_onDidDeleteNode
|
||||
{ ...options, onDidCreateNode, onDidDeleteNode }
|
||||
);
|
||||
}
|
||||
|
||||
@ -182,7 +178,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
const location = this.getElementLocation(element);
|
||||
const node = this.model.getNode(location);
|
||||
|
||||
this._setChildren(location, this.resortChildren(node, recursive));
|
||||
this._setChildren(location, this.resortChildren(node, recursive), {});
|
||||
}
|
||||
|
||||
private resortChildren(node: ITreeNode<T | null, TFilterData>, recursive: boolean, first = true): Iterable<ITreeElement<T>> {
|
||||
|
Reference in New Issue
Block a user