diff --git a/internal/web/static/paste-lines.js b/internal/web/static/paste-lines.js index 02944a6..231a473 100644 --- a/internal/web/static/paste-lines.js +++ b/internal/web/static/paste-lines.js @@ -42,28 +42,22 @@ return; } - // Measure each logical line's offsetTop (viewport-relative for - // comparison with gutter rows rendered in the same scroll flow). - // Reading all rects first avoids interleaved layout reads/writes. - var tops = []; - for (var i = 0; i < lines.length; i++) { - 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. + // 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). var bodyTop = body.getBoundingClientRect().top; var lh = parseFloat(getComputedStyle(body).lineHeight) || 1; - var maxBottom = 0; - for (var j = 0; j < lines.length; j++) { - var btm = lines[j].getBoundingClientRect().bottom - bodyTop; - if (btm > maxBottom) maxBottom = btm; + var rowIdx = []; + var totalRows = 1; + for (var i = 0; i < lines.length; i++) { + 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 - // 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); + if (totalRows < lines.length) totalRows = lines.length; gutter.textContent = ''; var frag = document.createDocumentFragment(); var spans = []; @@ -75,11 +69,8 @@ frag.appendChild(cell); } gutter.appendChild(frag); - for (var j = 0; j < tops.length; j++) { - var row = Math.round((tops[j] - bodyTop) / lh); - if (row < 0) row = 0; - if (row > totalRows - 1) row = totalRows - 1; - spans[row].textContent = String(j + 1); + for (var j = 0; j < rowIdx.length; j++) { + spans[rowIdx[j]].textContent = String(j + 1); } // 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