Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c (#8525)

* Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c

* remove files we don't want

* fix hygiene

* update distro

* update distro

* fix hygiene

* fix strict nulls

* distro

* distro

* fix tests

* fix tests

* add another edit

* fix viewlet icon

* fix azure dialog

* fix some padding

* fix more padding issues
This commit is contained in:
Anthony Dresser
2019-12-04 19:28:22 -08:00
committed by GitHub
parent a8818ab0df
commit f5ce7fb2a5
1507 changed files with 42813 additions and 27370 deletions

View File

@@ -7,7 +7,7 @@ import 'vs/css!./media/tree';
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom';
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
@@ -27,10 +27,24 @@ import { clamp } from 'vs/base/common/numbers';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { SetMap } from 'vs/base/common/collections';
class TreeElementsDragAndDropData<T, TFilterData, TContext> extends ElementsDragAndDropData<T, TContext> {
set context(context: TContext | undefined) {
this.data.context = context;
}
get context(): TContext | undefined {
return this.data.context;
}
constructor(private data: ElementsDragAndDropData<ITreeNode<T, TFilterData>, TContext>) {
super(data.elements.map(node => node.element));
}
}
function asTreeDragAndDropData<T, TFilterData>(data: IDragAndDropData): IDragAndDropData {
if (data instanceof ElementsDragAndDropData) {
const nodes = (data as ElementsDragAndDropData<ITreeNode<T, TFilterData>>).elements;
return new ElementsDragAndDropData(nodes.map(node => node.element));
return new TreeElementsDragAndDropData(data);
}
return data;
@@ -47,9 +61,9 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
return this.dnd.getDragURI(node.element);
}
getDragLabel(nodes: ITreeNode<T, TFilterData>[]): string | undefined {
getDragLabel(nodes: ITreeNode<T, TFilterData>[], originalEvent: DragEvent): string | undefined {
if (this.dnd.getDragLabel) {
return this.dnd.getDragLabel(nodes.map(node => node.element));
return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent);
}
return undefined;
@@ -87,7 +101,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
}, 500);
}
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined' || result.feedback) {
if (!raw) {
const accept = typeof result === 'boolean' ? result : result.accept;
const effect = typeof result === 'boolean' ? undefined : result.effect;
@@ -121,6 +135,12 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
}
}
function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T, TFilterData, TRef>, options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
@@ -141,12 +161,16 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
}
},
accessibilityProvider: options.accessibilityProvider && {
...options.accessibilityProvider,
getAriaLabel(e) {
return options.accessibilityProvider!.getAriaLabel(e.element);
},
getAriaLevel(node) {
return node.depth;
}
},
getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
return options.accessibilityProvider!.getActiveDescendantId!(node.element);
})
},
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
...options.keyboardNavigationLabelProvider,
@@ -211,6 +235,8 @@ export enum RenderIndentGuides {
interface ITreeRendererOptions {
readonly indent?: number;
readonly renderIndentGuides?: RenderIndentGuides;
// TODO@joao replace this with collapsible: boolean | 'ondemand'
readonly hideTwistiesOfChildlessElements?: boolean;
}
interface IRenderData<TTemplateData> {
@@ -244,6 +270,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
private renderedElements = new Map<T, ITreeNode<T, TFilterData>>();
private renderedNodes = new Map<ITreeNode<T, TFilterData>, IRenderData<TTemplateData>>();
private indent: number = TreeRenderer.DefaultIndent;
private hideTwistiesOfChildlessElements: boolean = false;
private shouldRenderIndentGuides: boolean = false;
private renderedIndentGuides = new SetMap<ITreeNode<T, TFilterData>, HTMLDivElement>();
@@ -290,6 +317,10 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
}
}
}
if (typeof options.hideTwistiesOfChildlessElements !== 'undefined') {
this.hideTwistiesOfChildlessElements = options.hideTwistiesOfChildlessElements;
}
}
renderTemplate(container: HTMLElement): ITreeListTemplateData<TTemplateData> {
@@ -309,7 +340,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
}
const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent;
templateData.twistie.style.marginLeft = `${indent}px`;
templateData.twistie.style.paddingLeft = `${indent}px`;
templateData.indent.style.width = `${indent + this.indent - 16}px`;
this.renderTwistie(node, templateData);
@@ -365,10 +396,12 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
this.renderer.renderTwistie(node.element, templateData.twistie);
}
toggleClass(templateData.twistie, 'codicon', node.collapsible);
toggleClass(templateData.twistie, 'codicon-chevron-down', node.collapsible);
toggleClass(templateData.twistie, 'collapsible', node.collapsible);
toggleClass(templateData.twistie, 'collapsed', node.collapsible && node.collapsed);
if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) {
addClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible');
toggleClass(templateData.twistie, 'collapsed', node.collapsed);
} else {
removeClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible', 'collapsed');
}
if (node.collapsible) {
templateData.container.setAttribute('aria-expanded', String(!node.collapsed));
@@ -430,12 +463,16 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
nodes.forEach(node => {
const ref = model.getNodeLocation(node);
const parentRef = model.getParentNodeLocation(ref);
try {
const parentRef = model.getParentNodeLocation(ref);
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
set.add(node);
} else if (parentRef) {
set.add(model.getNode(parentRef));
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
set.add(node);
} else if (parentRef) {
set.add(model.getNode(parentRef));
}
} catch {
// noop
}
});
@@ -601,14 +638,14 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
const controls = append(this.domNode, $('.controls'));
this._filterOnType = !!tree.options.filterOnType;
this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter'));
this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter.codicon.codicon-list-selection'));
this.filterOnTypeDomNode.type = 'checkbox';
this.filterOnTypeDomNode.checked = this._filterOnType;
this.filterOnTypeDomNode.tabIndex = -1;
this.updateFilterOnTypeTitle();
domEvent(this.filterOnTypeDomNode, 'input')(this.onDidChangeFilterOnType, this, this.disposables);
this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear'));
this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear.codicon.codicon-close'));
this.clearDomNode.tabIndex = -1;
this.clearDomNode.title = localize('clear', "Clear");
@@ -657,7 +694,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown'))
.filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
.filter(e => e.key !== 'Dead')
.filter(e => e.key !== 'Dead' && !/^Media/.test(e.key))
.map(e => new StandardKeyboardEvent(e))
.filter(this.keyboardNavigationEventFilter || (() => true))
.filter(() => this.automaticKeyboardNavigation || this.triggered)
@@ -918,7 +955,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;
@@ -1385,8 +1421,13 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return this.view.renderHeight;
}
get firstVisibleElement(): T {
get firstVisibleElement(): T | undefined {
const index = this.view.firstVisibleIndex;
if (index < 0 || index >= this.view.length) {
return undefined;
}
const node = this.view.element(index);
return node.element;
}

View File

@@ -6,7 +6,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 { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree';
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';
import { Emitter, Event } from 'vs/base/common/event';
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
@@ -19,6 +19,8 @@ import { toggleClass } from 'vs/base/browser/dom';
import { values } from 'vs/base/common/map';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { IThemable } from 'vs/base/common/styler';
import { isFilterResult, getVisibleState } from 'vs/base/browser/ui/tree/indexTreeModel';
interface IAsyncDataTreeNode<TInput, T> {
element: TInput | T;
@@ -149,20 +151,24 @@ function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTr
};
}
export enum ChildrenResolutionReason {
Refresh,
Expand
}
class AsyncDataTreeElementsDragAndDropData<TInput, T, TContext> extends ElementsDragAndDropData<T, TContext> {
export interface IChildrenResolutionEvent<T> {
readonly element: T | null;
readonly reason: ChildrenResolutionReason;
set context(context: TContext | undefined) {
this.data.context = context;
}
get context(): TContext | undefined {
return this.data.context;
}
constructor(private data: ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>, TContext>) {
super(data.elements.map(node => node.element as T));
}
}
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
if (data instanceof ElementsDragAndDropData) {
const nodes = (data as ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>>).elements;
return new ElementsDragAndDropData(nodes.map(node => node.element));
return new AsyncDataTreeElementsDragAndDropData(data);
}
return data;
@@ -176,9 +182,9 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
return this.dnd.getDragURI(node.element as T);
}
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[]): string | undefined {
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[], originalEvent: DragEvent): string | undefined {
if (this.dnd.getDragLabel) {
return this.dnd.getDragLabel(nodes.map(node => node.element as T));
return this.dnd.getDragLabel(nodes.map(node => node.element as T), originalEvent);
}
return undefined;
@@ -197,6 +203,12 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
}
}
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
@@ -218,9 +230,16 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
}
},
accessibilityProvider: options.accessibilityProvider && {
...options.accessibilityProvider,
getAriaLabel(e) {
return options.accessibilityProvider!.getAriaLabel(e.element as T);
}
},
getAriaLevel: options.accessibilityProvider!.getAriaLevel && (node => {
return options.accessibilityProvider!.getAriaLevel!(node.element as T);
}),
getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
return options.accessibilityProvider!.getActiveDescendantId!(node.element as T);
})
},
filter: options.filter && {
filter(e, parentVisibility) {
@@ -271,10 +290,10 @@ function dfs<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, fn: (node: IAsyncDa
node.children.forEach(child => dfs(child, fn));
}
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable {
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable, IThemable {
private readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
private readonly root: IAsyncDataTreeNode<TInput, T>;
protected readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
protected readonly root: IAsyncDataTreeNode<TInput, T>;
private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
private readonly sorter?: ITreeSorter<T>;
private readonly collapseByDefault?: { (e: T): boolean; };
@@ -282,7 +301,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 +342,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>[],
@@ -411,10 +430,6 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return this.tree.renderHeight;
}
get firstVisibleElement(): T {
return this.tree.firstVisibleElement!.element as T;
}
get lastVisibleElement(): T {
return this.tree.lastVisibleElement!.element as T;
}
@@ -445,7 +460,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext<TInput, T>;
await this._updateChildren(input, true, viewStateContext);
await this._updateChildren(input, true, false, viewStateContext);
if (viewStateContext) {
this.tree.setFocus(viewStateContext.focus);
@@ -457,11 +472,11 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
}
}
async updateChildren(element: TInput | T = this.root.element, recursive = true): Promise<void> {
await this._updateChildren(element, recursive);
async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise<void> {
await this._updateChildren(element, recursive, rerender);
}
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
if (typeof this.root.element === 'undefined') {
throw new TreeError(this.user, 'Tree input not set');
}
@@ -471,7 +486,17 @@ 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);
const node = this.getDataNode(element);
await this.refreshAndRenderNode(node, recursive, viewStateContext);
if (rerender) {
try {
this.tree.rerender(node);
} catch {
// missing nodes are fine, this could've resulted from
// parallel refresh calls, removing `node` altogether
}
}
}
resort(element: TInput | T = this.root.element, recursive = true): void {
@@ -653,18 +678,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> {
@@ -752,12 +768,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
result = createCancelablePromise(async () => {
const children = await this.dataSource.getChildren(node.element!);
if (this.sorter) {
children.sort(this.sorter.compare.bind(this.sorter));
}
return children;
return this.processChildren(children);
});
this.refreshPromises.set(node, result);
@@ -770,7 +781,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 +794,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 +822,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 +835,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,10 +880,16 @@ 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;
}
private render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
this.tree.setChildren(node === this.root ? null : node, children);
@@ -881,6 +901,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) {
@@ -899,6 +927,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
};
}
protected processChildren(children: T[]): T[] {
if (this.sorter) {
children.sort(this.sorter.compare.bind(this.sorter));
}
return children;
}
// view state
getViewState(): IAsyncDataTreeViewState {
@@ -994,6 +1030,12 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
}
}
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeCompressedElements) {
this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
}
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.disposeTemplate(templateData.templateData);
}
@@ -1023,12 +1065,19 @@ 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));
private filter?: ITreeFilter<T, TFilterData>;
constructor(
user: string,
@@ -1037,9 +1086,10 @@ 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);
this.filter = options.filter;
}
protected createTree(
@@ -1062,4 +1112,137 @@ 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 };
}
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
if (!this.identityProvider) {
return super.render(node, viewStateContext);
}
// Preserve traits across compressions. Hacky but does the trick.
// This is hard to fix properly since it requires rewriting the traits
// across trees and lists. Let's just keep it this way for now.
const getId = (element: T) => this.identityProvider!.getId(element).toString();
const getUncompressedIds = (nodes: IAsyncDataTreeNode<TInput, T>[]): Set<string> => {
const result = new Set<string>();
for (const node of nodes) {
const compressedNode = this.tree.getCompressedTreeNode(node === this.root ? null : node);
if (!compressedNode.element) {
continue;
}
for (const node of compressedNode.element.elements) {
result.add(getId(node.element as T));
}
}
return result;
};
const oldSelection = getUncompressedIds(this.tree.getSelection() as IAsyncDataTreeNode<TInput, T>[]);
const oldFocus = getUncompressedIds(this.tree.getFocus() as IAsyncDataTreeNode<TInput, T>[]);
super.render(node, viewStateContext);
const selection = this.getSelection();
let didChangeSelection = false;
const focus = this.getFocus();
let didChangeFocus = false;
const visit = (node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>> | null, TFilterData>) => {
const compressedNode = node.element;
if (compressedNode) {
for (let i = 0; i < compressedNode.elements.length; i++) {
const id = getId(compressedNode.elements[i].element as T);
if (oldSelection.has(id)) {
selection.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
didChangeSelection = true;
}
if (oldFocus.has(id)) {
focus.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
didChangeFocus = true;
}
}
}
node.children.forEach(visit);
};
visit(this.tree.getCompressedTreeNode(node === this.root ? null : node));
if (didChangeSelection) {
this.setSelection(selection);
}
if (didChangeFocus) {
this.setFocus(focus);
}
}
// For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work
// and we have to filter everything beforehand
// Related to #85193 and #85835
protected processChildren(children: T[]): T[] {
if (this.filter) {
children = children.filter(e => {
const result = this.filter!.filter(e, TreeVisibility.Visible);
const visibility = getVisibility(result);
if (visibility === TreeVisibility.Recurse) {
throw new Error('Recursive tree visibility not supported in async data compressed trees');
}
return visibility === TreeVisibility.Visible;
});
}
return super.processChildren(children);
}
}
function getVisibility<TFilterData>(filterResult: TreeFilterResult<TFilterData>): TreeVisibility {
if (typeof filterResult === 'boolean') {
return filterResult ? TreeVisibility.Visible : TreeVisibility.Hidden;
} else if (isFilterResult(filterResult)) {
return getVisibleState(filterResult.visibility);
} else {
return getVisibleState(filterResult);
}
}

View File

@@ -11,7 +11,7 @@ import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/b
// Exported only for test reasons, do not use directly
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
readonly children?: Iterator<ICompressedTreeElement<T>> | ICompressedTreeElement<T>[];
readonly children?: ISequence<ICompressedTreeElement<T>>;
readonly incompressible?: boolean;
}
@@ -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,13 +131,13 @@ 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(
element: T | null,
children: ISequence<ICompressedTreeElement<T>> | undefined
): void {
if (element === null) {
const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress);
this._setChildren(null, compressedChildren);
@@ -368,6 +367,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 +493,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);
}
}

View File

@@ -11,7 +11,7 @@ import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list
import { Iterator } from 'vs/base/common/iterator';
export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
sorter?: ITreeSorter<T>;
readonly sorter?: ITreeSorter<T>;
}
export interface IDataTreeViewState {

View File

@@ -442,6 +442,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
if (visibility === TreeVisibility.Hidden) {
node.visible = false;
node.renderNodeCount = 0;
return false;
}

View File

@@ -3,11 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-panel-view .panel > .panel-header h3.title {
.monaco-pane-view .pane > .pane-header h3.title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 11px;
-webkit-margin-before: 0;
-webkit-margin-after: 0;
margin: 0;
}

View File

@@ -14,7 +14,7 @@
height: 100%;
position: absolute;
top: 0;
left: 18px;
left: 16px;
pointer-events: none;
}
@@ -41,7 +41,7 @@
.monaco-tl-twistie {
font-size: 10px;
text-align: right;
margin-right: 6px;
padding-right: 6px;
flex-shrink: 0;
width: 16px;
display: flex !important;

View File

@@ -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);
if (typeof optionsUpdate.compressionEnabled !== 'undefined') {
this.model.setCompressionEnabled(optionsUpdate.compressionEnabled);
}
}
setCompressionEnabled(enabled: boolean): void {
this.model.setCompressionEnabled(enabled);
}
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);
}
}

View File

@@ -257,7 +257,12 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
throw new TreeError(this.user, `Invalid getParentNodeLocation call`);
}
const node = this.nodes.get(element)!;
const node = this.nodes.get(element);
if (!node) {
throw new TreeError(this.user, `Tree element not found: ${element}`);
}
const location = this.model.getNodeLocation(node);
const parentLocation = this.model.getParentNodeLocation(location);
const parent = this.model.getNode(parentLocation);