Release v0.5.0: dev -> main #288
@@ -319,8 +319,10 @@ html[data-wrap] .float { overflow-x: hidden; }
|
||||
.codebody { padding: 0 18px; white-space: pre; overflow-x: auto; }
|
||||
/* #167: each logical line is its own block so offsetTop identifies its first visual row */
|
||||
.codeline { display: block; }
|
||||
/* #167 rev: gutter number spans must stack one per visual row (wrap on) */
|
||||
.code .gutter .gutline { display: block; }
|
||||
/* #167: gutter number spans must stack one per visual row (wrap on).
|
||||
#274: the /new editor gutter uses the same .gutline blocks when its own
|
||||
wrap toggle is on, so scope the rule to any gutter, not just .code. */
|
||||
.code .gutter .gutline, .editor-wrap .gutter .gutline { display: block; }
|
||||
/* #194: codeline blocks are adjacent (no '\n' text between them), so an
|
||||
empty block (blank source line) needs its own line box to stay one row */
|
||||
.codeline:empty::before { content: "\200B"; }
|
||||
|
||||
@@ -2,13 +2,71 @@
|
||||
const $ = id => document.getElementById(id);
|
||||
const content = $('content'), gutter = $('gutter');
|
||||
|
||||
// #274: with wrap on, a logical line occupies several VISUAL rows in the
|
||||
// textarea, so one number per logical line drifts off its text (same bug the
|
||||
// paste view fixed in #167). A textarea can't be split into spans, so the
|
||||
// wrapped row count per logical line is measured with a hidden mirror div
|
||||
// that shares the editor's font, line metrics and wrapping rules, and the
|
||||
// gutter renders one .gutline block per visual row with the number on the
|
||||
// FIRST row of its logical line (fillers elsewhere).
|
||||
let mirror = null;
|
||||
function measureRows(lines) {
|
||||
if (!mirror) {
|
||||
mirror = document.createElement('div');
|
||||
mirror.style.position = 'absolute';
|
||||
mirror.style.visibility = 'hidden';
|
||||
mirror.style.top = '0';
|
||||
mirror.style.left = '-9999px';
|
||||
document.body.appendChild(mirror);
|
||||
}
|
||||
const cs = getComputedStyle(content);
|
||||
mirror.style.font = cs.font;
|
||||
mirror.style.lineHeight = cs.lineHeight;
|
||||
mirror.style.whiteSpace = 'pre-wrap';
|
||||
mirror.style.overflowWrap = 'anywhere';
|
||||
mirror.style.wordBreak = 'break-all';
|
||||
mirror.style.width = (content.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight)) + 'px';
|
||||
const lh = parseFloat(cs.lineHeight) || 1;
|
||||
const starts = [];
|
||||
let total = 0;
|
||||
const n = Math.max(lines.length, 1);
|
||||
for (let i = 0; i < n; i++) {
|
||||
// A trailing newline yields an empty last line: it still occupies one row.
|
||||
mirror.textContent = lines[i] + '\n';
|
||||
let rows = Math.max(1, Math.round(mirror.getBoundingClientRect().height / lh));
|
||||
starts.push(total);
|
||||
total += rows;
|
||||
}
|
||||
return { starts, total };
|
||||
}
|
||||
|
||||
function updateGutter() {
|
||||
const lines = content.value.split('\n').length;
|
||||
const lines = content.value.split('\n');
|
||||
const n = Math.max(lines.length, 1);
|
||||
if (!document.documentElement.hasAttribute('data-wrap')) {
|
||||
let s = '';
|
||||
for (let i = 1; i <= Math.max(lines, 1); i++) s += i + '\n';
|
||||
gutter.textContent = s;
|
||||
for (let i = 1; i <= n; i++) s += i + '\n';
|
||||
gutter.textContent = s.slice(0, -1);
|
||||
return;
|
||||
}
|
||||
const { starts, total } = measureRows(lines);
|
||||
gutter.textContent = '';
|
||||
const frag = document.createDocumentFragment();
|
||||
const spans = [];
|
||||
for (let r = 0; r < total; r++) {
|
||||
const c = document.createElement('span');
|
||||
c.className = 'gutline';
|
||||
c.textContent = '\u00a0';
|
||||
spans.push(c);
|
||||
frag.appendChild(c);
|
||||
}
|
||||
gutter.appendChild(frag);
|
||||
for (let j = 0; j < starts.length; j++) spans[starts[j]].textContent = String(j + 1);
|
||||
}
|
||||
content.addEventListener('input', updateGutter);
|
||||
// #274: the wrap toggle and width changes re-wrap the textarea; re-measure.
|
||||
new MutationObserver(updateGutter).observe(document.documentElement, { attributes: true, attributeFilter: ['data-wrap'] });
|
||||
window.addEventListener('resize', updateGutter);
|
||||
// #259: the editor scrolls itself; keep the gutter's numbers in step with it.
|
||||
content.addEventListener('scroll', () => { gutter.scrollTop = content.scrollTop; });
|
||||
updateGutter();
|
||||
|
||||
Reference in New Issue
Block a user