perf(editor): build the closing-fence pattern once per fence, not once per line (#18629)

markdownFenceRanges recompiled the closing-fence RegExp for every line inside an
open code fence, though it only depends on the fence's own marker and length.
A 100k-line fenced block went from 16.6ms to 5.8ms.
This commit is contained in:
Neil
2026-09-04 14:32:37 -07:00
committed by GitHub
parent fb99bcc3a4
commit 9130c6ee6c
@@ -94,7 +94,7 @@ export function renderDetailsAttributes(attrs: Record<string, unknown> | undefin
function markdownFenceRanges(content: string): MarkdownFenceRanges {
const ranges: [number, number][] = []
let offset = 0
let openFence: { marker: '`' | '~'; length: number; start: number } | null = null
let openFence: { closingPattern: RegExp; start: number } | null = null
for (const lineMatch of content.matchAll(/[^\r\n]*(?:\r\n|\n|\r|$)/g)) {
const line = lineMatch[0]
@@ -104,11 +104,8 @@ function markdownFenceRanges(content: string): MarkdownFenceRanges {
const lineText = line.replace(/(?:\r\n|\n|\r)$/u, '')
if (openFence) {
const closingFencePattern =
openFence.marker === '`'
? new RegExp(`^ {0,3}\`{${openFence.length},}\\s*$`)
: new RegExp(`^ {0,3}~{${openFence.length},}\\s*$`)
if (closingFencePattern.test(lineText)) {
// Built once per fence: rebuilding it per line recompiled the same regex for every fenced line.
if (openFence.closingPattern.test(lineText)) {
ranges.push([openFence.start, offset + line.length])
openFence = null
}
@@ -116,8 +113,9 @@ function markdownFenceRanges(content: string): MarkdownFenceRanges {
const openingFenceMatch = lineText.match(/^ {0,3}(`{3,}|~{3,})/u)
if (openingFenceMatch?.[1]) {
openFence = {
marker: openingFenceMatch[1][0] as '`' | '~',
length: openingFenceMatch[1].length,
closingPattern: new RegExp(
`^ {0,3}${openingFenceMatch[1][0]}{${openingFenceMatch[1].length},}\\s*$`
),
start: offset
}
}