Skip to content

Commit 7a29d89

Browse files
committed
optimize line numbers update to avoid unnecessary DOM operations
1 parent b8233d1 commit 7a29d89

File tree

1 file changed

+67
-23
lines changed

1 file changed

+67
-23
lines changed

src/extensions/markdown/CodeBlock/CodeBlockHighlight/CodeBlockHighlight.ts

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,12 @@ export const CodeBlockHighlight: ExtensionAuto<CodeBlockHighlightOptions> = (bui
176176
const contentDOM = document.createElement('div');
177177

178178
let lineNumbersContainer: HTMLDivElement | undefined;
179+
let prevLineCount = 0;
179180

180181
if (opts.lineNumbers?.enabled) {
181-
lineNumbersContainer = initializeLineNumbers(node, code);
182+
const result = initializeLineNumbers(node, code);
183+
lineNumbersContainer = result.container;
184+
prevLineCount = result.lineCount;
182185
}
183186

184187
code.append(contentDOM);
@@ -207,11 +210,14 @@ export const CodeBlockHighlight: ExtensionAuto<CodeBlockHighlightOptions> = (bui
207210
);
208211

209212
if (opts.lineNumbers?.enabled) {
210-
lineNumbersContainer = updateLineNumbers(
213+
const result = updateLineNumbers(
211214
newNode,
212215
code,
213216
lineNumbersContainer,
217+
prevLineCount,
214218
);
219+
lineNumbersContainer = result.container;
220+
prevLineCount = result.lineCount;
215221
}
216222

217223
return true;
@@ -290,64 +296,102 @@ function updateDomAttribute(elem: Element, attr: string, value: string | null |
290296
elem.removeAttribute(attr);
291297
}
292298
}
293-
function initializeLineNumbers(node: Node, code: HTMLElement) {
299+
function initializeLineNumbers(
300+
node: Node,
301+
code: HTMLElement,
302+
): {container?: HTMLDivElement; lineCount: number} {
294303
const showLineNumbers = node.attrs[CodeBlockNodeAttr.ShowLineNumbers];
295304

296305
if (!showLineNumbers) {
297-
return undefined;
306+
return {container: undefined, lineCount: 0};
298307
}
299308

300309
const lineNumbersContainer = document.createElement('div');
301310
lineNumbersContainer.className = 'yfm-line-numbers';
302311
lineNumbersContainer.contentEditable = 'false';
303312

304-
const lineNumbersContent = createLineNumbersContent(node.textContent);
305-
lineNumbersContainer.innerHTML = lineNumbersContent;
313+
const lines = node.textContent ? node.textContent.split('\n') : [''];
314+
const lineCount = lines.length;
315+
316+
appendLineNumbers(lineNumbersContainer, 1, lineCount);
306317

307318
code.prepend(lineNumbersContainer);
308319
code.classList.add('show-line-numbers');
309320

310-
return lineNumbersContainer;
321+
return {container: lineNumbersContainer, lineCount};
311322
}
312323

313324
function updateLineNumbers(
314325
node: Node,
315326
code: HTMLElement,
316327
prevLineNumbersContainer?: HTMLDivElement,
317-
) {
328+
prevLineCount = 0,
329+
): {container?: HTMLDivElement; lineCount: number} {
318330
const showLineNumbers = node.attrs[CodeBlockNodeAttr.ShowLineNumbers];
319331

320332
if (!prevLineNumbersContainer && showLineNumbers !== 'true') {
321-
return undefined;
333+
return {container: undefined, lineCount: 0};
322334
} else if (!prevLineNumbersContainer && showLineNumbers === 'true') {
323335
return initializeLineNumbers(node, code);
324336
} else if (prevLineNumbersContainer && showLineNumbers !== 'true') {
325337
code.removeChild(prevLineNumbersContainer);
326338
code.classList.remove('show-line-numbers');
327-
return undefined;
339+
return {container: undefined, lineCount: 0};
328340
}
329341

330342
if (!prevLineNumbersContainer) {
331-
return prevLineNumbersContainer;
343+
return {container: prevLineNumbersContainer, lineCount: prevLineCount};
332344
}
333345

334-
const lineNumbersContent = createLineNumbersContent(node.textContent);
335-
prevLineNumbersContainer.innerHTML = lineNumbersContent;
346+
const lines = node.textContent ? node.textContent.split('\n') : [''];
347+
const currentLineCount = lines.length;
348+
336349
code.classList.add('show-line-numbers');
337350

338-
return prevLineNumbersContainer;
351+
if (currentLineCount === prevLineCount) {
352+
return {container: prevLineNumbersContainer, lineCount: prevLineCount};
353+
}
354+
355+
if (currentLineCount > prevLineCount) {
356+
appendLineNumbers(prevLineNumbersContainer, prevLineCount + 1, currentLineCount);
357+
} else if (currentLineCount < prevLineCount) {
358+
removeExcessLineNumbers(prevLineNumbersContainer, currentLineCount, prevLineCount);
359+
}
360+
361+
return {container: prevLineNumbersContainer, lineCount: currentLineCount};
339362
}
340363

341-
function createLineNumbersContent(content: string) {
342-
const lines = content ? content.split('\n') : [''];
343-
const lineCount = lines.length;
344-
const maxDigits = String(lineCount).length;
364+
function appendLineNumbers(container: HTMLDivElement, startLine: number, endLine: number) {
365+
const maxDigits = String(endLine).length;
345366

346-
let lineNumbersHtml = '';
347-
for (let i = 1; i <= lineCount; i++) {
348-
const num = String(i).padStart(maxDigits, ' ');
349-
lineNumbersHtml += `<div class="yfm-line-number">${num}</div>`;
367+
for (let i = startLine; i <= endLine; i++) {
368+
const lineNumberElement = document.createElement('div');
369+
lineNumberElement.className = 'yfm-line-number';
370+
lineNumberElement.textContent = String(i).padStart(maxDigits, ' ');
371+
container.appendChild(lineNumberElement);
350372
}
351373

352-
return lineNumbersHtml;
374+
// Update padding on all line numbers if digit count changed
375+
if (startLine === 1) {
376+
updateLineNumberPadding(container, maxDigits);
377+
}
378+
}
379+
380+
function removeExcessLineNumbers(container: HTMLDivElement, keepCount: number, prevCount: number) {
381+
for (let i = prevCount; i > keepCount; i--) {
382+
if (container.lastChild) {
383+
container.removeChild(container.lastChild);
384+
}
385+
}
386+
387+
// Update padding on remaining line numbers if digit count changed
388+
const maxDigits = String(keepCount).length;
389+
updateLineNumberPadding(container, maxDigits);
390+
}
391+
392+
function updateLineNumberPadding(container: HTMLDivElement, maxDigits: number) {
393+
Array.from(container.children).forEach((lineNumber, index) => {
394+
const lineNum = index + 1;
395+
lineNumber.textContent = String(lineNum).padStart(maxDigits, ' ');
396+
});
353397
}

0 commit comments

Comments
 (0)