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

@@ -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);
}
}