diff --git a/src/line-by-line-renderer.ts b/src/line-by-line-renderer.ts index 4258095..2dd310f 100644 --- a/src/line-by-line-renderer.ts +++ b/src/line-by-line-renderer.ts @@ -82,6 +82,7 @@ export default class LineByLineRenderer { generateEmptyDiff(): string { return this.hoganUtils.render(genericTemplatesPath, 'empty-diff', { contentClass: 'd2h-code-line', + colspan: '2', CSSLineClass: renderUtils.CSSLineClass, }); } @@ -93,35 +94,43 @@ export default class LineByLineRenderer { return file.blocks .map(block => { - let lines = this.hoganUtils.render(genericTemplatesPath, 'block-header', { - CSSLineClass: renderUtils.CSSLineClass, - blockHeader: file.isTooBig ? block.header : renderUtils.escapeForHtml(block.header), - lineClass: 'd2h-code-linenumber', - contentClass: 'd2h-code-line', - }); + const lines: string[] = []; - this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => { + lines.push( + this.hoganUtils.render(genericTemplatesPath, 'block-header', { + CSSLineClass: renderUtils.CSSLineClass, + margin_colspan: '2', + colspan: '1', + blockHeader: file.isTooBig ? block.header : renderUtils.escapeForHtml(block.header), + lineClass: 'd2h-code-linenumber', + contentClass: 'd2h-code-line', + }), + ); + + this.applyLineGrouping(block).forEach(([contextLines, oldLines, newLines]) => { if (oldLines.length && newLines.length && !contextLines.length) { this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => { const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines); - lines += left; - lines += right; + lines.push(...left); + lines.push(...right); }); } else if (contextLines.length) { contextLines.forEach(line => { const { prefix, content } = renderUtils.deconstructLine(line.content, file.isCombined); - lines += this.generateSingleLineHtml({ - type: renderUtils.CSSLineClass.CONTEXT, - prefix: prefix, - content: content, - oldNumber: line.oldNumber, - newNumber: line.newNumber, - }); + lines.push( + this.generateSingleLineHtml({ + type: renderUtils.CSSLineClass.CONTEXT, + prefix: prefix, + content: content, + oldNumber: line.oldNumber, + newNumber: line.newNumber, + }), + ); }); } else if (oldLines.length || newLines.length) { const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines); - lines += left; - lines += right; + lines.push(...left); + lines.push(...right); } else { console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines); } @@ -129,10 +138,13 @@ export default class LineByLineRenderer { return lines; }) + .map((block_html: string[]) => { + return block_html.map(line => `${line}`).join('\n'); + }) .join('\n'); } - applyLineGroupping(block: DiffBlock): DiffLineGroups { + applyLineGrouping(block: DiffBlock): DiffLineGroups { const blockLinesGroups: DiffLineGroups = []; let oldLines: (DiffLineDeleted & DiffLineContent)[] = []; @@ -189,9 +201,9 @@ export default class LineByLineRenderer { } processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml { - const fileHtml = { - right: '', - left: '', + const fileHtml: FileHtml = { + left: [], + right: [], }; const maxLinesNumber = Math.max(oldLines.length, newLines.length); @@ -241,8 +253,8 @@ export default class LineByLineRenderer { : undefined; const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine); - fileHtml.left += left; - fileHtml.right += right; + fileHtml.left.push(...left); + fileHtml.right.push(...right); } return fileHtml; @@ -250,27 +262,34 @@ export default class LineByLineRenderer { generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml { return { - left: this.generateSingleLineHtml(oldLine), - right: this.generateSingleLineHtml(newLine), + left: [this.generateSingleLineHtml(oldLine)], + right: [this.generateSingleLineHtml(newLine)], }; } generateSingleLineHtml(line?: DiffPreparedLine): string { if (line === undefined) return ''; - const lineNumberHtml = this.hoganUtils.render(baseTemplatesPath, 'numbers', { - oldNumber: line.oldNumber || '', - newNumber: line.newNumber || '', + const oldLineNumberHtml = this.hoganUtils.render(genericTemplatesPath, 'line-number', { + type: line.type, + lineClass: line.oldNumber ? 'd2h-code-linenumber' : 'd2h-code-emptyplaceholder', + lineNumber: line.oldNumber, }); - return this.hoganUtils.render(genericTemplatesPath, 'line', { + const newLineNumberHtml = this.hoganUtils.render(genericTemplatesPath, 'line-number', { + type: line.type, + lineClass: line.newNumber ? 'd2h-code-linenumber' : 'd2h-code-emptyplaceholder', + lineNumber: line.newNumber, + }); + + const newLineContentHtml = this.hoganUtils.render(genericTemplatesPath, 'line', { type: line.type, - lineClass: 'd2h-code-linenumber', contentClass: 'd2h-code-line', prefix: line.prefix === ' ' ? ' ' : line.prefix, content: line.content, - lineNumber: lineNumberHtml, }); + + return oldLineNumberHtml.concat(newLineNumberHtml, newLineContentHtml); } } @@ -289,6 +308,6 @@ type DiffPreparedLine = { }; type FileHtml = { - left: string; - right: string; + left: string[]; + right: string[]; }; diff --git a/src/side-by-side-renderer.ts b/src/side-by-side-renderer.ts index 9a0fb6b..1216632 100644 --- a/src/side-by-side-renderer.ts +++ b/src/side-by-side-renderer.ts @@ -55,7 +55,7 @@ export default class SideBySideRenderer { return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml }); } - makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string { + makeFileDiffHtml(file: DiffFile, diffs: string): string { if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return ''; const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, 'file-diff'); @@ -79,34 +79,32 @@ export default class SideBySideRenderer { }); } - generateEmptyDiff(): FileHtml { - return { - right: '', - left: this.hoganUtils.render(genericTemplatesPath, 'empty-diff', { - contentClass: 'd2h-code-side-line', - CSSLineClass: renderUtils.CSSLineClass, - }), - }; + generateEmptyDiff(): string { + return this.hoganUtils.render(genericTemplatesPath, 'empty-diff', { + contentClass: 'd2h-code-side-line', + colspan: '4', + CSSLineClass: renderUtils.CSSLineClass, + }); } - generateFileHtml(file: DiffFile): FileHtml { + generateFileHtml(file: DiffFile): string { const matcher = Rematch.newMatcherFn( Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content), ); return file.blocks .map(block => { - const fileHtml = { - left: this.makeHeaderHtml(block.header, file), - right: this.makeHeaderHtml(''), + const fileHtml: FileHtml = { + left: [this.makeHeaderHtml(block.header, file)], + right: [''], }; - this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => { + this.applyLineGrouping(block).forEach(([contextLines, oldLines, newLines]) => { if (oldLines.length && newLines.length && !contextLines.length) { this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => { const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines); - fileHtml.left += left; - fileHtml.right += right; + fileHtml.left.push(...left); + fileHtml.right.push(...right); }); } else if (contextLines.length) { contextLines.forEach(line => { @@ -125,13 +123,13 @@ export default class SideBySideRenderer { number: line.newNumber, }, ); - fileHtml.left += left; - fileHtml.right += right; + fileHtml.left.push(...left); + fileHtml.right.push(...right); }); } else if (oldLines.length || newLines.length) { const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines); - fileHtml.left += left; - fileHtml.right += right; + fileHtml.left.push(...left); + fileHtml.right.push(...right); } else { console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines); } @@ -139,15 +137,19 @@ export default class SideBySideRenderer { return fileHtml; }) - .reduce( - (accomulated, html) => { - return { left: accomulated.left + html.left, right: accomulated.right + html.right }; - }, - { left: '', right: '' }, - ); + .map((block_html: FileHtml) => { + let block_html_string = ''; + for (let block_line_index = 0; block_line_index < block_html.left.length; block_line_index++) { + block_html_string = block_html_string.concat( + `${block_html.left[block_line_index]} ${block_html.right[block_line_index]}`, + ); + } + return block_html_string; + }) + .join('\n'); } - applyLineGroupping(block: DiffBlock): DiffLineGroups { + applyLineGrouping(block: DiffBlock): DiffLineGroups { const blockLinesGroups: DiffLineGroups = []; let oldLines: (DiffLineDeleted & DiffLineContent)[] = []; @@ -206,6 +208,8 @@ export default class SideBySideRenderer { makeHeaderHtml(blockHeader: string, file?: DiffFile): string { return this.hoganUtils.render(genericTemplatesPath, 'block-header', { CSSLineClass: renderUtils.CSSLineClass, + margin_colspan: '1', + colspan: '3', blockHeader: file?.isTooBig ? blockHeader : renderUtils.escapeForHtml(blockHeader), lineClass: 'd2h-code-side-linenumber', contentClass: 'd2h-code-side-line', @@ -213,9 +217,9 @@ export default class SideBySideRenderer { } processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml { - const fileHtml = { - right: '', - left: '', + const fileHtml: FileHtml = { + left: [], + right: [], }; const maxLinesNumber = Math.max(oldLines.length, newLines.length); @@ -263,8 +267,8 @@ export default class SideBySideRenderer { : undefined; const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine); - fileHtml.left += left; - fileHtml.right += right; + fileHtml.left.push(...left); + fileHtml.right.push(...right); } return fileHtml; @@ -272,8 +276,8 @@ export default class SideBySideRenderer { generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml { return { - left: this.generateSingleHtml(oldLine), - right: this.generateSingleHtml(newLine), + left: [this.generateSingleHtml(oldLine)], + right: [this.generateSingleHtml(newLine)], }; } @@ -281,14 +285,22 @@ export default class SideBySideRenderer { const lineClass = 'd2h-code-side-linenumber'; const contentClass = 'd2h-code-side-line'; - return this.hoganUtils.render(genericTemplatesPath, 'line', { - type: line?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`, + const type = line?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`; + + const line_number_cell = this.hoganUtils.render(genericTemplatesPath, 'line-number', { + type: type, lineClass: line !== undefined ? lineClass : `${lineClass} d2h-code-side-emptyplaceholder`, + lineNumber: line?.number, + }); + + const line_content_cell = this.hoganUtils.render(genericTemplatesPath, 'line', { + type: type, contentClass: line !== undefined ? contentClass : `${contentClass} d2h-code-side-emptyplaceholder`, prefix: line?.prefix === ' ' ? ' ' : line?.prefix, content: line?.content, - lineNumber: line?.number, }); + + return line_number_cell.concat(line_content_cell); } } @@ -306,6 +318,6 @@ type DiffPreparedLine = { }; type FileHtml = { - left: string; - right: string; + left: string[]; + right: string[]; }; diff --git a/src/templates/generic-block-header.mustache b/src/templates/generic-block-header.mustache index efbbc42..ebf67ec 100644 --- a/src/templates/generic-block-header.mustache +++ b/src/templates/generic-block-header.mustache @@ -1,6 +1,4 @@ - - - -
{{#blockHeader}}{{{blockHeader}}}{{/blockHeader}}{{^blockHeader}} {{/blockHeader}}
- - + + +
{{#blockHeader}}{{{blockHeader}}}{{/blockHeader}}{{^blockHeader}} {{/blockHeader}}
+ diff --git a/src/templates/generic-empty-diff.mustache b/src/templates/generic-empty-diff.mustache index 8cf1443..9107aa3 100644 --- a/src/templates/generic-empty-diff.mustache +++ b/src/templates/generic-empty-diff.mustache @@ -1,7 +1,7 @@ - -
- File without changes -
- + +
+ File without changes +
+ diff --git a/src/templates/generic-line-number.mustache b/src/templates/generic-line-number.mustache new file mode 100644 index 0000000..01a2ffa --- /dev/null +++ b/src/templates/generic-line-number.mustache @@ -0,0 +1 @@ + diff --git a/src/templates/generic-line.mustache b/src/templates/generic-line.mustache index cf8f7e6..df01661 100644 --- a/src/templates/generic-line.mustache +++ b/src/templates/generic-line.mustache @@ -1,21 +1,5 @@ - - - {{{lineNumber}}} - - -
- {{#prefix}} - {{{prefix}}} - {{/prefix}} - {{^prefix}} -   - {{/prefix}} - {{#content}} - {{{content}}} - {{/content}} - {{^content}} -
- {{/content}} -
- - + +
+ {{#prefix}}{{/prefix}}{{^prefix}}{{/prefix}}{{#content}}{{content}}{{/content}}{{^content}}
{{/content}} +
+ diff --git a/src/templates/line-by-line-file-diff.mustache b/src/templates/line-by-line-file-diff.mustache index 8af1ccd..f5ce559 100644 --- a/src/templates/line-by-line-file-diff.mustache +++ b/src/templates/line-by-line-file-diff.mustache @@ -5,6 +5,14 @@
+ + + + + + + + {{{diffs}}} diff --git a/src/templates/line-by-line-numbers.mustache b/src/templates/line-by-line-numbers.mustache deleted file mode 100644 index c0ef8c6..0000000 --- a/src/templates/line-by-line-numbers.mustache +++ /dev/null @@ -1,2 +0,0 @@ -
{{oldNumber}}
-
{{newNumber}}
diff --git a/src/templates/side-by-side-file-diff.mustache b/src/templates/side-by-side-file-diff.mustache index 3f3e5d3..e0e9d00 100644 --- a/src/templates/side-by-side-file-diff.mustache +++ b/src/templates/side-by-side-file-diff.mustache @@ -5,18 +5,25 @@
-
+
+ + + + + + + + + + + + + + + + - {{{diffs.left}}} - -
-
-
-
-
- - - {{{diffs.right}}} + {{{diffs}}}
diff --git a/src/ui/css/diff2html.css b/src/ui/css/diff2html.css index e88a20f..1d547d0 100644 --- a/src/ui/css/diff2html.css +++ b/src/ui/css/diff2html.css @@ -7,6 +7,17 @@ .d2h-wrapper { text-align: left; + word-wrap: break-word; +} + +.d2h-wrapper * { + box-sizing: border-box; +} + +.d2h-wrapper table { + border-spacing: 0; + border-collapse: collapse; + table-layout: fixed; } .d2h-file-header { @@ -94,9 +105,14 @@ .d2h-diff-table { width: 100%; - border-collapse: collapse; + border-collapse: separate; font-family: 'Menlo', 'Consolas', monospace; font-size: 13px; + word-wrap: break-word; +} + +.d2h-diff-table.d2h-split-diff { + table-layout: fixed; } .d2h-files-diff { @@ -104,46 +120,40 @@ width: 100%; } -.d2h-file-diff { - overflow-y: hidden; -} - .d2h-files-diff.d2h-d-none, .d2h-file-diff.d2h-d-none { display: none; } .d2h-file-side-diff { - display: inline-block; - overflow-x: scroll; - overflow-y: hidden; - width: 50%; -} - -.d2h-code-line { - display: inline-block; - white-space: nowrap; - user-select: none; - width: calc(100% - 16em); - /* Compensate for the absolute positioning of the line numbers */ - padding: 0 8em; + width: 100%; } +.d2h-code-line, .d2h-code-side-line { - display: inline-block; - white-space: nowrap; user-select: none; - width: calc(100% - 9em); - /* Compensate for the absolute positioning of the line numbers */ - padding: 0 4.5em; + padding-left: 22px; + position: relative; + padding-right: 10px; + line-height: 20px; + vertical-align: top; +} + +.d2h-code-marker::before { + position: absolute; + top: 1px; + left: 8px; + padding-right: 8px; + content: attr(data-code-marker); } .d2h-code-line-ctn { - display: inline-block; + display: table-cell; background: none; padding: 0; - word-wrap: normal; - white-space: pre; + word-wrap: anywhere; + white-space: pre-wrap; + overflow: visible; user-select: text; width: 100%; vertical-align: middle; @@ -178,63 +188,33 @@ white-space: pre; } -.line-num1 { - box-sizing: border-box; - float: left; - width: 3.5em; - overflow: hidden; - text-overflow: ellipsis; - padding: 0 0.5em 0 0.5em; -} - -.line-num2 { - box-sizing: border-box; - float: right; - width: 3.5em; - overflow: hidden; - text-overflow: ellipsis; - padding: 0 0.5em 0 0.5em; -} - -.d2h-code-linenumber { - box-sizing: border-box; - width: 7.5em; - /* Keep the numbers fixed on line contents scroll */ - position: absolute; - display: inline-block; - background-color: #fff; - color: rgba(0, 0, 0, 0.3); - text-align: right; - border: solid #eeeeee; - border-width: 0 1px 0 1px; - cursor: pointer; -} - -.d2h-code-linenumber:after { - content: '\200b'; -} - -.d2h-code-side-linenumber { - /* Keep the numbers fixed on line contents scroll */ - position: absolute; - display: inline-block; - box-sizing: border-box; - width: 4em; - background-color: #fff; - color: rgba(0, 0, 0, 0.3); - text-align: right; - border: solid #eeeeee; - border-width: 0 1px 0 1px; - cursor: pointer; - overflow: hidden; - text-overflow: ellipsis; - padding: 0 0.5em 0 0.5em; +.d2h-code-linenumber::before, +.d2h-code-side-linenumber::before { + content: attr(data-line-number); } +.d2h-code-linenumber:after, .d2h-code-side-linenumber:after { content: '\200b'; } +.d2h-code-side-linenumber, +.d2h-code-linenumber { + /* Keep the numbers fixed on line contents scroll */ + position: relative; + background-color: #fff; + color: rgba(0, 0, 0, 0.3); + text-align: right; + border: solid #eeeeee; + border-width: 0 1px 0 1px; + line-height: 20px; + font-size: 12px; + width: 1%; + min-width: 50px; + padding-right: 10px; + padding-left: 10px; +} + .d2h-code-side-emptyplaceholder, .d2h-emptyplaceholder { background-color: #f1f1f1;