diff --git a/frontend/src/tools/devtools.jsx b/frontend/src/tools/devtools.jsx index b72dcba..be68047 100644 --- a/frontend/src/tools/devtools.jsx +++ b/frontend/src/tools/devtools.jsx @@ -945,47 +945,37 @@ function diffLines(a, b) { const aLines = a.split('\n'); const bLines = b.split('\n'); const m = aLines.length, n = bLines.length; - // LCS dp const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0)); for (let i = m-1; i >= 0; i--) for (let j = n-1; j >= 0; j--) dp[i][j] = aLines[i] === bLines[j] ? dp[i+1][j+1]+1 : Math.max(dp[i+1][j], dp[i][j+1]); - // Build flat ops - const ops = []; - let i = 0, j = 0; + // Build rows: same / del / add — consecutive del+add stay paired (same display line number) + const rows = []; // {type, lineA, lineB, text} + let i = 0, j = 0, lnA = 0, lnB = 0; while (i < m || j < n) { if (i < m && j < n && aLines[i] === bLines[j]) { - ops.push({type:'same', a:aLines[i], b:bLines[j]}); i++; j++; + lnA++; lnB++; + rows.push({type:'same', ln: lnA, text: aLines[i]}); + i++; j++; } else if (j < n && (i >= m || dp[i][j+1] >= dp[i+1][j])) { - ops.push({type:'add', a:null, b:bLines[j]}); j++; + lnB++; + rows.push({type:'add', ln: lnB, text: bLines[j]}); + j++; } else { - ops.push({type:'del', a:aLines[i], b:null}); i++; + lnA++; + rows.push({type:'del', ln: lnA, text: aLines[i]}); + i++; } } - // Pair up consecutive del+add into 'change' rows - const paired = []; - let k = 0; - while (k < ops.length) { - if (ops[k].type==='del' && k+1 < ops.length && ops[k+1].type==='add') { - paired.push({type:'change', a:ops[k].a, b:ops[k+1].b}); k+=2; - } else { - paired.push(ops[k]); k++; - } - } - return paired; + return rows; } -// Compute set of changed line numbers (1-based) for a given side -function changedLineNums(pairs, side) { +// Which line numbers (1-based) in text A/B are changed +function changedLineNums(rows, side) { const set = new Set(); - let ln = 0; - pairs.forEach(p => { - if (p.type==='same') { ln++; } - else if (p.type==='change') { ln++; set.add(ln); } - else if (p.type==='del' && side==='a') { ln++; set.add(ln); } - else if (p.type==='add' && side==='b') { ln++; set.add(ln); } - else if (p.type==='del' && side==='b') { /* b has no line here */ } - else if (p.type==='add' && side==='a') { /* a has no line here */ } + rows.forEach(r => { + if (r.type === 'del' && side === 'a') set.add(r.ln); + if (r.type === 'add' && side === 'b') set.add(r.ln); }); return set; } @@ -1011,11 +1001,10 @@ function TextDiff({ mobile }) { e.target.value = ''; } - const added = diff ? diff.filter(d=>d.type==='add'||d.type==='change').length : 0; - const removed = diff ? diff.filter(d=>d.type==='del'||d.type==='change').length : 0; + const added = diff ? diff.filter(d=>d.type==='add').length : 0; + const removed = diff ? diff.filter(d=>d.type==='del').length : 0; const same = diff ? diff.filter(d=>d.type==='same').length : 0; - // Which lines are changed in each textarea (for background highlight) const changedA = diff ? changedLineNums(diff, 'a') : new Set(); const changedB = diff ? changedLineNums(diff, 'b') : new Set(); @@ -1023,14 +1012,13 @@ function TextDiff({ mobile }) { boxSizing:'border-box', minHeight:180, resize:'vertical', lineHeight:'1.6', padding:'7px 8px' }; function LineNumCol({ text, changedSet }) { - const lines = (text||' ').split('\n'); return (
| - {(isDel||isChange||isSame) ? lnA : ''} - | -- {symA} - | -- {(isDel||isChange||isSame) ? (d.a ?? d.text ?? ' ') : ''} - | - {/* Side B */} -- {(isAdd||isChange||isSame) ? lnB : ''} - | -- {symB} - | -- {(isAdd||isChange||isSame) ? (d.b ?? d.text ?? ' ') : ''} - | -
| + {d.ln} + | ++ {d.type==='add'?'+': d.type==='del'?'−':''} + | ++ {d.text || ' '} + | +