Release v0.4.0: dev -> main #251

Merged
fen merged 138 commits from dev into main 2026-09-17 15:35:21 +00:00
Showing only changes of commit e1056df747 - Show all commits
+15 -24
View File
@@ -42,28 +42,22 @@
return; return;
} }
// Measure each logical line's offsetTop (viewport-relative for // Measure each logical line's geometry. Reading all rects first avoids
// comparison with gutter rows rendered in the same scroll flow). // interleaved layout reads/writes. A line's number goes on the visual row
// Reading all rects first avoids interleaved layout reads/writes. // whose top matches the line's top; the gutter is sized to cover the
var tops = []; // deepest row any line STARTS on (a trailing empty logical line renders
for (var i = 0; i < lines.length; i++) { // with zero height but still needs its number row).
tops.push(lines[i].getBoundingClientRect().top);
}
// Build one gutline per visual row of the tallest column (the code
// body itself); each number is assigned to the visual row whose top
// is closest to its line's top.
var bodyTop = body.getBoundingClientRect().top; var bodyTop = body.getBoundingClientRect().top;
var lh = parseFloat(getComputedStyle(body).lineHeight) || 1; var lh = parseFloat(getComputedStyle(body).lineHeight) || 1;
var maxBottom = 0; var rowIdx = [];
for (var j = 0; j < lines.length; j++) { var totalRows = 1;
var btm = lines[j].getBoundingClientRect().bottom - bodyTop; for (var i = 0; i < lines.length; i++) {
if (btm > maxBottom) maxBottom = btm; var row = Math.round((lines[i].getBoundingClientRect().top - bodyTop) / lh);
if (row < 0) row = 0;
rowIdx.push(row);
if (row + 1 > totalRows) totalRows = row + 1;
} }
// Rows needed through the last visual row of the deepest line (ceil, so a if (totalRows < lines.length) totalRows = lines.length;
// partially-filled last row still gets its gutter span).
// Rows needed: floor(last line bottom / lh) + 1 — the row a line STARTS on
// must exist even when its bottom lands exactly on the body's edge.
var totalRows = Math.max(lines.length, Math.floor(maxBottom / lh) + 1);
gutter.textContent = ''; gutter.textContent = '';
var frag = document.createDocumentFragment(); var frag = document.createDocumentFragment();
var spans = []; var spans = [];
@@ -75,11 +69,8 @@
frag.appendChild(cell); frag.appendChild(cell);
} }
gutter.appendChild(frag); gutter.appendChild(frag);
for (var j = 0; j < tops.length; j++) { for (var j = 0; j < rowIdx.length; j++) {
var row = Math.round((tops[j] - bodyTop) / lh); spans[rowIdx[j]].textContent = String(j + 1);
if (row < 0) row = 0;
if (row > totalRows - 1) row = totalRows - 1;
spans[row].textContent = String(j + 1);
} }
// A wrapped line whose last visual row is only partially filled can // A wrapped line whose last visual row is only partially filled can
// settle a hair under N * line-height after the gutter is rebuilt; a // settle a hair under N * line-height after the gutter is rebuilt; a