Path: blob/main/src/vs/base/browser/ui/list/rowCache.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { $ } from '../../dom.js';6import { IDisposable } from '../../../common/lifecycle.js';7import { IListRenderer } from './list.js';89export interface IRow {10domNode: HTMLElement;11templateId: string;12templateData: any;13}1415export class RowCache<T> implements IDisposable {1617private cache = new Map<string, IRow[]>();1819private readonly transactionNodesPendingRemoval = new Set<HTMLElement>();20private inTransaction = false;2122constructor(private renderers: Map<string, IListRenderer<T, any>>) { }2324/**25* Returns a row either by creating a new one or reusing26* a previously released row which shares the same templateId.27*28* @returns A row and `isReusingConnectedDomNode` if the row's node is already in the dom in a stale position.29*/30alloc(templateId: string): { row: IRow; isReusingConnectedDomNode: boolean } {31let result = this.getTemplateCache(templateId).pop();3233let isStale = false;34if (result) {35isStale = this.transactionNodesPendingRemoval.has(result.domNode);36if (isStale) {37this.transactionNodesPendingRemoval.delete(result.domNode);38}39} else {40const domNode = $('.monaco-list-row');41const renderer = this.getRenderer(templateId);42const templateData = renderer.renderTemplate(domNode);43result = { domNode, templateId, templateData };44}4546return { row: result, isReusingConnectedDomNode: isStale };47}4849/**50* Releases the row for eventual reuse.51*/52release(row: IRow): void {53if (!row) {54return;55}5657this.releaseRow(row);58}5960/**61* Begin a set of changes that use the cache. This lets us skip work when a row is removed and then inserted again.62*/63transact(makeChanges: () => void) {64if (this.inTransaction) {65throw new Error('Already in transaction');66}6768this.inTransaction = true;6970try {71makeChanges();72} finally {73for (const domNode of this.transactionNodesPendingRemoval) {74this.doRemoveNode(domNode);75}7677this.transactionNodesPendingRemoval.clear();78this.inTransaction = false;79}80}8182private releaseRow(row: IRow): void {83const { domNode, templateId } = row;84if (domNode) {85if (this.inTransaction) {86this.transactionNodesPendingRemoval.add(domNode);87} else {88this.doRemoveNode(domNode);89}90}9192const cache = this.getTemplateCache(templateId);93cache.push(row);94}9596private doRemoveNode(domNode: HTMLElement) {97domNode.classList.remove('scrolling');98domNode.remove();99}100101private getTemplateCache(templateId: string): IRow[] {102let result = this.cache.get(templateId);103104if (!result) {105result = [];106this.cache.set(templateId, result);107}108109return result;110}111112dispose(): void {113this.cache.forEach((cachedRows, templateId) => {114for (const cachedRow of cachedRows) {115const renderer = this.getRenderer(templateId);116renderer.disposeTemplate(cachedRow.templateData);117cachedRow.templateData = null;118}119});120121this.cache.clear();122this.transactionNodesPendingRemoval.clear();123}124125private getRenderer(templateId: string): IListRenderer<T, any> {126const renderer = this.renderers.get(templateId);127if (!renderer) {128throw new Error(`No renderer found for ${templateId}`);129}130return renderer;131}132}133134135