Fix #167: gutter alignment with wrap on
Rebuild renumber() so gutter numbers are placed by measured geometry: each .gutline block is one visual row and each number is assigned to the row whose top matches its .codeline's top, instead of counting rows per line. This keeps numbers aligned with wrapped line starts regardless of how many visual rows a line occupies. Wrap OFF path unchanged.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user