diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 08f1ed4cdd3..825a83761f2 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -22,7 +22,6 @@ "vs/editor/*", "vs/base/common/*", "vs/base/browser/*", - "vs/base/parts/tree/*", "vs/platform/*/common/*", "vs/platform/*/browser/*" ], diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css deleted file mode 100644 index 3e709334db6..00000000000 --- a/src/vs/base/parts/tree/browser/tree.css +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -.monaco-tree { - height: 100%; - width: 100%; - white-space: nowrap; - user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - position: relative; -} - -.monaco-tree > .monaco-scrollable-element { - height: 100%; -} - -.monaco-tree > .monaco-scrollable-element > .monaco-tree-wrapper { - height: 100%; - width: 100%; - position: relative; -} - -.monaco-tree .monaco-tree-rows { - position: absolute; - width: 100%; - height: 100%; -} - -.monaco-tree .monaco-tree-rows > .monaco-tree-row { - box-sizing: border-box; - cursor: pointer; - overflow: hidden; - width: 100%; - touch-action: none; -} - -.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content { - position: relative; - height: 100%; -} - -.monaco-tree-drag-image { - display: inline-block; - padding: 1px 7px; - border-radius: 10px; - font-size: 12px; - position: absolute; -} - -/* for OS X ballistic scrolling */ -.monaco-tree .monaco-tree-rows > .monaco-tree-row.scrolling { - display: none; -} - -/* Highlighted */ - -.monaco-tree.highlighted .monaco-tree-rows > .monaco-tree-row:not(.highlighted) { - opacity: 0.3; -} diff --git a/src/vs/base/parts/tree/browser/tree.ts b/src/vs/base/parts/tree/browser/tree.ts deleted file mode 100644 index 82b4015e6dd..00000000000 --- a/src/vs/base/parts/tree/browser/tree.ts +++ /dev/null @@ -1,582 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as Touch from 'vs/base/browser/touch'; -import * as Mouse from 'vs/base/browser/mouseEvent'; -import * as Keyboard from 'vs/base/browser/keyboardEvent'; -import { INavigator } from 'vs/base/common/iterator'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { Event } from 'vs/base/common/event'; -import { IAction } from 'vs/base/common/actions'; -import { Color } from 'vs/base/common/color'; -import { IItemCollapseEvent, IItemExpandEvent } from 'vs/base/parts/tree/browser/treeModel'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; - -export interface ITree { - - onDidFocus: Event; - onDidBlur: Event; - onDidChangeFocus: Event; - onDidChangeSelection: Event; - onDidChangeHighlight: Event; - onDidExpandItem: Event; - onDidCollapseItem: Event; - onDidDispose: Event; - onDidScroll: Event; - - /** - * Returns the tree's DOM element. - */ - getHTMLElement(): HTMLElement; - - /** - * Lays out the tree. - * Provide a specific height to save an (expensive) height computation. - */ - layout(height?: number): void; - - /** - * Notifies the tree that is has become visible. - */ - onVisible(): void; - - /** - * Notifies the tree that is has become hidden. - */ - onHidden(): void; - - /** - * Sets the input of the tree. - */ - setInput(element: any): Promise; - - /** - * Returns the tree's input. - */ - getInput(): any; - - /** - * Sets DOM focus on the tree. - */ - domFocus(): void; - - /** - * Returns whether the tree has DOM focus. - */ - isDOMFocused(): boolean; - - /** - * Removes DOM focus from the tree. - */ - domBlur(): void; - - /** - * Refreshes an element. - * Provide no arguments and it will refresh the input element. - */ - refresh(element?: any, recursive?: boolean): Promise; - - /** - * Expands an element. - * The returned promise returns a boolean for whether the element was expanded or not. - */ - expand(element: any): Promise; - - /** - * Expands several elements. - * The returned promise returns a boolean array for whether the elements were expanded or not. - */ - expandAll(elements?: any[]): Promise; - - /** - * Collapses an element. - * The returned promise returns a boolean for whether the element was collapsed or not. - */ - collapse(element: any, recursive?: boolean): Promise; - - /** - * Collapses several elements. - * Provide no arguments and it will recursively collapse all elements in the tree - * The returned promise returns a boolean for whether the elements were collapsed or not. - */ - collapseAll(elements?: any[], recursive?: boolean): Promise; - - /** - * Toggles an element's expansion state. - */ - toggleExpansion(element: any, recursive?: boolean): Promise; - - /** - * Returns whether an element is expanded or not. - */ - isExpanded(element: any): boolean; - - /** - * Reveals an element in the tree. The relativeTop is a value between 0 and 1. The closer to 0 the more the - * element will scroll up to the top. - */ - reveal(element: any, relativeTop?: number): Promise; - - /** - * Returns the currently highlighted element. - */ - getHighlight(includeHidden?: boolean): any; - - /** - * Clears the highlight. - */ - clearHighlight(eventPayload?: any): void; - - /** - * Replaces the current selection with the given elements. - */ - setSelection(elements: any[], eventPayload?: any): void; - - /** - * Returns the currently selected elements. - */ - getSelection(includeHidden?: boolean): any[]; - - /** - * Clears the selection. - */ - clearSelection(eventPayload?: any): void; - - /** - * Sets the focused element. - */ - setFocus(element?: any, eventPayload?: any): void; - - /** - * Returns focused element. - */ - getFocus(includeHidden?: boolean): any; - - /** - * Focuses the next `count`-nth element, in visible order. - */ - focusNext(count?: number, eventPayload?: any): void; - - /** - * Focuses the previous `count`-nth element, in visible order. - */ - focusPrevious(count?: number, eventPayload?: any): void; - - /** - * Focuses the currently focused element's parent. - */ - focusParent(eventPayload?: any): void; - - /** - * Focuses the first child of the currently focused element. - */ - focusFirstChild(eventPayload?: any): void; - - /** - * Focuses the second element, in visible order. Will focus the first - * child from the provided element's parent if any. - */ - focusFirst(eventPayload?: any, from?: any): void; - - /** - * Focuses the nth element, in visible order. - */ - focusNth(index: number, eventPayload?: any): void; - - /** - * Focuses the last element, in visible order. Will focus the last - * child from the provided element's parent if any. - */ - focusLast(eventPayload?: any, from?: any): void; - - /** - * Focuses the element at the end of the next page, in visible order. - */ - focusNextPage(eventPayload?: any): void; - - /** - * Focuses the element at the beginning of the previous page, in visible order. - */ - focusPreviousPage(eventPayload?: any): void; - - /** - * Clears the focus. - */ - clearFocus(eventPayload?: any): void; - - /** - * Returns a navigator which allows to discover the visible and - * expanded elements in the tree. - */ - getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator; - - /** - * Apply styles to the tree. - */ - style(styles: ITreeStyles): void; - - /** - * Disposes the tree - */ - dispose(): void; -} - -export interface IDataSource { - - /** - * Returns the unique identifier of the given element. - * No more than one element may use a given identifier. - * - * You should not attempt to "move" an element to a different - * parent by keeping its ID. The idea here is to have tree location - * related IDs (e.g. full file path, in the Explorer example). - */ - getId(tree: ITree, element: any): string; - - /** - * Returns a boolean value indicating whether the element has children. - */ - hasChildren(tree: ITree, element: any): boolean; - - /** - * Returns the element's children as an array in a promise. - */ - getChildren(tree: ITree, element: any): Promise; - - /** - * Returns the element's parent in a promise. - */ - getParent(tree: ITree, element: any): Promise; - - /** - * Returns whether an element should be expanded when first added to the tree. - */ - shouldAutoexpand?(tree: ITree, element: any): boolean; -} - -export interface IRenderer { - - /** - * Returns the element's height in the tree, in pixels. - */ - getHeight(tree: ITree, element: any): number; - - /** - * Returns a template ID for a given element. This will be used as an identifier - * for the next 3 methods. - */ - getTemplateId(tree: ITree, element: any): string; - - /** - * Renders the template in a DOM element. This method should render all the DOM - * structure for an hypothetical element leaving its contents blank. It should - * return an object bag which will be passed along to `renderElement` and used - * to fill in those blanks. - * - * You should do all DOM creating and object allocation in this method. It - * will be called only a few times. - */ - renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any; - - /** - * Renders an element, given an object bag returned by `renderTemplate`. - * This method should do as little as possible and ideally it should only fill - * in the blanks left by `renderTemplate`. - * - * Try to make this method do as little possible, since it will be called very - * often. - */ - renderElement(tree: ITree, element: any, templateId: string, templateData: any): void; - - /** - * Disposes a template that was once rendered. - */ - disposeTemplate(tree: ITree, templateId: string, templateData: any): void; -} - -export interface IAccessibilityProvider { - - /** - * Given an element in the tree, return the ARIA label that should be associated with the - * item. This helps screen readers to provide a meaningful label for the currently focused - * tree element. - * - * Returning null will not disable ARIA for the element. Instead it is up to the screen reader - * to compute a meaningful label based on the contents of the element in the DOM - * - * See also: https://www.w3.org/TR/wai-aria/states_and_properties#aria-label - */ - getAriaLabel(tree: ITree, element: any): string | null; - - /** - * Given an element in the tree return its aria-posinset. Should be between 1 and aria-setsize - * https://www.w3.org/TR/wai-aria/states_and_properties#aria-posinset - */ - getPosInSet?(tree: ITree, element: any): string; - - /** - * Return the aria-setsize of the tree. - * https://www.w3.org/TR/wai-aria/states_and_properties#aria-setsize - */ - getSetSize?(): string; -} - -export /* abstract */ class ContextMenuEvent { - - private _posx: number; - private _posy: number; - private _target: HTMLElement; - - constructor(posx: number, posy: number, target: HTMLElement) { - this._posx = posx; - this._posy = posy; - this._target = target; - } - - public preventDefault(): void { - // no-op - } - - public stopPropagation(): void { - // no-op - } - - public get posx(): number { - return this._posx; - } - - public get posy(): number { - return this._posy; - } - - public get target(): HTMLElement { - return this._target; - } -} - -export class MouseContextMenuEvent extends ContextMenuEvent { - - private originalEvent: Mouse.IMouseEvent; - - constructor(originalEvent: Mouse.IMouseEvent) { - super(originalEvent.posx, originalEvent.posy, originalEvent.target); - this.originalEvent = originalEvent; - } - - public preventDefault(): void { - this.originalEvent.preventDefault(); - } - - public stopPropagation(): void { - this.originalEvent.stopPropagation(); - } -} - -export class KeyboardContextMenuEvent extends ContextMenuEvent { - - private originalEvent: Keyboard.IKeyboardEvent; - - constructor(posx: number, posy: number, originalEvent: Keyboard.IKeyboardEvent) { - super(posx, posy, originalEvent.target); - this.originalEvent = originalEvent; - } - - public preventDefault(): void { - this.originalEvent.preventDefault(); - } - - public stopPropagation(): void { - this.originalEvent.stopPropagation(); - } -} - -export interface IController { - - /** - * Called when an element is clicked. - */ - onClick(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean; - - /** - * Called when an element is requested for a context menu. - */ - onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean; - - /** - * Called when an element is tapped. - */ - onTap(tree: ITree, element: any, event: Touch.GestureEvent): boolean; - - /** - * Called when a key is pressed down while selecting elements. - */ - onKeyDown(tree: ITree, event: Keyboard.IKeyboardEvent): boolean; - - /** - * Called when a key is released while selecting elements. - */ - onKeyUp(tree: ITree, event: Keyboard.IKeyboardEvent): boolean; - - /** - * Called when a mouse middle button is pressed down on an element. - */ - onMouseMiddleClick?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean; - - /** - * Called when a mouse button is pressed down on an element. - */ - onMouseDown?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean; - - /** - * Called when a mouse button goes up on an element. - */ - onMouseUp?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean; -} - -export const enum DragOverEffect { - COPY, - MOVE -} - -export const enum DragOverBubble { - BUBBLE_DOWN, - BUBBLE_UP -} - -export interface IDragOverReaction { - accept: boolean; - effect?: DragOverEffect; - bubble?: DragOverBubble; - autoExpand?: boolean; -} - -export interface IDragAndDrop { - - /** - * Returns a uri if the given element should be allowed to drag. - * Returns null, otherwise. - */ - getDragURI(tree: ITree, element: any): string | null; - - /** - * Returns a label to display when dragging the element. - */ - getDragLabel?(tree: ITree, elements: any[]): string; - - /** - * Sent when the drag operation is starting. - */ - onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: Mouse.DragMouseEvent): void; - - /** - * Returns a DragOverReaction indicating whether sources can be - * dropped into target or some parent of the target. - */ - onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: Mouse.DragMouseEvent): IDragOverReaction | null; - - /** - * Handles the action of dropping sources into target. - */ - drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: Mouse.DragMouseEvent): void; -} - -export interface IFilter { - - /** - * Returns whether the given element should be visible. - */ - isVisible(tree: ITree, element: any): boolean; -} - -export interface ISorter { - - /** - * Compare two elements in the viewer to define the sorting order. - */ - compare(tree: ITree, element: any, otherElement: any): number; -} - -// Events - -export interface ISelectionEvent { - selection: any[]; - payload?: any; -} - -export interface IFocusEvent { - focus: any; - payload?: any; -} - -export interface IHighlightEvent { - highlight: any; - payload?: any; -} - -// Options - -export interface ITreeConfiguration { - dataSource: IDataSource; - renderer?: IRenderer; - controller?: IController; - dnd?: IDragAndDrop; - filter?: IFilter; - sorter?: ISorter; - accessibilityProvider?: IAccessibilityProvider; - styler?: ITreeStyler; -} - -export interface ITreeOptions extends ITreeStyles { - twistiePixels?: number; - showTwistie?: boolean; - indentPixels?: number; - verticalScrollMode?: ScrollbarVisibility; - horizontalScrollMode?: ScrollbarVisibility; - alwaysFocused?: boolean; - autoExpandSingleChildren?: boolean; - useShadows?: boolean; - paddingOnRow?: boolean; - ariaLabel?: string; - keyboardSupport?: boolean; - preventRootFocus?: boolean; - showLoading?: boolean; -} - -export interface ITreeStyler { - style(styles: ITreeStyles): void; -} - -export interface ITreeStyles { - listFocusBackground?: Color; - listFocusForeground?: Color; - listActiveSelectionBackground?: Color; - listActiveSelectionForeground?: Color; - listFocusAndSelectionBackground?: Color; - listFocusAndSelectionForeground?: Color; - listInactiveSelectionBackground?: Color; - listInactiveSelectionForeground?: Color; - listHoverBackground?: Color; - listHoverForeground?: Color; - listDropBackground?: Color; - listFocusOutline?: Color; -} - -export interface ITreeContext extends ITreeConfiguration { - tree: ITree; - options: ITreeOptions; -} - -export interface IActionProvider { - - /** - * Returns whether or not the element has actions. These show up in place right to the element in the tree. - */ - hasActions(tree: ITree | null, element: any): boolean; - - /** - * Returns an array with the actions of the element that should show up in place right to the element in the tree. - */ - getActions(tree: ITree | null, element: any): ReadonlyArray | null; -} diff --git a/src/vs/base/parts/tree/browser/treeDefaults.ts b/src/vs/base/parts/tree/browser/treeDefaults.ts deleted file mode 100644 index f91ca2bcf84..00000000000 --- a/src/vs/base/parts/tree/browser/treeDefaults.ts +++ /dev/null @@ -1,574 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import * as platform from 'vs/base/common/platform'; -import * as touch from 'vs/base/browser/touch'; -import * as errors from 'vs/base/common/errors'; -import * as dom from 'vs/base/browser/dom'; -import * as mouse from 'vs/base/browser/mouseEvent'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import * as _ from 'vs/base/parts/tree/browser/tree'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { KeyCode, KeyMod, Keybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; - -export interface IKeyBindingCallback { - (tree: _.ITree, event: IKeyboardEvent): void; -} - -export interface ICancelableEvent { - preventDefault(): void; - stopPropagation(): void; -} - -export const enum ClickBehavior { - - /** - * Handle the click when the mouse button is pressed but not released yet. - */ - ON_MOUSE_DOWN, - - /** - * Handle the click when the mouse button is released. - */ - ON_MOUSE_UP -} - -export const enum OpenMode { - SINGLE_CLICK, - DOUBLE_CLICK -} - -export interface IControllerOptions { - clickBehavior?: ClickBehavior; - openMode?: OpenMode; - keyboardSupport?: boolean; -} - -interface IKeybindingDispatcherItem { - keybinding: Keybinding | null; - callback: IKeyBindingCallback; -} - -export class KeybindingDispatcher { - - private _arr: IKeybindingDispatcherItem[]; - - constructor() { - this._arr = []; - } - - public has(keybinding: KeyCode): boolean { - let target = createKeybinding(keybinding, platform.OS); - if (target !== null) { - for (const a of this._arr) { - if (target.equals(a.keybinding)) { - return true; - } - } - } - return false; - } - - public set(keybinding: number, callback: IKeyBindingCallback) { - this._arr.push({ - keybinding: createKeybinding(keybinding, platform.OS), - callback: callback - }); - } - - public dispatch(keybinding: SimpleKeybinding): IKeyBindingCallback | null { - // Loop from the last to the first to handle overwrites - for (let i = this._arr.length - 1; i >= 0; i--) { - let item = this._arr[i]; - if (keybinding.toChord().equals(item.keybinding)) { - return item.callback; - } - } - return null; - } -} - -export class DefaultController implements _.IController { - - protected downKeyBindingDispatcher: KeybindingDispatcher; - protected upKeyBindingDispatcher: KeybindingDispatcher; - - private options: IControllerOptions; - - constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: true, openMode: OpenMode.SINGLE_CLICK }) { - this.options = options; - - this.downKeyBindingDispatcher = new KeybindingDispatcher(); - this.upKeyBindingDispatcher = new KeybindingDispatcher(); - - if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) { - this.downKeyBindingDispatcher.set(KeyCode.UpArrow, (t, e) => this.onUp(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.DownArrow, (t, e) => this.onDown(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.LeftArrow, (t, e) => this.onLeft(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.RightArrow, (t, e) => this.onRight(t, e)); - if (platform.isMacintosh) { - this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.UpArrow, (t, e) => this.onLeft(t, e)); - this.downKeyBindingDispatcher.set(KeyMod.WinCtrl | KeyCode.KEY_N, (t, e) => this.onDown(t, e)); - this.downKeyBindingDispatcher.set(KeyMod.WinCtrl | KeyCode.KEY_P, (t, e) => this.onUp(t, e)); - } - this.downKeyBindingDispatcher.set(KeyCode.PageUp, (t, e) => this.onPageUp(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.PageDown, (t, e) => this.onPageDown(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.Home, (t, e) => this.onHome(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.End, (t, e) => this.onEnd(t, e)); - - this.downKeyBindingDispatcher.set(KeyCode.Space, (t, e) => this.onSpace(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.Escape, (t, e) => this.onEscape(t, e)); - - this.upKeyBindingDispatcher.set(KeyCode.Enter, this.onEnter.bind(this)); - this.upKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, this.onEnter.bind(this)); - } - } - - public onMouseDown(tree: _.ITree, element: any, event: mouse.IMouseEvent, origin: string = 'mouse'): boolean { - if (this.options.clickBehavior === ClickBehavior.ON_MOUSE_DOWN && (event.leftButton || event.middleButton)) { - if (event.target) { - if (event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return false; // Ignore event if target is a form input field (avoids browser specific issues) - } - - if (dom.findParentWithClass(event.target, 'scrollbar', 'monaco-tree')) { - return false; - } - - if (dom.findParentWithClass(event.target, 'monaco-action-bar', 'row')) { // TODO@Joao not very nice way of checking for the action bar (implicit knowledge) - return false; // Ignore event if target is over an action bar of the row - } - } - - // Propagate to onLeftClick now - return this.onLeftClick(tree, element, event, origin); - } - - return false; - } - - public onClick(tree: _.ITree, element: any, event: mouse.IMouseEvent): boolean { - const isMac = platform.isMacintosh; - - // A Ctrl click on the Mac is a context menu event - if (isMac && event.ctrlKey) { - event.preventDefault(); - event.stopPropagation(); - return false; - } - - if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return false; // Ignore event if target is a form input field (avoids browser specific issues) - } - - if (this.options.clickBehavior === ClickBehavior.ON_MOUSE_DOWN && (event.leftButton || event.middleButton)) { - return false; // Already handled by onMouseDown - } - - return this.onLeftClick(tree, element, event); - } - - protected onLeftClick(tree: _.ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean { - const event = eventish; - const payload = { origin: origin, originalEvent: eventish, didClickOnTwistie: this.isClickOnTwistie(event) }; - - if (tree.getInput() === element) { - tree.clearFocus(payload); - tree.clearSelection(payload); - } else { - const isSingleMouseDown = eventish && event.browserEvent && event.browserEvent.type === 'mousedown' && event.browserEvent.detail === 1; - if (!isSingleMouseDown) { - eventish.preventDefault(); // we cannot preventDefault onMouseDown with single click because this would break DND otherwise - } - eventish.stopPropagation(); - - tree.domFocus(); - tree.setSelection([element], payload); - tree.setFocus(element, payload); - - if (this.shouldToggleExpansion(element, event, origin)) { - if (tree.isExpanded(element)) { - tree.collapse(element).then(undefined, errors.onUnexpectedError); - } else { - tree.expand(element).then(undefined, errors.onUnexpectedError); - } - } - } - - return true; - } - - protected shouldToggleExpansion(element: any, event: mouse.IMouseEvent, origin: string): boolean { - const isDoubleClick = (origin === 'mouse' && event.detail === 2); - return this.openOnSingleClick || isDoubleClick || this.isClickOnTwistie(event); - } - - protected setOpenMode(openMode: OpenMode) { - this.options.openMode = openMode; - } - - protected get openOnSingleClick(): boolean { - return this.options.openMode === OpenMode.SINGLE_CLICK; - } - - protected isClickOnTwistie(event: mouse.IMouseEvent): boolean { - let element = event.target as HTMLElement; - - if (!dom.hasClass(element, 'content')) { - return false; - } - - const twistieStyle = window.getComputedStyle(element, ':before'); - - if (twistieStyle.backgroundImage === 'none' || twistieStyle.display === 'none') { - return false; - } - - const twistieWidth = parseInt(twistieStyle.width!) + parseInt(twistieStyle.paddingRight!); - return event.browserEvent.offsetX <= twistieWidth; - } - - public onContextMenu(tree: _.ITree, element: any, event: _.ContextMenuEvent): boolean { - if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return false; // allow context menu on input fields - } - - // Prevent native context menu from showing up - if (event) { - event.preventDefault(); - event.stopPropagation(); - } - - return false; - } - - public onTap(tree: _.ITree, element: any, event: touch.GestureEvent): boolean { - const target = event.initialTarget; - - if (target && target.tagName && target.tagName.toLowerCase() === 'input') { - return false; // Ignore event if target is a form input field (avoids browser specific issues) - } - - return this.onLeftClick(tree, element, event, 'touch'); - } - - public onKeyDown(tree: _.ITree, event: IKeyboardEvent): boolean { - return this.onKey(this.downKeyBindingDispatcher, tree, event); - } - - public onKeyUp(tree: _.ITree, event: IKeyboardEvent): boolean { - return this.onKey(this.upKeyBindingDispatcher, tree, event); - } - - private onKey(bindings: KeybindingDispatcher, tree: _.ITree, event: IKeyboardEvent): boolean { - const handler: any = bindings.dispatch(event.toKeybinding()); - if (handler) { - // TODO: TS 3.1 upgrade. Why are we checking against void? - if (handler(tree, event)) { - event.preventDefault(); - event.stopPropagation(); - return true; - } - } - return false; - } - - protected onUp(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusPrevious(1, payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onPageUp(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusPreviousPage(payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onDown(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusNext(1, payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onPageDown(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusNextPage(payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onHome(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusFirst(payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onEnd(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusLast(payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onLeft(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - const focus = tree.getFocus(); - tree.collapse(focus).then(didCollapse => { - if (focus && !didCollapse) { - tree.focusParent(payload); - return tree.reveal(tree.getFocus()); - } - return undefined; - }).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onRight(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - const focus = tree.getFocus(); - tree.expand(focus).then(didExpand => { - if (focus && !didExpand) { - tree.focusFirstChild(payload); - return tree.reveal(tree.getFocus()); - } - return undefined; - }).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onEnter(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - return false; - } - const focus = tree.getFocus(); - if (focus) { - tree.setSelection([focus], payload); - } - return true; - } - - protected onSpace(tree: _.ITree, event: IKeyboardEvent): boolean { - if (tree.getHighlight()) { - return false; - } - const focus = tree.getFocus(); - if (focus) { - tree.toggleExpansion(focus); - } - return true; - } - - protected onEscape(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - return true; - } - - if (tree.getSelection().length) { - tree.clearSelection(payload); - return true; - } - - if (tree.getFocus()) { - tree.clearFocus(payload); - return true; - } - - return false; - } -} - -export class DefaultDragAndDrop implements _.IDragAndDrop { - - public getDragURI(tree: _.ITree, element: any): string | null { - return null; - } - - public onDragStart(tree: _.ITree, data: IDragAndDropData, originalEvent: mouse.DragMouseEvent): void { - return; - } - - public onDragOver(tree: _.ITree, data: IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): _.IDragOverReaction | null { - return null; - } - - public drop(tree: _.ITree, data: IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): void { - return; - } -} - -export class DefaultFilter implements _.IFilter { - - public isVisible(tree: _.ITree, element: any): boolean { - return true; - } -} - -export class DefaultSorter implements _.ISorter { - - public compare(tree: _.ITree, element: any, otherElement: any): number { - return 0; - } -} - -export class DefaultAccessibilityProvider implements _.IAccessibilityProvider { - - getAriaLabel(tree: _.ITree, element: any): string | null { - return null; - } -} - -export class DefaultTreestyler implements _.ITreeStyler { - - constructor(private styleElement: HTMLStyleElement, private selectorSuffix?: string) { } - - style(styles: _.ITreeStyles): void { - const suffix = this.selectorSuffix ? `.${this.selectorSuffix}` : ''; - const content: string[] = []; - - if (styles.listFocusBackground) { - content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { background-color: ${styles.listFocusBackground}; }`); - } - - if (styles.listFocusForeground) { - content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { color: ${styles.listFocusForeground}; }`); - } - - if (styles.listActiveSelectionBackground) { - content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listActiveSelectionBackground}; }`); - } - - if (styles.listActiveSelectionForeground) { - content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { color: ${styles.listActiveSelectionForeground}; }`); - } - - if (styles.listFocusAndSelectionBackground) { - content.push(` - .monaco-tree-drag-image, - .monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: ${styles.listFocusAndSelectionBackground}; } - `); - } - - if (styles.listFocusAndSelectionForeground) { - content.push(` - .monaco-tree-drag-image, - .monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { color: ${styles.listFocusAndSelectionForeground}; } - `); - } - - if (styles.listInactiveSelectionBackground) { - content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listInactiveSelectionBackground}; }`); - } - - if (styles.listInactiveSelectionForeground) { - content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { color: ${styles.listInactiveSelectionForeground}; }`); - } - - if (styles.listHoverBackground) { - content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`); - } - - if (styles.listHoverForeground) { - content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`); - } - - if (styles.listDropBackground) { - content.push(` - .monaco-tree${suffix} .monaco-tree-wrapper.drop-target, - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } - `); - } - - if (styles.listFocusOutline) { - content.push(` - .monaco-tree-drag-image { border: 1px solid ${styles.listFocusOutline}; background: #000; } - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row { border: 1px solid transparent; } - .monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { border: 1px dotted ${styles.listFocusOutline}; } - .monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; } - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; } - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { border: 1px dashed ${styles.listFocusOutline}; } - .monaco-tree${suffix} .monaco-tree-wrapper.drop-target, - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.drop-target { border: 1px dashed ${styles.listFocusOutline}; } - `); - } - - const newStyles = content.join('\n'); - if (newStyles !== this.styleElement.innerHTML) { - this.styleElement.innerHTML = newStyles; - } - } -} - -export class CollapseAllAction extends Action { - - constructor(private viewer: _.ITree, enabled: boolean) { - super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'monaco-tree-action collapse-all', enabled); - } - - public run(context?: any): Promise { - if (this.viewer.getHighlight()) { - return Promise.resolve(); // Global action disabled if user is in edit mode from another action - } - - this.viewer.collapseAll(); - this.viewer.clearSelection(); - this.viewer.clearFocus(); - this.viewer.domFocus(); - this.viewer.focusFirst(); - - return Promise.resolve(); - } -} diff --git a/src/vs/base/parts/tree/browser/treeDnd.ts b/src/vs/base/parts/tree/browser/treeDnd.ts deleted file mode 100644 index 810e6ce98ee..00000000000 --- a/src/vs/base/parts/tree/browser/treeDnd.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as _ from 'vs/base/parts/tree/browser/tree'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; - -export class ElementsDragAndDropData implements IDragAndDropData { - - private elements: any[]; - - constructor(elements: any[]) { - this.elements = elements; - } - - public update(dataTransfer: DataTransfer): void { - // no-op - } - - public getData(): any { - return this.elements; - } -} - -export class ExternalElementsDragAndDropData implements IDragAndDropData { - - private elements: any[]; - - constructor(elements: any[]) { - this.elements = elements; - } - - public update(dataTransfer: DataTransfer): void { - // no-op - } - - public getData(): any { - return this.elements; - } -} - -export class DesktopDragAndDropData implements IDragAndDropData { - - private types: any[]; - private files: any[]; - - constructor() { - this.types = []; - this.files = []; - } - - public update(dataTransfer: DataTransfer): void { - if (dataTransfer.types) { - this.types = []; - Array.prototype.push.apply(this.types, dataTransfer.types as any); - } - - if (dataTransfer.files) { - this.files = []; - Array.prototype.push.apply(this.files, dataTransfer.files as any); - - this.files = this.files.filter(f => f.size || f.type); - } - } - - public getData(): any { - return { - types: this.types, - files: this.files - }; - } -} \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/treeImpl.ts b/src/vs/base/parts/tree/browser/treeImpl.ts deleted file mode 100644 index 8b8f7b28078..00000000000 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ /dev/null @@ -1,275 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./tree'; -import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults'; -import * as Model from 'vs/base/parts/tree/browser/treeModel'; -import * as View from './treeView'; -import * as _ from 'vs/base/parts/tree/browser/tree'; -import { INavigator, MappedNavigator } from 'vs/base/common/iterator'; -import { Event, Emitter, Relay } from 'vs/base/common/event'; -import { Color } from 'vs/base/common/color'; -import { mixin } from 'vs/base/common/objects'; - -export class TreeContext implements _.ITreeContext { - - public tree: _.ITree; - public configuration: _.ITreeConfiguration; - public options: _.ITreeOptions; - - public dataSource: _.IDataSource; - public renderer?: _.IRenderer; - public controller: _.IController; - public dnd: _.IDragAndDrop; - public filter: _.IFilter; - public sorter?: _.ISorter; - public accessibilityProvider: _.IAccessibilityProvider; - public styler?: _.ITreeStyler; - - constructor(tree: _.ITree, configuration: _.ITreeConfiguration, options: _.ITreeOptions = {}) { - this.tree = tree; - this.configuration = configuration; - this.options = options; - - if (!configuration.dataSource) { - throw new Error('You must provide a Data Source to the tree.'); - } - - this.dataSource = configuration.dataSource; - this.renderer = configuration.renderer; - this.controller = configuration.controller || new TreeDefaults.DefaultController({ clickBehavior: TreeDefaults.ClickBehavior.ON_MOUSE_UP, keyboardSupport: typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport }); - this.dnd = configuration.dnd || new TreeDefaults.DefaultDragAndDrop(); - this.filter = configuration.filter || new TreeDefaults.DefaultFilter(); - this.sorter = configuration.sorter; - this.accessibilityProvider = configuration.accessibilityProvider || new TreeDefaults.DefaultAccessibilityProvider(); - this.styler = configuration.styler; - } -} - -const defaultStyles: _.ITreeStyles = { - listFocusBackground: Color.fromHex('#073655'), - listActiveSelectionBackground: Color.fromHex('#0E639C'), - listActiveSelectionForeground: Color.fromHex('#FFFFFF'), - listFocusAndSelectionBackground: Color.fromHex('#094771'), - listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'), - listInactiveSelectionBackground: Color.fromHex('#3F3F46'), - listHoverBackground: Color.fromHex('#2A2D2E'), - listDropBackground: Color.fromHex('#383B3D') -}; - -export class Tree implements _.ITree { - - private container: HTMLElement; - - private context: _.ITreeContext; - private model: Model.TreeModel; - private view: View.TreeView; - - private _onDidChangeFocus = new Relay<_.IFocusEvent>(); - readonly onDidChangeFocus: Event<_.IFocusEvent> = this._onDidChangeFocus.event; - private _onDidChangeSelection = new Relay<_.ISelectionEvent>(); - readonly onDidChangeSelection: Event<_.ISelectionEvent> = this._onDidChangeSelection.event; - private _onHighlightChange = new Relay<_.IHighlightEvent>(); - readonly onDidChangeHighlight: Event<_.IHighlightEvent> = this._onHighlightChange.event; - private _onDidExpandItem = new Relay(); - readonly onDidExpandItem: Event = this._onDidExpandItem.event; - private _onDidCollapseItem = new Relay(); - readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - private readonly _onDispose = new Emitter(); - readonly onDidDispose: Event = this._onDispose.event; - - constructor(container: HTMLElement, configuration: _.ITreeConfiguration, options: _.ITreeOptions = {}) { - this.container = container; - mixin(options, defaultStyles, false); - - options.twistiePixels = typeof options.twistiePixels === 'number' ? options.twistiePixels : 32; - options.showTwistie = options.showTwistie === false ? false : true; - options.indentPixels = typeof options.indentPixels === 'number' ? options.indentPixels : 12; - options.alwaysFocused = options.alwaysFocused === true ? true : false; - options.useShadows = options.useShadows === false ? false : true; - options.paddingOnRow = options.paddingOnRow === false ? false : true; - options.showLoading = options.showLoading === false ? false : true; - - this.context = new TreeContext(this, configuration, options); - this.model = new Model.TreeModel(this.context); - this.view = new View.TreeView(this.context, this.container); - - this.view.setModel(this.model); - - this._onDidChangeFocus.input = this.model.onDidFocus; - this._onDidChangeSelection.input = this.model.onDidSelect; - this._onHighlightChange.input = this.model.onDidHighlight; - this._onDidExpandItem.input = this.model.onDidExpandItem; - this._onDidCollapseItem.input = this.model.onDidCollapseItem; - } - - public style(styles: _.ITreeStyles): void { - this.view.applyStyles(styles); - } - - get onDidFocus(): Event { - return this.view.onDOMFocus; - } - - get onDidBlur(): Event { - return this.view.onDOMBlur; - } - - get onDidScroll(): Event { - return this.view.onDidScroll; - } - - public getHTMLElement(): HTMLElement { - return this.view.getHTMLElement(); - } - - public layout(height?: number, width?: number): void { - this.view.layout(height, width); - } - - public domFocus(): void { - this.view.focus(); - } - - public isDOMFocused(): boolean { - return this.view.isFocused(); - } - - public domBlur(): void { - this.view.blur(); - } - - public onVisible(): void { - this.view.onVisible(); - } - - public onHidden(): void { - this.view.onHidden(); - } - - public setInput(element: any): Promise { - return this.model.setInput(element); - } - - public getInput(): any { - return this.model.getInput(); - } - - public refresh(element: any = null, recursive = true): Promise { - return this.model.refresh(element, recursive); - } - - public expand(element: any): Promise { - return this.model.expand(element); - } - - public expandAll(elements: any[]): Promise { - return this.model.expandAll(elements); - } - - public collapse(element: any, recursive: boolean = false): Promise { - return this.model.collapse(element, recursive); - } - - public collapseAll(elements: any[] | null = null, recursive: boolean = false): Promise { - return this.model.collapseAll(elements, recursive); - } - - public toggleExpansion(element: any, recursive: boolean = false): Promise { - return this.model.toggleExpansion(element, recursive); - } - - public isExpanded(element: any): boolean { - return this.model.isExpanded(element); - } - - public reveal(element: any, relativeTop: number | null = null): Promise { - return this.model.reveal(element, relativeTop); - } - - public getHighlight(): any { - return this.model.getHighlight(); - } - - public clearHighlight(eventPayload?: any): void { - this.model.setHighlight(null, eventPayload); - } - - public setSelection(elements: any[], eventPayload?: any): void { - this.model.setSelection(elements, eventPayload); - } - - public getSelection(): any[] { - return this.model.getSelection(); - } - - public clearSelection(eventPayload?: any): void { - this.model.setSelection([], eventPayload); - } - - public setFocus(element?: any, eventPayload?: any): void { - this.model.setFocus(element, eventPayload); - } - - public getFocus(): any { - return this.model.getFocus(); - } - - public focusNext(count?: number, eventPayload?: any): void { - this.model.focusNext(count, eventPayload); - } - - public focusPrevious(count?: number, eventPayload?: any): void { - this.model.focusPrevious(count, eventPayload); - } - - public focusParent(eventPayload?: any): void { - this.model.focusParent(eventPayload); - } - - public focusFirstChild(eventPayload?: any): void { - this.model.focusFirstChild(eventPayload); - } - - public focusFirst(eventPayload?: any, from?: any): void { - this.model.focusFirst(eventPayload, from); - } - - public focusNth(index: number, eventPayload?: any): void { - this.model.focusNth(index, eventPayload); - } - - public focusLast(eventPayload?: any, from?: any): void { - this.model.focusLast(eventPayload, from); - } - - public focusNextPage(eventPayload?: any): void { - this.view.focusNextPage(eventPayload); - } - - public focusPreviousPage(eventPayload?: any): void { - this.view.focusPreviousPage(eventPayload); - } - - public clearFocus(eventPayload?: any): void { - this.model.setFocus(null, eventPayload); - } - - getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator { - return new MappedNavigator(this.model.getNavigator(fromElement, subTreeOnly), i => i && i.getElement()); - } - - public dispose(): void { - this._onDispose.fire(); - this.model.dispose(); - this.view.dispose(); - this._onDidChangeFocus.dispose(); - this._onDidChangeSelection.dispose(); - this._onHighlightChange.dispose(); - this._onDidExpandItem.dispose(); - this._onDidCollapseItem.dispose(); - this._onDispose.dispose(); - } -} diff --git a/src/vs/base/parts/tree/browser/treeModel.ts b/src/vs/base/parts/tree/browser/treeModel.ts deleted file mode 100644 index 6d2999bf7e7..00000000000 --- a/src/vs/base/parts/tree/browser/treeModel.ts +++ /dev/null @@ -1,1494 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as Assert from 'vs/base/common/assert'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { INavigator } from 'vs/base/common/iterator'; -import * as _ from './tree'; -import { Event, Emitter, EventMultiplexer, Relay } from 'vs/base/common/event'; - -interface IMap { [id: string]: T; } -interface IItemMap extends IMap { } -interface ITraitMap extends IMap { } - -export class LockData { - - private _item: Item; - private _onDispose?= new Emitter(); - readonly onDispose: Event = this._onDispose!.event; - - constructor(item: Item) { - this._item = item; - } - - get item(): Item { - return this._item; - } - - dispose(): void { - if (this._onDispose) { - this._onDispose.fire(); - this._onDispose.dispose(); - this._onDispose = undefined; - } - } -} - -export class Lock { - - /* When refreshing tree items, the tree's structured can be altered, by - inserting and removing sub-items. This lock helps to manage several - possibly-structure-changing calls. - - API-wise, there are two possibly-structure-changing: refresh(...), - expand(...) and collapse(...). All these calls must call Lock#run(...). - - Any call to Lock#run(...) needs to provide the affecting item and a - callback to execute when unlocked. It must also return a promise - which fulfills once the operation ends. Once it is called, there - are three possibilities: - - - Nothing is currently running. The affecting item is remembered, and - the callback is executed. - - - Or, there are on-going operations. There are two outcomes: - - - The affecting item intersects with any other affecting items - of on-going run calls. In such a case, the given callback should - be executed only when the on-going one completes. - - - Or, it doesn't. In such a case, both operations can be run in - parallel. - - Note: two items A and B intersect if A is a descendant of B, or - vice-versa. - */ - - private locks: { [id: string]: LockData; }; - - constructor() { - this.locks = Object.create({}); - } - - public isLocked(item: Item): boolean { - return !!this.locks[item.id]; - } - - public run(item: Item, fn: () => Promise): Promise { - const lock = this.getLock(item); - - if (lock) { - return new Promise((c, e) => { - Event.once(lock.onDispose)(() => { - return this.run(item, fn).then(c, e); - }); - }); - } - - let result: Promise; - - return new Promise((c, e) => { - - if (item.isDisposed()) { - return e(new Error('Item is disposed.')); - } - - let lock = this.locks[item.id] = new LockData(item); - - result = fn().then((r) => { - delete this.locks[item.id]; - lock.dispose(); - - return r; - }).then(c, e); - - return result; - }); - } - - private getLock(item: Item): LockData | null { - let key: string; - - for (key in this.locks) { - let lock = this.locks[key]; - - if (item.intersects(lock.item)) { - return lock; - } - } - - return null; - } -} - -export class ItemRegistry { - - private _isDisposed = false; - private items: IMap<{ item: Item; disposable: IDisposable; }>; - - private _onDidRevealItem = new EventMultiplexer(); - readonly onDidRevealItem: Event = this._onDidRevealItem.event; - private _onExpandItem = new EventMultiplexer(); - readonly onExpandItem: Event = this._onExpandItem.event; - private _onDidExpandItem = new EventMultiplexer(); - readonly onDidExpandItem: Event = this._onDidExpandItem.event; - private _onCollapseItem = new EventMultiplexer(); - readonly onCollapseItem: Event = this._onCollapseItem.event; - private _onDidCollapseItem = new EventMultiplexer(); - readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - private _onDidAddTraitItem = new EventMultiplexer(); - readonly onDidAddTraitItem: Event = this._onDidAddTraitItem.event; - private _onDidRemoveTraitItem = new EventMultiplexer(); - readonly onDidRemoveTraitItem: Event = this._onDidRemoveTraitItem.event; - private _onDidRefreshItem = new EventMultiplexer(); - readonly onDidRefreshItem: Event = this._onDidRefreshItem.event; - private _onRefreshItemChildren = new EventMultiplexer(); - readonly onRefreshItemChildren: Event = this._onRefreshItemChildren.event; - private _onDidRefreshItemChildren = new EventMultiplexer(); - readonly onDidRefreshItemChildren: Event = this._onDidRefreshItemChildren.event; - private _onDidDisposeItem = new EventMultiplexer(); - readonly onDidDisposeItem: Event = this._onDidDisposeItem.event; - - constructor() { - this.items = {}; - } - - public register(item: Item): void { - Assert.ok(!this.isRegistered(item.id), 'item already registered: ' + item.id); - - const disposable = combinedDisposable( - this._onDidRevealItem.add(item.onDidReveal), - this._onExpandItem.add(item.onExpand), - this._onDidExpandItem.add(item.onDidExpand), - this._onCollapseItem.add(item.onCollapse), - this._onDidCollapseItem.add(item.onDidCollapse), - this._onDidAddTraitItem.add(item.onDidAddTrait), - this._onDidRemoveTraitItem.add(item.onDidRemoveTrait), - this._onDidRefreshItem.add(item.onDidRefresh), - this._onRefreshItemChildren.add(item.onRefreshChildren), - this._onDidRefreshItemChildren.add(item.onDidRefreshChildren), - this._onDidDisposeItem.add(item.onDidDispose) - ); - - this.items[item.id] = { item, disposable }; - } - - public deregister(item: Item): void { - Assert.ok(this.isRegistered(item.id), 'item not registered: ' + item.id); - this.items[item.id].disposable.dispose(); - delete this.items[item.id]; - } - - public isRegistered(id: string): boolean { - return this.items.hasOwnProperty(id); - } - - public getItem(id: string): Item | null { - const result = this.items[id]; - return result ? result.item : null; - } - - public dispose(): void { - this.items = {}; - - this._onDidRevealItem.dispose(); - this._onExpandItem.dispose(); - this._onDidExpandItem.dispose(); - this._onCollapseItem.dispose(); - this._onDidCollapseItem.dispose(); - this._onDidAddTraitItem.dispose(); - this._onDidRemoveTraitItem.dispose(); - this._onDidRefreshItem.dispose(); - this._onRefreshItemChildren.dispose(); - this._onDidRefreshItemChildren.dispose(); - - this._isDisposed = true; - } - - public isDisposed(): boolean { - return this._isDisposed; - } -} - -export interface IBaseItemEvent { - item: Item; -} - -export interface IItemRefreshEvent extends IBaseItemEvent { } -export interface IItemExpandEvent extends IBaseItemEvent { } -export interface IItemCollapseEvent extends IBaseItemEvent { } - -export interface IItemTraitEvent extends IBaseItemEvent { - trait: string; -} - -export interface IItemRevealEvent extends IBaseItemEvent { - relativeTop: number | null; -} - -export interface IItemChildrenRefreshEvent extends IBaseItemEvent { - isNested: boolean; -} - -export class Item { - - private registry: ItemRegistry; - private context: _.ITreeContext; - private element: any; - private lock: Lock; - - public id: string; - - private needsChildrenRefresh: boolean; - private doesHaveChildren: boolean; - - public parent: Item | null; - public previous: Item | null; - public next: Item | null; - public firstChild: Item | null; - public lastChild: Item | null; - - private height: number; - private depth: number; - - private visible: boolean; - private expanded: boolean; - - private traits: { [trait: string]: boolean; }; - - private readonly _onDidCreate = new Emitter(); - readonly onDidCreate: Event = this._onDidCreate.event; - private readonly _onDidReveal = new Emitter(); - readonly onDidReveal: Event = this._onDidReveal.event; - private readonly _onExpand = new Emitter(); - readonly onExpand: Event = this._onExpand.event; - private readonly _onDidExpand = new Emitter(); - readonly onDidExpand: Event = this._onDidExpand.event; - private readonly _onCollapse = new Emitter(); - readonly onCollapse: Event = this._onCollapse.event; - private readonly _onDidCollapse = new Emitter(); - readonly onDidCollapse: Event = this._onDidCollapse.event; - private readonly _onDidAddTrait = new Emitter(); - readonly onDidAddTrait: Event = this._onDidAddTrait.event; - private readonly _onDidRemoveTrait = new Emitter(); - readonly onDidRemoveTrait: Event = this._onDidRemoveTrait.event; - private readonly _onDidRefresh = new Emitter(); - readonly onDidRefresh: Event = this._onDidRefresh.event; - private readonly _onRefreshChildren = new Emitter(); - readonly onRefreshChildren: Event = this._onRefreshChildren.event; - private readonly _onDidRefreshChildren = new Emitter(); - readonly onDidRefreshChildren: Event = this._onDidRefreshChildren.event; - private readonly _onDidDispose = new Emitter(); - readonly onDidDispose: Event = this._onDidDispose.event; - - private _isDisposed: boolean; - - constructor(id: string, registry: ItemRegistry, context: _.ITreeContext, lock: Lock, element: any) { - this.registry = registry; - this.context = context; - this.lock = lock; - this.element = element; - - this.id = id; - this.registry.register(this); - - this.doesHaveChildren = this.context.dataSource.hasChildren(this.context.tree, this.element); - this.needsChildrenRefresh = true; - - this.parent = null; - this.previous = null; - this.next = null; - this.firstChild = null; - this.lastChild = null; - - this.traits = {}; - this.depth = 0; - this.expanded = !!(this.context.dataSource.shouldAutoexpand && this.context.dataSource.shouldAutoexpand(this.context.tree, element)); - - this._onDidCreate.fire(this); - - this.visible = this._isVisible(); - this.height = this._getHeight(); - - this._isDisposed = false; - } - - public getElement(): any { - return this.element; - } - - public hasChildren(): boolean { - return this.doesHaveChildren; - } - - public getDepth(): number { - return this.depth; - } - - public isVisible(): boolean { - return this.visible; - } - - public setVisible(value: boolean): void { - this.visible = value; - } - - public isExpanded(): boolean { - return this.expanded; - } - - /* protected */ public _setExpanded(value: boolean): void { - this.expanded = value; - } - - public reveal(relativeTop: number | null = null): void { - let eventData: IItemRevealEvent = { item: this, relativeTop: relativeTop }; - this._onDidReveal.fire(eventData); - } - - public expand(): Promise { - if (this.isExpanded() || !this.doesHaveChildren || this.lock.isLocked(this)) { - return Promise.resolve(false); - } - - let result = this.lock.run(this, () => { - if (this.isExpanded() || !this.doesHaveChildren) { - return Promise.resolve(false); - } - - let eventData: IItemExpandEvent = { item: this }; - let result: Promise; - this._onExpand.fire(eventData); - - if (this.needsChildrenRefresh) { - result = this.refreshChildren(false, true, true); - } else { - result = Promise.resolve(null); - } - - return result.then(() => { - this._setExpanded(true); - this._onDidExpand.fire(eventData); - return true; - }); - }); - - return result.then((r) => { - if (this.isDisposed()) { - return false; - } - - // Auto expand single child folders - if (this.context.options.autoExpandSingleChildren && r && this.firstChild !== null && this.firstChild === this.lastChild && this.firstChild.isVisible()) { - return this.firstChild.expand().then(() => { return true; }); - } - - return r; - }); - } - - public collapse(recursive: boolean = false): Promise { - if (recursive) { - let collapseChildrenPromise = Promise.resolve(null); - this.forEachChild((child) => { - collapseChildrenPromise = collapseChildrenPromise.then(() => child.collapse(true)); - }); - return collapseChildrenPromise.then(() => { - return this.collapse(false); - }); - } else { - if (!this.isExpanded() || this.lock.isLocked(this)) { - return Promise.resolve(false); - } - - return this.lock.run(this, () => { - let eventData: IItemCollapseEvent = { item: this }; - this._onCollapse.fire(eventData); - this._setExpanded(false); - this._onDidCollapse.fire(eventData); - - return Promise.resolve(true); - }); - } - } - - public addTrait(trait: string): void { - let eventData: IItemTraitEvent = { item: this, trait: trait }; - this.traits[trait] = true; - this._onDidAddTrait.fire(eventData); - } - - public removeTrait(trait: string): void { - let eventData: IItemTraitEvent = { item: this, trait: trait }; - delete this.traits[trait]; - this._onDidRemoveTrait.fire(eventData); - } - - public hasTrait(trait: string): boolean { - return this.traits[trait] || false; - } - - public getAllTraits(): string[] { - let result: string[] = []; - let trait: string; - for (trait in this.traits) { - if (this.traits.hasOwnProperty(trait) && this.traits[trait]) { - result.push(trait); - } - } - return result; - } - - public getHeight(): number { - return this.height; - } - - private refreshChildren(recursive: boolean, safe: boolean = false, force: boolean = false): Promise { - if (!force && !this.isExpanded()) { - const setNeedsChildrenRefresh = (item: Item) => { - item.needsChildrenRefresh = true; - item.forEachChild(setNeedsChildrenRefresh); - }; - - setNeedsChildrenRefresh(this); - - return Promise.resolve(this); - } - - this.needsChildrenRefresh = false; - - let doRefresh = () => { - let eventData: IItemChildrenRefreshEvent = { item: this, isNested: safe }; - this._onRefreshChildren.fire(eventData); - - let childrenPromise: Promise; - if (this.doesHaveChildren) { - childrenPromise = this.context.dataSource.getChildren(this.context.tree, this.element); - } else { - childrenPromise = Promise.resolve([]); - } - - const result = childrenPromise.then((elements: any[]) => { - if (this.isDisposed() || this.registry.isDisposed()) { - return Promise.resolve(null); - } - - if (!Array.isArray(elements)) { - return Promise.reject(new Error('Please return an array of children.')); - } - - elements = !elements ? [] : elements.slice(0); - elements = this.sort(elements); - - let staleItems: IItemMap = {}; - while (this.firstChild !== null) { - staleItems[this.firstChild.id] = this.firstChild; - this.removeChild(this.firstChild); - } - - for (let i = 0, len = elements.length; i < len; i++) { - let element = elements[i]; - let id = this.context.dataSource.getId(this.context.tree, element); - let item = staleItems[id] || new Item(id, this.registry, this.context, this.lock, element); - item.element = element; - if (recursive) { - item.needsChildrenRefresh = recursive; - } - delete staleItems[id]; - this.addChild(item); - } - - for (let staleItemId in staleItems) { - if (staleItems.hasOwnProperty(staleItemId)) { - staleItems[staleItemId].dispose(); - } - } - - if (recursive) { - return Promise.all(this.mapEachChild((child) => { - return child.doRefresh(recursive, true); - })); - } else { - return Promise.all(this.mapEachChild((child) => { - if (child.isExpanded() && child.needsChildrenRefresh) { - return child.doRefresh(recursive, true); - } else { - child.updateVisibility(); - return Promise.resolve(null); - } - })); - } - }); - - return result - .then(undefined, onUnexpectedError) - .then(() => this._onDidRefreshChildren.fire(eventData)); - }; - - return safe ? doRefresh() : this.lock.run(this, doRefresh); - } - - private doRefresh(recursive: boolean, safe: boolean = false): Promise { - this.doesHaveChildren = this.context.dataSource.hasChildren(this.context.tree, this.element); - this.height = this._getHeight(); - this.updateVisibility(); - - this._onDidRefresh.fire(this); - - return this.refreshChildren(recursive, safe); - } - - private updateVisibility(): void { - this.setVisible(this._isVisible()); - } - - public refresh(recursive: boolean): Promise { - return this.doRefresh(recursive); - } - - public getNavigator(): INavigator { - return new TreeNavigator(this); - } - - public intersects(other: Item): boolean { - return this.isAncestorOf(other) || other.isAncestorOf(this); - } - - private isAncestorOf(startItem: Item): boolean { - let item: Item | null = startItem; - while (item) { - if (item.id === this.id) { - return true; - } - item = item.parent; - } - return false; - } - - private addChild(item: Item, afterItem: Item | null = this.lastChild): void { - let isEmpty = this.firstChild === null; - let atHead = afterItem === null; - let atTail = afterItem === this.lastChild; - - if (isEmpty) { - this.firstChild = this.lastChild = item; - item.next = item.previous = null; - } else if (atHead) { - if (!this.firstChild) { - throw new Error('Invalid tree state'); - } - this.firstChild.previous = item; - item.next = this.firstChild; - item.previous = null; - this.firstChild = item; - } else if (atTail) { - if (!this.lastChild) { - throw new Error('Invalid tree state'); - } - this.lastChild.next = item; - item.next = null; - item.previous = this.lastChild; - this.lastChild = item; - } else { - item.previous = afterItem; - if (!afterItem) { - throw new Error('Invalid tree state'); - } - item.next = afterItem.next; - if (!afterItem.next) { - throw new Error('Invalid tree state'); - } - afterItem.next.previous = item; - afterItem.next = item; - } - - item.parent = this; - item.depth = this.depth + 1; - } - - private removeChild(item: Item): void { - let isFirstChild = this.firstChild === item; - let isLastChild = this.lastChild === item; - - if (isFirstChild && isLastChild) { - this.firstChild = this.lastChild = null; - } else if (isFirstChild) { - if (!item.next) { - throw new Error('Invalid tree state'); - } - item.next.previous = null; - this.firstChild = item.next; - } else if (isLastChild) { - if (!item.previous) { - throw new Error('Invalid tree state'); - } - item.previous.next = null; - this.lastChild = item.previous; - } else { - if (!item.next) { - throw new Error('Invalid tree state'); - } - item.next.previous = item.previous; - if (!item.previous) { - throw new Error('Invalid tree state'); - } - item.previous.next = item.next; - } - - item.parent = null; - item.depth = NaN; - } - - private forEachChild(fn: (child: Item) => void): void { - let child = this.firstChild; - let next: Item | null; - while (child) { - next = child.next; - fn(child); - child = next; - } - } - - private mapEachChild(fn: (child: Item) => T): T[] { - let result: T[] = []; - this.forEachChild((child) => { - result.push(fn(child)); - }); - return result; - } - - private sort(elements: any[]): any[] { - const sorter = this.context.sorter; - if (sorter) { - return elements.sort((element, otherElement) => { - return sorter.compare(this.context.tree, element, otherElement); - }); - } - - return elements; - } - - /* protected */ public _getHeight(): number { - if (!this.context.renderer) { - return 0; - } - return this.context.renderer.getHeight(this.context.tree, this.element); - } - - /* protected */ public _isVisible(): boolean { - if (!this.context.filter) { - return false; - } - return this.context.filter.isVisible(this.context.tree, this.element); - } - - public isDisposed(): boolean { - return this._isDisposed; - } - - public dispose(): void { - this.forEachChild((child) => child.dispose()); - - this.parent = null; - this.previous = null; - this.next = null; - this.firstChild = null; - this.lastChild = null; - - this._onDidDispose.fire(this); - - this.registry.deregister(this); - - this._onDidCreate.dispose(); - this._onDidReveal.dispose(); - this._onExpand.dispose(); - this._onDidExpand.dispose(); - this._onCollapse.dispose(); - this._onDidCollapse.dispose(); - this._onDidAddTrait.dispose(); - this._onDidRemoveTrait.dispose(); - this._onDidRefresh.dispose(); - this._onRefreshChildren.dispose(); - this._onDidRefreshChildren.dispose(); - this._onDidDispose.dispose(); - - this._isDisposed = true; - } -} - -class RootItem extends Item { - - constructor(id: string, registry: ItemRegistry, context: _.ITreeContext, lock: Lock, element: any) { - super(id, registry, context, lock, element); - } - - public isVisible(): boolean { - return false; - } - - public setVisible(value: boolean): void { - // no-op - } - - public isExpanded(): boolean { - return true; - } - - /* protected */ public _setExpanded(value: boolean): void { - // no-op - } - - public render(): void { - // no-op - } - - /* protected */ public _getHeight(): number { - return 0; - } - - /* protected */ public _isVisible(): boolean { - return false; - } -} - -export class TreeNavigator implements INavigator { - - private start: Item | null; - private item: Item | null; - - static lastDescendantOf(item: Item | null): Item | null { - if (!item) { - return null; - } - - if (item instanceof RootItem) { - return TreeNavigator.lastDescendantOf(item.lastChild); - } - - if (!item.isVisible()) { - return TreeNavigator.lastDescendantOf(item.previous); - } - - if (!item.isExpanded() || item.lastChild === null) { - return item; - } - - return TreeNavigator.lastDescendantOf(item.lastChild); - } - - constructor(item: Item | null, subTreeOnly: boolean = true) { - this.item = item; - this.start = subTreeOnly ? item : null; - } - - public current(): Item | null { - return this.item || null; - } - - public next(): Item | null { - if (this.item) { - do { - if ((this.item instanceof RootItem || (this.item.isVisible() && this.item.isExpanded())) && this.item.firstChild) { - this.item = this.item.firstChild; - } else if (this.item === this.start) { - this.item = null; - } else { - // select next brother, next uncle, next great-uncle, etc... - while (this.item && this.item !== this.start && !this.item.next) { - this.item = this.item.parent; - } - if (this.item === this.start) { - this.item = null; - } - this.item = !this.item ? null : this.item.next; - } - } while (this.item && !this.item.isVisible()); - } - return this.item || null; - } - - public previous(): Item | null { - if (this.item) { - do { - let previous = TreeNavigator.lastDescendantOf(this.item.previous); - if (previous) { - this.item = previous; - } else if (this.item.parent && this.item.parent !== this.start && this.item.parent.isVisible()) { - this.item = this.item.parent; - } else { - this.item = null; - } - } while (this.item && !this.item.isVisible()); - } - return this.item || null; - } - - public parent(): Item | null { - if (this.item) { - let parent = this.item.parent; - if (parent && parent !== this.start && parent.isVisible()) { - this.item = parent; - } else { - this.item = null; - } - } - return this.item || null; - } - - public first(): Item | null { - this.item = this.start; - this.next(); - return this.item || null; - } - - public last(): Item | null { - return TreeNavigator.lastDescendantOf(this.start); - } -} - -export interface IBaseEvent { - item: Item | null; -} - -export interface IInputEvent extends IBaseEvent { } - -export interface IRefreshEvent extends IBaseEvent { - recursive: boolean; -} - -export class TreeModel { - - private context: _.ITreeContext; - private lock!: Lock; - private input: Item | null; - private registry: ItemRegistry = new ItemRegistry(); - private registryDisposable: IDisposable = Disposable.None; - private traitsToItems: ITraitMap; - - private readonly _onSetInput = new Emitter(); - readonly onSetInput: Event = this._onSetInput.event; - private readonly _onDidSetInput = new Emitter(); - readonly onDidSetInput: Event = this._onDidSetInput.event; - private readonly _onRefresh = new Emitter(); - readonly onRefresh: Event = this._onRefresh.event; - private readonly _onDidRefresh = new Emitter(); - readonly onDidRefresh: Event = this._onDidRefresh.event; - private readonly _onDidHighlight = new Emitter<_.IHighlightEvent>(); - readonly onDidHighlight: Event<_.IHighlightEvent> = this._onDidHighlight.event; - private readonly _onDidSelect = new Emitter<_.ISelectionEvent>(); - readonly onDidSelect: Event<_.ISelectionEvent> = this._onDidSelect.event; - private readonly _onDidFocus = new Emitter<_.IFocusEvent>(); - readonly onDidFocus: Event<_.IFocusEvent> = this._onDidFocus.event; - - private _onDidRevealItem = new Relay(); - readonly onDidRevealItem: Event = this._onDidRevealItem.event; - private _onExpandItem = new Relay(); - readonly onExpandItem: Event = this._onExpandItem.event; - private _onDidExpandItem = new Relay(); - readonly onDidExpandItem: Event = this._onDidExpandItem.event; - private _onCollapseItem = new Relay(); - readonly onCollapseItem: Event = this._onCollapseItem.event; - private _onDidCollapseItem = new Relay(); - readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - private _onDidAddTraitItem = new Relay(); - readonly onDidAddTraitItem: Event = this._onDidAddTraitItem.event; - private _onDidRemoveTraitItem = new Relay(); - readonly onDidRemoveTraitItem: Event = this._onDidRemoveTraitItem.event; - private _onDidRefreshItem = new Relay(); - readonly onDidRefreshItem: Event = this._onDidRefreshItem.event; - private _onRefreshItemChildren = new Relay(); - readonly onRefreshItemChildren: Event = this._onRefreshItemChildren.event; - private _onDidRefreshItemChildren = new Relay(); - readonly onDidRefreshItemChildren: Event = this._onDidRefreshItemChildren.event; - private _onDidDisposeItem = new Relay(); - readonly onDidDisposeItem: Event = this._onDidDisposeItem.event; - - constructor(context: _.ITreeContext) { - this.context = context; - this.input = null; - this.traitsToItems = {}; - } - - public setInput(element: any): Promise { - let eventData: IInputEvent = { item: this.input }; - this._onSetInput.fire(eventData); - - this.setSelection([]); - this.setFocus(); - this.setHighlight(); - - this.lock = new Lock(); - - if (this.input) { - this.input.dispose(); - } - - if (this.registry) { - this.registry.dispose(); - this.registryDisposable.dispose(); - } - - this.registry = new ItemRegistry(); - - this._onDidRevealItem.input = this.registry.onDidRevealItem; - this._onExpandItem.input = this.registry.onExpandItem; - this._onDidExpandItem.input = this.registry.onDidExpandItem; - this._onCollapseItem.input = this.registry.onCollapseItem; - this._onDidCollapseItem.input = this.registry.onDidCollapseItem; - this._onDidAddTraitItem.input = this.registry.onDidAddTraitItem; - this._onDidRemoveTraitItem.input = this.registry.onDidRemoveTraitItem; - this._onDidRefreshItem.input = this.registry.onDidRefreshItem; - this._onRefreshItemChildren.input = this.registry.onRefreshItemChildren; - this._onDidRefreshItemChildren.input = this.registry.onDidRefreshItemChildren; - this._onDidDisposeItem.input = this.registry.onDidDisposeItem; - - this.registryDisposable = this.registry - .onDidDisposeItem(item => item.getAllTraits().forEach(trait => delete this.traitsToItems[trait][item.id])); - - let id = this.context.dataSource.getId(this.context.tree, element); - this.input = new RootItem(id, this.registry, this.context, this.lock, element); - eventData = { item: this.input }; - this._onDidSetInput.fire(eventData); - return this.refresh(this.input); - } - - public getInput(): any { - return this.input ? this.input.getElement() : null; - } - - public refresh(element: any = null, recursive: boolean = true): Promise { - let item = this.getItem(element); - - if (!item) { - return Promise.resolve(null); - } - - let eventData: IRefreshEvent = { item: item, recursive: recursive }; - this._onRefresh.fire(eventData); - return item.refresh(recursive).then(() => { - this._onDidRefresh.fire(eventData); - }); - } - - public expand(element: any): Promise { - let item = this.getItem(element); - - if (!item) { - return Promise.resolve(false); - } - - return item.expand(); - } - - public expandAll(elements?: any[]): Promise { - if (!elements) { - elements = []; - - let item: Item | null; - let nav = this.getNavigator(); - - while (item = nav.next()) { - elements.push(item); - } - } - - return this._expandAll(elements); - } - - private _expandAll(elements: any[]): Promise { - if (elements.length === 0) { - return Promise.resolve(null); - } - - const elementsToExpand: any[] = []; - const elementsToDelay: any[] = []; - - for (const element of elements) { - let item = this.getItem(element); - - if (item) { - elementsToExpand.push(element); - } else { - elementsToDelay.push(element); - } - } - - if (elementsToExpand.length === 0) { - return Promise.resolve(null); - } - - return this.__expandAll(elementsToExpand) - .then(() => this._expandAll(elementsToDelay)); - } - - private __expandAll(elements: any[]): Promise { - const promises: Array> = []; - for (let i = 0, len = elements.length; i < len; i++) { - promises.push(this.expand(elements[i])); - } - return Promise.all(promises); - } - - public collapse(element: any, recursive: boolean = false): Promise { - const item = this.getItem(element); - - if (!item) { - return Promise.resolve(false); - } - - return item.collapse(recursive); - } - - public collapseAll(elements: any[] | null = null, recursive: boolean = false): Promise { - if (!elements) { - elements = [this.input]; - recursive = true; - } - let promises: Array> = []; - for (let i = 0, len = elements.length; i < len; i++) { - promises.push(this.collapse(elements[i], recursive)); - } - return Promise.all(promises); - } - - public toggleExpansion(element: any, recursive: boolean = false): Promise { - return this.isExpanded(element) ? this.collapse(element, recursive) : this.expand(element); - } - - public toggleExpansionAll(elements: any[]): Promise { - let promises: Array> = []; - for (let i = 0, len = elements.length; i < len; i++) { - promises.push(this.toggleExpansion(elements[i])); - } - return Promise.all(promises); - } - - public isExpanded(element: any): boolean { - let item = this.getItem(element); - - if (!item) { - return false; - } - - return item.isExpanded(); - } - - public getExpandedElements(): any[] { - let result: any[] = []; - let item: Item | null; - let nav = this.getNavigator(); - - while (item = nav.next()) { - if (item.isExpanded()) { - result.push(item.getElement()); - } - } - - return result; - } - - public reveal(element: any, relativeTop: number | null = null): Promise { - return this.resolveUnknownParentChain(element).then((chain: any[]) => { - let result = Promise.resolve(null); - - chain.forEach((e) => { - result = result.then(() => this.expand(e)); - }); - - return result; - }).then(() => { - let item = this.getItem(element); - - if (item) { - return item.reveal(relativeTop); - } - }); - } - - private resolveUnknownParentChain(element: any): Promise { - return this.context.dataSource.getParent(this.context.tree, element).then((parent) => { - if (!parent) { - return Promise.resolve([]); - } - - return this.resolveUnknownParentChain(parent).then((result) => { - result.push(parent); - return result; - }); - }); - } - - public setHighlight(element?: any, eventPayload?: any): void { - this.setTraits('highlighted', element ? [element] : []); - let eventData: _.IHighlightEvent = { highlight: this.getHighlight(), payload: eventPayload }; - this._onDidHighlight.fire(eventData); - } - - public getHighlight(includeHidden: boolean = false): any { - let result = this.getElementsWithTrait('highlighted', includeHidden); - return result.length === 0 ? null : result[0]; - } - - public isHighlighted(element: any): boolean { - let item = this.getItem(element); - - if (!item) { - return false; - } - - return item.hasTrait('highlighted'); - } - - public select(element: any, eventPayload?: any): void { - this.selectAll([element], eventPayload); - } - - public selectAll(elements: any[], eventPayload?: any): void { - this.addTraits('selected', elements); - let eventData: _.ISelectionEvent = { selection: this.getSelection(), payload: eventPayload }; - this._onDidSelect.fire(eventData); - } - - public deselect(element: any, eventPayload?: any): void { - this.deselectAll([element], eventPayload); - } - - public deselectAll(elements: any[], eventPayload?: any): void { - this.removeTraits('selected', elements); - let eventData: _.ISelectionEvent = { selection: this.getSelection(), payload: eventPayload }; - this._onDidSelect.fire(eventData); - } - - public setSelection(elements: any[], eventPayload?: any): void { - this.setTraits('selected', elements); - let eventData: _.ISelectionEvent = { selection: this.getSelection(), payload: eventPayload }; - this._onDidSelect.fire(eventData); - } - - public isSelected(element: any): boolean { - let item = this.getItem(element); - - if (!item) { - return false; - } - - return item.hasTrait('selected'); - } - - public getSelection(includeHidden: boolean = false): any[] { - return this.getElementsWithTrait('selected', includeHidden); - } - - public selectNext(count: number = 1, clearSelection: boolean = true, eventPayload?: any): void { - let selection = this.getSelection(); - let item: Item = selection.length > 0 ? selection[0] : this.input; - let nextItem: Item | null; - let nav = this.getNavigator(item, false); - - for (let i = 0; i < count; i++) { - nextItem = nav.next(); - if (!nextItem) { - break; - } - item = nextItem; - } - - if (clearSelection) { - this.setSelection([item], eventPayload); - } else { - this.select(item, eventPayload); - } - } - - public selectPrevious(count: number = 1, clearSelection: boolean = true, eventPayload?: any): void { - let selection = this.getSelection(), - item: Item | null = null, - previousItem: Item | null = null; - - if (selection.length === 0) { - let nav = this.getNavigator(this.input); - - while (item = nav.next()) { - previousItem = item; - } - - item = previousItem; - - } else { - item = selection[0]; - let nav = this.getNavigator(item, false); - - for (let i = 0; i < count; i++) { - previousItem = nav.previous(); - if (!previousItem) { - break; - } - item = previousItem; - } - } - - if (clearSelection) { - this.setSelection([item], eventPayload); - } else { - this.select(item, eventPayload); - } - } - - public setFocus(element?: any, eventPayload?: any): void { - this.setTraits('focused', element ? [element] : []); - let eventData: _.IFocusEvent = { focus: this.getFocus(), payload: eventPayload }; - this._onDidFocus.fire(eventData); - } - - public isFocused(element: any): boolean { - let item = this.getItem(element); - - if (!item) { - return false; - } - - return item.hasTrait('focused'); - } - - public getFocus(includeHidden: boolean = false): any { - let result = this.getElementsWithTrait('focused', includeHidden); - return result.length === 0 ? null : result[0]; - } - - public focusNext(count: number = 1, eventPayload?: any): void { - let item: Item = this.getFocus() || this.input; - let nextItem: Item | null; - let nav = this.getNavigator(item, false); - - for (let i = 0; i < count; i++) { - nextItem = nav.next(); - if (!nextItem) { - break; - } - item = nextItem; - } - - this.setFocus(item, eventPayload); - } - - public focusPrevious(count: number = 1, eventPayload?: any): void { - let item: Item = this.getFocus() || this.input; - let previousItem: Item | null; - let nav = this.getNavigator(item, false); - - for (let i = 0; i < count; i++) { - previousItem = nav.previous(); - if (!previousItem) { - break; - } - item = previousItem; - } - - this.setFocus(item, eventPayload); - } - - public focusParent(eventPayload?: any): void { - let item: Item = this.getFocus() || this.input; - let nav = this.getNavigator(item, false); - let parent = nav.parent(); - - if (parent) { - this.setFocus(parent, eventPayload); - } - } - - public focusFirstChild(eventPayload?: any): void { - const item = this.getItem(this.getFocus() || this.input); - const nav = this.getNavigator(item, false); - const next = nav.next(); - const parent = nav.parent(); - - if (parent === item) { - this.setFocus(next, eventPayload); - } - } - - public focusFirst(eventPayload?: any, from?: any): void { - this.focusNth(0, eventPayload, from); - } - - public focusNth(index: number, eventPayload?: any, from?: any): void { - let navItem = this.getParent(from); - let nav = this.getNavigator(navItem); - let item = nav.first(); - for (let i = 0; i < index; i++) { - item = nav.next(); - } - - if (item) { - this.setFocus(item, eventPayload); - } - } - - public focusLast(eventPayload?: any, from?: any): void { - const navItem = this.getParent(from); - let item: Item | null; - if (from && navItem) { - item = navItem.lastChild; - } else { - const nav = this.getNavigator(navItem); - item = nav.last(); - } - - if (item) { - this.setFocus(item, eventPayload); - } - } - - private getParent(from?: any): Item | null { - if (from) { - const fromItem = this.getItem(from); - if (fromItem && fromItem.parent) { - return fromItem.parent; - } - } - - return this.getItem(this.input); - } - - public getNavigator(element: any = null, subTreeOnly: boolean = true): INavigator { - return new TreeNavigator(this.getItem(element), subTreeOnly); - } - - public getItem(element: any = null): Item | null { - if (element === null) { - return this.input; - } else if (element instanceof Item) { - return element; - } else if (typeof element === 'string') { - return this.registry.getItem(element); - } else { - return this.registry.getItem(this.context.dataSource.getId(this.context.tree, element)); - } - } - - public addTraits(trait: string, elements: any[]): void { - let items: IItemMap = this.traitsToItems[trait] || {}; - let item: Item | null; - for (let i = 0, len = elements.length; i < len; i++) { - item = this.getItem(elements[i]); - - if (item) { - item.addTrait(trait); - items[item.id] = item; - } - } - this.traitsToItems[trait] = items; - } - - public removeTraits(trait: string, elements: any[]): void { - let items: IItemMap = this.traitsToItems[trait] || {}; - let item: Item | null; - let id: string; - - if (elements.length === 0) { - for (id in items) { - if (items.hasOwnProperty(id)) { - item = items[id]; - item.removeTrait(trait); - } - } - - delete this.traitsToItems[trait]; - - } else { - for (let i = 0, len = elements.length; i < len; i++) { - item = this.getItem(elements[i]); - - if (item) { - item.removeTrait(trait); - delete items[item.id]; - } - } - } - } - - private setTraits(trait: string, elements: any[]): void { - if (elements.length === 0) { - this.removeTraits(trait, elements); - } else { - let items: { [id: string]: Item; } = {}; - let item: Item | null; - - for (let i = 0, len = elements.length; i < len; i++) { - item = this.getItem(elements[i]); - - if (item) { - items[item.id] = item; - } - } - - let traitItems: IItemMap = this.traitsToItems[trait] || {}; - let itemsToRemoveTrait: Item[] = []; - let id: string; - - for (id in traitItems) { - if (traitItems.hasOwnProperty(id)) { - if (items.hasOwnProperty(id)) { - delete items[id]; - } else { - itemsToRemoveTrait.push(traitItems[id]); - } - } - } - - for (let i = 0, len = itemsToRemoveTrait.length; i < len; i++) { - item = itemsToRemoveTrait[i]; - item.removeTrait(trait); - delete traitItems[item.id]; - } - - for (id in items) { - if (items.hasOwnProperty(id)) { - item = items[id]; - item.addTrait(trait); - traitItems[id] = item; - } - } - - this.traitsToItems[trait] = traitItems; - } - } - - private getElementsWithTrait(trait: string, includeHidden: boolean): any[] { - let elements: any[] = []; - let items = this.traitsToItems[trait] || {}; - let id: string; - for (id in items) { - if (items.hasOwnProperty(id) && (items[id].isVisible() || includeHidden)) { - elements.push(items[id].getElement()); - } - } - return elements; - } - - public dispose(): void { - this.registry.dispose(); - this._onSetInput.dispose(); - this._onDidSetInput.dispose(); - this._onRefresh.dispose(); - this._onDidRefresh.dispose(); - this._onDidHighlight.dispose(); - this._onDidSelect.dispose(); - this._onDidFocus.dispose(); - this._onDidRevealItem.dispose(); - this._onExpandItem.dispose(); - this._onDidExpandItem.dispose(); - this._onCollapseItem.dispose(); - this._onDidCollapseItem.dispose(); - this._onDidAddTraitItem.dispose(); - this._onDidRemoveTraitItem.dispose(); - this._onDidRefreshItem.dispose(); - this._onRefreshItemChildren.dispose(); - this._onDidRefreshItemChildren.dispose(); - this._onDidDisposeItem.dispose(); - } -} diff --git a/src/vs/base/parts/tree/browser/treeUtils.ts b/src/vs/base/parts/tree/browser/treeUtils.ts deleted file mode 100644 index 6d536a7fa5a..00000000000 --- a/src/vs/base/parts/tree/browser/treeUtils.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as _ from 'vs/base/parts/tree/browser/tree'; - -export function isEqualOrParent(tree: _.ITree, element: any, candidateParent: any): boolean { - const nav = tree.getNavigator(element); - - do { - if (element === candidateParent) { - return true; - } - } while (element = nav.parent()); - - return false; -} diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts deleted file mode 100644 index ae04970a579..00000000000 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ /dev/null @@ -1,1608 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as Platform from 'vs/base/common/platform'; -import * as Browser from 'vs/base/browser/browser'; -import * as Lifecycle from 'vs/base/common/lifecycle'; -import * as DOM from 'vs/base/browser/dom'; -import * as Diff from 'vs/base/common/diff/diff'; -import * as Touch from 'vs/base/browser/touch'; -import * as strings from 'vs/base/common/strings'; -import * as Mouse from 'vs/base/browser/mouseEvent'; -import * as Keyboard from 'vs/base/browser/keyboardEvent'; -import * as Model from 'vs/base/parts/tree/browser/treeModel'; -import * as dnd from './treeDnd'; -import { ArrayIterator, MappedIterator } from 'vs/base/common/iterator'; -import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { HeightMap, IViewItem } from 'vs/base/parts/tree/browser/treeViewModel'; -import * as _ from 'vs/base/parts/tree/browser/tree'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Event, Emitter } from 'vs/base/common/event'; -import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd'; -import { DefaultTreestyler } from './treeDefaults'; -import { Delayer, timeout } from 'vs/base/common/async'; - -export interface IRow { - element: HTMLElement | null; - templateId: string; - templateData: any; -} - -function removeFromParent(element: HTMLElement): void { - try { - element.parentElement!.removeChild(element); - } catch (e) { - // this will throw if this happens due to a blur event, nasty business - } -} - -export class RowCache implements Lifecycle.IDisposable { - - private _cache: { [templateId: string]: IRow[]; } | null; - - constructor(private context: _.ITreeContext) { - this._cache = { '': [] }; - } - - public alloc(templateId: string): IRow { - let result = this.cache(templateId).pop(); - - if (!result) { - let content = document.createElement('div'); - content.className = 'content'; - - let row = document.createElement('div'); - row.appendChild(content); - - let templateData: any = null; - - try { - templateData = this.context.renderer!.renderTemplate(this.context.tree, templateId, content); - } catch (err) { - console.error('Tree usage error: exception while rendering template'); - console.error(err); - } - - result = { - element: row, - templateId: templateId, - templateData - }; - } - - return result; - } - - public release(templateId: string, row: IRow): void { - removeFromParent(row.element!); - this.cache(templateId).push(row); - } - - private cache(templateId: string): IRow[] { - return this._cache![templateId] || (this._cache![templateId] = []); - } - - public garbageCollect(): void { - if (this._cache) { - Object.keys(this._cache).forEach(templateId => { - this._cache![templateId].forEach(cachedRow => { - this.context.renderer!.disposeTemplate(this.context.tree, templateId, cachedRow.templateData); - cachedRow.element = null; - cachedRow.templateData = null; - }); - - delete this._cache![templateId]; - }); - } - } - - public dispose(): void { - this.garbageCollect(); - this._cache = null; - } -} - -export interface IViewContext extends _.ITreeContext { - cache: RowCache; - horizontalScrolling: boolean; -} - -export class ViewItem implements IViewItem { - - private context: IViewContext; - - public model: Model.Item; - public id: string; - protected row: IRow | null; - - public top: number; - public height: number; - public width: number = 0; - public onDragStart!: (e: DragEvent) => void; - - public needsRender: boolean = false; - public uri: string | null = null; - public unbindDragStart: Lifecycle.IDisposable = Lifecycle.Disposable.None; - public loadingTimer: any; - - public _styles: any; - private _draggable: boolean = false; - - constructor(context: IViewContext, model: Model.Item) { - this.context = context; - this.model = model; - - this.id = this.model.id; - this.row = null; - - this.top = 0; - this.height = model.getHeight(); - - this._styles = {}; - model.getAllTraits().forEach(t => this._styles[t] = true); - - if (model.isExpanded()) { - this.addClass('expanded'); - } - } - - set expanded(value: boolean) { - value ? this.addClass('expanded') : this.removeClass('expanded'); - } - - set loading(value: boolean) { - value ? this.addClass('codicon-loading') : this.removeClass('codicon-loading'); - } - - set draggable(value: boolean) { - this._draggable = value; - this.render(true); - } - - get draggable() { - return this._draggable; - } - - set dropTarget(value: boolean) { - value ? this.addClass('drop-target') : this.removeClass('drop-target'); - } - - public get element(): HTMLElement { - return (this.row && this.row.element)!; - } - - private _templateId: string | undefined; - private get templateId(): string { - return this._templateId || (this._templateId = (this.context.renderer!.getTemplateId && this.context.renderer!.getTemplateId(this.context.tree, this.model.getElement()))); - } - - public addClass(name: string): void { - this._styles[name] = true; - this.render(true); - } - - public removeClass(name: string): void { - delete this._styles[name]; // is this slow? - this.render(true); - } - - public render(skipUserRender = false): void { - if (!this.model || !this.element) { - return; - } - - let classes = ['monaco-tree-row']; - classes.push.apply(classes, Object.keys(this._styles)); - - if (this.model.hasChildren()) { - classes.push('has-children'); - } - - this.element.className = classes.join(' '); - this.element.draggable = this.draggable; - this.element.style.height = this.height + 'px'; - - // ARIA - this.element.setAttribute('role', 'treeitem'); - const accessibility = this.context.accessibilityProvider!; - const ariaLabel = accessibility.getAriaLabel(this.context.tree, this.model.getElement()); - if (ariaLabel) { - this.element.setAttribute('aria-label', ariaLabel); - } - if (accessibility.getPosInSet && accessibility.getSetSize) { - this.element.setAttribute('aria-setsize', accessibility.getSetSize()); - this.element.setAttribute('aria-posinset', accessibility.getPosInSet(this.context.tree, this.model.getElement())); - } - if (this.model.hasTrait('focused')) { - const base64Id = strings.safeBtoa(this.model.id); - this.element.setAttribute('id', base64Id); - this.element.setAttribute('aria-selected', 'true'); - } else { - this.element.removeAttribute('id'); - this.element.setAttribute('aria-selected', 'false'); - } - if (this.model.hasChildren()) { - this.element.setAttribute('aria-expanded', String(!!this._styles['expanded'])); - } else { - this.element.removeAttribute('aria-expanded'); - } - this.element.setAttribute('aria-level', String(this.model.getDepth())); - - if (this.context.options.paddingOnRow) { - this.element.style.paddingLeft = this.context.options.twistiePixels! + ((this.model.getDepth() - 1) * this.context.options.indentPixels!) + 'px'; - } else { - this.element.style.paddingLeft = ((this.model.getDepth() - 1) * this.context.options.indentPixels!) + 'px'; - (this.row!.element!.firstElementChild).style.paddingLeft = this.context.options.twistiePixels + 'px'; - } - - let uri = this.context.dnd!.getDragURI(this.context.tree, this.model.getElement()); - - if (uri !== this.uri) { - if (this.unbindDragStart) { - this.unbindDragStart.dispose(); - } - - if (uri) { - this.uri = uri; - this.draggable = true; - this.unbindDragStart = DOM.addDisposableListener(this.element, 'dragstart', (e) => { - this.onDragStart(e); - }); - } else { - this.uri = null; - } - } - - if (!skipUserRender && this.element) { - let paddingLeft: number = 0; - if (this.context.horizontalScrolling) { - const style = window.getComputedStyle(this.element); - paddingLeft = parseFloat(style.paddingLeft!); - } - - if (this.context.horizontalScrolling) { - this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content'; - } - - try { - this.context.renderer!.renderElement(this.context.tree, this.model.getElement(), this.templateId, this.row!.templateData); - } catch (err) { - console.error('Tree usage error: exception while rendering element'); - console.error(err); - } - - if (this.context.horizontalScrolling) { - this.width = DOM.getContentWidth(this.element) + paddingLeft; - this.element.style.width = ''; - } - } - } - - updateWidth(): any { - if (!this.context.horizontalScrolling || !this.element) { - return; - } - - const style = window.getComputedStyle(this.element); - const paddingLeft = parseFloat(style.paddingLeft!); - this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content'; - this.width = DOM.getContentWidth(this.element) + paddingLeft; - this.element.style.width = ''; - } - - public insertInDOM(container: HTMLElement, afterElement: HTMLElement | null): void { - if (!this.row) { - this.row = this.context.cache.alloc(this.templateId); - - // used in reverse lookup from HTMLElement to Item - (this.element)[TreeView.BINDING] = this; - } - - if (this.element.parentElement) { - return; - } - - if (afterElement === null) { - container.appendChild(this.element); - } else { - try { - container.insertBefore(this.element, afterElement); - } catch (e) { - console.warn('Failed to locate previous tree element'); - container.appendChild(this.element); - } - } - - this.render(); - } - - public removeFromDOM(): void { - if (!this.row) { - return; - } - - this.unbindDragStart.dispose(); - - this.uri = null; - - (this.element)[TreeView.BINDING] = null; - this.context.cache.release(this.templateId, this.row); - this.row = null; - } - - public dispose(): void { - this.row = null; - } -} - -class RootViewItem extends ViewItem { - - constructor(context: IViewContext, model: Model.Item, wrapper: HTMLElement) { - super(context, model); - - this.row = { - element: wrapper, - templateData: null, - templateId: null! - }; - } - - public render(): void { - if (!this.model || !this.element) { - return; - } - - let classes = ['monaco-tree-wrapper']; - classes.push.apply(classes, Object.keys(this._styles)); - - if (this.model.hasChildren()) { - classes.push('has-children'); - } - - this.element.className = classes.join(' '); - } - - public insertInDOM(container: HTMLElement, afterElement: HTMLElement): void { - // noop - } - - public removeFromDOM(): void { - // noop - } -} - -function reactionEquals(one: _.IDragOverReaction, other: _.IDragOverReaction | null): boolean { - if (!one && !other) { - return true; - } else if (!one || !other) { - return false; - } else if (one.accept !== other.accept) { - return false; - } else if (one.bubble !== other.bubble) { - return false; - } else if (one.effect !== other.effect) { - return false; - } else { - return true; - } -} - -export class TreeView extends HeightMap { - - static readonly BINDING = 'monaco-tree-row'; - static readonly LOADING_DECORATION_DELAY = 800; - - private static counter: number = 0; - private instance: number; - - private context: IViewContext; - private modelListeners: Lifecycle.IDisposable[]; - private model: Model.TreeModel | null = null; - - private viewListeners: Lifecycle.IDisposable[]; - private domNode: HTMLElement; - private wrapper: HTMLElement; - private styleElement: HTMLStyleElement; - private treeStyler: _.ITreeStyler; - private rowsContainer: HTMLElement; - private scrollableElement: ScrollableElement; - private msGesture: MSGesture | undefined; - private lastPointerType: string = ''; - - private horizontalScrolling: boolean; - private contentWidthUpdateDelayer = new Delayer(50); - - private lastRenderTop: number; - private lastRenderHeight: number; - - private inputItem!: ViewItem; - private items: { [id: string]: ViewItem; }; - - private isRefreshing = false; - private refreshingPreviousChildrenIds: { [id: string]: string[] } = {}; - private currentDragAndDropData: IDragAndDropData | null = null; - private currentDropElement: any; - private currentDropElementReaction!: _.IDragOverReaction; - private currentDropTarget: ViewItem | null = null; - private shouldInvalidateDropReaction: boolean; - private currentDropTargets: ViewItem[] | null = null; - private currentDropDisposable: Lifecycle.IDisposable = Lifecycle.Disposable.None; - private gestureDisposable: Lifecycle.IDisposable = Lifecycle.Disposable.None; - private dragAndDropScrollInterval: number | null = null; - private dragAndDropScrollTimeout: number | null = null; - private dragAndDropMouseY: number | null = null; - - private didJustPressContextMenuKey: boolean; - - private highlightedItemWasDraggable: boolean = false; - private onHiddenScrollTop: number | null = null; - - private readonly _onDOMFocus = new Emitter(); - readonly onDOMFocus: Event = this._onDOMFocus.event; - - private readonly _onDOMBlur = new Emitter(); - readonly onDOMBlur: Event = this._onDOMBlur.event; - - private readonly _onDidScroll = new Emitter(); - readonly onDidScroll: Event = this._onDidScroll.event; - - constructor(context: _.ITreeContext, container: HTMLElement) { - super(); - - TreeView.counter++; - this.instance = TreeView.counter; - - const horizontalScrollMode = typeof context.options.horizontalScrollMode === 'undefined' ? ScrollbarVisibility.Hidden : context.options.horizontalScrollMode; - this.horizontalScrolling = horizontalScrollMode !== ScrollbarVisibility.Hidden; - - this.context = { - dataSource: context.dataSource, - renderer: context.renderer, - controller: context.controller, - dnd: context.dnd, - filter: context.filter, - sorter: context.sorter, - tree: context.tree, - accessibilityProvider: context.accessibilityProvider, - options: context.options, - cache: new RowCache(context), - horizontalScrolling: this.horizontalScrolling - }; - - this.modelListeners = []; - this.viewListeners = []; - - this.items = {}; - - this.domNode = document.createElement('div'); - this.domNode.className = `monaco-tree no-focused-item monaco-tree-instance-${this.instance}`; - // to allow direct tabbing into the tree instead of first focusing the tree - this.domNode.tabIndex = context.options.preventRootFocus ? -1 : 0; - - this.styleElement = DOM.createStyleSheet(this.domNode); - - this.treeStyler = context.styler || new DefaultTreestyler(this.styleElement, `monaco-tree-instance-${this.instance}`); - - // ARIA - this.domNode.setAttribute('role', 'tree'); - if (this.context.options.ariaLabel) { - this.domNode.setAttribute('aria-label', this.context.options.ariaLabel); - } - - if (this.context.options.alwaysFocused) { - DOM.addClass(this.domNode, 'focused'); - } - - if (!this.context.options.paddingOnRow) { - DOM.addClass(this.domNode, 'no-row-padding'); - } - - this.wrapper = document.createElement('div'); - this.wrapper.className = 'monaco-tree-wrapper'; - this.scrollableElement = new ScrollableElement(this.wrapper, { - alwaysConsumeMouseWheel: true, - horizontal: horizontalScrollMode, - vertical: (typeof context.options.verticalScrollMode !== 'undefined' ? context.options.verticalScrollMode : ScrollbarVisibility.Auto), - useShadows: context.options.useShadows - }); - this.scrollableElement.onScroll((e) => { - this.render(e.scrollTop, e.height, e.scrollLeft, e.width, e.scrollWidth); - this._onDidScroll.fire(); - }); - - this.gestureDisposable = Touch.Gesture.addTarget(this.wrapper); - - this.rowsContainer = document.createElement('div'); - this.rowsContainer.className = 'monaco-tree-rows'; - if (context.options.showTwistie) { - this.rowsContainer.className += ' show-twisties'; - } - - let focusTracker = DOM.trackFocus(this.domNode); - this.viewListeners.push(focusTracker.onDidFocus(() => this.onFocus())); - this.viewListeners.push(focusTracker.onDidBlur(() => this.onBlur())); - this.viewListeners.push(focusTracker); - - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'keydown', (e) => this.onKeyDown(e))); - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'keyup', (e) => this.onKeyUp(e))); - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'mousedown', (e) => this.onMouseDown(e))); - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'mouseup', (e) => this.onMouseUp(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'auxclick', (e: MouseEvent) => { - if (e && e.button === 1) { - this.onMouseMiddleClick(e); - } - })); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'click', (e) => this.onClick(e))); - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'contextmenu', (e) => this.onContextMenu(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, Touch.EventType.Tap, (e) => this.onTap(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, Touch.EventType.Change, (e) => this.onTouchChange(e))); - - this.viewListeners.push(DOM.addDisposableListener(window, 'dragover', (e) => this.onDragOver(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'drop', (e) => this.onDrop(e))); - this.viewListeners.push(DOM.addDisposableListener(window, 'dragend', (e) => this.onDragEnd(e))); - this.viewListeners.push(DOM.addDisposableListener(window, 'dragleave', (e) => this.onDragOver(e))); - - this.wrapper.appendChild(this.rowsContainer); - this.domNode.appendChild(this.scrollableElement.getDomNode()); - container.appendChild(this.domNode); - - this.lastRenderTop = 0; - this.lastRenderHeight = 0; - - this.didJustPressContextMenuKey = false; - - this.currentDropTarget = null; - this.currentDropTargets = []; - this.shouldInvalidateDropReaction = false; - - this.dragAndDropScrollInterval = null; - this.dragAndDropScrollTimeout = null; - - this.onRowsChanged(); - this.layout(); - - this.setupMSGesture(); - - this.applyStyles(context.options); - } - - public applyStyles(styles: _.ITreeStyles): void { - this.treeStyler.style(styles); - } - - protected createViewItem(item: Model.Item): IViewItem { - return new ViewItem(this.context, item); - } - - public getHTMLElement(): HTMLElement { - return this.domNode; - } - - public focus(): void { - this.domNode.focus(); - } - - public isFocused(): boolean { - return document.activeElement === this.domNode; - } - - public blur(): void { - this.domNode.blur(); - } - - public onVisible(): void { - this.scrollTop = this.onHiddenScrollTop!; - this.onHiddenScrollTop = null; - this.setupMSGesture(); - } - - private setupMSGesture(): void { - if ((window).MSGesture) { - this.msGesture = new MSGesture(); - setTimeout(() => this.msGesture!.target = this.wrapper, 100); // TODO@joh, TODO@IETeam - } - } - - public onHidden(): void { - this.onHiddenScrollTop = this.scrollTop; - } - - private isTreeVisible(): boolean { - return this.onHiddenScrollTop === null; - } - - public layout(height?: number, width?: number): void { - if (!this.isTreeVisible()) { - return; - } - - this.viewHeight = height || DOM.getContentHeight(this.wrapper); // render - this.scrollHeight = this.getContentHeight(); - - if (this.horizontalScrolling) { - this.viewWidth = width || DOM.getContentWidth(this.wrapper); - } - } - - private render(scrollTop: number, viewHeight: number, scrollLeft: number, viewWidth: number, scrollWidth: number): void { - let i: number; - let stop: number; - - let renderTop = scrollTop; - let renderBottom = scrollTop + viewHeight; - let thisRenderBottom = this.lastRenderTop + this.lastRenderHeight; - - // when view scrolls down, start rendering from the renderBottom - for (i = this.indexAfter(renderBottom) - 1, stop = this.indexAt(Math.max(thisRenderBottom, renderTop)); i >= stop; i--) { - this.insertItemInDOM(this.itemAtIndex(i)); - } - - // when view scrolls up, start rendering from either this.renderTop or renderBottom - for (i = Math.min(this.indexAt(this.lastRenderTop), this.indexAfter(renderBottom)) - 1, stop = this.indexAt(renderTop); i >= stop; i--) { - this.insertItemInDOM(this.itemAtIndex(i)); - } - - // when view scrolls down, start unrendering from renderTop - for (i = this.indexAt(this.lastRenderTop), stop = Math.min(this.indexAt(renderTop), this.indexAfter(thisRenderBottom)); i < stop; i++) { - this.removeItemFromDOM(this.itemAtIndex(i)); - } - - // when view scrolls up, start unrendering from either renderBottom this.renderTop - for (i = Math.max(this.indexAfter(renderBottom), this.indexAt(this.lastRenderTop)), stop = this.indexAfter(thisRenderBottom); i < stop; i++) { - this.removeItemFromDOM(this.itemAtIndex(i)); - } - - let topItem = this.itemAtIndex(this.indexAt(renderTop)); - - if (topItem) { - this.rowsContainer.style.top = (topItem.top - renderTop) + 'px'; - } - - if (this.horizontalScrolling) { - this.rowsContainer.style.left = -scrollLeft + 'px'; - this.rowsContainer.style.width = `${Math.max(scrollWidth, viewWidth)}px`; - } - - this.lastRenderTop = renderTop; - this.lastRenderHeight = renderBottom - renderTop; - } - - public setModel(newModel: Model.TreeModel): void { - this.releaseModel(); - this.model = newModel; - - this.model.onRefresh(this.onRefreshing, this, this.modelListeners); - this.model.onDidRefresh(this.onRefreshed, this, this.modelListeners); - this.model.onSetInput(this.onClearingInput, this, this.modelListeners); - this.model.onDidSetInput(this.onSetInput, this, this.modelListeners); - this.model.onDidFocus(this.onModelFocusChange, this, this.modelListeners); - - this.model.onRefreshItemChildren(this.onItemChildrenRefreshing, this, this.modelListeners); - this.model.onDidRefreshItemChildren(this.onItemChildrenRefreshed, this, this.modelListeners); - this.model.onDidRefreshItem(this.onItemRefresh, this, this.modelListeners); - this.model.onExpandItem(this.onItemExpanding, this, this.modelListeners); - this.model.onDidExpandItem(this.onItemExpanded, this, this.modelListeners); - this.model.onCollapseItem(this.onItemCollapsing, this, this.modelListeners); - this.model.onDidRevealItem(this.onItemReveal, this, this.modelListeners); - this.model.onDidAddTraitItem(this.onItemAddTrait, this, this.modelListeners); - this.model.onDidRemoveTraitItem(this.onItemRemoveTrait, this, this.modelListeners); - } - - private onRefreshing(): void { - this.isRefreshing = true; - } - - private onRefreshed(): void { - this.isRefreshing = false; - this.onRowsChanged(); - } - - private onRowsChanged(scrollTop: number = this.scrollTop): void { - if (this.isRefreshing) { - return; - } - - this.scrollTop = scrollTop; - this.updateScrollWidth(); - } - - private updateScrollWidth(): void { - if (!this.horizontalScrolling) { - return; - } - - this.contentWidthUpdateDelayer.trigger(() => { - const keys = Object.keys(this.items); - let scrollWidth = 0; - - for (const key of keys) { - scrollWidth = Math.max(scrollWidth, this.items[key].width); - } - - this.scrollWidth = scrollWidth + 10 /* scrollbar */; - }); - } - - public focusNextPage(eventPayload?: any): void { - let lastPageIndex = this.indexAt(this.scrollTop + this.viewHeight); - lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1; - let lastPageElement = this.itemAtIndex(lastPageIndex).model.getElement(); - let currentlyFocusedElement = this.model!.getFocus(); - - if (currentlyFocusedElement !== lastPageElement) { - this.model!.setFocus(lastPageElement, eventPayload); - } else { - let previousScrollTop = this.scrollTop; - this.scrollTop += this.viewHeight; - - if (this.scrollTop !== previousScrollTop) { - - // Let the scroll event listener run - setTimeout(() => { - this.focusNextPage(eventPayload); - }, 0); - } - } - } - - public focusPreviousPage(eventPayload?: any): void { - let firstPageIndex: number; - - if (this.scrollTop === 0) { - firstPageIndex = this.indexAt(this.scrollTop); - } else { - firstPageIndex = this.indexAfter(this.scrollTop - 1); - } - - let firstPageElement = this.itemAtIndex(firstPageIndex).model.getElement(); - let currentlyFocusedElement = this.model!.getFocus(); - - if (currentlyFocusedElement !== firstPageElement) { - this.model!.setFocus(firstPageElement, eventPayload); - } else { - let previousScrollTop = this.scrollTop; - this.scrollTop -= this.viewHeight; - - if (this.scrollTop !== previousScrollTop) { - - // Let the scroll event listener run - setTimeout(() => { - this.focusPreviousPage(eventPayload); - }, 0); - } - } - } - - public get viewHeight() { - const scrollDimensions = this.scrollableElement.getScrollDimensions(); - return scrollDimensions.height; - } - - public set viewHeight(height: number) { - this.scrollableElement.setScrollDimensions({ height }); - } - - private set scrollHeight(scrollHeight: number) { - scrollHeight = scrollHeight + (this.horizontalScrolling ? 10 : 0); - this.scrollableElement.setScrollDimensions({ scrollHeight }); - } - - public get viewWidth(): number { - const scrollDimensions = this.scrollableElement.getScrollDimensions(); - return scrollDimensions.width; - } - - public set viewWidth(viewWidth: number) { - this.scrollableElement.setScrollDimensions({ width: viewWidth }); - } - - private set scrollWidth(scrollWidth: number) { - this.scrollableElement.setScrollDimensions({ scrollWidth }); - } - - public get scrollTop(): number { - const scrollPosition = this.scrollableElement.getScrollPosition(); - return scrollPosition.scrollTop; - } - - public set scrollTop(scrollTop: number) { - const scrollHeight = this.getContentHeight() + (this.horizontalScrolling ? 10 : 0); - this.scrollableElement.setScrollDimensions({ scrollHeight }); - this.scrollableElement.setScrollPosition({ scrollTop }); - } - - public getScrollPosition(): number { - const height = this.getContentHeight() - this.viewHeight; - return height <= 0 ? 1 : this.scrollTop / height; - } - - public setScrollPosition(pos: number): void { - const height = this.getContentHeight() - this.viewHeight; - this.scrollTop = height * pos; - } - - // Events - - private onClearingInput(e: Model.IInputEvent): void { - let item = e.item; - if (item) { - this.onRemoveItems(new MappedIterator(item.getNavigator(), item => item && item.id)); - this.onRowsChanged(); - } - } - - private onSetInput(e: Model.IInputEvent): void { - this.context.cache.garbageCollect(); - this.inputItem = new RootViewItem(this.context, e.item, this.wrapper); - } - - private onItemChildrenRefreshing(e: Model.IItemChildrenRefreshEvent): void { - let item = e.item; - let viewItem = this.items[item.id]; - - if (viewItem && this.context.options.showLoading) { - viewItem.loadingTimer = setTimeout(() => { - viewItem.loadingTimer = 0; - viewItem.loading = true; - }, TreeView.LOADING_DECORATION_DELAY); - } - - if (!e.isNested) { - let childrenIds: string[] = []; - let navigator = item.getNavigator(); - let childItem: Model.Item | null; - - while (childItem = navigator.next()) { - childrenIds.push(childItem.id); - } - - this.refreshingPreviousChildrenIds[item.id] = childrenIds; - } - } - - private onItemChildrenRefreshed(e: Model.IItemChildrenRefreshEvent): void { - let item = e.item; - let viewItem = this.items[item.id]; - - if (viewItem) { - if (viewItem.loadingTimer) { - clearTimeout(viewItem.loadingTimer); - viewItem.loadingTimer = 0; - } - - viewItem.loading = false; - } - - if (!e.isNested) { - let previousChildrenIds = this.refreshingPreviousChildrenIds[item.id]; - let afterModelItems: Model.Item[] = []; - let navigator = item.getNavigator(); - let childItem: Model.Item | null; - - while (childItem = navigator.next()) { - afterModelItems.push(childItem); - } - - let skipDiff = Math.abs(previousChildrenIds.length - afterModelItems.length) > 1000; - let diff: Diff.IDiffChange[] = []; - let doToInsertItemsAlreadyExist: boolean = false; - - if (!skipDiff) { - const lcs = new Diff.LcsDiff( - { - getElements: () => previousChildrenIds - }, - { - getElements: () => afterModelItems.map(item => item.id) - }, - null - ); - - diff = lcs.ComputeDiff(false).changes; - - // this means that the result of the diff algorithm would result - // in inserting items that were already registered. this can only - // happen if the data provider returns bad ids OR if the sorting - // of the elements has changed - doToInsertItemsAlreadyExist = diff.some(d => { - if (d.modifiedLength > 0) { - for (let i = d.modifiedStart, len = d.modifiedStart + d.modifiedLength; i < len; i++) { - if (this.items.hasOwnProperty(afterModelItems[i].id)) { - return true; - } - } - } - return false; - }); - } - - // 50 is an optimization number, at some point we're better off - // just replacing everything - if (!skipDiff && !doToInsertItemsAlreadyExist && diff.length < 50) { - for (const diffChange of diff) { - - if (diffChange.originalLength > 0) { - this.onRemoveItems(new ArrayIterator(previousChildrenIds, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength)); - } - - if (diffChange.modifiedLength > 0) { - let beforeItem: Model.Item | null = afterModelItems[diffChange.modifiedStart - 1] || item; - beforeItem = beforeItem.getDepth() > 0 ? beforeItem : null; - - this.onInsertItems(new ArrayIterator(afterModelItems, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength), beforeItem ? beforeItem.id : null); - } - } - - } else if (skipDiff || diff.length) { - this.onRemoveItems(new ArrayIterator(previousChildrenIds)); - this.onInsertItems(new ArrayIterator(afterModelItems), item.getDepth() > 0 ? item.id : null); - } - - if (skipDiff || diff.length) { - this.onRowsChanged(); - } - } - } - - private onItemRefresh(item: Model.Item): void { - this.onItemsRefresh([item]); - } - - private onItemsRefresh(items: Model.Item[]): void { - this.onRefreshItemSet(items.filter(item => this.items.hasOwnProperty(item.id))); - this.onRowsChanged(); - } - - private onItemExpanding(e: Model.IItemExpandEvent): void { - let viewItem = this.items[e.item.id]; - if (viewItem) { - viewItem.expanded = true; - } - } - - private onItemExpanded(e: Model.IItemExpandEvent): void { - let item = e.item; - let viewItem = this.items[item.id]; - if (viewItem) { - viewItem.expanded = true; - - let height = this.onInsertItems(item.getNavigator(), item.id) || 0; - let scrollTop = this.scrollTop; - - if (viewItem.top + viewItem.height <= this.scrollTop) { - scrollTop += height; - } - - this.onRowsChanged(scrollTop); - } - } - - private onItemCollapsing(e: Model.IItemCollapseEvent): void { - let item = e.item; - let viewItem = this.items[item.id]; - if (viewItem) { - viewItem.expanded = false; - this.onRemoveItems(new MappedIterator(item.getNavigator(), item => item && item.id)); - this.onRowsChanged(); - } - } - - private onItemReveal(e: Model.IItemRevealEvent): void { - let item = e.item; - let relativeTop = e.relativeTop; - let viewItem = this.items[item.id]; - if (viewItem) { - if (relativeTop !== null) { - relativeTop = relativeTop < 0 ? 0 : relativeTop; - relativeTop = relativeTop > 1 ? 1 : relativeTop; - - // y = mx + b - let m = viewItem.height - this.viewHeight; - this.scrollTop = m * relativeTop + viewItem.top; - } else { - let viewItemBottom = viewItem.top + viewItem.height; - let wrapperBottom = this.scrollTop + this.viewHeight; - - if (viewItem.top < this.scrollTop) { - this.scrollTop = viewItem.top; - } else if (viewItemBottom >= wrapperBottom) { - this.scrollTop = viewItemBottom - this.viewHeight; - } - } - } - } - - private onItemAddTrait(e: Model.IItemTraitEvent): void { - let item = e.item; - let trait = e.trait; - let viewItem = this.items[item.id]; - if (viewItem) { - viewItem.addClass(trait); - } - if (trait === 'highlighted') { - DOM.addClass(this.domNode, trait); - - // Ugly Firefox fix: input fields can't be selected if parent nodes are draggable - if (viewItem) { - this.highlightedItemWasDraggable = !!viewItem.draggable; - if (viewItem.draggable) { - viewItem.draggable = false; - } - } - } - } - - private onItemRemoveTrait(e: Model.IItemTraitEvent): void { - let item = e.item; - let trait = e.trait; - let viewItem = this.items[item.id]; - if (viewItem) { - viewItem.removeClass(trait); - } - if (trait === 'highlighted') { - DOM.removeClass(this.domNode, trait); - - // Ugly Firefox fix: input fields can't be selected if parent nodes are draggable - if (this.highlightedItemWasDraggable) { - viewItem.draggable = true; - } - this.highlightedItemWasDraggable = false; - } - } - - private onModelFocusChange(): void { - const focus = this.model && this.model.getFocus(); - - DOM.toggleClass(this.domNode, 'no-focused-item', !focus); - - // ARIA - if (focus) { - this.domNode.setAttribute('aria-activedescendant', strings.safeBtoa(this.context.dataSource.getId(this.context.tree, focus))); - } else { - this.domNode.removeAttribute('aria-activedescendant'); - } - } - - // HeightMap "events" - - public onInsertItem(item: ViewItem): void { - item.onDragStart = (e) => { this.onDragStart(item, e); }; - item.needsRender = true; - this.refreshViewItem(item); - this.items[item.id] = item; - } - - public onRefreshItem(item: ViewItem, needsRender = false): void { - item.needsRender = item.needsRender || needsRender; - this.refreshViewItem(item); - } - - public onRemoveItem(item: ViewItem): void { - this.removeItemFromDOM(item); - item.dispose(); - delete this.items[item.id]; - } - - // ViewItem refresh - - private refreshViewItem(item: ViewItem): void { - item.render(); - - if (this.shouldBeRendered(item)) { - this.insertItemInDOM(item); - } else { - this.removeItemFromDOM(item); - } - } - - // DOM Events - - private onClick(e: MouseEvent): void { - if (this.lastPointerType && this.lastPointerType !== 'mouse') { - return; - } - - let event = new Mouse.StandardMouseEvent(e); - let item = this.getItemAround(event.target); - - if (!item) { - return; - } - - this.context.controller!.onClick(this.context.tree, item.model.getElement(), event); - } - - private onMouseMiddleClick(e: MouseEvent): void { - if (!this.context.controller!.onMouseMiddleClick!) { - return; - } - - let event = new Mouse.StandardMouseEvent(e); - let item = this.getItemAround(event.target); - - if (!item) { - return; - } - this.context.controller!.onMouseMiddleClick!(this.context.tree, item.model.getElement(), event); - } - - private onMouseDown(e: MouseEvent): void { - this.didJustPressContextMenuKey = false; - - if (!this.context.controller!.onMouseDown!) { - return; - } - - if (this.lastPointerType && this.lastPointerType !== 'mouse') { - return; - } - - let event = new Mouse.StandardMouseEvent(e); - - if (event.ctrlKey && Platform.isNative && Platform.isMacintosh) { - return; - } - - let item = this.getItemAround(event.target); - - if (!item) { - return; - } - - this.context.controller!.onMouseDown!(this.context.tree, item.model.getElement(), event); - } - - private onMouseUp(e: MouseEvent): void { - if (!this.context.controller!.onMouseUp!) { - return; - } - - if (this.lastPointerType && this.lastPointerType !== 'mouse') { - return; - } - - let event = new Mouse.StandardMouseEvent(e); - - if (event.ctrlKey && Platform.isNative && Platform.isMacintosh) { - return; - } - - let item = this.getItemAround(event.target); - - if (!item) { - return; - } - - this.context.controller!.onMouseUp!(this.context.tree, item.model.getElement(), event); - } - - private onTap(e: Touch.GestureEvent): void { - let item = this.getItemAround(e.initialTarget); - - if (!item) { - return; - } - - this.context.controller!.onTap(this.context.tree, item.model.getElement(), e); - } - - private onTouchChange(event: Touch.GestureEvent): void { - event.preventDefault(); - event.stopPropagation(); - - this.scrollTop -= event.translationY; - } - - private onContextMenu(keyboardEvent: KeyboardEvent): void; - private onContextMenu(mouseEvent: MouseEvent): void; - private onContextMenu(event: KeyboardEvent | MouseEvent): void { - let resultEvent: _.ContextMenuEvent; - let element: any; - - if (event instanceof KeyboardEvent || this.didJustPressContextMenuKey) { - this.didJustPressContextMenuKey = false; - - let keyboardEvent = new Keyboard.StandardKeyboardEvent(event); - element = this.model!.getFocus(); - - let position: DOM.IDomNodePagePosition; - - if (!element) { - element = this.model!.getInput(); - position = DOM.getDomNodePagePosition(this.inputItem.element); - } else { - const id = this.context.dataSource.getId(this.context.tree, element); - const viewItem = this.items[id!]; - position = DOM.getDomNodePagePosition(viewItem.element); - } - - resultEvent = new _.KeyboardContextMenuEvent(position.left + position.width, position.top, keyboardEvent); - - } else { - let mouseEvent = new Mouse.StandardMouseEvent(event); - let item = this.getItemAround(mouseEvent.target); - - if (!item) { - return; - } - - element = item.model.getElement(); - resultEvent = new _.MouseContextMenuEvent(mouseEvent); - } - - this.context.controller!.onContextMenu(this.context.tree, element, resultEvent); - } - - private onKeyDown(e: KeyboardEvent): void { - let event = new Keyboard.StandardKeyboardEvent(e); - - this.didJustPressContextMenuKey = event.keyCode === KeyCode.ContextMenu || (event.shiftKey && event.keyCode === KeyCode.F10); - - if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return; // Ignore event if target is a form input field (avoids browser specific issues) - } - - if (this.didJustPressContextMenuKey) { - event.preventDefault(); - event.stopPropagation(); - } - - this.context.controller!.onKeyDown(this.context.tree, event); - } - - private onKeyUp(e: KeyboardEvent): void { - if (this.didJustPressContextMenuKey) { - this.onContextMenu(e); - } - - this.didJustPressContextMenuKey = false; - this.context.controller!.onKeyUp(this.context.tree, new Keyboard.StandardKeyboardEvent(e)); - } - - private onDragStart(item: ViewItem, e: any): void { - if (this.model!.getHighlight()) { - return; - } - - let element = item.model.getElement(); - let selection = this.model!.getSelection(); - let elements: any[]; - - if (selection.indexOf(element) > -1) { - elements = selection; - } else { - elements = [element]; - } - - e.dataTransfer.effectAllowed = 'copyMove'; - e.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([item.uri])); - if (e.dataTransfer.setDragImage) { - let label: string; - - if (this.context.dnd!.getDragLabel) { - label = this.context.dnd!.getDragLabel!(this.context.tree, elements); - } else { - label = String(elements.length); - } - - const dragImage = document.createElement('div'); - dragImage.className = 'monaco-tree-drag-image'; - dragImage.textContent = label; - document.body.appendChild(dragImage); - e.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => document.body.removeChild(dragImage), 0); - } - - this.currentDragAndDropData = new dnd.ElementsDragAndDropData(elements); - StaticDND.CurrentDragAndDropData = new dnd.ExternalElementsDragAndDropData(elements); - - this.context.dnd!.onDragStart(this.context.tree, this.currentDragAndDropData, new Mouse.DragMouseEvent(e)); - } - - private setupDragAndDropScrollInterval(): void { - let viewTop = DOM.getTopLeftOffset(this.wrapper).top; - - if (!this.dragAndDropScrollInterval) { - this.dragAndDropScrollInterval = window.setInterval(() => { - if (this.dragAndDropMouseY === null) { - return; - } - - let diff = this.dragAndDropMouseY - viewTop; - let scrollDiff = 0; - let upperLimit = this.viewHeight - 35; - - if (diff < 35) { - scrollDiff = Math.max(-14, 0.2 * (diff - 35)); - } else if (diff > upperLimit) { - scrollDiff = Math.min(14, 0.2 * (diff - upperLimit)); - } - - this.scrollTop += scrollDiff; - }, 10); - - this.cancelDragAndDropScrollTimeout(); - - this.dragAndDropScrollTimeout = window.setTimeout(() => { - this.cancelDragAndDropScrollInterval(); - this.dragAndDropScrollTimeout = null; - }, 1000); - } - } - - private cancelDragAndDropScrollInterval(): void { - if (this.dragAndDropScrollInterval) { - window.clearInterval(this.dragAndDropScrollInterval); - this.dragAndDropScrollInterval = null; - } - - this.cancelDragAndDropScrollTimeout(); - } - - private cancelDragAndDropScrollTimeout(): void { - if (this.dragAndDropScrollTimeout) { - window.clearTimeout(this.dragAndDropScrollTimeout); - this.dragAndDropScrollTimeout = null; - } - } - - private onDragOver(e: DragEvent): boolean { - e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) - - let event = new Mouse.DragMouseEvent(e); - - let viewItem = this.getItemAround(event.target); - - if (!viewItem || (event.posx === 0 && event.posy === 0 && event.browserEvent.type === DOM.EventType.DRAG_LEAVE)) { - // dragging outside of tree - - if (this.currentDropTarget) { - // clear previously hovered element feedback - - this.currentDropTargets!.forEach(i => i.dropTarget = false); - this.currentDropTargets = []; - this.currentDropDisposable.dispose(); - } - - this.cancelDragAndDropScrollInterval(); - this.currentDropTarget = null; - this.currentDropElement = null; - this.dragAndDropMouseY = null; - - return false; - } - - // dragging inside the tree - this.setupDragAndDropScrollInterval(); - this.dragAndDropMouseY = event.posy; - - if (!this.currentDragAndDropData) { - // just started dragging - - if (StaticDND.CurrentDragAndDropData) { - this.currentDragAndDropData = StaticDND.CurrentDragAndDropData; - } else { - if (!event.dataTransfer.types) { - return false; - } - - this.currentDragAndDropData = new dnd.DesktopDragAndDropData(); - } - } - - this.currentDragAndDropData.update((event.browserEvent as DragEvent).dataTransfer!); - - let element: any; - let item: Model.Item | null = viewItem.model; - let reaction: _.IDragOverReaction | null; - - // check the bubble up behavior - do { - element = item ? item.getElement() : this.model!.getInput(); - reaction = this.context.dnd!.onDragOver(this.context.tree, this.currentDragAndDropData, element, event); - - if (!reaction || reaction.bubble !== _.DragOverBubble.BUBBLE_UP) { - break; - } - - item = item && item.parent; - } while (item); - - if (!item) { - this.currentDropElement = null; - return false; - } - - let canDrop = reaction && reaction.accept; - - if (canDrop) { - this.currentDropElement = item.getElement(); - event.preventDefault(); - event.dataTransfer.dropEffect = reaction!.effect === _.DragOverEffect.COPY ? 'copy' : 'move'; - } else { - this.currentDropElement = null; - } - - // item is the model item where drop() should be called - - // can be null - let currentDropTarget = item.id === this.inputItem.id ? this.inputItem : this.items[item.id]; - - if (this.shouldInvalidateDropReaction || this.currentDropTarget !== currentDropTarget || !reactionEquals(this.currentDropElementReaction, reaction)) { - this.shouldInvalidateDropReaction = false; - - if (this.currentDropTarget) { - this.currentDropTargets!.forEach(i => i.dropTarget = false); - this.currentDropTargets = []; - this.currentDropDisposable.dispose(); - } - - this.currentDropTarget = currentDropTarget; - this.currentDropElementReaction = reaction!; - - if (canDrop) { - // setup hover feedback for drop target - - if (this.currentDropTarget) { - this.currentDropTarget.dropTarget = true; - this.currentDropTargets!.push(this.currentDropTarget); - } - - if (reaction!.bubble === _.DragOverBubble.BUBBLE_DOWN) { - let nav = item.getNavigator(); - let child: Model.Item | null; - while (child = nav.next()) { - viewItem = this.items[child.id]; - if (viewItem) { - viewItem.dropTarget = true; - this.currentDropTargets!.push(viewItem); - } - } - } - - if (reaction!.autoExpand) { - const timeoutPromise = timeout(500); - this.currentDropDisposable = Lifecycle.toDisposable(() => timeoutPromise.cancel()); - - timeoutPromise - .then(() => this.context.tree.expand(this.currentDropElement)) - .then(() => this.shouldInvalidateDropReaction = true); - } - } - } - - return true; - } - - private onDrop(e: DragEvent): void { - if (this.currentDropElement) { - let event = new Mouse.DragMouseEvent(e); - event.preventDefault(); - this.currentDragAndDropData!.update((event.browserEvent as DragEvent).dataTransfer!); - this.context.dnd!.drop(this.context.tree, this.currentDragAndDropData!, this.currentDropElement, event); - this.onDragEnd(e); - } - this.cancelDragAndDropScrollInterval(); - } - - private onDragEnd(e: DragEvent): void { - if (this.currentDropTarget) { - this.currentDropTargets!.forEach(i => i.dropTarget = false); - this.currentDropTargets = []; - } - - this.currentDropDisposable.dispose(); - - this.cancelDragAndDropScrollInterval(); - this.currentDragAndDropData = null; - StaticDND.CurrentDragAndDropData = undefined; - this.currentDropElement = null; - this.currentDropTarget = null; - this.dragAndDropMouseY = null; - } - - private onFocus(): void { - if (!this.context.options.alwaysFocused) { - DOM.addClass(this.domNode, 'focused'); - } - - this._onDOMFocus.fire(); - } - - private onBlur(): void { - if (!this.context.options.alwaysFocused) { - DOM.removeClass(this.domNode, 'focused'); - } - - this.domNode.removeAttribute('aria-activedescendant'); // ARIA - - this._onDOMBlur.fire(); - } - - // DOM changes - - private insertItemInDOM(item: ViewItem): void { - let elementAfter: HTMLElement | null = null; - let itemAfter = this.itemAfter(item); - - if (itemAfter && itemAfter.element) { - elementAfter = itemAfter.element; - } - - item.insertInDOM(this.rowsContainer, elementAfter); - } - - private removeItemFromDOM(item: ViewItem): void { - if (!item) { - return; - } - - item.removeFromDOM(); - } - - // Helpers - - private shouldBeRendered(item: ViewItem): boolean { - return item.top < this.lastRenderTop + this.lastRenderHeight && item.top + item.height > this.lastRenderTop; - } - - private getItemAround(element: HTMLElement): ViewItem | undefined { - let candidate: ViewItem = this.inputItem; - let el: HTMLElement | null = element; - - do { - if ((el)[TreeView.BINDING]) { - candidate = (el)[TreeView.BINDING]; - } - - if (el === this.wrapper || el === this.domNode) { - return candidate; - } - - if (el === this.scrollableElement.getDomNode() || el === document.body) { - return undefined; - } - } while (el = el.parentElement); - - return undefined; - } - - // Cleanup - - private releaseModel(): void { - if (this.model) { - this.modelListeners = Lifecycle.dispose(this.modelListeners); - this.model = null; - } - } - - public dispose(): void { - // TODO@joao: improve - this.scrollableElement.dispose(); - - this.releaseModel(); - - this.viewListeners = Lifecycle.dispose(this.viewListeners); - - this._onDOMFocus.dispose(); - this._onDOMBlur.dispose(); - - if (this.domNode.parentNode) { - this.domNode.parentNode.removeChild(this.domNode); - } - - if (this.items) { - Object.keys(this.items).forEach(key => this.items[key].removeFromDOM()); - } - - if (this.context.cache) { - this.context.cache.dispose(); - } - this.gestureDisposable.dispose(); - - super.dispose(); - } -} diff --git a/src/vs/base/parts/tree/browser/treeViewModel.ts b/src/vs/base/parts/tree/browser/treeViewModel.ts deleted file mode 100644 index b78c4d8184f..00000000000 --- a/src/vs/base/parts/tree/browser/treeViewModel.ts +++ /dev/null @@ -1,234 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { INextIterator, ArrayIterator } from 'vs/base/common/iterator'; -import { Item } from './treeModel'; - -export interface IViewItem { - model: Item; - top: number; - height: number; - width: number; -} - -export class HeightMap { - - private heightMap: IViewItem[] = []; - private indexes: { [item: string]: number; } = {}; - - getContentHeight(): number { - let last = this.heightMap[this.heightMap.length - 1]; - return !last ? 0 : last.top + last.height; - } - - onInsertItems(iterator: INextIterator, afterItemId: string | null = null): number | undefined { - let item: Item | null = null; - let viewItem: IViewItem; - let i: number, j: number; - let totalSize: number; - let sizeDiff = 0; - - if (afterItemId === null) { - i = 0; - totalSize = 0; - } else { - i = this.indexes[afterItemId] + 1; - viewItem = this.heightMap[i - 1]; - - if (!viewItem) { - console.error('view item doesnt exist'); - return undefined; - } - - totalSize = viewItem.top + viewItem.height; - } - - const startingIndex = i; - let itemsToInsert: IViewItem[] = []; - - while (item = iterator.next()) { - viewItem = this.createViewItem(item); - viewItem.top = totalSize + sizeDiff; - - this.indexes[item.id] = i++; - itemsToInsert.push(viewItem); - sizeDiff += viewItem.height; - } - - this.heightMap.splice(startingIndex, 0, ...itemsToInsert); - - for (j = i; j < this.heightMap.length; j++) { - viewItem = this.heightMap[j]; - viewItem.top += sizeDiff; - this.indexes[viewItem.model.id] = j; - } - - for (j = itemsToInsert.length - 1; j >= 0; j--) { - this.onInsertItem(itemsToInsert[j]); - } - - for (j = this.heightMap.length - 1; j >= i; j--) { - this.onRefreshItem(this.heightMap[j]); - } - - return sizeDiff; - } - - onInsertItem(item: IViewItem): void { - // noop - } - - // Contiguous items - onRemoveItems(iterator: INextIterator): void { - let itemId: string | null = null; - let viewItem: IViewItem; - let startIndex: number | null = null; - let i = 0; - let sizeDiff = 0; - - while (itemId = iterator.next()) { - i = this.indexes[itemId]; - viewItem = this.heightMap[i]; - - if (!viewItem) { - console.error('view item doesnt exist'); - return; - } - - sizeDiff -= viewItem.height; - delete this.indexes[itemId]; - this.onRemoveItem(viewItem); - - if (startIndex === null) { - startIndex = i; - } - } - - if (sizeDiff === 0 || startIndex === null) { - return; - } - - this.heightMap.splice(startIndex, i - startIndex + 1); - - for (i = startIndex; i < this.heightMap.length; i++) { - viewItem = this.heightMap[i]; - viewItem.top += sizeDiff; - this.indexes[viewItem.model.id] = i; - this.onRefreshItem(viewItem); - } - } - - onRemoveItem(item: IViewItem): void { - // noop - } - - onRefreshItemSet(items: Item[]): void { - let sortedItems = items.sort((a, b) => this.indexes[a.id] - this.indexes[b.id]); - this.onRefreshItems(new ArrayIterator(sortedItems)); - } - - // Ordered, but not necessarily contiguous items - onRefreshItems(iterator: INextIterator): void { - let item: Item | null = null; - let viewItem: IViewItem; - let newHeight: number; - let i: number, j: number | null = null; - let cummDiff = 0; - - while (item = iterator.next()) { - i = this.indexes[item.id]; - - for (; cummDiff !== 0 && j !== null && j < i; j++) { - viewItem = this.heightMap[j]; - viewItem.top += cummDiff; - this.onRefreshItem(viewItem); - } - - viewItem = this.heightMap[i]; - newHeight = item.getHeight(); - viewItem.top += cummDiff; - cummDiff += newHeight - viewItem.height; - viewItem.height = newHeight; - this.onRefreshItem(viewItem, true); - - j = i + 1; - } - - if (cummDiff !== 0 && j !== null) { - for (; j < this.heightMap.length; j++) { - viewItem = this.heightMap[j]; - viewItem.top += cummDiff; - this.onRefreshItem(viewItem); - } - } - } - - onRefreshItem(item: IViewItem, needsRender: boolean = false): void { - // noop - } - - itemsCount(): number { - return this.heightMap.length; - } - - itemAt(position: number): string { - return this.heightMap[this.indexAt(position)].model.id; - } - - withItemsInRange(start: number, end: number, fn: (item: string) => void): void { - start = this.indexAt(start); - end = this.indexAt(end); - for (let i = start; i <= end; i++) { - fn(this.heightMap[i].model.id); - } - } - - indexAt(position: number): number { - let left = 0; - let right = this.heightMap.length; - let center: number; - let item: IViewItem; - - // Binary search - while (left < right) { - center = Math.floor((left + right) / 2); - item = this.heightMap[center]; - - if (position < item.top) { - right = center; - } else if (position >= item.top + item.height) { - if (left === center) { - break; - } - left = center; - } else { - return center; - } - } - - return this.heightMap.length; - } - - indexAfter(position: number): number { - return Math.min(this.indexAt(position) + 1, this.heightMap.length); - } - - itemAtIndex(index: number): IViewItem { - return this.heightMap[index]; - } - - itemAfter(item: IViewItem): IViewItem { - return this.heightMap[this.indexes[item.model.id] + 1] || null; - } - - protected createViewItem(item: Item): IViewItem { - throw new Error('not implemented'); - } - - dispose(): void { - this.heightMap = []; - this.indexes = {}; - } -} diff --git a/src/vs/base/parts/tree/test/browser/treeModel.test.ts b/src/vs/base/parts/tree/test/browser/treeModel.test.ts deleted file mode 100644 index 18d27eefada..00000000000 --- a/src/vs/base/parts/tree/test/browser/treeModel.test.ts +++ /dev/null @@ -1,1662 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as lifecycle from 'vs/base/common/lifecycle'; -import * as _ from 'vs/base/parts/tree/browser/tree'; -import * as model from 'vs/base/parts/tree/browser/treeModel'; -import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults'; -import { Event, Emitter } from 'vs/base/common/event'; -import { timeout } from 'vs/base/common/async'; - -export class FakeRenderer { - - public getHeight(tree: _.ITree, element: any): number { - return 20; - } - - public getTemplateId(tree: _.ITree, element: any): string { - return 'fake'; - } - - public renderTemplate(tree: _.ITree, templateId: string, container: any): any { - return null; - } - - public renderElement(tree: _.ITree, element: any, templateId: string, templateData: any): void { - // noop - } - - public disposeTemplate(tree: _.ITree, templateId: string, templateData: any): void { - // noop - } -} - -class TreeContext implements _.ITreeContext { - - public tree: _.ITree = null!; - public options: _.ITreeOptions = { autoExpandSingleChildren: true }; - public dataSource: _.IDataSource; - public renderer: _.IRenderer; - public controller?: _.IController; - public dnd?: _.IDragAndDrop; - public filter: _.IFilter; - public sorter: _.ISorter; - - constructor(public configuration: _.ITreeConfiguration) { - this.dataSource = configuration.dataSource; - this.renderer = configuration.renderer || new FakeRenderer(); - this.controller = configuration.controller; - this.dnd = configuration.dnd; - this.filter = configuration.filter || new TreeDefaults.DefaultFilter(); - this.sorter = configuration.sorter || new TreeDefaults.DefaultSorter(); - } -} - -class TreeModel extends model.TreeModel { - - constructor(configuration: _.ITreeConfiguration) { - super(new TreeContext(configuration)); - } -} - -class EventCounter { - - private listeners: lifecycle.IDisposable[]; - private _count: number; - - constructor() { - this.listeners = []; - this._count = 0; - } - - public listen(event: Event, fn: ((e: T) => void) | null = null): () => void { - let r = event(data => { - this._count++; - if (fn) { - fn(data); - } - }); - - this.listeners.push(r); - - return () => { - let idx = this.listeners.indexOf(r); - if (idx > -1) { - this.listeners.splice(idx, 1); - r.dispose(); - } - }; - } - - public up(): void { - this._count++; - } - - public get count(): number { - return this._count; - } - - public dispose(): void { - this.listeners = lifecycle.dispose(this.listeners); - this._count = -1; - } -} - -const SAMPLE: any = { - ONE: { id: 'one' }, - - AB: { - id: 'ROOT', children: [ - { - id: 'a', children: [ - { id: 'aa' }, - { id: 'ab' } - ] - }, - { id: 'b' }, - { - id: 'c', children: [ - { id: 'ca' }, - { id: 'cb' } - ] - } - ] - }, - - DEEP: { - id: 'ROOT', children: [ - { - id: 'a', children: [ - { - id: 'x', children: [ - { id: 'xa' }, - { id: 'xb' }, - ] - } - ] - }, - { id: 'b' } - ] - }, - - DEEP2: { - id: 'ROOT', children: [ - { - id: 'a', children: [ - { - id: 'x', children: [ - { id: 'xa' }, - { id: 'xb' }, - ] - }, - { id: 'y' } - ] - }, - { id: 'b' } - ] - } -}; - -class TestDataSource implements _.IDataSource { - public getId(tree: _.ITree, element: any): string { - return element.id; - } - - public hasChildren(tree: _.ITree, element: any): boolean { - return !!element.children; - } - - public getChildren(tree: _.ITree, element: any): Promise { - return Promise.resolve(element.children); - } - - public getParent(tree: _.ITree, element: any): Promise { - throw new Error('Not implemented'); - } -} - -suite('TreeModel', () => { - let model: model.TreeModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - model = new TreeModel({ - dataSource: new TestDataSource() - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('setInput, getInput', () => { - model.setInput(SAMPLE.ONE); - assert.equal(model.getInput(), SAMPLE.ONE); - }); - - test('refresh() refreshes all', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 4 - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(null); - }).then(() => { - assert.equal(counter.count, 8); - }); - }); - - test('refresh(root) refreshes all', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 4 - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(SAMPLE.AB); - }).then(() => { - assert.equal(counter.count, 8); - }); - }); - - test('refresh(root, false) refreshes the root', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 1 - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(SAMPLE.AB, false); - }).then(() => { - assert.equal(counter.count, 5); - }); - }); - - test('refresh(collapsed element) does not refresh descendants', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 1 - counter.listen(model.onRefreshItemChildren); // 0 - counter.listen(model.onDidRefreshItemChildren); // 0 - return model.refresh(SAMPLE.AB.children[0]); - }).then(() => { - assert.equal(counter.count, 3); - }); - }); - - test('refresh(expanded element) refreshes the element and descendants', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expand(SAMPLE.AB.children[0]).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 3 - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(SAMPLE.AB.children[0]); - }); - }).then(() => { - assert.equal(counter.count, 7); - }); - }); - - test('refresh(element, false) refreshes the element', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expand(SAMPLE.AB.children[0]).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem, item => { // 1 - assert.equal(item.id, 'a'); - counter.up(); - }); - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(SAMPLE.AB.children[0], false); - }); - }).then(() => { - assert.equal(counter.count, 6); - }); - }); - - test('depths', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll(['a', 'c']).then(() => { - counter.listen(model.onDidRefreshItem, item => { - switch (item.id) { - case 'ROOT': assert.equal(item.getDepth(), 0); break; - case 'a': assert.equal(item.getDepth(), 1); break; - case 'aa': assert.equal(item.getDepth(), 2); break; - case 'ab': assert.equal(item.getDepth(), 2); break; - case 'b': assert.equal(item.getDepth(), 1); break; - case 'c': assert.equal(item.getDepth(), 1); break; - case 'ca': assert.equal(item.getDepth(), 2); break; - case 'cb': assert.equal(item.getDepth(), 2); break; - default: return; - } - counter.up(); - }); - - return model.refresh(); - }); - }).then(() => { - assert.equal(counter.count, 16); - }); - }); - - test('intersections', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll(['a', 'c']).then(() => { - // going internals - const r = (model).registry; - - assert(r.getItem('a').intersects(r.getItem('a'))); - assert(r.getItem('a').intersects(r.getItem('aa'))); - assert(r.getItem('a').intersects(r.getItem('ab'))); - assert(r.getItem('aa').intersects(r.getItem('a'))); - assert(r.getItem('ab').intersects(r.getItem('a'))); - assert(!r.getItem('aa').intersects(r.getItem('ab'))); - assert(!r.getItem('a').intersects(r.getItem('b'))); - assert(!r.getItem('a').intersects(r.getItem('c'))); - assert(!r.getItem('a').intersects(r.getItem('ca'))); - assert(!r.getItem('aa').intersects(r.getItem('ca'))); - }); - }); - }); -}); - -suite('TreeModel - TreeNavigator', () => { - let model: model.TreeModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - model = new TreeModel({ - dataSource: new TestDataSource() - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('next()', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - }); - }); - - test('previous()', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(); - - nav.next(); - nav.next(); - - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - - test('parent()', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(); - - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.parent()!.id, 'a'); - - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.parent()!.id, 'a'); - - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next()!.id, 'ca'); - assert.equal(nav.parent()!.id, 'c'); - - assert.equal(nav.parent() && false, null); - }); - }); - }); - - test('next() - scoped', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0]); - return model.expand({ id: 'a' }).then(() => { - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next() && false, null); - }); - }); - }); - - test('previous() - scoped', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0]); - return model.expand({ id: 'a' }).then(() => { - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.previous()!.id, 'aa'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('parent() - scoped', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0]); - - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.parent() && false, null); - }); - }); - }); - - test('next() - non sub tree only', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0], false); - return model.expand({ id: 'a' }).then(() => { - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - }); - }); - }); - - test('previous() - non sub tree only', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0], false); - return model.expand({ id: 'a' }).then(() => { - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous()!.id, 'ab'); - assert.equal(nav.previous()!.id, 'aa'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('parent() - non sub tree only', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0], false); - - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.parent()!.id, 'a'); - assert.equal(nav.parent() && false, null); - }); - }); - }); - - test('deep next() - scoped', () => { - return model.setInput(SAMPLE.DEEP).then(() => { - return model.expand(SAMPLE.DEEP.children[0]).then(() => { - return model.expand(SAMPLE.DEEP.children[0].children[0]).then(() => { - const nav = model.getNavigator(SAMPLE.DEEP.children[0].children[0]); - assert.equal(nav.next()!.id, 'xa'); - assert.equal(nav.next()!.id, 'xb'); - assert.equal(nav.next() && false, null); - }); - }); - }); - }); - - test('deep previous() - scoped', () => { - return model.setInput(SAMPLE.DEEP).then(() => { - return model.expand(SAMPLE.DEEP.children[0]).then(() => { - return model.expand(SAMPLE.DEEP.children[0].children[0]).then(() => { - const nav = model.getNavigator(SAMPLE.DEEP.children[0].children[0]); - assert.equal(nav.next()!.id, 'xa'); - assert.equal(nav.next()!.id, 'xb'); - assert.equal(nav.previous()!.id, 'xa'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - }); - - test('last()', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.last()!.id, 'cb'); - }); - }); - }); -}); - -suite('TreeModel - Expansion', () => { - let model: model.TreeModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - model = new TreeModel({ - dataSource: new TestDataSource() - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('collapse, expand', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onExpandItem, (e) => { - assert.equal(e.item.id, 'a'); - const nav = model.getNavigator(e.item); - assert.equal(nav.next() && false, null); - }); - - counter.listen(model.onDidExpandItem, (e) => { - assert.equal(e.item.id, 'a'); - const nav = model.getNavigator(e.item); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next() && false, null); - }); - - assert(!model.isExpanded(SAMPLE.AB.children[0])); - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - - assert.equal(model.getExpandedElements().length, 0); - - return model.expand(SAMPLE.AB.children[0]).then(() => { - assert(model.isExpanded(SAMPLE.AB.children[0])); - - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - - const expandedElements = model.getExpandedElements(); - assert.equal(expandedElements.length, 1); - assert.equal(expandedElements[0].id, 'a'); - - assert.equal(counter.count, 2); - }); - }); - }); - - test('toggleExpansion', () => { - return model.setInput(SAMPLE.AB).then(() => { - assert(!model.isExpanded(SAMPLE.AB.children[0])); - - return model.toggleExpansion(SAMPLE.AB.children[0]).then(() => { - assert(model.isExpanded(SAMPLE.AB.children[0])); - assert(!model.isExpanded(SAMPLE.AB.children[0].children[0])); - - return model.toggleExpansion(SAMPLE.AB.children[0].children[0]).then(() => { - assert(!model.isExpanded(SAMPLE.AB.children[0].children[0])); - - return model.toggleExpansion(SAMPLE.AB.children[0]).then(() => { - assert(!model.isExpanded(SAMPLE.AB.children[0])); - }); - }); - }); - }); - }); - - test('collapseAll', () => { - return model.setInput(SAMPLE.DEEP2).then(() => { - return model.expand(SAMPLE.DEEP2.children[0]).then(() => { - return model.expand(SAMPLE.DEEP2.children[0].children[0]).then(() => { - - assert(model.isExpanded(SAMPLE.DEEP2.children[0])); - assert(model.isExpanded(SAMPLE.DEEP2.children[0].children[0])); - - return model.collapseAll().then(() => { - assert(!model.isExpanded(SAMPLE.DEEP2.children[0])); - - return model.expand(SAMPLE.DEEP2.children[0]).then(() => { - assert(!model.isExpanded(SAMPLE.DEEP2.children[0].children[0])); - }); - }); - }); - }); - }); - }); - - test('auto expand single child folders', () => { - return model.setInput(SAMPLE.DEEP).then(() => { - return model.expand(SAMPLE.DEEP.children[0]).then(() => { - assert(model.isExpanded(SAMPLE.DEEP.children[0])); - assert(model.isExpanded(SAMPLE.DEEP.children[0].children[0])); - }); - }); - }); - - test('expand can trigger refresh', () => { - // MUnit.expect(16); - return model.setInput(SAMPLE.AB).then(() => { - - assert(!model.isExpanded(SAMPLE.AB.children[0])); - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - - const f: () => void = counter.listen(model.onRefreshItemChildren, (e) => { - assert.equal(e.item.id, 'a'); - f(); - }); - - const g: () => void = counter.listen(model.onDidRefreshItemChildren, (e) => { - assert.equal(e.item.id, 'a'); - g(); - }); - - return model.expand(SAMPLE.AB.children[0]).then(() => { - assert(model.isExpanded(SAMPLE.AB.children[0])); - - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - - assert.equal(counter.count, 2); - }); - }); - }); - - test('top level collapsed', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.collapseAll([{ id: 'a' }, { id: 'b' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('shouldAutoexpand', () => { - // setup - const model = new TreeModel({ - dataSource: { - getId: (_, e) => e, - hasChildren: (_, e) => true, - getChildren: (_, e) => { - if (e === 'root') { return Promise.resolve(['a', 'b', 'c']); } - if (e === 'b') { return Promise.resolve(['b1']); } - return Promise.resolve([]); - }, - getParent: (_, e): Promise => { throw new Error('not implemented'); }, - shouldAutoexpand: (_, e) => e === 'b' - } - }); - - return model.setInput('root').then(() => { - return model.refresh('root', true); - }).then(() => { - assert(!model.isExpanded('a')); - assert(model.isExpanded('b')); - assert(!model.isExpanded('c')); - }); - }); -}); - -class TestFilter implements _.IFilter { - - public fn: (element: any) => boolean; - - constructor() { - this.fn = () => true; - } - - public isVisible(tree: _.ITree, element: any): boolean { - return this.fn(element); - } -} - -suite('TreeModel - Filter', () => { - let model: model.TreeModel; - let counter: EventCounter; - let filter: TestFilter; - - setup(() => { - counter = new EventCounter(); - filter = new TestFilter(); - model = new TreeModel({ - dataSource: new TestDataSource(), - filter: filter - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('no filter', () => { - return model.setInput(SAMPLE.AB).then(() => { - - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next()!.id, 'ca'); - assert.equal(nav.next()!.id, 'cb'); - - assert.equal(nav.previous()!.id, 'ca'); - assert.equal(nav.previous()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous()!.id, 'ab'); - assert.equal(nav.previous()!.id, 'aa'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('filter all', () => { - filter.fn = () => false; - - return model.setInput(SAMPLE.AB).then(() => { - return model.refresh().then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next() && false, null); - }); - }); - }); - - test('simple filter', () => { - // hide elements that do not start with 'a' - filter.fn = (e) => e.id[0] === 'a'; - - return model.setInput(SAMPLE.AB).then(() => { - return model.expand({ id: 'a' }).then(() => { - - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.previous()!.id, 'aa'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('simple filter 2', () => { - // hide 'ab' - filter.fn = (e) => e.id !== 'ab'; - - return model.setInput(SAMPLE.AB).then(() => { - return model.expand({ id: 'a' }).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - }); - }); - }); - - test('simple filter, opposite', () => { - // hide elements that start with 'a' - filter.fn = (e) => e.id[0] !== 'a'; - - return model.setInput(SAMPLE.AB).then(() => { - return model.expand({ id: 'c' }).then(() => { - - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next()!.id, 'ca'); - assert.equal(nav.next()!.id, 'cb'); - assert.equal(nav.previous()!.id, 'ca'); - assert.equal(nav.previous()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('simple filter, mischieving', () => { - // hide the element 'a' - filter.fn = (e) => e.id !== 'a'; - - return model.setInput(SAMPLE.AB).then(() => { - return model.expand({ id: 'c' }).then(() => { - - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next()!.id, 'ca'); - assert.equal(nav.next()!.id, 'cb'); - assert.equal(nav.previous()!.id, 'ca'); - assert.equal(nav.previous()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('simple filter & previous', () => { - // hide 'b' - filter.fn = (e) => e.id !== 'b'; - - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator({ id: 'c' }, false); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); -}); - -suite('TreeModel - Traits', () => { - let model: model.TreeModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - model = new TreeModel({ - dataSource: new TestDataSource() - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('Selection', () => { - return model.setInput(SAMPLE.AB).then(() => { - assert.equal(model.getSelection().length, 0); - model.select(SAMPLE.AB.children[1]); - assert(model.isSelected(SAMPLE.AB.children[1])); - assert.equal(model.getSelection().length, 1); - model.select(SAMPLE.AB.children[0]); - assert(model.isSelected(SAMPLE.AB.children[0])); - assert.equal(model.getSelection().length, 2); - model.select(SAMPLE.AB.children[2]); - assert(model.isSelected(SAMPLE.AB.children[2])); - assert.equal(model.getSelection().length, 3); - model.deselect(SAMPLE.AB.children[0]); - assert(!model.isSelected(SAMPLE.AB.children[0])); - assert.equal(model.getSelection().length, 2); - model.setSelection([]); - assert(!model.isSelected(SAMPLE.AB.children[0])); - assert(!model.isSelected(SAMPLE.AB.children[1])); - assert(!model.isSelected(SAMPLE.AB.children[2])); - assert.equal(model.getSelection().length, 0); - model.selectAll([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]); - assert.equal(model.getSelection().length, 3); - model.select(SAMPLE.AB.children[0]); - assert.equal(model.getSelection().length, 3); - model.deselectAll([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]); - assert.equal(model.getSelection().length, 0); - model.deselect(SAMPLE.AB.children[0]); - assert.equal(model.getSelection().length, 0); - - model.setSelection([SAMPLE.AB.children[0]]); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - assert(!model.isSelected(SAMPLE.AB.children[1])); - assert(!model.isSelected(SAMPLE.AB.children[2])); - - model.setSelection([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]); - assert.equal(model.getSelection().length, 3); - assert(model.isSelected(SAMPLE.AB.children[0])); - assert(model.isSelected(SAMPLE.AB.children[1])); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.setSelection([SAMPLE.AB.children[1], SAMPLE.AB.children[2]]); - assert.equal(model.getSelection().length, 2); - assert(!model.isSelected(SAMPLE.AB.children[0])); - assert(model.isSelected(SAMPLE.AB.children[1])); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.setSelection([]); - assert.deepEqual(model.getSelection(), []); - assert.equal(model.getSelection().length, 0); - assert(!model.isSelected(SAMPLE.AB.children[0])); - assert(!model.isSelected(SAMPLE.AB.children[1])); - assert(!model.isSelected(SAMPLE.AB.children[2])); - - model.selectNext(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - - model.selectNext(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[1])); - - model.selectNext(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.selectNext(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.selectPrevious(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[1])); - - model.selectPrevious(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - - model.selectPrevious(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - - model.selectNext(2); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.selectPrevious(4); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - - assert.equal(model.isSelected(SAMPLE.AB.children[0]), true); - assert.equal(model.isSelected(SAMPLE.AB.children[2]), false); - }); - }); - - test('Focus', () => { - return model.setInput(SAMPLE.AB).then(() => { - assert(!model.getFocus()); - model.setFocus(SAMPLE.AB.children[1]); - assert(model.isFocused(SAMPLE.AB.children[1])); - assert(model.getFocus()); - model.setFocus(SAMPLE.AB.children[0]); - assert(model.isFocused(SAMPLE.AB.children[0])); - assert(model.getFocus()); - model.setFocus(SAMPLE.AB.children[2]); - assert(model.isFocused(SAMPLE.AB.children[2])); - assert(model.getFocus()); - model.setFocus(); - assert(!model.isFocused(SAMPLE.AB.children[0])); - assert(!model.isFocused(SAMPLE.AB.children[1])); - assert(!model.isFocused(SAMPLE.AB.children[2])); - assert(!model.getFocus()); - - model.setFocus(SAMPLE.AB.children[0]); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - assert(!model.isFocused(SAMPLE.AB.children[1])); - assert(!model.isFocused(SAMPLE.AB.children[2])); - - model.setFocus(); - assert(!model.getFocus()); - assert(!model.isFocused(SAMPLE.AB.children[0])); - assert(!model.isFocused(SAMPLE.AB.children[1])); - assert(!model.isFocused(SAMPLE.AB.children[2])); - - model.focusNext(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - - model.focusNext(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[1])); - - model.focusNext(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[2])); - - model.focusNext(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[2])); - - model.focusPrevious(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[1])); - - model.focusPrevious(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - - model.focusPrevious(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - - model.focusNext(2); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[2])); - - model.focusPrevious(4); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - - assert.equal(model.isFocused(SAMPLE.AB.children[0]), true); - assert.equal(model.isFocused(SAMPLE.AB.children[2]), false); - - model.focusFirst(); - assert(model.isFocused(SAMPLE.AB.children[0])); - model.focusNth(0); - assert(model.isFocused(SAMPLE.AB.children[0])); - model.focusNth(1); - assert(model.isFocused(SAMPLE.AB.children[1])); - }); - }); - - test('Highlight', () => { - return model.setInput(SAMPLE.AB).then(() => { - assert(!model.getHighlight()); - model.setHighlight(SAMPLE.AB.children[1]); - assert(model.isHighlighted(SAMPLE.AB.children[1])); - assert(model.getHighlight()); - model.setHighlight(SAMPLE.AB.children[0]); - assert(model.isHighlighted(SAMPLE.AB.children[0])); - assert(model.getHighlight()); - model.setHighlight(SAMPLE.AB.children[2]); - assert(model.isHighlighted(SAMPLE.AB.children[2])); - assert(model.getHighlight()); - model.setHighlight(); - assert(!model.isHighlighted(SAMPLE.AB.children[0])); - assert(!model.isHighlighted(SAMPLE.AB.children[1])); - assert(!model.isHighlighted(SAMPLE.AB.children[2])); - assert(!model.getHighlight()); - - model.setHighlight(SAMPLE.AB.children[0]); - assert(model.getHighlight()); - assert(model.isHighlighted(SAMPLE.AB.children[0])); - assert(!model.isHighlighted(SAMPLE.AB.children[1])); - assert(!model.isHighlighted(SAMPLE.AB.children[2])); - - assert.equal(model.isHighlighted(SAMPLE.AB.children[0]), true); - assert.equal(model.isHighlighted(SAMPLE.AB.children[2]), false); - - model.setHighlight(); - assert(!model.getHighlight()); - assert(!model.isHighlighted(SAMPLE.AB.children[0])); - assert(!model.isHighlighted(SAMPLE.AB.children[1])); - assert(!model.isHighlighted(SAMPLE.AB.children[2])); - }); - }); -}); - -class DynamicModel implements _.IDataSource { - - private data: any; - public promiseFactory: { (): Promise; } | null; - - private readonly _onGetChildren = new Emitter(); - readonly onGetChildren: Event = this._onGetChildren.event; - - private readonly _onDidGetChildren = new Emitter(); - readonly onDidGetChildren: Event = this._onDidGetChildren.event; - - constructor() { - this.data = { root: [] }; - this.promiseFactory = null; - } - - public addChild(parent: string, child: string): void { - if (!this.data[parent]) { - this.data[parent] = []; - } - this.data[parent].push(child); - } - - public removeChild(parent: string, child: string): void { - this.data[parent].splice(this.data[parent].indexOf(child), 1); - if (this.data[parent].length === 0) { - delete this.data[parent]; - } - } - - public move(element: string, oldParent: string, newParent: string): void { - this.removeChild(oldParent, element); - this.addChild(newParent, element); - } - - public rename(parent: string, oldName: string, newName: string): void { - this.removeChild(parent, oldName); - this.addChild(parent, newName); - } - - public getId(tree: _.ITree, element: any): string { - return element; - } - - public hasChildren(tree: _.ITree, element: any): boolean { - return !!this.data[element]; - } - - public getChildren(tree: _.ITree, element: any): Promise { - this._onGetChildren.fire(element); - const result = this.promiseFactory ? this.promiseFactory() : Promise.resolve(null); - return result.then(() => { - this._onDidGetChildren.fire(element); - return Promise.resolve(this.data[element]); - }); - } - - public getParent(tree: _.ITree, element: any): Promise { - throw new Error('Not implemented'); - } -} - -suite('TreeModel - Dynamic data model', () => { - let model: model.TreeModel; - let dataModel: DynamicModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - dataModel = new DynamicModel(); - model = new TreeModel({ - dataSource: dataModel, - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('items get property disposed', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - dataModel.addChild('father', 'daughter'); - dataModel.addChild('son', 'baby'); - - return model.setInput('root').then(() => { - return model.expandAll(['grandfather', 'father', 'son']).then(() => { - dataModel.removeChild('grandfather', 'father'); - - const items = ['baby', 'son', 'daughter', 'father']; - let times = 0; - counter.listen(model.onDidDisposeItem, item => { - assert.equal(items[times++], item.id); - }); - - return model.refresh().then(() => { - assert.equal(times, items.length); - assert.equal(counter.count, 4); - }); - }); - }); - }); - - test('addChild, removeChild, collapse', () => { - dataModel.addChild('root', 'super'); - dataModel.addChild('root', 'hyper'); - dataModel.addChild('root', 'mega'); - - return model.setInput('root').then(() => { - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'hyper'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next() && false, null); - - dataModel.removeChild('root', 'hyper'); - return model.refresh().then(() => { - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next() && false, null); - - dataModel.addChild('mega', 'micro'); - dataModel.addChild('mega', 'nano'); - dataModel.addChild('mega', 'pico'); - - return model.refresh().then(() => { - return model.expand('mega').then(() => { - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next()!.id, 'micro'); - assert.equal(nav.next()!.id, 'nano'); - assert.equal(nav.next()!.id, 'pico'); - assert.equal(nav.next() && false, null); - - model.collapse('mega'); - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next() && false, null); - }); - }); - }); - }); - }); - - test('move', () => { - dataModel.addChild('root', 'super'); - dataModel.addChild('super', 'apples'); - dataModel.addChild('super', 'bananas'); - dataModel.addChild('super', 'pears'); - dataModel.addChild('root', 'hyper'); - dataModel.addChild('root', 'mega'); - - return model.setInput('root').then(() => { - - return model.expand('super').then(() => { - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'apples'); - assert.equal(nav.next()!.id, 'bananas'); - assert.equal(nav.next()!.id, 'pears'); - assert.equal(nav.next()!.id, 'hyper'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next() && false, null); - - dataModel.move('bananas', 'super', 'hyper'); - dataModel.move('apples', 'super', 'mega'); - - return model.refresh().then(() => { - - return model.expandAll(['hyper', 'mega']).then(() => { - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'pears'); - assert.equal(nav.next()!.id, 'hyper'); - assert.equal(nav.next()!.id, 'bananas'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next()!.id, 'apples'); - assert.equal(nav.next() && false, null); - }); - }); - }); - }); - }); - - test('refreshing grandfather recursively should not refresh collapsed father\'s children immediately', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.collapse('father').then(() => { - let times = 0; - let listener = dataModel.onGetChildren((element) => { - times++; - assert.equal(element, 'grandfather'); - }); - - return model.refresh('grandfather').then(() => { - assert.equal(times, 1); - listener.dispose(); - - listener = dataModel.onGetChildren((element) => { - times++; - assert.equal(element, 'father'); - }); - - return model.expand('father').then(() => { - assert.equal(times, 2); - listener.dispose(); - }); - }); - }); - }); - }); - }); - - test('simultaneously refreshing two disjoint elements should parallelize the refreshes', () => { - dataModel.addChild('root', 'father'); - dataModel.addChild('root', 'mother'); - dataModel.addChild('father', 'son'); - dataModel.addChild('mother', 'daughter'); - - return model.setInput('root').then(() => { - return model.expand('father').then(() => { - return model.expand('mother').then(() => { - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'son'); - assert.equal(nav.next()!.id, 'mother'); - assert.equal(nav.next()!.id, 'daughter'); - assert.equal(nav.next() && false, null); - - dataModel.removeChild('father', 'son'); - dataModel.removeChild('mother', 'daughter'); - dataModel.addChild('father', 'brother'); - dataModel.addChild('mother', 'sister'); - - dataModel.promiseFactory = () => { return timeout(0); }; - - let getTimes = 0; - let gotTimes = 0; - const getListener = dataModel.onGetChildren((element) => { getTimes++; }); - const gotListener = dataModel.onDidGetChildren((element) => { gotTimes++; }); - - const p1 = model.refresh('father'); - assert.equal(getTimes, 1); - assert.equal(gotTimes, 0); - - const p2 = model.refresh('mother'); - assert.equal(getTimes, 2); - assert.equal(gotTimes, 0); - - return Promise.all([p1, p2]).then(() => { - assert.equal(getTimes, 2); - assert.equal(gotTimes, 2); - - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'brother'); - assert.equal(nav.next()!.id, 'mother'); - assert.equal(nav.next()!.id, 'sister'); - assert.equal(nav.next() && false, null); - - getListener.dispose(); - gotListener.dispose(); - }); - }); - }); - }); - }); - - test('simultaneously recursively refreshing two intersecting elements should concatenate the refreshes - ancestor first', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'grandfather'); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'son'); - assert.equal(nav.next() && false, null); - - let refreshTimes = 0; - counter.listen(model.onDidRefreshItem, (e) => { refreshTimes++; }); - - let getTimes = 0; - const getListener = dataModel.onGetChildren((element) => { getTimes++; }); - - let gotTimes = 0; - const gotListener = dataModel.onDidGetChildren((element) => { gotTimes++; }); - - const p1Completes: Array<(value?: any) => void> = []; - dataModel.promiseFactory = () => { return new Promise((c) => { p1Completes.push(c); }); }; - - model.refresh('grandfather').then(() => { - // just a single get - assert.equal(refreshTimes, 1); // (+1) grandfather - assert.equal(getTimes, 1); - assert.equal(gotTimes, 0); - - // unblock the first get - p1Completes.shift()!(); - - // once the first get is unblocked, the second get should appear - assert.equal(refreshTimes, 2); // (+1) first father refresh - assert.equal(getTimes, 2); - assert.equal(gotTimes, 1); - - let p2Complete: () => void; - dataModel.promiseFactory = () => { return new Promise((c) => { p2Complete = c; }); }; - const p2 = model.refresh('father'); - - // same situation still - assert.equal(refreshTimes, 3); // (+1) second father refresh - assert.equal(getTimes, 2); - assert.equal(gotTimes, 1); - - // unblock the second get - p1Completes.shift()!(); - - // the third get should have appeared, it should've been waiting for the second one - assert.equal(refreshTimes, 4); // (+1) first son request - assert.equal(getTimes, 3); - assert.equal(gotTimes, 2); - - p2Complete!(); - - // all good - assert.equal(refreshTimes, 5); // (+1) second son request - assert.equal(getTimes, 3); - assert.equal(gotTimes, 3); - - return p2.then(() => { - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'grandfather'); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'son'); - assert.equal(nav.next() && false, null); - - getListener.dispose(); - gotListener.dispose(); - }); - }); - }); - }); - }); - }); - - test('refreshing an empty element that adds children should still keep it collapsed', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - assert(!model.isExpanded('father')); - - dataModel.addChild('father', 'son'); - - return model.refresh('father').then(() => { - assert(!model.isExpanded('father')); - }); - }); - }); - }); - }); - - test('refreshing a collapsed element that adds children should still keep it collapsed', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - return model.collapse('father').then(() => { - assert(!model.isExpanded('father')); - - dataModel.addChild('father', 'daughter'); - - return model.refresh('father').then(() => { - assert(!model.isExpanded('father')); - }); - }); - }); - }); - }); - }); - - test('recursively refreshing an ancestor of an expanded element, should keep that element expanded', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - assert(model.isExpanded('grandfather')); - assert(model.isExpanded('father')); - - return model.refresh('grandfather').then(() => { - assert(model.isExpanded('grandfather')); - assert(model.isExpanded('father')); - }); - }); - }); - }); - }); - - test('recursively refreshing an ancestor of a collapsed element, should keep that element collapsed', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - return model.collapse('father').then(() => { - assert(model.isExpanded('grandfather')); - assert(!model.isExpanded('father')); - - return model.refresh('grandfather').then(() => { - assert(model.isExpanded('grandfather')); - assert(!model.isExpanded('father')); - }); - }); - }); - }); - }); - }); - - test('Bug 10855:[explorer] quickly deleting things causes NPE in tree - intersectsLock should always be called when trying to unlock', () => { - dataModel.addChild('root', 'father'); - dataModel.addChild('father', 'son'); - dataModel.addChild('root', 'mother'); - dataModel.addChild('mother', 'daughter'); - - return model.setInput('root').then(() => { - - // delay expansions and refreshes - dataModel.promiseFactory = () => { return timeout(0); }; - - const promises: Promise[] = []; - - promises.push(model.expand('father')); - dataModel.removeChild('root', 'father'); - promises.push(model.refresh('root')); - - promises.push(model.expand('mother')); - dataModel.removeChild('root', 'mother'); - promises.push(model.refresh('root')); - - return Promise.all(promises).then(() => { - assert(true, 'all good'); - }, (errs) => { - assert(false, 'should not fail'); - }); - }); - }); -}); - -suite('TreeModel - bugs', () => { - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - }); - - teardown(() => { - counter.dispose(); - }); - - /** - * This bug occurs when an item is expanded right during its removal - */ - test('Bug 10566:[tree] build viewlet is broken after some time', () => { - // setup - let model = new TreeModel({ - dataSource: { - getId: (_, e) => e, - hasChildren: (_, e) => e === 'root' || e === 'bart', - getChildren: (_, e) => { - if (e === 'root') { return getRootChildren(); } - if (e === 'bart') { return getBartChildren(); } - return Promise.resolve([]); - }, - getParent: (_, e): Promise => { throw new Error('not implemented'); }, - } - }); - - let listeners = []; - - // helpers - const getGetRootChildren = (children: string[], millis = 0) => () => timeout(millis).then(() => children); - let getRootChildren = getGetRootChildren(['homer', 'bart', 'lisa', 'marge', 'maggie'], 0); - const getGetBartChildren = (millis = 0) => () => timeout(millis).then(() => ['milhouse', 'nelson']); - const getBartChildren = getGetBartChildren(0); - - // item expanding should not exist! - counter.listen(model.onExpandItem, () => { assert(false, 'should never receive item:expanding event'); }); - counter.listen(model.onDidExpandItem, () => { assert(false, 'should never receive item:expanded event'); }); - - return model.setInput('root').then(() => { - - // remove bart - getRootChildren = getGetRootChildren(['homer', 'lisa', 'marge', 'maggie'], 10); - - // refresh root - const p1 = model.refresh('root', true).then(() => { - assert(true); - }, () => { - assert(false, 'should never reach this'); - }); - - // at the same time, try to expand bart! - const p2 = model.expand('bart').then(() => { - assert(false, 'should never reach this'); - }, () => { - assert(true, 'bart should fail to expand since he was removed meanwhile'); - }); - - // what now? - return Promise.all([p1, p2]); - - }).then(() => { - - // teardown - while (listeners.length > 0) { listeners.pop()(); } - listeners = null; - model.dispose(); - - assert.equal(counter.count, 0); - }); - }); - - test('collapsed resolved parent should also update all children visibility on refresh', async function () { - const counter = new EventCounter(); - const dataModel = new DynamicModel(); - - let isSonVisible = true; - const filter: _.IFilter = { - isVisible(_, element) { - return element !== 'son' || isSonVisible; - } - }; - - const model = new TreeModel({ dataSource: dataModel, filter }); - - dataModel.addChild('root', 'father'); - dataModel.addChild('father', 'son'); - - await model.setInput('root'); - await model.expand('father'); - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'son'); - assert.equal(nav.next(), null); - - await model.collapse('father'); - isSonVisible = false; - - await model.refresh(undefined, true); - await model.expand('father'); - - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next(), null); - - counter.dispose(); - model.dispose(); - }); -}); diff --git a/src/vs/base/parts/tree/test/browser/treeViewModel.test.ts b/src/vs/base/parts/tree/test/browser/treeViewModel.test.ts deleted file mode 100644 index 97c2846ebb6..00000000000 --- a/src/vs/base/parts/tree/test/browser/treeViewModel.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { ArrayIterator } from 'vs/base/common/iterator'; -import { HeightMap, IViewItem } from 'vs/base/parts/tree/browser/treeViewModel'; - -function makeItem(id: any, height: any): any { - return { - id: id, - getHeight: function () { return height; }, - isExpanded: function () { return false; }, - getAllTraits: () => [] - }; -} - -function makeItems(...args: any[]) { - let r: any[] = []; - - for (let i = 0; i < args.length; i += 2) { - r.push(makeItem(args[i], args[i + 1])); - } - - return r; -} - -function makeNavigator(...args: any[]): any { - let items = makeItems.apply(null, args); - let i = 0; - - return { - next: function () { - return items[i++] || null; - } - }; -} - -class TestHeightMap extends HeightMap { - - protected createViewItem(item: any): IViewItem { - return { - model: item, - top: 0, - height: item.getHeight(), - width: 0 - }; - } -} - -suite('TreeView - HeightMap', () => { - let rangeMap: HeightMap; - - setup(() => { - rangeMap = new TestHeightMap(); - rangeMap.onInsertItems(makeNavigator('a', 3, 'b', 30, 'c', 25, 'd', 2)); - }); - - teardown(() => { - rangeMap.dispose(); - rangeMap = null!; - }); - - test('simple', () => { - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.equal(rangeMap.itemAt(33), 'c'); - assert.equal(rangeMap.itemAt(40), 'c'); - assert.equal(rangeMap.itemAt(57), 'c'); - assert.equal(rangeMap.itemAt(58), 'd'); - assert.equal(rangeMap.itemAt(59), 'd'); - assert.throws(() => rangeMap.itemAt(60)); - }); - - test('onInsertItems at beginning', () => { - let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8); - rangeMap.onInsertItems(navigator); - - assert.equal(rangeMap.itemAt(0), 'x'); - assert.equal(rangeMap.itemAt(3), 'x'); - assert.equal(rangeMap.itemAt(4), 'y'); - assert.equal(rangeMap.itemAt(23), 'y'); - assert.equal(rangeMap.itemAt(24), 'z'); - assert.equal(rangeMap.itemAt(31), 'z'); - assert.equal(rangeMap.itemAt(32), 'a'); - assert.equal(rangeMap.itemAt(34), 'a'); - assert.equal(rangeMap.itemAt(35), 'b'); - assert.equal(rangeMap.itemAt(64), 'b'); - assert.equal(rangeMap.itemAt(65), 'c'); - assert.equal(rangeMap.itemAt(89), 'c'); - assert.equal(rangeMap.itemAt(90), 'd'); - assert.equal(rangeMap.itemAt(91), 'd'); - assert.throws(() => rangeMap.itemAt(92)); - }); - - test('onInsertItems in middle', () => { - let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8); - rangeMap.onInsertItems(navigator, 'a'); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'x'); - assert.equal(rangeMap.itemAt(6), 'x'); - assert.equal(rangeMap.itemAt(7), 'y'); - assert.equal(rangeMap.itemAt(26), 'y'); - assert.equal(rangeMap.itemAt(27), 'z'); - assert.equal(rangeMap.itemAt(34), 'z'); - assert.equal(rangeMap.itemAt(35), 'b'); - assert.equal(rangeMap.itemAt(64), 'b'); - assert.equal(rangeMap.itemAt(65), 'c'); - assert.equal(rangeMap.itemAt(89), 'c'); - assert.equal(rangeMap.itemAt(90), 'd'); - assert.equal(rangeMap.itemAt(91), 'd'); - assert.throws(() => rangeMap.itemAt(92)); - }); - - test('onInsertItems at end', () => { - let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8); - rangeMap.onInsertItems(navigator, 'd'); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.equal(rangeMap.itemAt(33), 'c'); - assert.equal(rangeMap.itemAt(57), 'c'); - assert.equal(rangeMap.itemAt(58), 'd'); - assert.equal(rangeMap.itemAt(59), 'd'); - assert.equal(rangeMap.itemAt(60), 'x'); - assert.equal(rangeMap.itemAt(63), 'x'); - assert.equal(rangeMap.itemAt(64), 'y'); - assert.equal(rangeMap.itemAt(83), 'y'); - assert.equal(rangeMap.itemAt(84), 'z'); - assert.equal(rangeMap.itemAt(91), 'z'); - assert.throws(() => rangeMap.itemAt(92)); - }); - - test('onRemoveItems at beginning', () => { - rangeMap.onRemoveItems(new ArrayIterator(['a', 'b'])); - - assert.equal(rangeMap.itemAt(0), 'c'); - assert.equal(rangeMap.itemAt(24), 'c'); - assert.equal(rangeMap.itemAt(25), 'd'); - assert.equal(rangeMap.itemAt(26), 'd'); - assert.throws(() => rangeMap.itemAt(27)); - }); - - test('onRemoveItems in middle', () => { - rangeMap.onRemoveItems(new ArrayIterator(['c'])); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.equal(rangeMap.itemAt(33), 'd'); - assert.equal(rangeMap.itemAt(34), 'd'); - assert.throws(() => rangeMap.itemAt(35)); - }); - - test('onRemoveItems at end', () => { - rangeMap.onRemoveItems(new ArrayIterator(['c', 'd'])); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.throws(() => rangeMap.itemAt(33)); - }); - - test('onRefreshItems at beginning', () => { - let navigator = makeNavigator('a', 1, 'b', 1); - rangeMap.onRefreshItems(navigator); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(1), 'b'); - assert.equal(rangeMap.itemAt(2), 'c'); - assert.equal(rangeMap.itemAt(26), 'c'); - assert.equal(rangeMap.itemAt(27), 'd'); - assert.equal(rangeMap.itemAt(28), 'd'); - assert.throws(() => rangeMap.itemAt(29)); - }); - - test('onRefreshItems in middle', () => { - let navigator = makeNavigator('b', 40, 'c', 4); - rangeMap.onRefreshItems(navigator); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(42), 'b'); - assert.equal(rangeMap.itemAt(43), 'c'); - assert.equal(rangeMap.itemAt(46), 'c'); - assert.equal(rangeMap.itemAt(47), 'd'); - assert.equal(rangeMap.itemAt(48), 'd'); - assert.throws(() => rangeMap.itemAt(49)); - }); - - test('onRefreshItems at end', () => { - let navigator = makeNavigator('d', 22); - rangeMap.onRefreshItems(navigator); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.equal(rangeMap.itemAt(33), 'c'); - assert.equal(rangeMap.itemAt(57), 'c'); - assert.equal(rangeMap.itemAt(58), 'd'); - assert.equal(rangeMap.itemAt(79), 'd'); - assert.throws(() => rangeMap.itemAt(80)); - }); - - test('withItemsInRange', () => { - let i = 0; - let itemsInRange = ['a', 'b']; - rangeMap.withItemsInRange(2, 27, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['a', 'b']; - rangeMap.withItemsInRange(0, 3, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['a']; - rangeMap.withItemsInRange(0, 2, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['a']; - rangeMap.withItemsInRange(0, 2, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['b', 'c']; - rangeMap.withItemsInRange(15, 39, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['a', 'b', 'c', 'd']; - rangeMap.withItemsInRange(1, 58, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['c', 'd']; - rangeMap.withItemsInRange(45, 58, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - }); -}); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 58a9d5e09ee..301812d07d1 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -9,17 +9,12 @@ import { IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging'; import { DefaultStyleController, IListOptions, IMultipleSelectionController, IOpenController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List } from 'vs/base/browser/ui/list/listWidget'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, dispose, IDisposable, toDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { isUndefinedOrNull } from 'vs/base/common/types'; -import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; -import { ClickBehavior, DefaultController, DefaultTreestyler, IControllerOptions, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; -import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { localize } from 'vs/nls'; import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } from 'vs/platform/theme/common/styler'; @@ -32,7 +27,7 @@ import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -export type ListWidget = List | PagedList | ITree | ObjectTree | DataTree | AsyncDataTree; +export type ListWidget = List | PagedList | ObjectTree | DataTree | AsyncDataTree; export const IListService = createDecorator('listService'); @@ -424,182 +419,6 @@ export class WorkbenchPagedList extends PagedList { } } -/** - * @deprecated - */ -let sharedTreeStyleSheet: HTMLStyleElement; -function getSharedTreeStyleSheet(): HTMLStyleElement { - if (!sharedTreeStyleSheet) { - sharedTreeStyleSheet = createStyleSheet(); - } - - return sharedTreeStyleSheet; -} - -/** - * @deprecated - */ -function handleTreeController(configuration: ITreeConfiguration, instantiationService: IInstantiationService): ITreeConfiguration { - if (!configuration.controller) { - configuration.controller = instantiationService.createInstance(WorkbenchTreeController, {}); - } - - if (!configuration.styler) { - configuration.styler = new DefaultTreestyler(getSharedTreeStyleSheet()); - } - - return configuration; -} - -/** - * @deprecated - */ -export class WorkbenchTree extends Tree { - - readonly contextKeyService: IContextKeyService; - - protected disposables: IDisposable[]; - - private listHasSelectionOrFocus: IContextKey; - private listDoubleSelection: IContextKey; - private listMultiSelection: IContextKey; - - private _openOnSingleClick: boolean; - private _useAltAsMultipleSelectionModifier: boolean; - - constructor( - container: HTMLElement, - configuration: ITreeConfiguration, - options: ITreeOptions | undefined, - @IContextKeyService contextKeyService: IContextKeyService, - @IListService listService: IListService, - @IThemeService themeService: IThemeService, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService - ) { - const config = handleTreeController(configuration, instantiationService); - const horizontalScrollMode = configurationService.getValue(horizontalScrollingKey) ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden; - const opts = { - horizontalScrollMode, - keyboardSupport: false, - ...computeStyles(themeService.getColorTheme(), defaultListStyles), - ...options - }; - - super(container, config, opts); - - this.disposables = []; - this.contextKeyService = createScopedContextKeyService(contextKeyService, this); - - WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); - - this.listHasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService); - this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService); - this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService); - - this._openOnSingleClick = useSingleClickToOpen(configurationService); - this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - - this.disposables.push( - this.contextKeyService, - (listService as ListService).register(this), - attachListStyler(this, themeService) - ); - - this.disposables.push(this.onDidChangeSelection(() => { - const selection = this.getSelection(); - const focus = this.getFocus(); - - this.listHasSelectionOrFocus.set((selection && selection.length > 0) || !!focus); - this.listDoubleSelection.set(selection && selection.length === 2); - this.listMultiSelection.set(selection && selection.length > 1); - })); - - this.disposables.push(this.onDidChangeFocus(() => { - const selection = this.getSelection(); - const focus = this.getFocus(); - - this.listHasSelectionOrFocus.set((selection && selection.length > 0) || !!focus); - })); - - this.disposables.push(configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(openModeSettingKey)) { - this._openOnSingleClick = useSingleClickToOpen(configurationService); - } - - if (e.affectsConfiguration(multiSelectModifierSettingKey)) { - this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - } - })); - } - - get openOnSingleClick(): boolean { - return this._openOnSingleClick; - } - - get useAltAsMultipleSelectionModifier(): boolean { - return this._useAltAsMultipleSelectionModifier; - } - - dispose(): void { - super.dispose(); - - this.disposables = dispose(this.disposables); - } -} - -/** - * @deprecated - */ -function massageControllerOptions(options: IControllerOptions): IControllerOptions { - if (typeof options.keyboardSupport !== 'boolean') { - options.keyboardSupport = false; - } - - if (typeof options.clickBehavior !== 'number') { - options.clickBehavior = ClickBehavior.ON_MOUSE_DOWN; - } - - return options; -} - -/** - * @deprecated - */ -export class WorkbenchTreeController extends DefaultController { - - protected readonly disposables = new DisposableStore(); - - constructor( - options: IControllerOptions, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(massageControllerOptions(options)); - - // if the open mode is not set, we configure it based on settings - if (isUndefinedOrNull(options.openMode)) { - this.setOpenMode(this.getOpenModeSetting()); - this.registerListeners(); - } - } - - private registerListeners(): void { - this.disposables.add(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(openModeSettingKey)) { - this.setOpenMode(this.getOpenModeSetting()); - } - })); - } - - private getOpenModeSetting(): OpenMode { - return useSingleClickToOpen(this.configurationService) ? OpenMode.SINGLE_CLICK : OpenMode.DOUBLE_CLICK; - } - - dispose(): void { - this.disposables.dispose(); - } -} - export interface IOpenResourceOptions { editorOptions: IEditorOptions; sideBySide: boolean; diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index c69376cd3a6..eee7fbc9d85 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -11,17 +11,11 @@ import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiS import { PagedList } from 'vs/base/browser/ui/list/listPaging'; import { range } from 'vs/base/common/arrays'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ITree } from 'vs/base/parts/tree/browser/tree'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree } from 'vs/base/browser/ui/tree/dataTree'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; - -function isLegacyTree(widget: ListWidget): widget is ITree { - return widget instanceof Tree; -} function ensureDOMFocus(widget: ListWidget | undefined): void { // it can happen that one of the commands is executed while @@ -51,7 +45,7 @@ function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = fa } } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; @@ -63,14 +57,6 @@ function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = fa tree.reveal(listFocus[0]); } } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusNext(count, { origin: 'keyboard' }); - tree.reveal(tree.getFocus()); - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -85,7 +71,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor, arg2) => focusDown(accessor, arg2) }); -function expandMultiSelection(focused: List | PagedList | ITree | ObjectTree | DataTree | AsyncDataTree, previousFocus: unknown): void { +function expandMultiSelection(focused: List | PagedList | ObjectTree | DataTree | AsyncDataTree, previousFocus: unknown): void { // List if (focused instanceof List || focused instanceof PagedList) { @@ -102,7 +88,7 @@ function expandMultiSelection(focused: List | PagedList | ITre } } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -121,19 +107,6 @@ function expandMultiSelection(focused: List | PagedList | ITre list.setSelection(selection.concat(focus), fakeKeyboardEvent); } } - - // Tree - else if (focused) { - const tree = focused; - - const focus = tree.getFocus(); - const selection = tree.getSelection(); - if (selection && selection.indexOf(focus) >= 0) { - tree.setSelection(selection.filter(s => s !== previousFocus)); - } else { - tree.setSelection(selection.concat(focus)); - } - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -144,7 +117,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor, arg2) => { const focused = accessor.get(IListService).lastFocusedList; - // List + // List / Tree if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -155,18 +128,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Then adjust selection expandMultiSelection(focused, previousFocus); } - - // Tree - else if (focused) { - const tree = focused; - - // Focus down first - const previousFocus = tree.getFocus(); - focusDown(accessor, arg2); - - // Then adjust selection - expandMultiSelection(focused, previousFocus); - } } }); @@ -188,7 +149,7 @@ function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = fals } } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; @@ -200,14 +161,6 @@ function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = fals tree.reveal(listFocus[0]); } } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusPrevious(count, { origin: 'keyboard' }); - tree.reveal(tree.getFocus()); - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -230,7 +183,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor, arg2) => { const focused = accessor.get(IListService).lastFocusedList; - // List + // List / Tree if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -241,18 +194,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Then adjust selection expandMultiSelection(focused, previousFocus); } - - // Tree - else if (focused) { - const tree = focused; - - // Focus up first - const previousFocus = tree.getFocus(); - focusUp(accessor, arg2); - - // Then adjust selection - expandMultiSelection(focused, previousFocus); - } } }); @@ -289,19 +230,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ tree.reveal(parent); } } - } else { - const tree = focused; - const focus = tree.getFocus(); - - tree.collapse(focus).then(didCollapse => { - if (focus && !didCollapse) { - tree.focusParent({ origin: 'keyboard' }); - - return tree.reveal(tree.getFocus()); - } - - return undefined; - }); } } } @@ -350,9 +278,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ tree.setFocus([parent], fakeKeyboardEvent); tree.reveal(parent); } - } else { - const tree = focused; - tree.focusParent({ origin: 'keyboard' }); } } }); @@ -416,19 +341,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } } }); - } else { - const tree = focused; - const focus = tree.getFocus(); - - tree.expand(focus).then(didExpand => { - if (focus && !didExpand) { - tree.focusFirstChild({ origin: 'keyboard' }); - - return tree.reveal(tree.getFocus()); - } - - return undefined; - }); } } } @@ -453,7 +365,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.reveal(list.getFocus()[0]); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -461,14 +373,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.focusPreviousPage(fakeKeyboardEvent); list.reveal(list.getFocus()[0]); } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusPreviousPage({ origin: 'keyboard' }); - tree.reveal(tree.getFocus()); - } } }); @@ -491,7 +395,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.reveal(list.getFocus()[0]); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -499,14 +403,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.focusNextPage(fakeKeyboardEvent); list.reveal(list.getFocus()[0]); } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusNextPage({ origin: 'keyboard' }); - tree.reveal(tree.getFocus()); - } } }); @@ -540,7 +436,7 @@ function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boo list.reveal(0); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -552,14 +448,6 @@ function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boo tree.reveal(focus[0]); } } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusFirst({ origin: 'keyboard' }, options?.fromFocused ? tree.getFocus() : undefined); - tree.reveal(tree.getFocus()); - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -592,7 +480,7 @@ function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: bool list.reveal(list.length - 1); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -604,14 +492,6 @@ function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: bool tree.reveal(focus[0]); } } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusLast({ origin: 'keyboard' }, options?.fromFocused ? tree.getFocus() : undefined); - tree.reveal(tree.getFocus()); - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -634,7 +514,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.open(list.getFocus(), fakeKeyboardEvent); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; const focus = list.getFocus(); @@ -656,16 +536,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.setSelection(focus, fakeKeyboardEvent); list.open(focus, fakeKeyboardEvent); } - - // Tree - else if (focused) { - const tree = focused; - const focus = tree.getFocus(); - - if (focus) { - tree.setSelection([focus], { origin: 'keyboard' }); - } - } } }); @@ -744,7 +614,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor) => { const widget = accessor.get(IListService).lastFocusedList; - if (!widget || isLegacyTree(widget)) { + if (!widget) { return; } @@ -784,13 +654,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } tree.toggleCollapsed(focus[0]); - } else { - const tree = focused; - const focus = tree.getFocus(); - - if (focus) { - tree.toggleExpansion(focus); - } } } } @@ -812,7 +675,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.setFocus([]); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -820,14 +683,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.setSelection([], fakeKeyboardEvent); list.setFocus([], fakeKeyboardEvent); } - - // Tree - else if (focused) { - const tree = focused; - - tree.clearSelection({ origin: 'keyboard' }); - tree.clearFocus({ origin: 'keyboard' }); - } } }); @@ -842,7 +697,7 @@ CommandsRegistry.registerCommand({ list.toggleKeyboardNavigation(); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; tree.toggleKeyboardNavigation(); @@ -860,7 +715,7 @@ CommandsRegistry.registerCommand({ // TODO@joao } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; tree.updateOptions({ filterOnType: !tree.filterOnType }); @@ -876,7 +731,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } @@ -892,7 +747,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } @@ -907,7 +762,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } @@ -922,7 +777,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index b647c97a7a5..8588ffe9aac 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -3,20 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as DOM from 'vs/base/browser/dom'; import { IAction } from 'vs/base/common/actions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService'; -import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; import { Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -145,30 +141,3 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { abstract getTitle(): string; } - -export class FileIconThemableWorkbenchTree extends WorkbenchTree { - - constructor( - container: HTMLElement, - configuration: ITreeConfiguration, - options: ITreeOptions, - @IContextKeyService contextKeyService: IContextKeyService, - @IListService listService: IListService, - @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService instantiationService: IInstantiationService - ) { - super(container, configuration, { ...options, ...{ showTwistie: false, twistiePixels: 12 } }, contextKeyService, listService, themeService, instantiationService, configurationService); - - DOM.addClass(container, 'file-icon-themable-tree'); - DOM.addClass(container, 'show-file-icons'); - - const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { - DOM.toggleClass(container, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); - DOM.toggleClass(container, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true); - }; - - this.disposables.push(themeService.onDidFileIconThemeChange(onFileIconThemeChange)); - onFileIconThemeChange(themeService.getFileIconTheme()); - } -}