Fix #167 r2: measure-based gutter rows + fix undefined tops ref in renumber
QA of the #167 follow-up found two defects: 1) renumber() referenced an undefined 'tops' variable in the rAF re-verification pass, so any wrap toggle that reflowed a partially filled last visual row left stale (non-idempotent) numbering. 2) rows were derived from per-span heights while the code column also renders newline text nodes between .codeline blocks, so the gutter could drift from the code column's real visual rows. renumber() now measures each .codeline's actual offsetTop and places every number on the visual row its line starts on (a line wrapping to N rows gets its number on the first of them), pads filler rows in between, and self-corrects one frame later if reflow moves any line. Wrap-OFF path stays unchanged ('1 2 3 4'). Verified in headless Chromium: gutter/code row alignment exact at 1200px and 640px, toggle cycles stable, no horizontal scroll, no CSP console errors; go build/test pass.
This commit is contained in:
@@ -28,9 +28,11 @@
|
||||
body.innerHTML = out.join('');
|
||||
}
|
||||
|
||||
// One .gutline block per visual row. Numbers are placed at the gutter row
|
||||
// whose top matches their .codeline's top; filler rows pad the gaps so
|
||||
// alignment is driven by measured geometry, not by uniform row counts.
|
||||
// One .gutline block per visual row. The gutter must reproduce the code
|
||||
// column's REAL rendered rows: each number goes on the visual row whose top
|
||||
// matches its .codeline's top (a line wrapping to N rows gets its number on
|
||||
// the FIRST of those rows), and filler rows pad the gaps. Geometry is
|
||||
// measured, not derived from span counts or heights.
|
||||
function renumber() {
|
||||
var lines = body.querySelectorAll('.codeline');
|
||||
if (!wrapOn() || !lines.length) {
|
||||
@@ -42,11 +44,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Measure each logical line's geometry. Reading all rects first avoids
|
||||
// interleaved layout reads/writes. A line's number goes on the visual row
|
||||
// whose top matches the line's top; the gutter is sized to cover the
|
||||
// deepest row any line STARTS on (a trailing empty logical line renders
|
||||
// with zero height but still needs its number row).
|
||||
// Read all rects first: batching layout reads before the writes below
|
||||
// avoids interleaved read/write reflows.
|
||||
var bodyTop = body.getBoundingClientRect().top;
|
||||
var lh = parseFloat(getComputedStyle(body).lineHeight) || 1;
|
||||
var rowIdx = [];
|
||||
@@ -76,15 +75,16 @@
|
||||
// settle a hair under N * line-height after the gutter is rebuilt; a
|
||||
// gutter pass that changes the code column width reflows it. Verify the
|
||||
// placement one frame later and re-run if any line's row moved (#167).
|
||||
var placed = {};
|
||||
for (var q = 0; q < tops.length; q++) placed[q] = Math.round((tops[q] - bodyTop) / lh);
|
||||
var placed = [];
|
||||
for (var p = 0; p < rowIdx.length; p++) placed.push(rowIdx[p]);
|
||||
requestAnimationFrame(function () {
|
||||
var moved = false;
|
||||
var bodyTop2 = body.getBoundingClientRect().top;
|
||||
var lh2 = parseFloat(getComputedStyle(body).lineHeight) || lh;
|
||||
var tops2 = [];
|
||||
for (var q2 = 0; q2 < lines.length; q2++) tops2.push(lines[q2].getBoundingClientRect().top);
|
||||
var bodyTop2 = body.getBoundingClientRect().top;
|
||||
for (var q3 = 0; q3 < tops2.length; q3++) {
|
||||
if (Math.round((tops2[q3] - bodyTop2) / lh) !== placed[q3]) { moved = true; break; }
|
||||
if (Math.round((tops2[q3] - bodyTop2) / lh2) !== placed[q3]) { moved = true; break; }
|
||||
}
|
||||
if (moved) renumber();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user