Highlight diffs by side not by line
Highlight diffs by side not by line
Syntax-highlighting a diff has a trap in it. The obvious approach — highlight each line on its own — produces wrong colours, because a highlighter needs context a single line doesn't have. A line that opens a template literal or a block comment and doesn't close it leaves the highlighter guessing, so multi-line strings, comments, and JSX come out miscoloured.
The fix is to highlight by side, not by line. Reconstruct the two versions the diff describes — the old side (context plus removed lines) and the new side (context plus added lines) — and highlight each as one continuous block. Now the highlighter sees whole constructs and colours them correctly. Then map each highlighted line back to its diff row by index.
The span-rebalancing catch
There's a second trap waiting once you highlight a block. A highlighter like highlight.js will happily open a <span> on one line and close it several lines later, when a token spans a newline. If you then split the block's HTML on \n to get per-line pieces, you tear the tags apart and every line after the break is broken markup.
So splitting on newlines isn't enough — you have to re-balance the tag stack at each break. Walk the highlighted HTML, keep a stack of currently-open spans, and at every newline: close all open spans to end the current line, then re-open them at the start of the next. Each line comes out as independently valid HTML.
// at each "\n": close open spans, emit the line, reopen them next line
out.push(cur + "</span>".repeat(open.length));
cur = open.join("");
Where this bit me
Both traps showed up building the diff viewer in the Review System Changes Skill. The full-file inline view had been splitting highlighted HTML on newlines directly and got away with it only because the test file had no cross-line tokens. The block-highlight-plus-rebalance approach is the robust version, and it's the same shape any side-by-side or inline diff viewer needs.
One styling note that pairs with this: keep the added/removed background tint but let the syntax colours show through the text, the way GitHub does. If you force green and red text on changed lines, you throw away the highlighting you just did.