提交 82d00f53 编写于 作者: J Joao Moreno

Merge commit 'refs/pull/62052/head' of github.com:Microsoft/vscode into pr/62052

...@@ -8,6 +8,7 @@ import { GestureEvent } from 'vs/base/browser/touch'; ...@@ -8,6 +8,7 @@ import { GestureEvent } from 'vs/base/browser/touch';
export interface IListVirtualDelegate<T> { export interface IListVirtualDelegate<T> {
getHeight(element: T): number; getHeight(element: T): number;
getTemplateId(element: T): string; getTemplateId(element: T): string;
hasDynamicHeight?(element: T): boolean;
} }
export interface IListRenderer<T, TTemplateData> { export interface IListRenderer<T, TTemplateData> {
......
...@@ -33,29 +33,34 @@ function canUseTranslate3d(): boolean { ...@@ -33,29 +33,34 @@ function canUseTranslate3d(): boolean {
return true; return true;
} }
interface IItem<T> { interface IItem<T> {
id: string; readonly id: string;
element: T; readonly element: T;
size: number; readonly templateId: string;
templateId: string;
row: IRow | null; row: IRow | null;
size: number;
hasDynamicHeight: boolean;
dynamicSizeSnapshotId: number;
} }
export interface IListViewOptions { export interface IListViewOptions {
useShadows?: boolean; readonly useShadows?: boolean;
verticalScrollMode?: ScrollbarVisibility; readonly verticalScrollMode?: ScrollbarVisibility;
setRowLineHeight?: boolean; readonly setRowLineHeight?: boolean;
readonly supportDynamicHeights?: boolean;
} }
const DefaultOptions = { const DefaultOptions = {
useShadows: true, useShadows: true,
verticalScrollMode: ScrollbarVisibility.Auto, verticalScrollMode: ScrollbarVisibility.Auto,
setRowLineHeight: true setRowLineHeight: true,
supportDynamicHeights: false
}; };
export class ListView<T> implements ISpliceable<T>, IDisposable { export class ListView<T> implements ISpliceable<T>, IDisposable {
readonly domNode: HTMLElement;
private items: IItem<T>[]; private items: IItem<T>[];
private itemId: number; private itemId: number;
private rangeMap: RangeMap; private rangeMap: RangeMap;
...@@ -63,7 +68,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -63,7 +68,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private renderers = new Map<string, IListRenderer<T, any>>(); private renderers = new Map<string, IListRenderer<T, any>>();
private lastRenderTop: number; private lastRenderTop: number;
private lastRenderHeight: number; private lastRenderHeight: number;
private _domNode: HTMLElement; private dynamicSizeSnapshotId = 0;
private gesture: Gesture; private gesture: Gesture;
private rowsContainer: HTMLElement; private rowsContainer: HTMLElement;
private scrollableElement: ScrollableElement; private scrollableElement: ScrollableElement;
...@@ -74,6 +79,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -74,6 +79,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private dragAndDropScrollTimeout: number; private dragAndDropScrollTimeout: number;
private dragAndDropMouseY: number; private dragAndDropMouseY: number;
private setRowLineHeight: boolean; private setRowLineHeight: boolean;
private supportDynamicHeights: boolean;
private disposables: IDisposable[]; private disposables: IDisposable[];
constructor( constructor(
...@@ -95,8 +101,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -95,8 +101,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.lastRenderTop = 0; this.lastRenderTop = 0;
this.lastRenderHeight = 0; this.lastRenderHeight = 0;
this._domNode = document.createElement('div'); this.domNode = document.createElement('div');
this._domNode.className = 'monaco-list'; this.domNode.className = 'monaco-list';
this.rowsContainer = document.createElement('div'); this.rowsContainer = document.createElement('div');
this.rowsContainer.className = 'monaco-list-rows'; this.rowsContainer.className = 'monaco-list-rows';
...@@ -109,8 +115,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -109,8 +115,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
useShadows: getOrDefault2(options, o => o.useShadows, DefaultOptions.useShadows) useShadows: getOrDefault2(options, o => o.useShadows, DefaultOptions.useShadows)
}); });
this._domNode.appendChild(this.scrollableElement.getDomNode()); this.domNode.appendChild(this.scrollableElement.getDomNode());
container.appendChild(this._domNode); container.appendChild(this.domNode);
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache]; this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache];
...@@ -126,14 +132,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -126,14 +132,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
onDragOver(this.onDragOver, this, this.disposables); onDragOver(this.onDragOver, this, this.disposables);
this.setRowLineHeight = getOrDefault2(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); this.setRowLineHeight = getOrDefault2(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
this.supportDynamicHeights = getOrDefault2(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
this.layout(); this.layout();
} }
get domNode(): HTMLElement {
return this._domNode;
}
splice(start: number, deleteCount: number, elements: T[] = []): T[] { splice(start: number, deleteCount: number, elements: T[] = []): T[] {
if (this.splicing) { if (this.splicing) {
throw new Error('Can\'t run recursive splices.'); throw new Error('Can\'t run recursive splices.');
...@@ -164,8 +167,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -164,8 +167,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const inserted = elements.map<IItem<T>>(element => ({ const inserted = elements.map<IItem<T>>(element => ({
id: String(this.itemId++), id: String(this.itemId++),
element, element,
size: this.virtualDelegate.getHeight(element),
templateId: this.virtualDelegate.getTemplateId(element), templateId: this.virtualDelegate.getTemplateId(element),
size: this.virtualDelegate.getHeight(element),
hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element),
dynamicSizeSnapshotId: this.dynamicSizeSnapshotId - 1,
row: null row: null
})); }));
...@@ -193,10 +198,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -193,10 +198,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const removeRanges = Range.relativeComplement(renderedRestRange, renderRange); const removeRanges = Range.relativeComplement(renderedRestRange, renderRange);
for (let r = 0; r < removeRanges.length; r++) { for (const range of removeRanges) {
const removeRange = removeRanges[r]; for (let i = range.start; i < range.end; i++) {
for (let i = removeRange.start; i < removeRange.end; i++) {
this.removeItemFromDOM(i); this.removeItemFromDOM(i);
} }
} }
...@@ -206,14 +209,22 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -206,14 +209,22 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => Range.intersect(renderRange, r)); const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => Range.intersect(renderRange, r));
const beforeElement = this.getNextToLastElement(insertRanges); const beforeElement = this.getNextToLastElement(insertRanges);
for (let r = 0; r < insertRanges.length; r++) { for (const range of insertRanges) {
const insertRange = insertRanges[r]; for (let i = range.start; i < range.end; i++) {
for (let i = insertRange.start; i < insertRange.end; i++) {
this.insertItemInDOM(i, beforeElement); 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.scrollHeight = this.getContentHeight();
this.rowsContainer.style.height = `${this.scrollHeight}px`; this.rowsContainer.style.height = `${this.scrollHeight}px`;
...@@ -225,8 +236,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -225,8 +236,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.didRequestScrollableElementUpdate = true; this.didRequestScrollableElementUpdate = true;
} }
return deleted.map(i => i.element);
} }
get length(): number { get length(): number {
...@@ -265,7 +274,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -265,7 +274,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
layout(height?: number): void { layout(height?: number): void {
this.scrollableElement.setScrollDimensions({ this.scrollableElement.setScrollDimensions({
height: height || DOM.getContentHeight(this._domNode) height: height || DOM.getContentHeight(this.domNode)
}); });
} }
...@@ -320,12 +329,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -320,12 +329,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); this.updateItemInDOM(item, index);
const renderer = this.renderers.get(item.templateId); const renderer = this.renderers.get(item.templateId);
...@@ -333,11 +336,17 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -333,11 +336,17 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
} }
private updateItemInDOM(item: IItem<T>, index: number): void { private updateItemInDOM(item: IItem<T>, index: number): void {
item.row!.domNode!.style.top = `${this.elementTop(index)}px`; item.row.domNode!.style.top = `${this.elementTop(index)}px`;
item.row!.domNode!.setAttribute('data-index', `${index}`); item.row.domNode!.style.height = `${item.size}px`;
item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`); if (this.setRowLineHeight) {
item.row!.domNode!.setAttribute('aria-posinset', `${index + 1}`); 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}`);
item.row.domNode!.setAttribute('aria-posinset', `${index + 1}`);
} }
private removeItemFromDOM(index: number): void { private removeItemFromDOM(index: number): void {
...@@ -411,6 +420,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -411,6 +420,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private onScroll(e: ScrollEvent): void { private onScroll(e: ScrollEvent): void {
try { try {
this.render(e.scrollTop, e.height); this.render(e.scrollTop, e.height);
if (this.supportDynamicHeights) {
this.rerender(e.scrollTop, e.height);
}
} catch (err) { } catch (err) {
console.log('Got bad scroll event:', e); console.log('Got bad scroll event:', e);
throw err; throw err;
...@@ -430,7 +443,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -430,7 +443,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
} }
private setupDragAndDropScrollInterval(): void { private setupDragAndDropScrollInterval(): void {
var viewTop = DOM.getTopLeftOffset(this._domNode).top; const viewTop = DOM.getTopLeftOffset(this.domNode).top;
if (!this.dragAndDropScrollInterval) { if (!this.dragAndDropScrollInterval) {
this.dragAndDropScrollInterval = window.setInterval(() => { this.dragAndDropScrollInterval = window.setInterval(() => {
...@@ -505,6 +518,94 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -505,6 +518,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.dynamicSizeSnapshotId === this.dynamicSizeSnapshotId) {
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.dynamicSizeSnapshotId = this.dynamicSizeSnapshotId;
this.rowsContainer.removeChild(row.domNode);
this.cache.release(row);
return item.size - size;
}
private getNextToLastElement(ranges: IRange[]): HTMLElement | null { private getNextToLastElement(ranges: IRange[]): HTMLElement | null {
const lastRange = ranges[ranges.length - 1]; const lastRange = ranges[ranges.length - 1];
...@@ -539,8 +640,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { ...@@ -539,8 +640,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.items = []; this.items = [];
} }
if (this._domNode && this._domNode.parentNode) { if (this.domNode && this.domNode.parentNode) {
this._domNode.parentNode.removeChild(this._domNode); this.domNode.parentNode.removeChild(this.domNode);
} }
this.disposables = dispose(this.disposables); this.disposables = dispose(this.disposables);
......
...@@ -70,6 +70,10 @@ export class ComposedTreeDelegate<T, N extends { element: T }> implements IListV ...@@ -70,6 +70,10 @@ export class ComposedTreeDelegate<T, N extends { element: T }> implements IListV
getTemplateId(element: N): string { getTemplateId(element: N): string {
return this.delegate.getTemplateId(element.element); return this.delegate.getTemplateId(element.element);
} }
hasDynamicHeight(element: N): boolean {
return !!this.delegate.hasDynamicHeight && this.delegate.hasDynamicHeight(element.element);
}
} }
interface ITreeListTemplateData<T> { interface ITreeListTemplateData<T> {
......
...@@ -45,13 +45,16 @@ ...@@ -45,13 +45,16 @@
function createIndexTree() { function createIndexTree() {
const delegate = { const delegate = {
getHeight() { return 22; }, getHeight() { return 22; },
getTemplateId() { return 'template'; } getTemplateId() { return 'template'; },
hasDynamicHeight() { return true; }
}; };
const renderer = { const renderer = {
templateId: 'template', templateId: 'template',
renderTemplate(container) { return container; }, renderTemplate(container) { return container; },
renderElement(element, index, container) { container.textContent = element.element; }, renderElement(element, index, container) {
container.innerHTML = `${element.element}<br />${element.element}<br />${element.element}`;
},
disposeElement() { }, disposeElement() { },
disposeTemplate() { } disposeTemplate() { }
}; };
...@@ -79,7 +82,7 @@ ...@@ -79,7 +82,7 @@
} }
}; };
const tree = new IndexTree(container, delegate, [renderer], { filter: treeFilter }); const tree = new IndexTree(container, delegate, [renderer], { filter: treeFilter, setRowLineHeight: false, supportDynamicHeights: true });
return { tree, treeFilter }; return { tree, treeFilter };
} }
......
...@@ -17,7 +17,7 @@ async function getTree(fsPath, level) { ...@@ -17,7 +17,7 @@ async function getTree(fsPath, level) {
const element = path.basename(fsPath); const element = path.basename(fsPath);
const stat = await fs.stat(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 }; return { element };
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册