Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/list/rowCache.ts
5222 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { $ } from '../../dom.js';
7
import { IDisposable } from '../../../common/lifecycle.js';
8
import { IListRenderer } from './list.js';
9
10
export interface IRow {
11
domNode: HTMLElement;
12
templateId: string;
13
templateData: any;
14
}
15
16
export class RowCache<T> implements IDisposable {
17
18
private cache = new Map<string, IRow[]>();
19
20
private readonly transactionNodesPendingRemoval = new Set<HTMLElement>();
21
private inTransaction = false;
22
23
constructor(private renderers: Map<string, IListRenderer<T, any>>) { }
24
25
/**
26
* Returns a row either by creating a new one or reusing
27
* a previously released row which shares the same templateId.
28
*
29
* @returns A row and `isReusingConnectedDomNode` if the row's node is already in the dom in a stale position.
30
*/
31
alloc(templateId: string): { row: IRow; isReusingConnectedDomNode: boolean } {
32
let result = this.getTemplateCache(templateId).pop();
33
34
let isStale = false;
35
if (result) {
36
isStale = this.transactionNodesPendingRemoval.delete(result.domNode);
37
} else {
38
const domNode = $('.monaco-list-row');
39
const renderer = this.getRenderer(templateId);
40
const templateData = renderer.renderTemplate(domNode);
41
result = { domNode, templateId, templateData };
42
}
43
44
return { row: result, isReusingConnectedDomNode: isStale };
45
}
46
47
/**
48
* Releases the row for eventual reuse.
49
*/
50
release(row: IRow): void {
51
if (!row) {
52
return;
53
}
54
55
this.releaseRow(row);
56
}
57
58
/**
59
* Begin a set of changes that use the cache. This lets us skip work when a row is removed and then inserted again.
60
*/
61
transact(makeChanges: () => void) {
62
if (this.inTransaction) {
63
throw new Error('Already in transaction');
64
}
65
66
this.inTransaction = true;
67
68
try {
69
makeChanges();
70
} finally {
71
for (const domNode of this.transactionNodesPendingRemoval) {
72
this.doRemoveNode(domNode);
73
}
74
75
this.transactionNodesPendingRemoval.clear();
76
this.inTransaction = false;
77
}
78
}
79
80
private releaseRow(row: IRow): void {
81
const { domNode, templateId } = row;
82
if (domNode) {
83
if (this.inTransaction) {
84
this.transactionNodesPendingRemoval.add(domNode);
85
} else {
86
this.doRemoveNode(domNode);
87
}
88
}
89
90
const cache = this.getTemplateCache(templateId);
91
cache.push(row);
92
}
93
94
private doRemoveNode(domNode: HTMLElement) {
95
domNode.classList.remove('scrolling');
96
domNode.remove();
97
}
98
99
private getTemplateCache(templateId: string): IRow[] {
100
let result = this.cache.get(templateId);
101
102
if (!result) {
103
result = [];
104
this.cache.set(templateId, result);
105
}
106
107
return result;
108
}
109
110
dispose(): void {
111
this.cache.forEach((cachedRows, templateId) => {
112
for (const cachedRow of cachedRows) {
113
const renderer = this.getRenderer(templateId);
114
renderer.disposeTemplate(cachedRow.templateData);
115
cachedRow.templateData = null;
116
}
117
});
118
119
this.cache.clear();
120
this.transactionNodesPendingRemoval.clear();
121
}
122
123
private getRenderer(templateId: string): IListRenderer<T, any> {
124
const renderer = this.renderers.get(templateId);
125
if (!renderer) {
126
throw new Error(`No renderer found for ${templateId}`);
127
}
128
return renderer;
129
}
130
}
131
132