Skip to content

Commit 8c325c0

Browse files
committed
fix: cache find regex and clean up highlights on table destroy
Cache the compiled RegExp on the Find instance so _applyHighlights() (called on every scroll RAF) avoids repeated string escaping and RegExp construction. Clear stale highlight ranges on tableDestroyed to prevent memory leaks when table instances are torn down.
1 parent 9a1d9a1 commit 8c325c0

1 file changed

Lines changed: 25 additions & 11 deletions

File tree

  • log-viewer/src/tabulator/module

log-viewer/src/tabulator/module/Find.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class Find extends Module {
2424
_myFindRanges: Range[] = [];
2525
_myCurrentRanges: Range[] = [];
2626
_findArgs: FindArgs | null = null;
27+
_cachedRegex: RegExp | null = null;
2728
_currentMatchIndex = 0;
2829
_matchIndexes: { [key: number]: RowComponent } = {};
2930

@@ -55,6 +56,10 @@ export class Find extends Module {
5556
this._cellTextCache = new WeakMap();
5657
});
5758

59+
this.table.on('tableDestroyed', () => {
60+
this._clearFindHighlights();
61+
});
62+
5863
this.table.on('renderComplete', () => {
5964
if (this._findArgs?.text) {
6065
this._applyHighlights();
@@ -118,10 +123,7 @@ export class Find extends Module {
118123
const grps = tbl.getGroups().flatMap(flattenFromGrps);
119124
const flattenedRows: RowComponent[] = grps.length ? grps : this._getRows(tbl.getRows('active'));
120125

121-
const findOptions = findArgs.options;
122-
let searchString = findOptions.matchCase ? findArgs.text : findArgs.text.toLowerCase();
123-
searchString = searchString.replaceAll(/[[\]*+?{}.()^$|\\-]/g, '\\$&');
124-
const regex = new RegExp(searchString, `g${findArgs.options.matchCase ? '' : 'i'}`);
126+
const regex = this._buildRegex(findArgs);
125127

126128
// Reset highlightIndexes on all rows (no reformat needed with CSS Highlight API)
127129
for (const row of flattenedRows) {
@@ -134,7 +136,7 @@ export class Find extends Module {
134136
}
135137

136138
let totalMatches = 0;
137-
if (searchString) {
139+
if (regex) {
138140
// Avoid row.getCells() — for uninitialized off-screen rows it calls generateCells()
139141
// which creates a DOM element per cell (document.createElement). For 10k rows that
140142
// is O(rows × cols) element creation before a single search character is matched.
@@ -249,12 +251,12 @@ export class Find extends Module {
249251
return;
250252
}
251253

252-
const findOptions = this._findArgs.options;
253-
let searchString = findOptions.matchCase
254-
? this._findArgs.text
255-
: this._findArgs.text.toLowerCase();
256-
searchString = searchString.replaceAll(/[[\]*+?{}.()^$|\\-]/g, '\\$&');
257-
const regex = new RegExp(searchString, `g${findOptions.matchCase ? '' : 'i'}`);
254+
const regex = this._cachedRegex;
255+
if (!regex) {
256+
CSS.highlights.set('find-match', Find._findHighlight);
257+
CSS.highlights.set('current-find-match', Find._currentHighlight);
258+
return;
259+
}
258260

259261
const rows = this._getRenderedRows();
260262
for (const row of rows) {
@@ -305,10 +307,22 @@ export class Find extends Module {
305307
_clearFindHighlights() {
306308
this._clearInstanceRanges();
307309
this._findArgs = null;
310+
this._cachedRegex = null;
308311
this._currentMatchIndex = 0;
309312
this._matchIndexes = {};
310313
}
311314

315+
_buildRegex(findArgs: FindArgs): RegExp | null {
316+
if (!findArgs.text) {
317+
this._cachedRegex = null;
318+
return null;
319+
}
320+
let searchString = findArgs.options.matchCase ? findArgs.text : findArgs.text.toLowerCase();
321+
searchString = searchString.replaceAll(/[[\]*+?{}.()^$|\\-]/g, '\\$&');
322+
this._cachedRegex = new RegExp(searchString, `g${findArgs.options.matchCase ? '' : 'i'}`);
323+
return this._cachedRegex;
324+
}
325+
312326
_clearInstanceRanges() {
313327
for (const range of this._myFindRanges) {
314328
Find._findHighlight?.delete(range);

0 commit comments

Comments
 (0)