@@ -397,8 +397,8 @@ function canonicalizeForDiff(text) {
397397 . replace ( / [ ‘ ’ ‚ ‛ ] / g, "'" )
398398 . replace ( / [ \u2013 \u2014 ] / g, '-' ) // en/em dash -> hyphen
399399 . replace ( / \u00a0 / g, ' ' ) // non-breaking space
400+ . replace ( / \n + / g, ' ' ) // collapse newlines
400401 . replace ( / [ \t ] + / g, ' ' ) // collapse spaces/tabs
401- . replace ( / \n { 2 , } / g, '\n' ) // collapse blank lines
402402 . trim ( )
403403 . toLowerCase ( ) ;
404404}
@@ -1736,43 +1736,41 @@ function renderDiffHtml(diff) {
17361736 const escapeHtmlFragment = ( str ) => String ( str )
17371737 . replace ( / & / g, '&' )
17381738 . replace ( / < / g, '<' )
1739- . replace ( / > / g, '>' ) ;
1740- const escapeWhitespace = ( str ) => escapeHtmlFragment ( str )
1741- . replace ( / / g, ' ' )
1739+ . replace ( / > / g, '>' )
17421740 . replace ( / \t / g, ' ' ) ;
1743- const opStyle = ( op ) => op === 1
1744- ? 'background:#e6ffe6;'
1745- : op === - 1
1746- ? 'background:#ffe6e6;text-decoration:line-through;'
1747- : '' ;
1748- const renderLeadingWhitespace = ( op , chunk ) => {
1749- if ( ! chunk ) return '' ;
1750- const safe = escapeWhitespace ( chunk ) ;
1751- if ( op === 0 ) return safe ;
1752- return `<span class="ldiff-leading" style="${ opStyle ( op ) } ">${ safe } </span>` ;
1741+ const listLinePattern = / ^ \s * (?: \( ? \d + \) | \d + [ . ) ] | [ a - z A - Z ] [ . ) ] | [ - * • ] ) \s + / ;
1742+ const shouldForceBreak = ( text , newlineIdx , runLength ) => {
1743+ if ( runLength !== 1 ) return false ;
1744+ const nextSlice = text . slice ( newlineIdx + 1 ) ;
1745+ const nextLine = nextSlice . split ( '\n' , 1 ) [ 0 ] || '' ;
1746+ return listLinePattern . test ( nextLine ) ;
1747+ } ;
1748+ const formatDiffText = ( text ) => {
1749+ if ( ! text ) return '' ;
1750+ let out = '' ;
1751+ let lastIndex = 0 ;
1752+ text . replace ( / \n + / g, ( match , idx ) => {
1753+ if ( idx > lastIndex ) out += escapeHtmlFragment ( text . slice ( lastIndex , idx ) ) ;
1754+ out += match . length === 1
1755+ ? ( shouldForceBreak ( text , idx , match . length ) ? '<br>' : ' ' )
1756+ : '<br>' . repeat ( match . length ) ;
1757+ lastIndex = idx + match . length ;
1758+ return match ;
1759+ } ) ;
1760+ if ( lastIndex < text . length ) out += escapeHtmlFragment ( text . slice ( lastIndex ) ) ;
1761+ return out ;
17531762 } ;
17541763 const renderBody = ( op , chunk ) => {
17551764 if ( ! chunk ) return '' ;
1756- const safe = escapeHtmlFragment ( chunk ) ;
1757- if ( op === 1 ) return `<ins>${ safe } </ins>` ;
1758- if ( op === - 1 ) return `<del>${ safe } </del>` ;
1759- return `<span>${ safe } </span>` ;
1765+ const formatted = formatDiffText ( chunk ) ;
1766+ const whitespaceOnly = ! / \S / . test ( chunk ) ;
1767+ if ( op === 1 && ! whitespaceOnly ) return `<ins>${ formatted } </ins>` ;
1768+ if ( op === - 1 && ! whitespaceOnly ) return `<del>${ formatted } </del>` ;
1769+ if ( op === 0 && ! whitespaceOnly ) return `<span>${ formatted } </span>` ;
1770+ return `<span></span>` ;
17601771 } ;
1761- const renderChunk = ( op , text ) => {
1762- if ( ! text ) return '' ;
1763- const lines = text . split ( '\n' ) ;
1764- const parts = [ ] ;
1765- for ( let i = 0 ; i < lines . length ; i ++ ) {
1766- const line = lines [ i ] ;
1767- const trimmed = line . replace ( / ^ [ \t ] + / , '' ) ;
1768- if ( trimmed . length ) {
1769- parts . push ( renderBody ( op , trimmed ) ) ;
1770- }
1771- if ( i < lines . length - 1 ) parts . push ( '<br>' ) ;
1772- }
1773- return parts . join ( '' ) ;
1774- } ;
1775-
1776- const html = sanitized . map ( ( [ op , data ] ) => renderChunk ( op , data ) ) . join ( '' ) ;
1772+ const viewable = sanitized ; // include deletions again
1773+ if ( ! viewable . length ) throw new Error ( 'Empty diff after filtering viewable segments' ) ;
1774+ const html = viewable . map ( ( [ op , data ] ) => renderBody ( op , data ) ) . join ( '' ) ;
17771775 return `<div class="ldiff-output" id="outputdiv">${ html } </div>` ;
17781776}
0 commit comments