listView.ts 40.0 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6
import { getOrDefault } from 'vs/base/common/objects';
7
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
8
import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
J
Joao Moreno 已提交
9
import * as DOM from 'vs/base/browser/dom';
J
Joao Moreno 已提交
10
import { Event, Emitter } from 'vs/base/common/event';
11
import { domEvent } from 'vs/base/browser/event';
A
Alex Dima 已提交
12
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
13
import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/common/scrollable';
J
Joao Moreno 已提交
14
import { RangeMap, shift } from './rangeMap';
15
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListDragEvent, IListDragAndDrop, ListDragOverEffect } from './list';
J
Joao Moreno 已提交
16
import { RowCache, IRow } from './rowCache';
J
Joao Moreno 已提交
17
import { ISpliceable } from 'vs/base/common/sequence';
J
Joao Moreno 已提交
18
import { memoize } from 'vs/base/common/decorators';
J
Joao Moreno 已提交
19
import { Range, IRange } from 'vs/base/common/range';
J
Joao Moreno 已提交
20
import { equals, distinct } from 'vs/base/common/arrays';
J
Joao Moreno 已提交
21
import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd';
22
import { disposableTimeout, Delayer } from 'vs/base/common/async';
23
import { isFirefox } from 'vs/base/browser/browser';
P
Peng Lyu 已提交
24
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
25

26
interface IItem<T> {
27 28 29
	readonly id: string;
	readonly element: T;
	readonly templateId: string;
J
Joao Moreno 已提交
30
	row: IRow | null;
31
	size: number;
32
	width: number | undefined;
33
	hasDynamicHeight: boolean;
34
	lastDynamicHeightWidth: number | undefined;
J
Joao Moreno 已提交
35
	uri: string | undefined;
J
Joao Moreno 已提交
36
	dropTarget: boolean;
J
Joao Moreno 已提交
37
	dragStartDisposable: IDisposable;
J
Joao Moreno 已提交
38 39
}

40
export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> {
J
Joao Moreno 已提交
41 42 43
	getDragElements(element: T): T[];
}

J
João Moreno 已提交
44 45 46
export interface IListViewAccessibilityProvider<T> {
	getSetSize?(element: T, index: number, listLength: number): number;
	getPosInSet?(element: T, index: number): number;
47
	getRole?(element: T): string | undefined;
J
João Moreno 已提交
48
	isChecked?(element: T): boolean | undefined;
J
Joao Moreno 已提交
49 50
}

J
Joao Moreno 已提交
51
export interface IListViewOptions<T> {
J
Joao Moreno 已提交
52
	readonly dnd?: IListViewDragAndDrop<T>;
53 54 55
	readonly useShadows?: boolean;
	readonly verticalScrollMode?: ScrollbarVisibility;
	readonly setRowLineHeight?: boolean;
R
rebornix 已提交
56
	readonly setRowHeight?: boolean;
J
Joao Moreno 已提交
57
	readonly supportDynamicHeights?: boolean;
J
Joao Moreno 已提交
58
	readonly mouseSupport?: boolean;
59
	readonly horizontalScrolling?: boolean;
J
João Moreno 已提交
60
	readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
I
isidor 已提交
61
	readonly additionalScrollHeight?: number;
62
	readonly transformOptimization?: boolean;
J
Joao Moreno 已提交
63 64
}

J
Joao Moreno 已提交
65
const DefaultOptions = {
66
	useShadows: true,
J
Joao Moreno 已提交
67
	verticalScrollMode: ScrollbarVisibility.Auto,
J
Joao Moreno 已提交
68
	setRowLineHeight: true,
R
rebornix 已提交
69
	setRowHeight: true,
J
Joao Moreno 已提交
70 71
	supportDynamicHeights: false,
	dnd: {
M
Matt Bierner 已提交
72
		getDragElements<T>(e: T) { return [e]; },
J
Joao Moreno 已提交
73 74 75 76
		getDragURI() { return null; },
		onDragStart(): void { },
		onDragOver() { return false; },
		drop() { }
77
	},
78 79
	horizontalScrolling: false,
	transformOptimization: true
J
Joao Moreno 已提交
80 81
};

J
Joao Moreno 已提交
82
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
J
Joao Moreno 已提交
83

J
Joao Moreno 已提交
84
	readonly elements: T[];
J
Joao Moreno 已提交
85
	context: TContext | undefined;
J
Joao Moreno 已提交
86 87 88 89 90

	constructor(elements: T[]) {
		this.elements = elements;
	}

J
Joao Moreno 已提交
91
	update(): void { }
J
Joao Moreno 已提交
92

J
types  
Joao Moreno 已提交
93
	getData(): T[] {
J
Joao Moreno 已提交
94 95 96 97 98 99
		return this.elements;
	}
}

export class ExternalElementsDragAndDropData<T> implements IDragAndDropData {

J
Joao Moreno 已提交
100
	readonly elements: T[];
J
Joao Moreno 已提交
101 102 103 104 105

	constructor(elements: T[]) {
		this.elements = elements;
	}

J
Joao Moreno 已提交
106
	update(): void { }
J
Joao Moreno 已提交
107

J
types  
Joao Moreno 已提交
108
	getData(): T[] {
J
Joao Moreno 已提交
109 110 111 112 113 114
		return this.elements;
	}
}

export class DesktopDragAndDropData implements IDragAndDropData {

J
Joao Moreno 已提交
115 116
	readonly types: any[];
	readonly files: any[];
J
Joao Moreno 已提交
117 118 119 120 121 122

	constructor() {
		this.types = [];
		this.files = [];
	}

J
Joao Moreno 已提交
123
	update(dataTransfer: DataTransfer): void {
J
Joao Moreno 已提交
124
		if (dataTransfer.types) {
J
Joao Moreno 已提交
125
			this.types.splice(0, this.types.length, ...dataTransfer.types);
J
Joao Moreno 已提交
126 127 128
		}

		if (dataTransfer.files) {
J
Joao Moreno 已提交
129
			this.files.splice(0, this.files.length);
J
Joao Moreno 已提交
130 131 132 133 134 135 136 137 138 139 140

			for (let i = 0; i < dataTransfer.files.length; i++) {
				const file = dataTransfer.files.item(i);

				if (file && (file.size || file.type)) {
					this.files.push(file);
				}
			}
		}
	}

J
Joao Moreno 已提交
141
	getData(): any {
J
Joao Moreno 已提交
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
		return {
			types: this.types,
			files: this.files
		};
	}
}

function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): boolean {
	if (Array.isArray(f1) && Array.isArray(f2)) {
		return equals(f1, f2!);
	}

	return f1 === f2;
}

J
João Moreno 已提交
157 158 159 160
class ListViewAccessibilityProvider<T> implements Required<IListViewAccessibilityProvider<T>> {

	readonly getSetSize: (element: any, index: number, listLength: number) => number;
	readonly getPosInSet: (element: any, index: number) => number;
161
	readonly getRole: (element: T) => string | undefined;
J
João Moreno 已提交
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
	readonly isChecked: (element: T) => boolean | undefined;

	constructor(accessibilityProvider?: IListViewAccessibilityProvider<T>) {
		if (accessibilityProvider?.getSetSize) {
			this.getSetSize = accessibilityProvider.getSetSize.bind(accessibilityProvider);
		} else {
			this.getSetSize = (e, i, l) => l;
		}

		if (accessibilityProvider?.getPosInSet) {
			this.getPosInSet = accessibilityProvider.getPosInSet.bind(accessibilityProvider);
		} else {
			this.getPosInSet = (e, i) => i + 1;
		}

		if (accessibilityProvider?.getRole) {
			this.getRole = accessibilityProvider.getRole.bind(accessibilityProvider);
		} else {
			this.getRole = _ => 'listitem';
		}

		if (accessibilityProvider?.isChecked) {
			this.isChecked = accessibilityProvider.isChecked.bind(accessibilityProvider);
		} else {
			this.isChecked = _ => undefined;
		}
	}
}

J
Joao Moreno 已提交
191
export class ListView<T> implements ISpliceable<T>, IDisposable {
J
Joao Moreno 已提交
192

J
Joao Moreno 已提交
193 194 195
	private static InstanceCount = 0;
	readonly domId = `list_id_${++ListView.InstanceCount}`;

196 197
	readonly domNode: HTMLElement;

198
	private items: IItem<T>[];
J
Joao Moreno 已提交
199
	private itemId: number;
J
Joao Moreno 已提交
200
	private rangeMap: RangeMap;
J
Joao Moreno 已提交
201
	private cache: RowCache<T>;
J
Joao Moreno 已提交
202
	private renderers = new Map<string, IListRenderer<any /* TODO@joao */, any>>();
203 204
	private lastRenderTop: number;
	private lastRenderHeight: number;
J
Joao Moreno 已提交
205
	private renderWidth = 0;
R
rebornix 已提交
206
	private rowsContainer: HTMLElement;
A
Alex Dima 已提交
207
	private scrollableElement: ScrollableElement;
J
Joao Moreno 已提交
208
	private _scrollHeight: number = 0;
209
	private scrollableElementUpdateDisposable: IDisposable | null = null;
210
	private scrollableElementWidthDelayer = new Delayer<void>(50);
J
Joao Moreno 已提交
211
	private splicing = false;
J
Joao Moreno 已提交
212 213
	private dragOverAnimationDisposable: IDisposable | undefined;
	private dragOverAnimationStopDisposable: IDisposable = Disposable.None;
J
Joao Moreno 已提交
214
	private dragOverMouseY: number = 0;
J
Joao Moreno 已提交
215
	private setRowLineHeight: boolean;
R
rebornix 已提交
216
	private setRowHeight: boolean;
J
Joao Moreno 已提交
217
	private supportDynamicHeights: boolean;
218
	private horizontalScrolling: boolean;
I
isidor 已提交
219
	private additionalScrollHeight: number;
J
João Moreno 已提交
220
	private accessibilityProvider: ListViewAccessibilityProvider<T>;
J
Joao Moreno 已提交
221
	private scrollWidth: number | undefined;
J
Joao Moreno 已提交
222 223

	private dnd: IListViewDragAndDrop<T>;
224
	private canDrop: boolean = false;
J
Joao Moreno 已提交
225 226 227 228 229
	private currentDragData: IDragAndDropData | undefined;
	private currentDragFeedback: number[] | undefined;
	private currentDragFeedbackDisposable: IDisposable = Disposable.None;
	private onDragLeaveTimeout: IDisposable = Disposable.None;

230
	private readonly disposables: DisposableStore = new DisposableStore();
J
Joao Moreno 已提交
231

232
	private readonly _onDidChangeContentHeight = new Emitter<number>();
J
Joao Moreno 已提交
233
	readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event);
J
Joao Moreno 已提交
234 235
	get contentHeight(): number { return this.rangeMap.size; }

J
Joao Moreno 已提交
236
	get onDidScroll(): Event<ScrollEvent> { return this.scrollableElement.onScroll; }
237
	get onWillScroll(): Event<ScrollEvent> { return this.scrollableElement.onWillScroll; }
R
rebornix 已提交
238
	get containerDomNode(): HTMLElement { return this.rowsContainer; }
J
Joao Moreno 已提交
239

240 241
	constructor(
		container: HTMLElement,
J
Joao Moreno 已提交
242
		private virtualDelegate: IListVirtualDelegate<T>,
J
Joao Moreno 已提交
243
		renderers: IListRenderer<any /* TODO@joao */, any>[],
J
Joao Moreno 已提交
244
		options: IListViewOptions<T> = DefaultOptions as IListViewOptions<T>
245
	) {
246 247 248 249
		if (options.horizontalScrolling && options.supportDynamicHeights) {
			throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously');
		}

J
Joao Moreno 已提交
250
		this.items = [];
J
Joao Moreno 已提交
251
		this.itemId = 0;
J
Joao Moreno 已提交
252
		this.rangeMap = new RangeMap();
J
Joao Moreno 已提交
253 254 255 256 257

		for (const renderer of renderers) {
			this.renderers.set(renderer.templateId, renderer);
		}

258
		this.cache = this.disposables.add(new RowCache(this.renderers));
J
Joao Moreno 已提交
259

260 261
		this.lastRenderTop = 0;
		this.lastRenderHeight = 0;
262

263 264
		this.domNode = document.createElement('div');
		this.domNode.className = 'monaco-list';
J
Joao Moreno 已提交
265 266 267 268

		DOM.addClass(this.domNode, this.domId);
		this.domNode.tabIndex = 0;

J
Joao Moreno 已提交
269
		DOM.toggleClass(this.domNode, 'mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true);
J
Joao Moreno 已提交
270

271 272 273
		this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling);
		DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling);

I
isidor 已提交
274 275
		this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight;

J
João Moreno 已提交
276
		this.accessibilityProvider = new ListViewAccessibilityProvider(options.accessibilityProvider);
J
Joao Moreno 已提交
277

J
Joao Moreno 已提交
278 279
		this.rowsContainer = document.createElement('div');
		this.rowsContainer.className = 'monaco-list-rows';
280

281 282
		const transformOptimization = getOrDefault(options, o => o.transformOptimization, DefaultOptions.transformOptimization);
		if (transformOptimization) {
283 284 285
			this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
		}

286
		this.disposables.add(Gesture.addTarget(this.rowsContainer));
J
Joao Moreno 已提交
287

288
		this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, {
289
			alwaysConsumeMouseWheel: true,
290
			horizontal: this.horizontalScrolling ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
J
Joao Moreno 已提交
291 292
			vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
			useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows)
293
		}));
J
Joao Moreno 已提交
294

295 296
		this.domNode.appendChild(this.scrollableElement.getDomNode());
		container.appendChild(this.domNode);
J
Joao Moreno 已提交
297

298 299
		this.scrollableElement.onScroll(this.onScroll, this, this.disposables);
		domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables);
300

J
Joao Moreno 已提交
301 302 303 304 305
		// Prevent the monaco-scrollable-element from scrolling
		// https://github.com/Microsoft/vscode/issues/44181
		domEvent(this.scrollableElement.getDomNode(), 'scroll')
			(e => (e.target as HTMLElement).scrollTop = 0, null, this.disposables);

J
Joao Moreno 已提交
306 307 308 309
		Event.map(domEvent(this.domNode, 'dragover'), e => this.toDragEvent(e))(this.onDragOver, this, this.disposables);
		Event.map(domEvent(this.domNode, 'drop'), e => this.toDragEvent(e))(this.onDrop, this, this.disposables);
		domEvent(this.domNode, 'dragleave')(this.onDragLeave, this, this.disposables);
		domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables);
310

J
Joao Moreno 已提交
311
		this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
R
rebornix 已提交
312
		this.setRowHeight = getOrDefault(options, o => o.setRowHeight, DefaultOptions.setRowHeight);
J
Joao Moreno 已提交
313
		this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
J
Joao Moreno 已提交
314
		this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
J
Joao Moreno 已提交
315

316 317 318
		this.layout();
	}

319 320 321 322 323 324
	updateOptions(options: IListViewOptions<T>) {
		if (options.additionalScrollHeight !== undefined) {
			this.additionalScrollHeight = options.additionalScrollHeight;
		}
	}

R
rebornix 已提交
325
	triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
P
Peng Lyu 已提交
326 327 328
		this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent);
	}

329
	updateElementHeight(index: number, size: number, anchorIndex: number | null): void {
P
Peng Lyu 已提交
330 331 332
		if (this.items[index].size === size) {
			return;
		}
333

R
rebornix 已提交
334 335
		const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);

R
rebornix 已提交
336 337 338 339 340 341 342 343 344 345 346 347 348 349
		let heightDiff = 0;

		if (index < lastRenderRange.start) {
			// do not scroll the viewport if resized element is out of viewport
			heightDiff = size - this.items[index].size;
		} else {
			if (anchorIndex !== null && anchorIndex > index && anchorIndex <= lastRenderRange.end) {
				// anchor in viewport
				// resized elemnet in viewport and above the anchor
				heightDiff = size - this.items[index].size;
			} else {
				heightDiff = 0;
			}
		}
350

R
rebornix 已提交
351
		this.rangeMap.splice(index, 1, [{ size: size }]);
352 353
		this.items[index].size = size;

R
rebornix 已提交
354
		this.render(lastRenderRange, Math.max(0, this.lastRenderTop + heightDiff), this.lastRenderHeight, undefined, undefined, true);
355

P
Peng Lyu 已提交
356
		this.eventuallyUpdateScrollDimensions();
357

R
rebornix 已提交
358 359 360
		if (this.supportDynamicHeights) {
			this._rerender(this.lastRenderTop, this.lastRenderHeight);
		}
R
rebornix 已提交
361
		return;
362 363
	}

J
Joao Moreno 已提交
364
	splice(start: number, deleteCount: number, elements: T[] = []): T[] {
J
Joao Moreno 已提交
365 366 367 368 369 370 371 372 373 374
		if (this.splicing) {
			throw new Error('Can\'t run recursive splices.');
		}

		this.splicing = true;

		try {
			return this._splice(start, deleteCount, elements);
		} finally {
			this.splicing = false;
J
Joao Moreno 已提交
375
			this._onDidChangeContentHeight.fire(this.contentHeight);
J
Joao Moreno 已提交
376 377 378 379
		}
	}

	private _splice(start: number, deleteCount: number, elements: T[] = []): T[] {
380
		const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
J
Joao Moreno 已提交
381
		const deleteRange = { start, end: start + deleteCount };
J
Joao Moreno 已提交
382
		const removeRange = Range.intersect(previousRenderRange, deleteRange);
J
Joao Moreno 已提交
383

J
Joao Moreno 已提交
384
		for (let i = removeRange.start; i < removeRange.end; i++) {
J
Joao Moreno 已提交
385
			this.removeItemFromDOM(i);
J
Joao Moreno 已提交
386
		}
387

J
Joao Moreno 已提交
388
		const previousRestRange: IRange = { start: start + deleteCount, end: this.items.length };
J
Joao Moreno 已提交
389 390
		const previousRenderedRestRange = Range.intersect(previousRestRange, previousRenderRange);
		const previousUnrenderedRestRanges = Range.relativeComplement(previousRestRange, previousRenderRange);
J
Joao Moreno 已提交
391

392
		const inserted = elements.map<IItem<T>>(element => ({
J
Joao Moreno 已提交
393
			id: String(this.itemId++),
394
			element,
J
Joao Moreno 已提交
395
			templateId: this.virtualDelegate.getTemplateId(element),
396
			size: this.virtualDelegate.getHeight(element),
397
			width: undefined,
398
			hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element),
399
			lastDynamicHeightWidth: undefined,
J
Joao Moreno 已提交
400 401
			row: null,
			uri: undefined,
J
Joao Moreno 已提交
402
			dropTarget: false,
J
Joao Moreno 已提交
403
			dragStartDisposable: Disposable.None
404 405
		}));

J
Joao Moreno 已提交
406 407 408 409 410 411 412 413 414 415 416 417
		let deleted: IItem<T>[];

		// TODO@joao: improve this optimization to catch even more cases
		if (start === 0 && deleteCount >= this.items.length) {
			this.rangeMap = new RangeMap();
			this.rangeMap.splice(0, 0, inserted);
			this.items = inserted;
			deleted = [];
		} else {
			this.rangeMap.splice(start, deleteCount, inserted);
			deleted = this.items.splice(start, deleteCount, ...inserted);
		}
J
Joao Moreno 已提交
418 419

		const delta = elements.length - deleteCount;
420
		const renderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
J
Joao Moreno 已提交
421
		const renderedRestRange = shift(previousRenderedRestRange, delta);
J
Joao Moreno 已提交
422
		const updateRange = Range.intersect(renderRange, renderedRestRange);
J
Joao Moreno 已提交
423 424 425 426 427

		for (let i = updateRange.start; i < updateRange.end; i++) {
			this.updateItemInDOM(this.items[i], i);
		}

J
Joao Moreno 已提交
428
		const removeRanges = Range.relativeComplement(renderedRestRange, renderRange);
J
Joao Moreno 已提交
429

430 431
		for (const range of removeRanges) {
			for (let i = range.start; i < range.end; i++) {
J
Joao Moreno 已提交
432
				this.removeItemFromDOM(i);
J
Joao Moreno 已提交
433 434 435 436 437
			}
		}

		const unrenderedRestRanges = previousUnrenderedRestRanges.map(r => shift(r, delta));
		const elementsRange = { start, end: start + elements.length };
J
Joao Moreno 已提交
438
		const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => Range.intersect(renderRange, r));
J
Joao Moreno 已提交
439
		const beforeElement = this.getNextToLastElement(insertRanges);
J
Joao Moreno 已提交
440

441 442
		for (const range of insertRanges) {
			for (let i = range.start; i < range.end; i++) {
J
Joao Moreno 已提交
443
				this.insertItemInDOM(i, beforeElement);
J
Joao Moreno 已提交
444
			}
J
Joao Moreno 已提交
445
		}
446

447
		this.eventuallyUpdateScrollDimensions();
448

J
Joao Moreno 已提交
449
		if (this.supportDynamicHeights) {
I
isidor 已提交
450
			this._rerender(this.scrollTop, this.renderHeight);
J
Joao Moreno 已提交
451
		}
452 453 454 455

		return deleted.map(i => i.element);
	}

456
	private eventuallyUpdateScrollDimensions(): void {
J
Joao Moreno 已提交
457
		this._scrollHeight = this.contentHeight;
R
rebornix 已提交
458
		this.rowsContainer.style.height = `${this._scrollHeight}px`;
459

460 461
		if (!this.scrollableElementUpdateDisposable) {
			this.scrollableElementUpdateDisposable = DOM.scheduleAtNextAnimationFrame(() => {
462
				this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
463
				this.updateScrollWidth();
464
				this.scrollableElementUpdateDisposable = null;
465 466
			});
		}
J
Joao Moreno 已提交
467 468
	}

469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
	private eventuallyUpdateScrollWidth(): void {
		if (!this.horizontalScrolling) {
			return;
		}

		this.scrollableElementWidthDelayer.trigger(() => this.updateScrollWidth());
	}

	private updateScrollWidth(): void {
		if (!this.horizontalScrolling) {
			return;
		}

		if (this.items.length === 0) {
			this.scrollableElement.setScrollDimensions({ scrollWidth: 0 });
		}

		let scrollWidth = 0;

		for (const item of this.items) {
			if (typeof item.width !== 'undefined') {
				scrollWidth = Math.max(scrollWidth, item.width);
			}
		}

J
Joao Moreno 已提交
494
		this.scrollWidth = scrollWidth;
495 496 497
		this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth + 10 });
	}

J
Joao Moreno 已提交
498 499 500 501 502 503 504 505 506 507 508 509 510 511
	updateWidth(index: number): void {
		if (!this.horizontalScrolling || typeof this.scrollWidth === 'undefined') {
			return;
		}

		const item = this.items[index];
		this.measureItemWidth(item);

		if (typeof item.width !== 'undefined' && item.width > this.scrollWidth) {
			this.scrollWidth = item.width;
			this.scrollableElement.setScrollDimensions({ scrollWidth: this.scrollWidth + 10 });
		}
	}

I
isidor 已提交
512 513 514 515 516 517 518 519 520 521 522 523
	rerender(): void {
		if (!this.supportDynamicHeights) {
			return;
		}

		for (const item of this.items) {
			item.lastDynamicHeightWidth = undefined;
		}

		this._rerender(this.lastRenderTop, this.lastRenderHeight);
	}

J
Joao Moreno 已提交
524 525 526 527
	get length(): number {
		return this.items.length;
	}

J
Joao Moreno 已提交
528
	get renderHeight(): number {
529 530
		const scrollDimensions = this.scrollableElement.getScrollDimensions();
		return scrollDimensions.height;
J
Joao Moreno 已提交
531 532
	}

J
Joao Moreno 已提交
533 534
	get firstVisibleIndex(): number {
		const range = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
535 536 537 538 539 540 541 542 543
		const firstElTop = this.rangeMap.positionAt(range.start);
		const nextElTop = this.rangeMap.positionAt(range.start + 1);
		if (nextElTop !== -1) {
			const firstElMidpoint = (nextElTop - firstElTop) / 2 + firstElTop;
			if (firstElMidpoint < this.scrollTop) {
				return range.start + 1;
			}
		}

J
Joao Moreno 已提交
544 545 546 547 548 549 550 551
		return range.start;
	}

	get lastVisibleIndex(): number {
		const range = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
		return range.end - 1;
	}

J
Joao Moreno 已提交
552 553 554 555
	element(index: number): T {
		return this.items[index].element;
	}

J
Joao Moreno 已提交
556
	domElement(index: number): HTMLElement | null {
557 558 559 560
		const row = this.items[index].row;
		return row && row.domNode;
	}

J
Joao Moreno 已提交
561 562 563 564 565 566 567 568
	elementHeight(index: number): number {
		return this.items[index].size;
	}

	elementTop(index: number): number {
		return this.rangeMap.positionAt(index);
	}

J
Joao Moreno 已提交
569 570 571 572 573 574 575 576
	indexAt(position: number): number {
		return this.rangeMap.indexAt(position);
	}

	indexAfter(position: number): number {
		return this.rangeMap.indexAfter(position);
	}

577
	layout(height?: number, width?: number): void {
578
		let scrollDimensions: INewScrollDimensions = {
J
Joao Moreno 已提交
579
			height: typeof height === 'number' ? height : DOM.getContentHeight(this.domNode)
580 581 582 583 584
		};

		if (this.scrollableElementUpdateDisposable) {
			this.scrollableElementUpdateDisposable.dispose();
			this.scrollableElementUpdateDisposable = null;
585
			scrollDimensions.scrollHeight = this.scrollHeight;
586 587 588
		}

		this.scrollableElement.setScrollDimensions(scrollDimensions);
J
Joao Moreno 已提交
589

590 591
		if (typeof width !== 'undefined') {
			this.renderWidth = width;
J
Joao Moreno 已提交
592

593
			if (this.supportDynamicHeights) {
I
isidor 已提交
594
				this._rerender(this.scrollTop, this.renderHeight);
595 596 597 598
			}

			if (this.horizontalScrolling) {
				this.scrollableElement.setScrollDimensions({
J
Joao Moreno 已提交
599
					width: typeof width === 'number' ? width : DOM.getContentWidth(this.domNode)
600 601
				});
			}
J
Joao Moreno 已提交
602 603 604
		}
	}

J
Joao Moreno 已提交
605 606
	// Render

R
rebornix 已提交
607
	private render(previousRenderRange: IRange, renderTop: number, renderHeight: number, renderLeft: number | undefined, scrollWidth: number | undefined, updateItemsInDOM: boolean = false): void {
608
		const renderRange = this.getRenderRange(renderTop, renderHeight);
J
Joao Moreno 已提交
609

J
Joao Moreno 已提交
610 611
		const rangesToInsert = Range.relativeComplement(renderRange, previousRenderRange);
		const rangesToRemove = Range.relativeComplement(previousRenderRange, renderRange);
J
Joao Moreno 已提交
612
		const beforeElement = this.getNextToLastElement(rangesToInsert);
J
Joao Moreno 已提交
613

R
rebornix 已提交
614 615 616 617 618 619 620 621
		if (updateItemsInDOM) {
			const rangesToUpdate = Range.intersect(previousRenderRange, renderRange);

			for (let i = rangesToUpdate.start; i < rangesToUpdate.end; i++) {
				this.updateItemInDOM(this.items[i], i);
			}
		}

J
Joao Moreno 已提交
622 623
		for (const range of rangesToInsert) {
			for (let i = range.start; i < range.end; i++) {
J
Joao Moreno 已提交
624
				this.insertItemInDOM(i, beforeElement);
J
Joao Moreno 已提交
625 626 627 628 629
			}
		}

		for (const range of rangesToRemove) {
			for (let i = range.start; i < range.end; i++) {
J
Joao Moreno 已提交
630
				this.removeItemFromDOM(i);
J
Joao Moreno 已提交
631 632
			}
		}
J
Joao Moreno 已提交
633

R
rebornix 已提交
634 635 636 637
		if (renderLeft !== undefined) {
			this.rowsContainer.style.left = `-${renderLeft}px`;
		}

J
Joao Moreno 已提交
638
		this.rowsContainer.style.top = `-${renderTop}px`;
639

R
rebornix 已提交
640
		if (this.horizontalScrolling && scrollWidth !== undefined) {
641 642 643
			this.rowsContainer.style.width = `${Math.max(scrollWidth, this.renderWidth)}px`;
		}

644
		this.lastRenderTop = renderTop;
645
		this.lastRenderHeight = renderHeight;
J
Joao Moreno 已提交
646
	}
647

J
Joao Moreno 已提交
648
	// DOM operations
J
Joao Moreno 已提交
649

J
Joao Moreno 已提交
650 651 652
	private insertItemInDOM(index: number, beforeElement: HTMLElement | null): void {
		const item = this.items[index];

J
Joao Moreno 已提交
653 654
		if (!item.row) {
			item.row = this.cache.alloc(item.templateId);
655
			const role = this.accessibilityProvider.getRole(item.element) || 'listitem';
656
			item.row!.domNode!.setAttribute('role', role);
J
João Moreno 已提交
657
			const checked = this.accessibilityProvider.isChecked(item.element);
I
polish  
isidor 已提交
658
			if (typeof checked !== 'undefined') {
J
João Moreno 已提交
659
				item.row!.domNode!.setAttribute('aria-checked', String(!!checked));
660
			}
J
Joao Moreno 已提交
661 662
		}

J
Joao Moreno 已提交
663
		if (!item.row.domNode!.parentElement) {
J
Joao Moreno 已提交
664
			if (beforeElement) {
J
Joao Moreno 已提交
665
				this.rowsContainer.insertBefore(item.row.domNode!, beforeElement);
J
Joao Moreno 已提交
666
			} else {
J
Joao Moreno 已提交
667
				this.rowsContainer.appendChild(item.row.domNode!);
J
Joao Moreno 已提交
668
			}
J
Joao Moreno 已提交
669 670
		}

J
Joao Moreno 已提交
671 672 673
		this.updateItemInDOM(item, index);

		const renderer = this.renderers.get(item.templateId);
674 675 676 677 678

		if (!renderer) {
			throw new Error(`No renderer found for template id ${item.templateId}`);
		}

679
		if (renderer) {
680
			renderer.renderElement(item.element, index, item.row.templateData, item.size);
681
		}
J
Joao Moreno 已提交
682 683 684

		const uri = this.dnd.getDragURI(item.element);
		item.dragStartDisposable.dispose();
J
Joao Moreno 已提交
685
		item.row.domNode!.draggable = !!uri;
J
Joao Moreno 已提交
686 687

		if (uri) {
J
Joao Moreno 已提交
688 689
			const onDragStart = domEvent(item.row.domNode!, 'dragstart');
			item.dragStartDisposable = onDragStart(event => this.onDragStart(item.element, uri, event));
J
Joao Moreno 已提交
690
		}
691 692

		if (this.horizontalScrolling) {
J
Joao Moreno 已提交
693
			this.measureItemWidth(item);
694 695
			this.eventuallyUpdateScrollWidth();
		}
J
Joao Moreno 已提交
696 697
	}

J
Joao Moreno 已提交
698 699 700 701 702
	private measureItemWidth(item: IItem<T>): void {
		if (!item.row || !item.row.domNode) {
			return;
		}

703
		item.row.domNode.style.width = isFirefox ? '-moz-fit-content' : 'fit-content';
J
Joao Moreno 已提交
704 705 706 707 708 709 710 711 712 713 714 715 716 717
		item.width = DOM.getContentWidth(item.row.domNode);
		const style = window.getComputedStyle(item.row.domNode);

		if (style.paddingLeft) {
			item.width += parseFloat(style.paddingLeft);
		}

		if (style.paddingRight) {
			item.width += parseFloat(style.paddingRight);
		}

		item.row.domNode.style.width = '';
	}

J
Joao Moreno 已提交
718
	private updateItemInDOM(item: IItem<T>, index: number): void {
J
Joao Moreno 已提交
719
		item.row!.domNode!.style.top = `${this.elementTop(index)}px`;
R
rebornix 已提交
720

R
rebornix 已提交
721 722 723
		if (this.setRowHeight) {
			item.row!.domNode!.style.height = `${item.size}px`;
		}
J
Joao Moreno 已提交
724 725

		if (this.setRowLineHeight) {
J
Joao Moreno 已提交
726
			item.row!.domNode!.style.lineHeight = `${item.size}px`;
J
Joao Moreno 已提交
727 728
		}

J
Joao Moreno 已提交
729 730
		item.row!.domNode!.setAttribute('data-index', `${index}`);
		item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
J
João Moreno 已提交
731 732
		item.row!.domNode!.setAttribute('aria-setsize', String(this.accessibilityProvider.getSetSize(item.element, index, this.length)));
		item.row!.domNode!.setAttribute('aria-posinset', String(this.accessibilityProvider.getPosInSet(item.element, index)));
J
Joao Moreno 已提交
733
		item.row!.domNode!.setAttribute('id', this.getElementDomId(index));
J
Joao Moreno 已提交
734

J
Joao Moreno 已提交
735
		DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget);
J
Joao Moreno 已提交
736 737
	}

J
Joao Moreno 已提交
738 739
	private removeItemFromDOM(index: number): void {
		const item = this.items[index];
J
Joao Moreno 已提交
740 741
		item.dragStartDisposable.dispose();

742
		const renderer = this.renderers.get(item.templateId);
R
rebornix 已提交
743
		if (renderer && renderer.disposeElement) {
744
			renderer.disposeElement(item.element, index, item.row!.templateData, item.size);
745 746
		}

J
Joao Moreno 已提交
747
		this.cache.release(item.row!);
J
Joao Moreno 已提交
748
		item.row = null;
749 750 751 752

		if (this.horizontalScrolling) {
			this.eventuallyUpdateScrollWidth();
		}
J
Joao Moreno 已提交
753 754
	}

J
Joao Moreno 已提交
755
	getScrollTop(): number {
756 757
		const scrollPosition = this.scrollableElement.getScrollPosition();
		return scrollPosition.scrollTop;
J
Joao Moreno 已提交
758 759 760
	}

	setScrollTop(scrollTop: number): void {
761 762 763
		if (this.scrollableElementUpdateDisposable) {
			this.scrollableElementUpdateDisposable.dispose();
			this.scrollableElementUpdateDisposable = null;
764
			this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
765 766
		}

767
		this.scrollableElement.setScrollPosition({ scrollTop });
J
Joao Moreno 已提交
768 769
	}

I
isidor 已提交
770 771 772 773 774
	getScrollLeft(): number {
		const scrollPosition = this.scrollableElement.getScrollPosition();
		return scrollPosition.scrollLeft;
	}

J
Joao Moreno 已提交
775
	setScrollLeft(scrollLeft: number): void {
I
isidor 已提交
776 777 778 779 780 781 782 783 784 785
		if (this.scrollableElementUpdateDisposable) {
			this.scrollableElementUpdateDisposable.dispose();
			this.scrollableElementUpdateDisposable = null;
			this.scrollableElement.setScrollDimensions({ scrollWidth: this.scrollWidth });
		}

		this.scrollableElement.setScrollPosition({ scrollLeft });
	}


786 787 788 789 790 791 792 793
	get scrollTop(): number {
		return this.getScrollTop();
	}

	set scrollTop(scrollTop: number) {
		this.setScrollTop(scrollTop);
	}

J
Joao Moreno 已提交
794
	get scrollHeight(): number {
I
isidor 已提交
795
		return this._scrollHeight + (this.horizontalScrolling ? 10 : 0) + this.additionalScrollHeight;
J
Joao Moreno 已提交
796 797
	}

J
Joao Moreno 已提交
798 799
	// Events

J
Joao Moreno 已提交
800 801 802 803 804 805 806 807 808 809 810
	@memoize get onMouseClick(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'click'), e => this.toMouseEvent(e)); }
	@memoize get onMouseDblClick(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'dblclick'), e => this.toMouseEvent(e)); }
	@memoize get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return Event.filter(Event.map(domEvent(this.domNode, 'auxclick'), e => this.toMouseEvent(e as MouseEvent)), e => e.browserEvent.button === 1); }
	@memoize get onMouseUp(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseup'), e => this.toMouseEvent(e)); }
	@memoize get onMouseDown(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mousedown'), e => this.toMouseEvent(e)); }
	@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseover'), e => this.toMouseEvent(e)); }
	@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mousemove'), e => this.toMouseEvent(e)); }
	@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)); }
	@memoize get onContextMenu(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)); }
	@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return Event.map(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)); }
	@memoize get onTap(): Event<IListGestureEvent<T>> { return Event.map(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e)); }
J
Joao Moreno 已提交
811

J
Joao Moreno 已提交
812
	private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent<T> {
J
Joao Moreno 已提交
813
		const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
J
Joao Moreno 已提交
814
		const item = typeof index === 'undefined' ? undefined : this.items[index];
J
Joao Moreno 已提交
815
		const element = item && item.element;
J
Joao Moreno 已提交
816
		return { browserEvent, index, element };
817
	}
J
Joao Moreno 已提交
818

J
Joao Moreno 已提交
819
	private toTouchEvent(browserEvent: TouchEvent): IListTouchEvent<T> {
J
Joao Moreno 已提交
820
		const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
J
Joao Moreno 已提交
821
		const item = typeof index === 'undefined' ? undefined : this.items[index];
J
Joao Moreno 已提交
822
		const element = item && item.element;
J
Joao Moreno 已提交
823 824
		return { browserEvent, index, element };
	}
J
Joao Moreno 已提交
825

J
Joao Moreno 已提交
826
	private toGestureEvent(browserEvent: GestureEvent): IListGestureEvent<T> {
J
Joao Moreno 已提交
827
		const index = this.getItemIndexFromEventTarget(browserEvent.initialTarget || null);
J
Joao Moreno 已提交
828
		const item = typeof index === 'undefined' ? undefined : this.items[index];
J
Joao Moreno 已提交
829
		const element = item && item.element;
J
Joao Moreno 已提交
830
		return { browserEvent, index, element };
J
Joao Moreno 已提交
831 832
	}

J
Joao Moreno 已提交
833 834 835 836 837 838 839
	private toDragEvent(browserEvent: DragEvent): IListDragEvent<T> {
		const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
		const item = typeof index === 'undefined' ? undefined : this.items[index];
		const element = item && item.element;
		return { browserEvent, index, element };
	}

840
	private onScroll(e: ScrollEvent): void {
841
		try {
R
rebornix 已提交
842 843
			const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
			this.render(previousRenderRange, e.scrollTop, e.height, e.scrollLeft, e.scrollWidth);
J
Joao Moreno 已提交
844 845

			if (this.supportDynamicHeights) {
I
isidor 已提交
846
				this._rerender(e.scrollTop, e.height);
J
Joao Moreno 已提交
847
			}
848
		} catch (err) {
J
Joao Moreno 已提交
849
			console.error('Got bad scroll event:', e);
850 851
			throw err;
		}
852 853
	}

854
	private onTouchChange(event: GestureEvent): void {
855 856 857
		event.preventDefault();
		event.stopPropagation();

858
		this.scrollTop -= event.translationY;
859 860
	}

J
Joao Moreno 已提交
861
	// DND
862

J
Joao Moreno 已提交
863 864 865 866 867 868 869 870 871 872 873
	private onDragStart(element: T, uri: string, event: DragEvent): void {
		if (!event.dataTransfer) {
			return;
		}

		const elements = this.dnd.getDragElements(element);

		event.dataTransfer.effectAllowed = 'copyMove';
		event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([uri]));

		if (event.dataTransfer.setDragImage) {
J
Joao Moreno 已提交
874
			let label: string | undefined;
J
Joao Moreno 已提交
875 876

			if (this.dnd.getDragLabel) {
J
Joao Moreno 已提交
877
				label = this.dnd.getDragLabel(elements, event);
J
Joao Moreno 已提交
878 879 880
			}

			if (typeof label === 'undefined') {
J
Joao Moreno 已提交
881 882 883
				label = String(elements.length);
			}

J
Joao Moreno 已提交
884
			const dragImage = DOM.$('.monaco-drag-image');
J
Joao Moreno 已提交
885 886 887 888 889 890 891
			dragImage.textContent = label;
			document.body.appendChild(dragImage);
			event.dataTransfer.setDragImage(dragImage, -10, -10);
			setTimeout(() => document.body.removeChild(dragImage), 0);
		}

		this.currentDragData = new ElementsDragAndDropData(elements);
J
Joao Moreno 已提交
892
		StaticDND.CurrentDragAndDropData = new ExternalElementsDragAndDropData(elements);
J
Joao Moreno 已提交
893

J
Joao Moreno 已提交
894 895 896
		if (this.dnd.onDragStart) {
			this.dnd.onDragStart(this.currentDragData, event);
		}
J
Joao Moreno 已提交
897 898 899
	}

	private onDragOver(event: IListDragEvent<T>): boolean {
900 901
		event.browserEvent.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)

J
Joao Moreno 已提交
902
		this.onDragLeaveTimeout.dispose();
903 904 905 906 907

		if (StaticDND.CurrentDragAndDropData && StaticDND.CurrentDragAndDropData.getData() === 'vscode-ui') {
			return false;
		}

J
Joao Moreno 已提交
908 909 910 911 912 913 914 915
		this.setupDragAndDropScrollTopAnimation(event.browserEvent);

		if (!event.browserEvent.dataTransfer) {
			return false;
		}

		// Drag over from outside
		if (!this.currentDragData) {
J
Joao Moreno 已提交
916
			if (StaticDND.CurrentDragAndDropData) {
J
Joao Moreno 已提交
917
				// Drag over from another list
J
Joao Moreno 已提交
918
				this.currentDragData = StaticDND.CurrentDragAndDropData;
J
Joao Moreno 已提交
919 920 921 922 923 924 925 926 927 928 929 930

			} else {
				// Drag over from the desktop
				if (!event.browserEvent.dataTransfer.types) {
					return false;
				}

				this.currentDragData = new DesktopDragAndDropData();
			}
		}

		const result = this.dnd.onDragOver(this.currentDragData, event.element, event.index, event.browserEvent);
931
		this.canDrop = typeof result === 'boolean' ? result : result.accept;
J
Joao Moreno 已提交
932

933
		if (!this.canDrop) {
934 935
			this.currentDragFeedback = undefined;
			this.currentDragFeedbackDisposable.dispose();
J
Joao Moreno 已提交
936 937 938
			return false;
		}

939
		event.browserEvent.dataTransfer.dropEffect = (typeof result !== 'boolean' && result.effect === ListDragOverEffect.Copy) ? 'copy' : 'move';
J
Joao Moreno 已提交
940

J
Joao Moreno 已提交
941 942 943 944 945 946 947 948 949 950 951 952 953
		let feedback: number[];

		if (typeof result !== 'boolean' && result.feedback) {
			feedback = result.feedback;
		} else {
			if (typeof event.index === 'undefined') {
				feedback = [-1];
			} else {
				feedback = [event.index];
			}
		}

		// sanitize feedback list
J
Joao Moreno 已提交
954
		feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort((a, b) => a - b);
J
Joao Moreno 已提交
955 956 957 958 959 960 961 962 963 964 965
		feedback = feedback[0] === -1 ? [-1] : feedback;

		if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
			return true;
		}

		this.currentDragFeedback = feedback;
		this.currentDragFeedbackDisposable.dispose();

		if (feedback[0] === -1) { // entire list feedback
			DOM.addClass(this.domNode, 'drop-target');
J
Joao Moreno 已提交
966 967 968 969 970
			DOM.addClass(this.rowsContainer, 'drop-target');
			this.currentDragFeedbackDisposable = toDisposable(() => {
				DOM.removeClass(this.domNode, 'drop-target');
				DOM.removeClass(this.rowsContainer, 'drop-target');
			});
J
Joao Moreno 已提交
971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995
		} else {
			for (const index of feedback) {
				const item = this.items[index]!;
				item.dropTarget = true;

				if (item.row && item.row.domNode) {
					DOM.addClass(item.row.domNode, 'drop-target');
				}
			}

			this.currentDragFeedbackDisposable = toDisposable(() => {
				for (const index of feedback) {
					const item = this.items[index]!;
					item.dropTarget = false;

					if (item.row && item.row.domNode) {
						DOM.removeClass(item.row.domNode, 'drop-target');
					}
				}
			});
		}

		return true;
	}

J
Joao Moreno 已提交
996
	private onDragLeave(): void {
J
Joao Moreno 已提交
997
		this.onDragLeaveTimeout.dispose();
J
Joao Moreno 已提交
998
		this.onDragLeaveTimeout = disposableTimeout(() => this.clearDragOverFeedback(), 100);
J
Joao Moreno 已提交
999 1000
	}

J
Joao Moreno 已提交
1001
	private onDrop(event: IListDragEvent<T>): void {
1002 1003 1004 1005
		if (!this.canDrop) {
			return;
		}

J
Joao Moreno 已提交
1006
		const dragData = this.currentDragData;
J
Joao Moreno 已提交
1007 1008
		this.teardownDragAndDropScrollTopAnimation();
		this.clearDragOverFeedback();
J
Joao Moreno 已提交
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
		this.currentDragData = undefined;
		StaticDND.CurrentDragAndDropData = undefined;

		if (!dragData || !event.browserEvent.dataTransfer) {
			return;
		}

		event.browserEvent.preventDefault();
		dragData.update(event.browserEvent.dataTransfer);
		this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
J
Joao Moreno 已提交
1019 1020
	}

J
Joao Moreno 已提交
1021
	private onDragEnd(event: DragEvent): void {
1022
		this.canDrop = false;
J
Joao Moreno 已提交
1023 1024
		this.teardownDragAndDropScrollTopAnimation();
		this.clearDragOverFeedback();
J
Joao Moreno 已提交
1025 1026
		this.currentDragData = undefined;
		StaticDND.CurrentDragAndDropData = undefined;
J
Joao Moreno 已提交
1027 1028 1029 1030

		if (this.dnd.onDragEnd) {
			this.dnd.onDragEnd(event);
		}
J
Joao Moreno 已提交
1031 1032 1033 1034 1035
	}

	private clearDragOverFeedback(): void {
		this.currentDragFeedback = undefined;
		this.currentDragFeedbackDisposable.dispose();
J
Joao Moreno 已提交
1036
		this.currentDragFeedbackDisposable = Disposable.None;
J
Joao Moreno 已提交
1037 1038 1039 1040 1041
	}

	// DND scroll top animation

	private setupDragAndDropScrollTopAnimation(event: DragEvent): void {
J
Joao Moreno 已提交
1042 1043 1044 1045
		if (!this.dragOverAnimationDisposable) {
			const viewTop = DOM.getTopLeftOffset(this.domNode).top;
			this.dragOverAnimationDisposable = DOM.animate(this.animateDragAndDropScrollTop.bind(this, viewTop));
		}
1046

J
Joao Moreno 已提交
1047
		this.dragOverAnimationStopDisposable.dispose();
J
Joao Moreno 已提交
1048
		this.dragOverAnimationStopDisposable = disposableTimeout(() => {
J
Joao Moreno 已提交
1049 1050 1051 1052
			if (this.dragOverAnimationDisposable) {
				this.dragOverAnimationDisposable.dispose();
				this.dragOverAnimationDisposable = undefined;
			}
J
Joao Moreno 已提交
1053
		}, 1000);
1054

J
Joao Moreno 已提交
1055
		this.dragOverMouseY = event.pageY;
J
Joao Moreno 已提交
1056
	}
1057

J
Joao Moreno 已提交
1058 1059 1060 1061
	private animateDragAndDropScrollTop(viewTop: number): void {
		if (this.dragOverMouseY === undefined) {
			return;
		}
1062

J
Joao Moreno 已提交
1063 1064
		const diff = this.dragOverMouseY - viewTop;
		const upperLimit = this.renderHeight - 35;
1065

J
Joao Moreno 已提交
1066 1067 1068 1069
		if (diff < 35) {
			this.scrollTop += Math.max(-14, Math.floor(0.3 * (diff - 35)));
		} else if (diff > upperLimit) {
			this.scrollTop += Math.min(14, Math.floor(0.3 * (diff - upperLimit)));
1070 1071 1072
		}
	}

J
Joao Moreno 已提交
1073
	private teardownDragAndDropScrollTopAnimation(): void {
J
Joao Moreno 已提交
1074
		this.dragOverAnimationStopDisposable.dispose();
1075

J
Joao Moreno 已提交
1076 1077 1078
		if (this.dragOverAnimationDisposable) {
			this.dragOverAnimationDisposable.dispose();
			this.dragOverAnimationDisposable = undefined;
1079 1080 1081
		}
	}

1082 1083
	// Util

J
Joao Moreno 已提交
1084
	private getItemIndexFromEventTarget(target: EventTarget | null): number | undefined {
R
rebornix 已提交
1085
		const scrollableElement = this.scrollableElement.getDomNode();
J
Joao Moreno 已提交
1086 1087
		let element: HTMLElement | null = target as (HTMLElement | null);

R
rebornix 已提交
1088
		while (element instanceof HTMLElement && element !== this.rowsContainer && scrollableElement.contains(element)) {
J
Joao Moreno 已提交
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
			const rawIndex = element.getAttribute('data-index');

			if (rawIndex) {
				const index = Number(rawIndex);

				if (!isNaN(index)) {
					return index;
				}
			}

J
Joao Moreno 已提交
1099
			element = element.parentElement;
J
Joao Moreno 已提交
1100 1101
		}

J
Joao Moreno 已提交
1102
		return undefined;
J
Joao Moreno 已提交
1103 1104
	}

1105 1106 1107 1108 1109 1110 1111
	private getRenderRange(renderTop: number, renderHeight: number): IRange {
		return {
			start: this.rangeMap.indexAt(renderTop),
			end: this.rangeMap.indexAfter(renderTop + renderHeight - 1)
		};
	}

1112 1113 1114 1115
	/**
	 * 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.
	 */
I
isidor 已提交
1116
	private _rerender(renderTop: number, renderHeight: number): void {
1117
		const previousRenderRange = this.getRenderRange(renderTop, renderHeight);
J
Joao Moreno 已提交
1118 1119 1120

		// Let's remember the second element's position, this helps in scrolling up
		// and preserving a linear upwards scroll movement
J
Joao Moreno 已提交
1121 1122 1123 1124 1125 1126 1127 1128 1129
		let anchorElementIndex: number | undefined;
		let anchorElementTopDelta: number | undefined;

		if (renderTop === this.elementTop(previousRenderRange.start)) {
			anchorElementIndex = previousRenderRange.start;
			anchorElementTopDelta = 0;
		} else if (previousRenderRange.end - previousRenderRange.start > 1) {
			anchorElementIndex = previousRenderRange.start + 1;
			anchorElementTopDelta = this.elementTop(anchorElementIndex) - renderTop;
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
		}

		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);
J
Joao Moreno 已提交
1141 1142

				if (diff !== 0) {
1143
					this.rangeMap.splice(i, 1, [this.items[i]]);
J
Joao Moreno 已提交
1144 1145
				}

1146 1147 1148 1149 1150 1151
				heightDiff += diff;
				didChange = didChange || diff !== 0;
			}

			if (!didChange) {
				if (heightDiff !== 0) {
1152
					this.eventuallyUpdateScrollDimensions();
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164
				}

				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);
						}
					}
				}

J
Joao Moreno 已提交
1165 1166 1167 1168
				const renderRanges = Range.relativeComplement(renderRange, previousRenderRange);

				for (const range of renderRanges) {
					for (let i = range.start; i < range.end; i++) {
J
Joao Moreno 已提交
1169 1170
						const afterIndex = i + 1;
						const beforeRow = afterIndex < this.items.length ? this.items[afterIndex].row : null;
J
Joao Moreno 已提交
1171 1172 1173 1174 1175
						const beforeElement = beforeRow ? beforeRow.domNode : null;
						this.insertItemInDOM(i, beforeElement);
					}
				}

1176 1177 1178 1179 1180 1181
				for (let i = renderRange.start; i < renderRange.end; i++) {
					if (this.items[i].row) {
						this.updateItemInDOM(this.items[i], i);
					}
				}

J
Joao Moreno 已提交
1182 1183
				if (typeof anchorElementIndex === 'number') {
					this.scrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta!;
1184 1185
				}

J
Joao Moreno 已提交
1186
				this._onDidChangeContentHeight.fire(this.contentHeight);
1187 1188 1189 1190 1191 1192 1193 1194
				return;
			}
		}
	}

	private probeDynamicHeight(index: number): number {
		const item = this.items[index];

1195 1196
		if (!item.hasDynamicHeight || item.lastDynamicHeightWidth === this.renderWidth) {
			return 0;
1197 1198
		}

R
rebornix 已提交
1199 1200 1201 1202
		if (!!this.virtualDelegate.hasDynamicHeight && !this.virtualDelegate.hasDynamicHeight(item.element)) {
			return 0;
		}

1203
		const size = item.size;
1204

1205
		if (!this.setRowHeight && item.row && item.row.domNode) {
1206 1207
			let newSize = item.row.domNode.offsetHeight;
			item.size = newSize;
1208
			item.lastDynamicHeightWidth = this.renderWidth;
1209 1210 1211
			return newSize - size;
		}

J
Joao Moreno 已提交
1212
		const row = this.cache.alloc(item.templateId);
1213

J
Joao Moreno 已提交
1214 1215
		row.domNode!.style.height = '';
		this.rowsContainer.appendChild(row.domNode!);
J
Joao Moreno 已提交
1216 1217

		const renderer = this.renderers.get(item.templateId);
1218
		if (renderer) {
1219
			renderer.renderElement(item.element, index, row.templateData, undefined);
J
Joao Moreno 已提交
1220 1221

			if (renderer.disposeElement) {
1222
				renderer.disposeElement(item.element, index, row.templateData, undefined);
J
Joao Moreno 已提交
1223
			}
1224
		}
J
Joao Moreno 已提交
1225

J
Joao Moreno 已提交
1226
		item.size = row.domNode!.offsetHeight;
J
Joao Moreno 已提交
1227 1228 1229 1230 1231

		if (this.virtualDelegate.setDynamicHeight) {
			this.virtualDelegate.setDynamicHeight(item.element, item.size);
		}

1232
		item.lastDynamicHeightWidth = this.renderWidth;
J
Joao Moreno 已提交
1233
		this.rowsContainer.removeChild(row.domNode!);
J
Joao Moreno 已提交
1234
		this.cache.release(row);
1235 1236 1237 1238

		return item.size - size;
	}

J
Joao Moreno 已提交
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258
	private getNextToLastElement(ranges: IRange[]): HTMLElement | null {
		const lastRange = ranges[ranges.length - 1];

		if (!lastRange) {
			return null;
		}

		const nextToLastItem = this.items[lastRange.end];

		if (!nextToLastItem) {
			return null;
		}

		if (!nextToLastItem.row) {
			return null;
		}

		return nextToLastItem.row.domNode;
	}

J
Joao Moreno 已提交
1259 1260 1261 1262
	getElementDomId(index: number): string {
		return `${this.domId}_${index}`;
	}

J
Joao Moreno 已提交
1263 1264
	// Dispose

J
Joao Moreno 已提交
1265
	dispose() {
1266 1267 1268 1269
		if (this.items) {
			for (const item of this.items) {
				if (item.row) {
					const renderer = this.renderers.get(item.row.templateId);
1270 1271 1272
					if (renderer) {
						renderer.disposeTemplate(item.row.templateData);
					}
1273 1274 1275
				}
			}

J
Joao Moreno 已提交
1276
			this.items = [];
1277
		}
J
Joao Moreno 已提交
1278

1279
		if (this.domNode && this.domNode.parentNode) {
1280
			this.domNode.parentNode.removeChild(this.domNode);
J
Joao Moreno 已提交
1281 1282
		}

1283
		dispose(this.disposables);
J
Joao Moreno 已提交
1284 1285
	}
}