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

Merge branch 'tree'

......@@ -104,6 +104,7 @@ const copyrightFilter = [
'!**/*.code-workspace',
'!build/**/*.init',
'!resources/linux/snap/snapcraft.yaml',
'!resources/linux/snap/electron-launch',
'!resources/win32/bin/code.js',
'!extensions/markdown-language-features/media/highlight.css',
'!extensions/html-language-features/server/src/modes/typescript/*',
......
......@@ -21,23 +21,13 @@ import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ISpliceable } from 'vs/base/common/sequence';
import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
import { clamp } from 'vs/base/common/numbers';
export interface IIdentityProvider<T> {
(element: T): string;
}
class CombinedSpliceable<T> implements ISpliceable<T> {
constructor(private spliceables: ISpliceable<T>[]) { }
splice(start: number, deleteCount: number, elements: T[]): void {
for (const spliceable of this.spliceables) {
spliceable.splice(start, deleteCount, elements);
}
}
}
interface ITraitChangeEvent {
indexes: number[];
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface ISpliceable<T> {
splice(start: number, deleteCount: number, elements: T[]): void;
}
export interface ISpreadSpliceable<T> {
splice(start: number, deleteCount: number, ...elements: T[]): void;
}
export class CombinedSpliceable<T> implements ISpliceable<T> {
constructor(private spliceables: ISpliceable<T>[]) { }
splice(start: number, deleteCount: number, elements: T[]): void {
this.spliceables.forEach(s => s.splice(start, deleteCount, elements));
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-tl-row {
display: flex;
height: 100%;
align-items: center;
}
.monaco-tl-row > .tl-twistie {
font-size: 10px;
text-align: right;
padding-right: 10px;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IListOptions, List, IIdentityProvider, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget';
import { TreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/treeModel';
import { IIterator, empty } from 'vs/base/common/iterator';
import { IDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list';
import { append, $ } from 'vs/base/browser/dom';
function toTreeListOptions<T>(options?: IListOptions<T>): IListOptions<ITreeNode<T>> {
if (!options) {
return undefined;
}
let identityProvider: IIdentityProvider<ITreeNode<T>> | undefined = undefined;
let multipleSelectionController: IMultipleSelectionController<ITreeNode<T>> | undefined = undefined;
if (options.identityProvider) {
identityProvider = el => options.identityProvider(el.element);
}
if (options.multipleSelectionController) {
multipleSelectionController = {
isSelectionSingleChangeEvent(e) {
return options.multipleSelectionController.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
},
isSelectionRangeChangeEvent(e) {
return options.multipleSelectionController.isSelectionRangeChangeEvent({ ...e, element: e.element } as any);
}
};
}
return {
...options,
identityProvider,
multipleSelectionController
};
}
class TreeDelegate<T> implements IDelegate<ITreeNode<T>> {
constructor(private delegate: IDelegate<T>) { }
getHeight(element: ITreeNode<T>): number {
return this.delegate.getHeight(element.element);
}
getTemplateId(element: ITreeNode<T>): string {
return this.delegate.getTemplateId(element.element);
}
}
interface ITreeListTemplateData<T> {
twistie: HTMLElement;
templateData: T;
}
class TreeRenderer<T, TTemplateData> implements IRenderer<ITreeNode<T>, ITreeListTemplateData<TTemplateData>> {
readonly templateId: string;
constructor(private renderer: IRenderer<T, TTemplateData>) {
this.templateId = renderer.templateId;
}
renderTemplate(container: HTMLElement): ITreeListTemplateData<TTemplateData> {
const el = append(container, $('.monaco-tl-row'));
const twistie = append(el, $('.tl-twistie'));
const contents = append(el, $('.tl-contents'));
const templateData = this.renderer.renderTemplate(contents);
return { twistie, templateData };
}
renderElement(element: ITreeNode<T>, index: number, templateData: ITreeListTemplateData<TTemplateData>): void {
const { twistie } = templateData;
twistie.innerText = element.children.length === 0 ? '' : (element.collapsed ? '' : '');
twistie.style.width = `${10 + element.depth * 10}px`;
this.renderer.renderElement(element.element, index, templateData.templateData);
}
disposeTemplate(templateData: ITreeListTemplateData<TTemplateData>): void {
this.renderer.disposeTemplate(templateData.templateData);
}
}
function getLocation<T>(node: ITreeNode<T>): number[] {
const location = [];
while (node.parent) {
location.push(node.parent.children.indexOf(node));
node = node.parent;
}
return location.reverse();
}
export class Tree<T> implements IDisposable {
private view: List<ITreeNode<T>>;
private model: TreeModel<T>;
private disposables: IDisposable[] = [];
constructor(
container: HTMLElement,
delegate: IDelegate<T>,
renderers: IRenderer<T, any>[],
options?: IListOptions<T>
) {
const treeDelegate = new TreeDelegate(delegate);
const treeRenderers = renderers.map(r => new TreeRenderer(r));
const treeOptions = toTreeListOptions(options);
this.view = new List(container, treeDelegate, treeRenderers, treeOptions);
this.model = new TreeModel<T>(this.view);
this.view.onMouseClick(this.onMouseClick, this, this.disposables);
}
splice(location: number[], deleteCount: number, toInsert: IIterator<ITreeElement<T>> = empty()): IIterator<ITreeElement<T>> {
return this.model.splice(location, deleteCount, toInsert);
}
private onMouseClick(e: IListMouseEvent<ITreeNode<T>>): void {
const node = e.element;
const location = getLocation(node);
this.model.toggleCollapsed(location);
}
dispose(): void {
this.disposables = dispose(this.disposables);
this.view.dispose();
this.view = null;
this.model = null;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ISpliceable } from 'vs/base/common/sequence';
import { IIterator, map, collect, iter, empty } from 'vs/base/common/iterator';
import { last } from 'vs/base/common/arrays';
export interface ITreeElement<T> {
readonly element: T;
readonly children?: IIterator<ITreeElement<T>> | ITreeElement<T>[];
readonly collapsed?: boolean;
}
export interface ITreeNode<T> {
readonly parent: IMutableTreeNode<T> | undefined;
readonly element: T;
readonly children: IMutableTreeNode<T>[];
readonly depth: number;
readonly collapsed: boolean;
readonly visibleCount: number;
}
interface IMutableTreeNode<T> extends ITreeNode<T> {
collapsed: boolean;
visibleCount: number;
}
function visibleCountReducer<T>(result: number, node: IMutableTreeNode<T>): number {
return result + (node.collapsed ? 1 : node.visibleCount);
}
function getVisibleCount<T>(nodes: IMutableTreeNode<T>[]): number {
return nodes.reduce(visibleCountReducer, 0);
}
function getVisibleNodes<T>(nodes: IMutableTreeNode<T>[], result: ITreeNode<T>[] = []): ITreeNode<T>[] {
for (const node of nodes) {
result.push(node);
if (!node.collapsed) {
getVisibleNodes(node.children, result);
}
}
return result;
}
function getTreeElementIterator<T>(elements: IIterator<ITreeElement<T>> | ITreeElement<T>[] | undefined): IIterator<ITreeElement<T>> {
if (!elements) {
return empty();
} else if (Array.isArray(elements)) {
return iter(elements);
} else {
return elements;
}
}
function treeElementToNode<T>(treeElement: ITreeElement<T>, parent: IMutableTreeNode<T>, visible: boolean, treeListElements: ITreeNode<T>[]): IMutableTreeNode<T> {
const depth = parent.depth + 1;
const { element, collapsed } = treeElement;
const node = { parent, element, children: [], depth, collapsed: !!collapsed, visibleCount: 0 };
if (visible) {
treeListElements.push(node);
}
const children = getTreeElementIterator(treeElement.children);
node.children = collect(map(children, el => treeElementToNode(el, node, visible && !treeElement.collapsed, treeListElements)));
node.visibleCount = 1 + getVisibleCount(node.children);
return node;
}
function treeNodeToElement<T>(node: IMutableTreeNode<T>): ITreeElement<T> {
const { element, collapsed } = node;
const children = map(iter(node.children), treeNodeToElement);
return { element, children, collapsed };
}
export class TreeModel<T> {
private root: IMutableTreeNode<T> = {
parent: undefined,
element: undefined,
children: [],
depth: 0,
collapsed: false,
visibleCount: 1
};
constructor(private list: ISpliceable<ITreeNode<T>>) { }
splice(location: number[], deleteCount: number, toInsert?: IIterator<ITreeElement<T>> | ITreeElement<T>[]): IIterator<ITreeElement<T>> {
if (location.length === 0) {
throw new Error('Invalid tree location');
}
const { parentNode, listIndex, visible } = this.findParentNode(location);
const treeListElementsToInsert: ITreeNode<T>[] = [];
const elementsToInsert = getTreeElementIterator(toInsert);
const nodesToInsert = collect(map(elementsToInsert, el => treeElementToNode(el, parentNode, visible, treeListElementsToInsert)));
const deletedNodes = parentNode.children.splice(last(location), deleteCount, ...nodesToInsert);
const visibleDeleteCount = getVisibleCount(deletedNodes);
parentNode.visibleCount += getVisibleCount(nodesToInsert) - visibleDeleteCount;
if (visible) {
this.list.splice(listIndex, visibleDeleteCount, treeListElementsToInsert);
}
return map(iter(deletedNodes), treeNodeToElement);
}
setCollapsed(location: number[], collapsed: boolean): void {
this._setCollapsed(location, collapsed);
}
toggleCollapsed(location: number[]): void {
this._setCollapsed(location);
}
private _setCollapsed(location: number[], collapsed?: boolean | undefined): void {
const { node, listIndex, visible } = this.findNode(location);
if (typeof collapsed === 'undefined') {
collapsed = !node.collapsed;
}
if (node.collapsed === collapsed) {
return;
}
node.collapsed = collapsed;
if (visible) {
if (collapsed) {
const deleteCount = getVisibleCount(node.children);
this.list.splice(listIndex, 1 + deleteCount, [node]);
node.visibleCount = 1;
} else {
const toInsert = [node, ...getVisibleNodes(node.children)];
this.list.splice(listIndex, 1, toInsert);
node.visibleCount = toInsert.length;
}
}
}
isCollapsed(location: number[]): boolean {
const { node } = this.findNode(location);
return node.collapsed;
}
private findNode(location: number[]): { node: IMutableTreeNode<T>, listIndex: number, visible: boolean } {
const { parentNode, listIndex, visible } = this.findParentNode(location);
const index = last(location);
if (index < 0 || index > parentNode.children.length) {
throw new Error('Invalid tree location');
}
const node = parentNode.children[index];
return { node, listIndex, visible };
}
private findParentNode(location: number[], node: IMutableTreeNode<T> = this.root, listIndex: number = 0, visible = true): { parentNode: IMutableTreeNode<T>; listIndex: number; visible: boolean; } {
const [index, ...rest] = location;
if (index < 0 || index > node.children.length) {
throw new Error('Invalid tree location');
}
// TODO@joao perf!
for (let i = 0; i < index; i++) {
listIndex += node.children[i].visibleCount;
}
visible = visible && !node.collapsed;
if (rest.length === 0) {
return { parentNode: node, listIndex, visible };
}
return this.findParentNode(rest, node.children[index], listIndex + 1, visible);
}
}
\ No newline at end of file
......@@ -24,6 +24,10 @@ export function tail2<T>(arr: T[]): [T[], T] {
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
}
export function last<T>(arr: T[]): T {
return arr[arr.length - 1];
}
export function equals<T>(one: ReadonlyArray<T>, other: ReadonlyArray<T>, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
if (one.length !== other.length) {
return false;
......
......@@ -7,11 +7,72 @@
export interface IIteratorResult<T> {
readonly done: boolean;
readonly value: T;
readonly value: T | undefined;
}
export interface IIterator<E> {
next(): IIteratorResult<E>;
export interface IIterator<T> {
next(): IIteratorResult<T>;
}
const _empty: IIterator<any> = {
next() {
return { done: true, value: undefined };
}
};
export function empty<T>(): IIterator<T> {
return _empty;
}
export function iter<T>(array: T[], index = 0, length = array.length): IIterator<T> {
return {
next(): IIteratorResult<T> {
if (index >= length) {
return { done: true, value: undefined };
}
return { done: false, value: array[index++] };
}
};
}
export function map<T, R>(iterator: IIterator<T>, fn: (t: T) => R): IIterator<R> {
return {
next() {
const { done, value } = iterator.next();
return { done, value: done ? undefined : fn(value) };
}
};
}
export function filter<T>(iterator: IIterator<T>, fn: (t: T) => boolean): IIterator<T> {
return {
next() {
while (true) {
const { done, value } = iterator.next();
if (done) {
return { done, value: undefined };
}
if (fn(value)) {
return { done, value };
}
}
}
};
}
export function forEach<T>(iterator: IIterator<T>, fn: (t: T) => void): void {
for (let next = iterator.next(); !next.done; next = iterator.next()) {
fn(next.value);
}
}
export function collect<T>(iterator: IIterator<T>): T[] {
const result: T[] = [];
forEach(iterator, value => result.push(value));
return result;
}
export interface INextIterator<T> {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { clamp } from './numbers';
export interface ITreeNode<T> {
readonly element: T;
readonly children: ITreeNode<T>[];
}
function clone<T>(nodes: ITreeNode<T>[]): ITreeNode<T>[] {
return nodes.map(({ element, children }) => ({ element, children: clone(children) }));
}
export class Tree<T> {
private root: ITreeNode<T>[] = [];
splice(start: number[], deleteCount: number, nodes: ITreeNode<T>[] = []): ITreeNode<T>[] {
if (start.length === 0) {
throw new Error('invalid tree location');
}
const children = this.findChildren(start);
const index = start[start.length - 1];
return children.splice(index, deleteCount, ...clone(nodes));
}
getElement(location: number[]): T | undefined {
const node = this.findElement(location);
return node && node.element;
}
getNodes(): ITreeNode<T>[] {
return clone(this.root);
}
private findElement(location: number[]): ITreeNode<T> {
const children = this.findChildren(location);
const lastIndex = clamp(location[location.length - 1], 0, children.length);
return children[lastIndex];
}
private findChildren(location: number[], children: ITreeNode<T>[] = this.root): ITreeNode<T>[] {
if (location.length === 1) {
return children;
}
let [i, ...rest] = location;
i = clamp(i, 0, children.length);
return this.findChildren(rest, children[i].children);
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 { TreeModel, ITreeNode } from 'vs/base/browser/ui/tree/treeModel';
import { ISpliceable } from 'vs/base/browser/ui/list/splice';
import { iter } from 'vs/base/common/iterator';
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
return {
splice(start: number, deleteCount: number, elements: T[]): void {
arr.splice(start, deleteCount, ...elements);
}
};
}
suite('TreeModel2', () => {
test('ctor', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
assert(model);
assert.equal(list.length, 0);
});
test('insert', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{ element: 0 },
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 1);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element, 2);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 1);
});
test('deep insert', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, children: iter([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 6);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element, 11);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element, 12);
assert.deepEqual(list[3].collapsed, false);
assert.deepEqual(list[3].depth, 2);
assert.deepEqual(list[4].element, 1);
assert.deepEqual(list[4].collapsed, false);
assert.deepEqual(list[4].depth, 1);
assert.deepEqual(list[5].element, 2);
assert.deepEqual(list[5].collapsed, false);
assert.deepEqual(list[5].depth, 1);
});
test('deep insert collapsed', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, collapsed: true, children: iter([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, true);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 1);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element, 2);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 1);
});
test('delete', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{ element: 0 },
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 3);
model.splice([1], 1);
assert.deepEqual(list.length, 2);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 2);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
model.splice([0], 2);
assert.deepEqual(list.length, 0);
});
test('nested delete', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, children: iter([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 6);
model.splice([1], 2);
assert.deepEqual(list.length, 4);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element, 11);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element, 12);
assert.deepEqual(list[3].collapsed, false);
assert.deepEqual(list[3].depth, 2);
});
test('deep delete', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, children: iter([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 6);
model.splice([0], 1);
assert.deepEqual(list.length, 2);
assert.deepEqual(list[0].element, 1);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 2);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
});
test('hidden delete', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, collapsed: true, children: iter([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 3);
model.splice([0, 1], 1);
assert.deepEqual(list.length, 3);
model.splice([0, 0], 2);
assert.deepEqual(list.length, 3);
});
test('collapse', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, children: iter([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 6);
model.setCollapsed([0], true);
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, true);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 1);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element, 2);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 1);
});
test('expand', () => {
const list = [] as ITreeNode<number>[];
const model = new TreeModel<number>(toSpliceable(list));
model.splice([0], 0, iter([
{
element: 0, collapsed: true, children: iter([
{ element: 10 },
{ element: 11 },
{ element: 12 },
])
},
{ element: 1 },
{ element: 2 }
]));
assert.deepEqual(list.length, 3);
model.setCollapsed([0], false);
assert.deepEqual(list.length, 6);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element, 11);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element, 12);
assert.deepEqual(list[3].collapsed, false);
assert.deepEqual(list[3].depth, 2);
assert.deepEqual(list[4].element, 1);
assert.deepEqual(list[4].collapsed, false);
assert.deepEqual(list[4].depth, 1);
assert.deepEqual(list[5].element, 2);
assert.deepEqual(list[5].collapsed, false);
assert.deepEqual(list[5].depth, 1);
});
});
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 { Tree } from 'vs/base/common/tree';
suite('Base Tree', () => {
test('ctor', () => {
const tree = new Tree<number>();
assert(tree);
const nodes = tree.getNodes();
assert.equal(nodes.length, 0);
});
test('insert', () => {
const tree = new Tree<number>();
tree.splice([0], 0, [
{ element: 0, children: [] },
{ element: 1, children: [] },
{ element: 2, children: [] }
]);
const nodes = tree.getNodes();
assert.deepEqual(nodes.length, 3);
assert.deepEqual(nodes[0].element, 0);
assert.deepEqual(nodes[1].element, 1);
assert.deepEqual(nodes[2].element, 2);
});
test('deep insert', () => {
const tree = new Tree<number>();
tree.splice([0], 0, [
{
element: 0, children: [
{ element: 10, children: [] },
{ element: 11, children: [] },
{ element: 12, children: [] },
]
},
{ element: 1, children: [] },
{ element: 2, children: [] }
]);
const nodes = tree.getNodes();
assert.deepEqual(nodes.length, 3);
assert.deepEqual(nodes[0].element, 0);
assert.deepEqual(nodes[0].children.length, 3);
assert.deepEqual(nodes[0].children[0].element, 10);
assert.deepEqual(nodes[0].children[1].element, 11);
assert.deepEqual(nodes[0].children[2].element, 12);
assert.deepEqual(nodes[1].element, 1);
assert.deepEqual(nodes[2].element, 2);
});
test('delete', () => {
const tree = new Tree<number>();
tree.splice([0], 0, [
{ element: 0, children: [] },
{ element: 1, children: [] },
{ element: 2, children: [] }
]);
tree.splice([0], 3, []);
const nodes = tree.getNodes();
assert.equal(nodes.length, 0);
});
test('nested delete', () => {
const tree = new Tree<number>();
tree.splice([0], 0, [
{
element: 0, children: [
{ element: 10, children: [] },
{ element: 11, children: [] },
{ element: 12, children: [] },
]
},
{ element: 1, children: [] },
{ element: 2, children: [] }
]);
tree.splice([0, 1], 1, []);
const nodes = tree.getNodes();
assert.deepEqual(nodes.length, 3);
assert.deepEqual(nodes[0].element, 0);
assert.deepEqual(nodes[0].children.length, 2);
assert.deepEqual(nodes[0].children[0].element, 10);
assert.deepEqual(nodes[0].children[1].element, 12);
assert.deepEqual(nodes[1].element, 1);
assert.deepEqual(nodes[2].element, 2);
});
test('deep delete', () => {
const tree = new Tree<number>();
tree.splice([0], 0, [
{
element: 0, children: [
{ element: 10, children: [] },
{ element: 11, children: [] },
{ element: 12, children: [] },
]
},
{ element: 1, children: [] },
{ element: 2, children: [] }
]);
tree.splice([0], 1, []);
const nodes = tree.getNodes();
assert.deepEqual(nodes.length, 2);
assert.deepEqual(nodes[0].element, 1);
assert.deepEqual(nodes[1].element, 2);
});
});
{
"name": "tree",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"koa": "^2.5.1",
"koa-mount": "^3.0.0",
"koa-route": "^3.2.0",
"koa-static": "^5.0.0",
"mz": "^2.7.0"
}
}
<html>
<head>
<meta charset="utf-8">
<title>Tree</title>
<style>
#container {
width: 400;
height: 600;
border: 1px solid black;
}
.monaco-scrollable-element>.scrollbar>.slider {
background: rgba(100, 100, 100, .4);
}
</style>
</head>
<body>
<div id="container"></div>
<script src="/static/vs/loader.js"></script>
<script>
require.config({ baseUrl: '/static' });
require(['vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ Tree }, { iter }) => {
const delegate = {
getHeight() { return 22; },
getTemplateId() { return 'template'; }
};
const renderer = {
templateId: 'template',
renderTemplate(container) { return container; },
renderElement(element, index, container) {
container.textContent = element;
},
disposeTemplate() { }
};
const tree = new Tree(container, delegate, [renderer]);
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/ls?path=');
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
const data = JSON.parse(this.responseText);
performance.mark('before splice');
const start = performance.now();
tree.splice([0], 0, [data]);
console.log('splice took', performance.now() - start);
performance.mark('after splice');
}
};
});
</script>
</body>
</html>
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const fs = require('mz/fs');
const path = require('path');
const Koa = require('koa');
const _ = require('koa-route');
const serve = require('koa-static');
const mount = require('koa-mount');
const app = new Koa();
const root = path.dirname(path.dirname(__dirname));
async function getTree(fsPath, level) {
const element = path.basename(fsPath);
const stat = await fs.stat(fsPath);
if (!stat.isDirectory() || element === '.git' || element === '.build' || level >= 2) {
return { element };
}
const childNames = await fs.readdir(fsPath);
const children = await Promise.all(childNames.map(async childName => await getTree(path.join(fsPath, childName), level + 1)));
return { element, collapsed: false, children };
}
app.use(serve('public'));
app.use(mount('/static', serve('../../out')));
app.use(_.get('/api/ls', async ctx => {
const relativePath = ctx.query.path;
const absolutePath = path.join(root, relativePath);
ctx.body = await getTree(absolutePath, 0);
}))
app.listen(3000);
console.log('http://localhost:3000');
\ No newline at end of file
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
accepts@^1.2.2:
version "1.3.5"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
dependencies:
mime-types "~2.1.18"
negotiator "0.6.1"
any-promise@^1.0.0, any-promise@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
content-disposition@~0.5.0:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
content-type@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
cookies@~0.7.0:
version "0.7.1"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.1.tgz#7c8a615f5481c61ab9f16c833731bcb8f663b99b"
dependencies:
depd "~1.1.1"
keygrip "~1.0.2"
debug@*, debug@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
debug@^2.6.1:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
ms "2.0.0"
deep-equal@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
depd@^1.1.0, depd@~1.1.1, depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
destroy@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
error-inject@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37"
escape-html@~1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
fresh@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
http-assert@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.3.0.tgz#a31a5cf88c873ecbb5796907d4d6f132e8c01e4a"
dependencies:
deep-equal "~1.0.1"
http-errors "~1.6.1"
http-errors@^1.2.8, http-errors@^1.6.3, http-errors@~1.6.1, http-errors@~1.6.2:
version "1.6.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.0"
statuses ">= 1.4.0 < 2"
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
is-generator-function@^1.0.3:
version "1.0.7"
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
keygrip@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91"
koa-compose@^3.0.0, koa-compose@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7"
dependencies:
any-promise "^1.1.0"
koa-compose@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
koa-convert@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0"
dependencies:
co "^4.6.0"
koa-compose "^3.0.0"
koa-is-json@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14"
koa-mount@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/koa-mount/-/koa-mount-3.0.0.tgz#08cab3b83d31442ed8b7e75c54b1abeb922ec197"
dependencies:
debug "^2.6.1"
koa-compose "^3.2.1"
koa-route@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/koa-route/-/koa-route-3.2.0.tgz#76298b99a6bcfa9e38cab6fe5c79a8733e758bce"
dependencies:
debug "*"
methods "~1.1.0"
path-to-regexp "^1.2.0"
koa-send@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.0.tgz#5e8441e07ef55737734d7ced25b842e50646e7eb"
dependencies:
debug "^3.1.0"
http-errors "^1.6.3"
mz "^2.7.0"
resolve-path "^1.4.0"
koa-static@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-5.0.0.tgz#5e92fc96b537ad5219f425319c95b64772776943"
dependencies:
debug "^3.1.0"
koa-send "^5.0.0"
koa@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/koa/-/koa-2.5.1.tgz#79f8b95f8d72d04fe9a58a8da5ebd6d341103f9c"
dependencies:
accepts "^1.2.2"
content-disposition "~0.5.0"
content-type "^1.0.0"
cookies "~0.7.0"
debug "*"
delegates "^1.0.0"
depd "^1.1.0"
destroy "^1.0.3"
error-inject "~1.0.0"
escape-html "~1.0.1"
fresh "^0.5.2"
http-assert "^1.1.0"
http-errors "^1.2.8"
is-generator-function "^1.0.3"
koa-compose "^4.0.0"
koa-convert "^1.2.0"
koa-is-json "^1.0.0"
mime-types "^2.0.7"
on-finished "^2.1.0"
only "0.0.2"
parseurl "^1.3.0"
statuses "^1.2.0"
type-is "^1.5.5"
vary "^1.0.0"
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
methods@~1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
mime-db@~1.33.0:
version "1.33.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
mime-types@^2.0.7, mime-types@~2.1.18:
version "2.1.18"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
dependencies:
mime-db "~1.33.0"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
on-finished@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
dependencies:
ee-first "1.1.1"
only@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
parseurl@^1.3.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
path-is-absolute@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
path-to-regexp@^1.2.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
dependencies:
isarray "0.0.1"
resolve-path@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/resolve-path/-/resolve-path-1.4.0.tgz#c4bda9f5efb2fce65247873ab36bb4d834fe16f7"
dependencies:
http-errors "~1.6.2"
path-is-absolute "1.0.1"
setprototypeof@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
"statuses@>= 1.4.0 < 2", statuses@^1.2.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
dependencies:
thenify ">= 3.1.0 < 4"
"thenify@>= 3.1.0 < 4":
version "3.3.0"
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
dependencies:
any-promise "^1.0.0"
type-is@^1.5.5:
version "1.6.16"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
dependencies:
media-typer "0.3.0"
mime-types "~2.1.18"
vary@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册