提交 b4681da8 编写于 作者: J Joao Moreno

wip: WorkbenchObjectTree

上级 9cb4d4bb
......@@ -5,7 +5,7 @@
import 'vs/css!./tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IListOptions, List, IIdentityProvider, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget';
import { IListOptions, List, IIdentityProvider, IMultipleSelectionController, IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { IVirtualDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list';
import { append, $ } from 'vs/base/browser/dom';
import { Event, Relay, chain, mapEvent } from 'vs/base/common/event';
......@@ -183,6 +183,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return mapEvent(this.view.onSelectionChange, e => e.elements.map(e => e.element));
}
get onDidFocus(): Event<void> { return this.view.onDidFocus; }
get onDidBlur(): Event<void> { return this.view.onDidBlur; }
get onDidDispose(): Event<void> { return this.view.onDidDispose; }
constructor(
container: HTMLElement,
delegate: IVirtualDelegate<T>,
......@@ -211,15 +215,41 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables);
}
// Widget
// TODO@joao rename to `get domElement`
getHTMLElement(): HTMLElement {
return this.view.getHTMLElement();
}
domFocus(): void {
this.view.domFocus();
}
layout(height?: number): void {
this.view.layout(height);
}
style(styles: IListStyles): void {
this.view.style(styles);
}
// Tree navigation
getParentElement(ref: TRef | null = null): T | null {
return this.model.getParentElement(ref);
}
getFirstElementChild(ref: TRef | null = null): T | null {
return this.model.getFirstElementChild(ref);
}
getLastElementAncestor(ref: TRef | null = null): T | null {
return this.model.getLastElementAncestor(ref);
}
// Tree
collapse(location: TRef): boolean {
return this.model.setCollapsed(location, true);
}
......@@ -268,11 +298,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
}
focusNext(n = 1, loop = false): void {
this.view.focusNext();
this.view.focusNext(n, loop);
}
focusPrevious(n = 1, loop = false): void {
this.view.focusPrevious();
this.view.focusPrevious(n, loop);
}
focusNextPage(): void {
......@@ -296,6 +326,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return nodes.map(n => n.element);
}
open(elements: TRef[]): void {
const indexes = elements.map(e => this.model.getListIndex(e));
this.view.open(indexes);
}
reveal(location: TRef, relativeTop?: number): void {
const index = this.model.getListIndex(location);
this.view.reveal(index, relativeTop);
......
......@@ -74,7 +74,7 @@ export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFil
throw new Error('Invalid tree location');
}
const { parentNode, listIndex, revealed } = this.findParentNode(location);
const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
const treeListElementsToInsert: ITreeNode<T, TFilterData>[] = [];
const nodesToInsertIterator = Iterator.map(Iterator.from(toInsert), el => this.createTreeNode(el, parentNode, revealed, treeListElementsToInsert, onDidCreateNode));
......@@ -109,16 +109,16 @@ export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFil
}
getListIndex(location: number[]): number {
return this.findNode(location).listIndex;
return this.getNodeWithListIndex(location).listIndex;
}
setCollapsed(location: number[], collapsed: boolean): boolean {
const { node, listIndex, revealed } = this.findNode(location);
const { node, listIndex, revealed } = this.getNodeWithListIndex(location);
return this._setCollapsed(node, listIndex, revealed, collapsed);
}
toggleCollapsed(location: number[]): void {
const { node, listIndex, revealed } = this.findNode(location);
const { node, listIndex, revealed } = this.getNodeWithListIndex(location);
this._setCollapsed(node, listIndex, revealed);
}
......@@ -140,7 +140,7 @@ export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFil
// }
isCollapsed(location: number[]): boolean {
return this.findNode(location).node.collapsed;
return this.getNode(location).collapsed;
}
refilter(): void {
......@@ -338,8 +338,25 @@ export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFil
}
}
private findNode(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean } {
const { parentNode, listIndex, revealed } = this.findParentNode(location);
/**
* Cheaper version of findNode, which doesn't require list indices.
*/
private getNode(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root): IMutableTreeNode<T, TFilterData> {
if (location.length === 0) {
return node;
}
const [index, ...rest] = location;
if (index < 0 || index > node.children.length) {
throw new Error('Invalid tree location');
}
return this.getNode(rest, node.children[index]);
}
private getNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean } {
const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
const index = location[location.length - 1];
if (index < 0 || index > parentNode.children.length) {
......@@ -351,7 +368,7 @@ export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFil
return { node, listIndex, revealed };
}
private findParentNode(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; } {
private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; } {
const [index, ...rest] = location;
if (index < 0 || index > node.children.length) {
......@@ -369,7 +386,7 @@ export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFil
return { parentNode: node, listIndex, revealed };
}
return this.findParentNode(rest, node.children[index], listIndex + 1, revealed);
return this.getParentNodeWithListIndex(rest, node.children[index], listIndex + 1, revealed);
}
// TODO@joao perf!
......@@ -391,4 +408,38 @@ export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFil
return tail2(location)[0];
}
getParentElement(location: number[]): T | null {
const parentLocation = this.getParentNodeLocation(location);
const node = this.getNode(parentLocation);
return node === this.root ? null : node.element;
}
getFirstElementChild(location: number[]): T | null {
const node = this.getNode(location);
if (node.children.length === 0) {
return null;
}
return node.children[0].element;
}
getLastElementAncestor(location: number[]): T | null {
const node = this.getNode(location);
if (node.children.length === 0) {
return null;
}
return this._getLastElementAncestor(node);
}
private _getLastElementAncestor(node: ITreeNode<T, TFilterData>): T | null {
if (node.children.length === 0) {
return node.element;
}
return this._getLastElementAncestor(node.children[node.children.length - 1]);
}
}
\ No newline at end of file
......@@ -45,6 +45,21 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData = void> imp
return this.model.splice([...location, 0], Number.MAX_VALUE, children, onDidCreateNode, onDidDeleteNode);
}
getParentElement(ref: T | null = null): T | null {
const location = this.getElementLocation(ref);
return this.model.getParentElement(location);
}
getFirstElementChild(ref: T | null = null): T | null {
const location = this.getElementLocation(ref);
return this.model.getFirstElementChild(location);
}
getLastElementAncestor(ref: T | null = null): T | null {
const location = this.getElementLocation(ref);
return this.model.getLastElementAncestor(location);
}
getListIndex(element: T): number {
const location = this.getElementLocation(element);
return this.model.getListIndex(location);
......
......@@ -54,4 +54,8 @@ export interface ITreeModel<T, TFilterData, TRef> {
getNodeLocation(node: ITreeNode<T, any>): TRef;
getParentNodeLocation(location: TRef): TRef | null;
getParentElement(location: TRef): T | null;
getFirstElementChild(location: TRef): T | null;
getLastElementAncestor(location: TRef): T | null;
}
\ No newline at end of file
......@@ -32,8 +32,10 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeRenderer, ITreeOptions as ITreeOptions2 } from 'vs/base/browser/ui/tree/abstractTree';
export type ListWidget = List<any> | PagedList<any> | ITree;
export type ListWidget = List<any> | PagedList<any> | ITree | ObjectTree<any, any>;
export const IListService = createDecorator<IListService>('listService');
......@@ -797,6 +799,89 @@ export class HighlightingWorkbenchTree extends WorkbenchTree {
}
}
export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> {
readonly contextKeyService: IContextKeyService;
protected disposables: IDisposable[];
private hasSelectionOrFocus: IContextKey<boolean>;
private hasDoubleSelection: IContextKey<boolean>;
private hasMultiSelection: IContextKey<boolean>;
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
delegate: IVirtualDelegate<T>,
renderers: ITreeRenderer<T, any>[],
options: ITreeOptions2<T, TFilterData>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService
) {
super(container, delegate, renderers, {
keyboardSupport: false,
selectOnMouseDown: true,
styleController: new DefaultStyleController(getSharedListStyleSheet()),
...computeStyles(themeService.getTheme(), defaultListStyles),
...handleListControllers(options, configurationService)
});
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
listSupportsMultiSelect.set(!(options.multipleSelectionSupport === false));
this.hasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);
this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
this.disposables.push(combinedDisposable([
this.contextKeyService,
(listService as ListService).register(this),
attachListStyler(this, themeService),
this.onDidChangeSelection(() => {
const selection = this.getSelection();
const focus = this.getFocus();
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
this.hasMultiSelection.set(selection.length > 1);
this.hasDoubleSelection.set(selection.length === 2);
}),
this.onDidChangeFocus(() => {
const selection = this.getSelection();
const focus = this.getFocus();
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
})
]));
this.registerListeners();
}
private registerListeners(): void {
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
}
}));
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
}
}
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
......
......@@ -22,6 +22,7 @@ import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platf
import { URI } from 'vs/base/common/uri';
import { IDownloadService } from 'vs/platform/download/common/download';
import { generateUuid } from 'vs/base/common/uuid';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
// --- List Commands
......@@ -56,6 +57,17 @@ export function registerCommands(): void {
}
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
list.focusNext(count);
const listFocus = list.getFocus();
if (listFocus.length) {
list.reveal(listFocus[0]);
}
}
// Tree
else if (focused) {
const tree = focused;
......@@ -77,7 +89,7 @@ export function registerCommands(): void {
handler: (accessor, arg2) => focusDown(accessor, arg2)
});
function expandMultiSelection(focused: List<any> | PagedList<any> | ITree, previousFocus: any): void {
function expandMultiSelection(focused: List<any> | PagedList<any> | ITree | ObjectTree<any, any>, previousFocus: any): void {
// List
if (focused instanceof List || focused instanceof PagedList) {
......@@ -92,6 +104,19 @@ export function registerCommands(): void {
}
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
const focus = list.getFocus() ? list.getFocus()[0] : void 0;
const selection = list.getSelection();
if (selection && selection.indexOf(focus) >= 0) {
list.setSelection(selection.filter(s => s !== previousFocus));
} else {
list.setSelection(selection.concat(focus));
}
}
// Tree
else if (focused) {
const tree = focused;
......@@ -126,6 +151,18 @@ export function registerCommands(): void {
expandMultiSelection(focused, previousFocus);
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
// Focus down first
const previousFocus = list.getFocus() ? list.getFocus()[0] : void 0;
focusDown(accessor, arg2);
// Then adjust selection
expandMultiSelection(focused, previousFocus);
}
// Tree
else if (focused) {
const tree = focused;
......@@ -158,6 +195,17 @@ export function registerCommands(): void {
}
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
list.focusPrevious(count);
const listFocus = list.getFocus();
if (listFocus.length) {
list.reveal(listFocus[0]);
}
}
// Tree
else if (focused) {
const tree = focused;
......@@ -227,18 +275,38 @@ export function registerCommands(): void {
// Tree only
if (focused && !(focused instanceof List || focused instanceof PagedList)) {
const tree = focused;
const focus = tree.getFocus();
if (focused instanceof ObjectTree) {
const tree = focused;
const focusedElements = tree.getFocus();
if (focusedElements.length === 0) {
return;
}
tree.collapse(focus).then(didCollapse => {
if (focus && !didCollapse) {
tree.focusParent({ origin: 'keyboard' });
const focus = focusedElements[0];
return tree.reveal(tree.getFocus());
if (!tree.collapse(focus)) {
const parent = tree.getParentElement(focus);
if (parent) {
tree.setFocus(parent);
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 void 0;
});
return void 0;
});
}
}
}
});
......@@ -253,18 +321,38 @@ export function registerCommands(): void {
// Tree only
if (focused && !(focused instanceof List || focused instanceof PagedList)) {
const tree = focused;
const focus = tree.getFocus();
if (focused instanceof ObjectTree) {
const tree = focused;
const focusedElements = tree.getFocus();
if (focusedElements.length === 0) {
return;
}
const focus = focusedElements[0];
tree.expand(focus).then(didExpand => {
if (focus && !didExpand) {
tree.focusFirstChild({ origin: 'keyboard' });
if (!tree.expand(focus)) {
const child = tree.getFirstElementChild(focus);
return tree.reveal(tree.getFocus());
if (child) {
tree.setFocus(child);
tree.reveal(child);
}
}
} else {
const tree = focused;
const focus = tree.getFocus();
return void 0;
});
tree.expand(focus).then(didExpand => {
if (focus && !didExpand) {
tree.focusFirstChild({ origin: 'keyboard' });
return tree.reveal(tree.getFocus());
}
return void 0;
});
}
}
}
});
......@@ -288,6 +376,14 @@ export function registerCommands(): void {
list.reveal(list.getFocus()[0]);
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
list.focusPreviousPage();
list.reveal(list.getFocus()[0]);
}
// Tree
else if (focused) {
const tree = focused;
......@@ -317,6 +413,14 @@ export function registerCommands(): void {
list.reveal(list.getFocus()[0]);
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
list.focusNextPage();
list.reveal(list.getFocus()[0]);
}
// Tree
else if (focused) {
const tree = focused;
......@@ -357,6 +461,14 @@ export function registerCommands(): void {
list.reveal(0);
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
list.setFocus([0]);
list.reveal(0);
}
// Tree
else if (focused) {
const tree = focused;
......@@ -396,6 +508,19 @@ export function registerCommands(): void {
list.reveal(list.length - 1);
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
const last = list.getLastElementAncestor();
if (!last) {
return;
}
list.setFocus([last]);
list.reveal(last);
}
// Tree
else if (focused) {
const tree = focused;
......@@ -424,6 +549,13 @@ export function registerCommands(): void {
list.open(list.getFocus());
}
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
list.setSelection(list.getFocus());
list.open(list.getFocus());
}
// Tree
else if (focused) {
const tree = focused;
......@@ -462,11 +594,22 @@ export function registerCommands(): void {
// Tree only
if (focused && !(focused instanceof List || focused instanceof PagedList)) {
const tree = focused;
const focus = tree.getFocus();
if (focused instanceof ObjectTree) {
const tree = focused;
const focus = tree.getFocus();
if (focus) {
tree.toggleExpansion(focus);
if (focus.length === 0) {
return;
}
tree.toggleCollapsed(focus);
} else {
const tree = focused;
const focus = tree.getFocus();
if (focus) {
tree.toggleExpansion(focus);
}
}
}
}
......@@ -486,14 +629,19 @@ export function registerCommands(): void {
if (list.getSelection().length > 0) {
list.setSelection([]);
return void 0;
} else if (list.getFocus().length > 0) {
list.setFocus([]);
}
}
if (list.getFocus().length > 0) {
list.setFocus([]);
// ObjectTree
else if (focused instanceof ObjectTree) {
const list = focused;
return void 0;
if (list.getSelection().length > 0) {
list.setSelection([]);
} else if (list.getFocus().length > 0) {
list.setFocus([]);
}
}
......@@ -503,14 +651,8 @@ export function registerCommands(): void {
if (tree.getSelection().length) {
tree.clearSelection({ origin: 'keyboard' });
return void 0;
}
if (tree.getFocus()) {
} else if (tree.getFocus()) {
tree.clearFocus({ origin: 'keyboard' });
return void 0;
}
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册