Merge from vscode 817eb6b0c720a4ecbc13c020afbbebfed667aa09 (#7356)

This commit is contained in:
Anthony Dresser
2019-09-24 21:36:17 -07:00
committed by GitHub
parent a29ae4d3b9
commit 6a6048d40f
541 changed files with 7045 additions and 7287 deletions

View File

@@ -5,16 +5,16 @@
import 'vs/css!./media/tree';
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IListOptions, List, IListStyles, mightProducePrintableCharacter, MouseController } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list';
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 { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd';
import { range, equals, distinctES6 } from 'vs/base/common/arrays';
import { range, equals, distinctES6, fromSet } from 'vs/base/common/arrays';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { domEvent } from 'vs/base/browser/event';
import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters';
@@ -567,7 +567,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
private _empty: boolean = false;
get empty(): boolean { return this._empty; }
private _onDidChangeEmptyState = new Emitter<boolean>();
private readonly _onDidChangeEmptyState = new Emitter<boolean>();
readonly onDidChangeEmptyState: Event<boolean> = Event.latch(this._onDidChangeEmptyState.event);
private positionClassName = 'ne';
@@ -581,7 +581,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
private automaticKeyboardNavigation = true;
private triggered = false;
private _onDidChangePattern = new Emitter<string>();
private readonly _onDidChangePattern = new Emitter<string>();
readonly onDidChangePattern = this._onDidChangePattern.event;
private enabledDisposables: IDisposable[] = [];
@@ -592,7 +592,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
model: ITreeModel<T, TFilterData, any>,
private view: List<ITreeNode<T, TFilterData>>,
private filter: TypeFilter<T>,
private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider<T>
private keyboardNavigationDelegate: IKeyboardNavigationDelegate
) {
this.domNode = $(`.monaco-list-type-filter.${this.positionClassName}`);
this.domNode.draggable = true;
@@ -658,13 +658,12 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
return;
}
const isPrintableCharEvent = this.keyboardNavigationLabelProvider.mightProducePrintableCharacter ? (e: IKeyboardEvent) => this.keyboardNavigationLabelProvider.mightProducePrintableCharacter!(e) : (e: IKeyboardEvent) => mightProducePrintableCharacter(e);
const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown'))
.filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
.map(e => new StandardKeyboardEvent(e))
.filter(this.keyboardNavigationEventFilter || (() => true))
.filter(() => this.automaticKeyboardNavigation || this.triggered)
.filter(e => isPrintableCharEvent(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
.filter(e => this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
.forEach(e => { e.stopPropagation(); e.preventDefault(); })
.event;
@@ -935,7 +934,7 @@ class Trait<T> {
private nodes: ITreeNode<T, any>[] = [];
private elements: T[] | undefined;
private _onDidChange = new Emitter<ITreeEvent<T>>();
private readonly _onDidChange = new Emitter<ITreeEvent<T>>();
readonly onDidChange = this._onDidChange.event;
private _nodeSet: Set<ITreeNode<T, any>> | undefined;
@@ -1189,6 +1188,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get onDidChangeFocus(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.focus.onDidChange); }
get onDidChangeSelection(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); }
get onDidOpen(): Event<ITreeEvent<T>> { return Event.map(this.view.onDidOpen, asTreeEvent); }
get onDidPin(): Event<ITreeEvent<T>> { return Event.map(this.view.onDidPin, asTreeEvent); }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseDblClick, asTreeMouseEvent); }
@@ -1204,7 +1204,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T, TFilterData>> { return this.model.onDidChangeCollapseState; }
get onDidChangeRenderNodeCount(): Event<ITreeNode<T, TFilterData>> { return this.model.onDidChangeRenderNodeCount; }
private _onWillRefilter = new Emitter<void>();
private readonly _onWillRefilter = new Emitter<void>();
readonly onWillRefilter: Event<void> = this._onWillRefilter.event;
get filterOnType(): boolean { return !!this._options.filterOnType; }
@@ -1213,7 +1213,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get openOnSingleClick(): boolean { return typeof this._options.openOnSingleClick === 'undefined' ? true : this._options.openOnSingleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; }
private _onDidUpdateOptions = new Emitter<IAbstractTreeOptions<T, TFilterData>>();
private readonly _onDidUpdateOptions = new Emitter<IAbstractTreeOptions<T, TFilterData>>();
readonly onDidUpdateOptions: Event<IAbstractTreeOptions<T, TFilterData>> = this._onDidUpdateOptions.event;
get onDidDispose(): Event<void> { return this.view.onDidDispose; }
@@ -1228,7 +1228,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
const treeDelegate = new ComposedTreeDelegate<T, ITreeNode<T, TFilterData>>(delegate);
const onDidChangeCollapseStateRelay = new Relay<ICollapseStateChangeEvent<T, TFilterData>>();
const onDidChangeActiveNodes = new Relay<ITreeNode<T, TFilterData>[]>();
const onDidChangeActiveNodes = new Emitter<ITreeNode<T, TFilterData>[]>();
const activeNodes = new EventCollection(onDidChangeActiveNodes.event);
this.disposables.push(activeNodes);
@@ -1251,11 +1251,23 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;
this.model.onDidSplice(e => {
this.focus.onDidModelSplice(e);
this.selection.onDidModelSplice(e);
}, null, this.disposables);
this.eventBufferer.bufferEvents(() => {
this.focus.onDidModelSplice(e);
this.selection.onDidModelSplice(e);
});
onDidChangeActiveNodes.input = Event.map(Event.any<any>(this.focus.onDidChange, this.selection.onDidChange, this.model.onDidSplice), () => [...this.focus.getNodes(), ...this.selection.getNodes()]);
const set = new Set<ITreeNode<T, TFilterData>>();
for (const node of this.focus.getNodes()) {
set.add(node);
}
for (const node of this.selection.getNodes()) {
set.add(node);
}
onDidChangeActiveNodes.fire(fromSet(set));
}, null, this.disposables);
if (_options.keyboardSupport !== false) {
const onKeyDown = Event.chain(this.view.onKeyDown)
@@ -1268,7 +1280,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
}
if (_options.keyboardNavigationLabelProvider) {
this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, _options.keyboardNavigationLabelProvider);
const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate;
this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, delegate);
this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node);
this.disposables.push(this.typeFilterController!);
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
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 { IDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -1010,6 +1010,24 @@ export interface ITreeCompressionDelegate<T> {
isIncompressible(element: T): boolean;
}
function asCompressibleObjectTreeOptions<TInput, T, TFilterData>(options?: ICompressibleAsyncDataTreeOptions<T, TFilterData>): ICompressibleObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
const objectTreeOptions = options && asObjectTreeOptions(options);
return objectTreeOptions && {
...objectTreeOptions,
keyboardNavigationLabelProvider: objectTreeOptions.keyboardNavigationLabelProvider && {
...objectTreeOptions.keyboardNavigationLabelProvider,
getCompressedNodeKeyboardNavigationLabel(els) {
return options!.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element as T));
}
}
};
}
export interface ICompressibleAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptions<T, TFilterData> {
readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
}
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
@@ -1031,11 +1049,11 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
options: IAsyncDataTreeOptions<T, TFilterData>
options: ICompressibleAsyncDataTreeOptions<T, TFilterData>
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new CompressibleAsyncDataTreeRenderer(r, this.nodeMapper, () => this.compressibleNodeMapper, this._onDidChangeNodeSlowState.event));
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
const objectTreeOptions = asCompressibleObjectTreeOptions<TInput, T, TFilterData>(options) || {};
return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
}

View File

@@ -336,7 +336,7 @@ function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwra
...options,
sorter: options.sorter && {
compare(node: ICompressedTreeNode<T>, otherNode: ICompressedTreeNode<T>): number {
return options.sorter!.compare(compressedNodeUnwrapper(node), compressedNodeUnwrapper(otherNode));
return options.sorter!.compare(node.elements[0], otherNode.elements[0]);
}
},
identityProvider: options.identityProvider && {

View File

@@ -162,9 +162,10 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
const children = this.dataSource.getChildren(element);
const elements = Iterator.map<any, ITreeElement<T>>(Iterator.fromArray(children), element => {
const { elements: children, size } = this.iterate(element, isCollapsed);
const collapsible = this.dataSource.hasChildren ? this.dataSource.hasChildren(element) : undefined;
const collapsed = size === 0 ? undefined : (isCollapsed && isCollapsed(element));
return { element, children, collapsed };
return { element, children, collapsible, collapsed };
});
return { elements, size: children.length };

View File

@@ -62,17 +62,17 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
private root: IIndexTreeNode<T, TFilterData>;
private eventBufferer = new EventBufferer();
private _onDidChangeCollapseState = new Emitter<ICollapseStateChangeEvent<T, TFilterData>>();
private readonly _onDidChangeCollapseState = new Emitter<ICollapseStateChangeEvent<T, TFilterData>>();
readonly onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event);
private _onDidChangeRenderNodeCount = new Emitter<ITreeNode<T, TFilterData>>();
private readonly _onDidChangeRenderNodeCount = new Emitter<ITreeNode<T, TFilterData>>();
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeRenderNodeCount.event);
private collapseByDefault: boolean;
private filter?: ITreeFilter<T, TFilterData>;
private autoExpandSingleChildren: boolean;
private _onDidSplice = new Emitter<ITreeModelSpliceEvent<T, TFilterData>>();
private readonly _onDidSplice = new Emitter<ITreeModelSpliceEvent<T, TFilterData>>();
readonly onDidSplice = this._onDidSplice.event;
constructor(
@@ -169,7 +169,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
parentNode.visibleChildrenCount += insertedVisibleChildrenCount - deletedVisibleChildrenCount;
if (revealed && visible) {
const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.renderNodeCount, 0);
const visibleDeleteCount = deletedNodes.reduce((r, node) => r + (node.visible ? node.renderNodeCount : 0), 0);
this._updateAncestorsRenderNodeCount(parentNode, renderNodeCount - visibleDeleteCount);
this.list.splice(listIndex, visibleDeleteCount, treeListElementsToInsert);

View File

@@ -8,9 +8,10 @@ import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abst
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';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IListVirtualDelegate, IKeyboardNavigationLabelProvider } 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';
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
sorter?: ITreeSorter<T>;
@@ -77,9 +78,12 @@ class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRender
readonly templateId: string;
readonly onDidChangeTwistieState: Event<T> | undefined;
compressedTreeNodeProvider: ICompressedTreeNodeProvider<T, TFilterData>;
@memoize
private get compressedTreeNodeProvider(): ICompressedTreeNodeProvider<T, TFilterData> {
return this._compressedTreeNodeProvider();
}
constructor(private renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>) {
constructor(private _compressedTreeNodeProvider: () => ICompressedTreeNodeProvider<T, TFilterData>, private renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>) {
this.templateId = renderer.templateId;
if (renderer.onDidChangeTwistieState) {
@@ -127,7 +131,38 @@ class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRender
}
}
export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> {
export interface ICompressibleKeyboardNavigationLabelProvider<T> extends IKeyboardNavigationLabelProvider<T> {
getCompressedNodeKeyboardNavigationLabel(elements: T[]): { toString(): string | undefined; } | undefined;
}
export interface ICompressibleObjectTreeOptions<T, TFilterData = void> extends IObjectTreeOptions<T, TFilterData> {
readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
}
function asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider: () => ICompressedTreeNodeProvider<T, TFilterData>, options?: ICompressibleObjectTreeOptions<T, TFilterData>): IObjectTreeOptions<T, TFilterData> | undefined {
return options && {
...options,
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
getKeyboardNavigationLabel(e: T) {
let compressedTreeNode: ITreeNode<ICompressedTreeNode<T>, TFilterData>;
try {
compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e);
} catch {
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e);
}
if (compressedTreeNode.element.elements.length === 1) {
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e);
} else {
return options.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(compressedTreeNode.element.elements);
}
}
}
};
}
export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> implements ICompressedTreeNodeProvider<T, TFilterData> {
protected model: CompressibleObjectTreeModel<T, TFilterData>;
@@ -136,11 +171,11 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
options: IObjectTreeOptions<T, TFilterData> = {}
options: ICompressibleObjectTreeOptions<T, TFilterData> = {}
) {
const compressibleRenderers = renderers.map(r => new CompressibleRenderer(r));
super(user, container, delegate, compressibleRenderers, options);
compressibleRenderers.forEach(r => r.compressedTreeNodeProvider = this);
const compressedTreeNodeProvider = () => this;
const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r));
super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options));
}
setChildren(element: T | null, children?: ISequence<ICompressedTreeElement<T>>): void {

View File

@@ -9,6 +9,7 @@ import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/
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';
import { mergeSort } from 'vs/base/common/arrays';
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
@@ -123,7 +124,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
let iterator = elements ? getSequenceIterator(elements) : Iterator.empty<ITreeElement<T>>();
if (this.sorter) {
iterator = Iterator.fromArray(Iterator.collect(iterator).sort(this.sorter.compare.bind(this.sorter)));
iterator = Iterator.fromArray(mergeSort(Iterator.collect(iterator), this.sorter.compare.bind(this.sorter)));
}
return Iterator.map(iterator, treeElement => {

View File

@@ -164,6 +164,7 @@ export interface ITreeNavigator<T> {
}
export interface IDataSource<TInput, T> {
hasChildren?(element: TInput | T): boolean;
getChildren(element: TInput | T): T[];
}

View File

@@ -22,4 +22,4 @@ export class CollapseAllAction<TInput, T, TFilterData = void> extends Action {
return Promise.resolve();
}
}
}