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

Merge branch 'pr/62052'

......@@ -8,6 +8,7 @@ import { GestureEvent } from 'vs/base/browser/touch';
export interface IListVirtualDelegate<T> {
getHeight(element: T): number;
getTemplateId(element: T): string;
hasDynamicHeight?(element: T): boolean;
}
export interface IListRenderer<T, TTemplateData> {
......
......@@ -33,29 +33,34 @@ function canUseTranslate3d(): boolean {
return true;
}
interface IItem<T> {
id: string;
element: T;
size: number;
templateId: string;
readonly id: string;
readonly element: T;
readonly templateId: string;
row: IRow | null;
size: number;
hasDynamicHeight: boolean;
renderWidth: number | undefined;
}
export interface IListViewOptions {
useShadows?: boolean;
verticalScrollMode?: ScrollbarVisibility;
setRowLineHeight?: boolean;
readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility;
readonly setRowLineHeight?: boolean;
readonly supportDynamicHeights?: boolean;
}
const DefaultOptions = {
useShadows: true,
verticalScrollMode: ScrollbarVisibility.Auto,
setRowLineHeight: true
setRowLineHeight: true,
supportDynamicHeights: false
};
export class ListView<T> implements ISpliceable<T>, IDisposable {
readonly domNode: HTMLElement;
private items: IItem<T>[];
private itemId: number;
private rangeMap: RangeMap;
......@@ -63,7 +68,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private renderers = new Map<string, IListRenderer<T, any>>();
private lastRenderTop: number;
private lastRenderHeight: number;
private _domNode: HTMLElement;
private renderWidth = 0;
private gesture: Gesture;
private rowsContainer: HTMLElement;
private scrollableElement: ScrollableElement;
......@@ -74,6 +79,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private dragAndDropScrollTimeout: number;
private dragAndDropMouseY: number;
private setRowLineHeight: boolean;
private supportDynamicHeights: boolean;
private disposables: IDisposable[];
constructor(
......@@ -95,8 +101,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.lastRenderTop = 0;
this.lastRenderHeight = 0;
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-list';
this.domNode = document.createElement('div');
this.domNode.className = 'monaco-list';
this.rowsContainer = document.createElement('div');
this.rowsContainer.className = 'monaco-list-rows';
......@@ -109,8 +115,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
useShadows: getOrDefault2(options, o => o.useShadows, DefaultOptions.useShadows)
});
this._domNode.appendChild(this.scrollableElement.getDomNode());
container.appendChild(this._domNode);
this.domNode.appendChild(this.scrollableElement.getDomNode());
container.appendChild(this.domNode);
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache];
......@@ -126,14 +132,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
onDragOver(this.onDragOver, this, this.disposables);
this.setRowLineHeight = getOrDefault2(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
this.supportDynamicHeights = getOrDefault2(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
this.layout();
}
get domNode(): HTMLElement {
return this._domNode;
}
splice(start: number, deleteCount: number, elements: T[] = []): T[] {
if (this.splicing) {
throw new Error('Can\'t run recursive splices.');
......@@ -164,8 +167,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const inserted = elements.map<IItem<T>>(element => ({
id: String(this.itemId++),
element,
size: this.virtualDelegate.getHeight(element),
templateId: this.virtualDelegate.getTemplateId(element),
size: this.virtualDelegate.getHeight(element),
hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element),
renderWidth: undefined,
row: null
}));
......@@ -193,10 +198,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const removeRanges = Range.relativeComplement(renderedRestRange, renderRange);
for (let r = 0; r < removeRanges.length; r++) {
const removeRange = removeRanges[r];
for (let i = removeRange.start; i < removeRange.end; i++) {
for (const range of removeRanges) {
for (let i = range.start; i < range.end; i++) {
this.removeItemFromDOM(i);
}
}
......@@ -206,14 +209,22 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => Range.intersect(renderRange, r));
const beforeElement = this.getNextToLastElement(insertRanges);
for (let r = 0; r < insertRanges.length; r++) {
const insertRange = insertRanges[r];
for (let i = insertRange.start; i < insertRange.end; i++) {
for (const range of insertRanges) {
for (let i = range.start; i < range.end; i++) {
this.insertItemInDOM(i, beforeElement);
}
}
this.updateScrollHeight();
if (this.supportDynamicHeights) {
this.rerender(this.scrollTop, this.renderHeight);
}
return deleted.map(i => i.element);
}
private updateScrollHeight(): void {
this.scrollHeight = this.getContentHeight();
this.rowsContainer.style.height = `${this.scrollHeight}px`;
......@@ -225,8 +236,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.didRequestScrollableElementUpdate = true;
}
return deleted.map(i => i.element);
}
get length(): number {
......@@ -265,10 +274,18 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
layout(height?: number): void {
this.scrollableElement.setScrollDimensions({
height: height || DOM.getContentHeight(this._domNode)
height: height || DOM.getContentHeight(this.domNode)
});
}
layoutWidth(width: number): void {
this.renderWidth = width;
if (this.supportDynamicHeights) {
this.rerender(this.scrollTop, this.renderHeight);
}
}
// Render
private render(renderTop: number, renderHeight: number): void {
......@@ -320,12 +337,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
}
item.row.domNode!.style.height = `${item.size}px`;
if (this.setRowLineHeight) {
item.row.domNode!.style.lineHeight = `${item.size}px`;
}
this.updateItemInDOM(item, index);
const renderer = this.renderers.get(item.templateId);
......@@ -334,6 +345,12 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private updateItemInDOM(item: IItem<T>, index: number): void {
item.row!.domNode!.style.top = `${this.elementTop(index)}px`;
item.row!.domNode!.style.height = `${item.size}px`;
if (this.setRowLineHeight) {
item.row!.domNode!.style.lineHeight = `${item.size}px`;
}
item.row!.domNode!.setAttribute('data-index', `${index}`);
item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`);
......@@ -411,6 +428,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private onScroll(e: ScrollEvent): void {
try {
this.render(e.scrollTop, e.height);
if (this.supportDynamicHeights) {
this.rerender(e.scrollTop, e.height);
}
} catch (err) {
console.log('Got bad scroll event:', e);
throw err;
......@@ -430,7 +451,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
private setupDragAndDropScrollInterval(): void {
var viewTop = DOM.getTopLeftOffset(this._domNode).top;
const viewTop = DOM.getTopLeftOffset(this.domNode).top;
if (!this.dragAndDropScrollInterval) {
this.dragAndDropScrollInterval = window.setInterval(() => {
......@@ -505,6 +526,94 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
};
}
/**
* Given a stable rendered state, checks every rendered element whether it needs
* to be probed for dynamic height. Adjusts scroll height and top if necessary.
*/
private rerender(renderTop: number, renderHeight: number): void {
const previousRenderRange = this.getRenderRange(renderTop, renderHeight);
// Let's remember the second element's position, this helps in scrolling up
// and preserving a linear upwards scroll movement
let secondElementIndex: number | undefined;
let secondElementTopDelta: number | undefined;
if (previousRenderRange.end - previousRenderRange.start > 1) {
secondElementIndex = previousRenderRange.start + 1;
secondElementTopDelta = this.elementTop(secondElementIndex) - renderTop;
}
let heightDiff = 0;
while (true) {
const renderRange = this.getRenderRange(renderTop, renderHeight);
let didChange = false;
for (let i = renderRange.start; i < renderRange.end; i++) {
const diff = this.probeDynamicHeight(i);
if (diff !== 0) {
this.rangeMap.splice(i, 1, [this.items[i]]);
}
heightDiff += diff;
didChange = didChange || diff !== 0;
}
if (!didChange) {
if (heightDiff !== 0) {
this.updateScrollHeight();
}
const unrenderRanges = Range.relativeComplement(previousRenderRange, renderRange);
for (const range of unrenderRanges) {
for (let i = range.start; i < range.end; i++) {
if (this.items[i].row) {
this.removeItemFromDOM(i);
}
}
}
for (let i = renderRange.start; i < renderRange.end; i++) {
if (this.items[i].row) {
this.updateItemInDOM(this.items[i], i);
}
}
if (typeof secondElementIndex === 'number') {
this.scrollTop = this.elementTop(secondElementIndex) - secondElementTopDelta!;
}
return;
}
}
}
private probeDynamicHeight(index: number): number {
const item = this.items[index];
if (!item.hasDynamicHeight || item.renderWidth === this.renderWidth) {
return 0;
}
const size = item.size;
const renderer = this.renderers.get(item.templateId);
const row = this.cache.alloc(item.templateId);
row.domNode!.style.height = '';
this.rowsContainer.appendChild(row.domNode!);
renderer.renderElement(item.element, index, row.templateData);
item.size = row.domNode!.offsetHeight;
item.renderWidth = this.renderWidth;
this.rowsContainer.removeChild(row.domNode!);
this.cache.release(row);
return item.size - size;
}
private getNextToLastElement(ranges: IRange[]): HTMLElement | null {
const lastRange = ranges[ranges.length - 1];
......@@ -539,8 +648,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.items = [];
}
if (this._domNode && this._domNode.parentNode) {
this._domNode.parentNode.removeChild(this._domNode);
if (this.domNode && this.domNode.parentNode) {
this.domNode.parentNode.removeChild(this.domNode);
}
this.disposables = dispose(this.disposables);
......
......@@ -1055,6 +1055,10 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.view.layout(height);
}
layoutWidth(width: number): void {
this.view.layoutWidth(width);
}
setSelection(indexes: number[], browserEvent?: UIEvent): void {
for (const index of indexes) {
if (index < 0 || index >= this.length) {
......
......@@ -70,6 +70,10 @@ export class ComposedTreeDelegate<T, N extends { element: T }> implements IListV
getTemplateId(element: N): string {
return this.delegate.getTemplateId(element.element);
}
hasDynamicHeight(element: N): boolean {
return !!this.delegate.hasDynamicHeight && this.delegate.hasDynamicHeight(element.element);
}
}
interface ITreeListTemplateData<T> {
......@@ -242,6 +246,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.view.layout(height);
}
layoutWidth(width: number): void {
this.view.layoutWidth(width);
}
style(styles: IListStyles): void {
this.view.style(styles);
}
......
......@@ -27,6 +27,7 @@
<body>
<input type="text" id="filter" />
<button id="collapseall">Collapse All</button>
<button id="renderwidth">Render Width</button>
<div id="container"></div>
<script src="/static/vs/loader.js"></script>
......@@ -42,16 +43,29 @@
require.config({ baseUrl: '/static' });
require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { DataTree }, { TreeVisibility }, { iter }) => {
function createIndexTree() {
function createIndexTree(opts) {
opts = opts || {};
const delegate = {
getHeight() { return 22; },
getTemplateId() { return 'template'; }
getTemplateId() { return 'template'; },
hasDynamicHeight() { return true; }
};
const renderer = {
templateId: 'template',
renderTemplate(container) { return container; },
renderElement(element, index, container) { container.textContent = element.element; },
renderElement(element, index, container) {
if (opts.supportDynamicHeights) {
let v = [];
for (let i = 1; i <= 3; i++) {
v.push(element.element);
}
container.innerHTML = v.join('<br />');
} else {
container.innerHTML = element.element;
}
},
disposeElement() { },
disposeTemplate() { }
};
......@@ -79,7 +93,7 @@
}
};
const tree = new IndexTree(container, delegate, [renderer], { filter: treeFilter });
const tree = new IndexTree(container, delegate, [renderer], { ...opts, filter: treeFilter, setRowLineHeight: false });
return { tree, treeFilter };
}
......@@ -158,6 +172,9 @@
case '?problems': {
const { tree, treeFilter } = createIndexTree();
collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
const files = [];
for (let i = 0; i < 100000; i++) {
const errors = [];
......@@ -175,13 +192,36 @@
case '?data': {
const { tree, treeFilter } = createDataTree();
collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
tree.refresh(null);
break;
}
default:
case '?height': {
const { tree, treeFilter } = createIndexTree({ supportDynamicHeights: true });
collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/ls?path=');
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
perf('splice', () => tree.splice([0], 0, [JSON.parse(this.responseText)]));
treeFilter.updatePattern();
}
};
break;
}
default: {
const { tree, treeFilter } = createIndexTree();
collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/ls?path=');
xhr.send();
......@@ -191,9 +231,8 @@
treeFilter.updatePattern();
}
};
}
}
collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
});
</script>
</body>
......
......@@ -17,7 +17,7 @@ async function getTree(fsPath, level) {
const element = path.basename(fsPath);
const stat = await fs.stat(fsPath);
if (!stat.isDirectory() || element === '.git' || element === '.build' || level >= 5) {
if (!stat.isDirectory() || element === '.git' || element === '.build' || level >= 2) {
return { element };
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册