UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

138 lines (135 loc) 4.72 kB
/** * A frame's precomputed plain-text fallback together with the inclusive * 1-based source line range it covers. * * `nodes` is the `frame.data.fallback` array — usually a single text node * whose concatenated value equals the frame's rendered plain text (with a * trailing newline on every line except the final line of the source). */ /** * A new frame's target line range (inclusive, 1-based). */ /** * Redistributes existing per-frame fallback node arrays onto a new set of * frame line ranges, shifting only the lines that cross a frame boundary. * * Enhancers never change the plain-text output of the code (they only add or * modify highlighting spans), so when a frame's line range is unchanged its * fallback is reused by reference without scanning. Only the boundary frames — * where lines move into an adjacent frame — are split, and the split walks just * the lines that need to move (counting newlines with an early exit) rather * than splitting the whole frame or re-deriving text via `toText`. * * Lines that fall in a gap between ranges (collapsed / dropped lines) have * their fallback text discarded. * * `oldFrames` must be ordered ascending and non-overlapping (gaps are allowed, * e.g. when a previous restructure already dropped lines); `ranges` must be * ordered ascending. Returns one node array per range, in range order. */ export function redistributeFrameFallbacks(oldFrames, ranges) { const result = []; // Index of the first old frame that might overlap the current range. Old // frames are ascending, ranges are ascending, so this only moves forward. let frameIndex = 0; for (const range of ranges) { // Skip old frames that end before this range begins. while (frameIndex < oldFrames.length && oldFrames[frameIndex].endLine < range.startLine) { frameIndex += 1; } const out = []; let cursor = frameIndex; while (cursor < oldFrames.length && oldFrames[cursor].startLine <= range.endLine) { const frame = oldFrames[cursor]; const overlapStart = Math.max(range.startLine, frame.startLine); const overlapEnd = Math.min(range.endLine, frame.endLine); if (overlapStart === frame.startLine && overlapEnd === frame.endLine) { // Whole frame falls inside this range — reuse its nodes without scanning. for (const node of frame.nodes) { out.push(node); } } else { // Partial frame — take only the lines that belong to this range. const skipLines = overlapStart - frame.startLine; const takeLines = overlapEnd - overlapStart + 1; for (const node of sliceFrameLines(frame.nodes, skipLines, takeLines)) { out.push(node); } } cursor += 1; } result.push(out); } return result; } /** * Extracts `takeLines` lines starting after `skipLines` lines from a frame's * fallback node array. Lines are delimited by `\n`; a line includes its * trailing newline. The scan exits as soon as `takeLines` newlines have been * collected, so it never walks past the lines it needs. * * Text nodes are split at line boundaries; any non-text node encountered while * collecting is passed through whole (it contributes no newline). */ function sliceFrameLines(nodes, skipLines, takeLines) { const out = []; let buffer = ''; const flush = () => { if (buffer) { out.push({ type: 'text', value: buffer }); buffer = ''; } }; let collecting = skipLines === 0; let skipped = 0; let taken = 0; for (const node of nodes) { if (node.type !== 'text') { if (collecting) { flush(); out.push(node); } continue; } const { value } = node; let pos = 0; while (pos < value.length) { if (!collecting) { const newlineIndex = value.indexOf('\n', pos); if (newlineIndex === -1) { // No newline left in this node; the remainder belongs to the line // still being skipped. pos = value.length; break; } skipped += 1; pos = newlineIndex + 1; if (skipped === skipLines) { collecting = true; } continue; } const newlineIndex = value.indexOf('\n', pos); if (newlineIndex === -1) { // Final line of the source (no trailing newline) — take the remainder. buffer += value.slice(pos); pos = value.length; break; } buffer += value.slice(pos, newlineIndex + 1); taken += 1; pos = newlineIndex + 1; if (taken === takeLines) { flush(); return out; } } } flush(); return out; }