diff --git a/swh/web/assets/src/bundles/revision/diff-utils.js b/swh/web/assets/src/bundles/revision/diff-utils.js
index bd3a093b..b258d41d 100644
--- a/swh/web/assets/src/bundles/revision/diff-utils.js
+++ b/swh/web/assets/src/bundles/revision/diff-utils.js
@@ -1,518 +1,539 @@
/**
- * Copyright (C) 2018 The Software Heritage developers
+ * Copyright (C) 2018-2019 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import 'waypoints/lib/jquery.waypoints';
import {staticAsset} from 'utils/functions';
// path to static spinner asset
let swhSpinnerSrc = staticAsset('img/swh-spinner.gif');
// number of changed files in the revision
let changes = null;
let nbChangedFiles = 0;
// to track the number of already computed files diffs
let nbDiffsComputed = 0;
// the no newline at end of file marker from Github
let noNewLineMarker = `
`;
// to track the total number of added lines in files diffs
let nbAdditions = 0;
// to track the total number of deleted lines in files diffs
let nbDeletions = 0;
// to track the already computed diffs by id
let computedDiffs = {};
// map a diff id to its computation url
let diffsUrls = {};
// to check if a DOM element is in the viewport
function isInViewport(elt) {
let elementTop = $(elt).offset().top;
let elementBottom = elementTop + $(elt).outerHeight();
let viewportTop = $(window).scrollTop();
let viewportBottom = viewportTop + $(window).height();
return elementBottom > viewportTop && elementTop < viewportBottom;
}
// to format the diffs line numbers
function formatDiffLineNumbers(fromLine, toLine, maxNumberChars) {
let ret = '';
if (fromLine != null) {
for (let i = 0; i < (maxNumberChars - fromLine.length); ++i) {
ret += ' ';
}
ret += fromLine;
}
if (fromLine != null && toLine != null) {
ret += ' ';
}
if (toLine != null) {
for (let i = 0; i < (maxNumberChars - toLine.length); ++i) {
ret += ' ';
}
ret += toLine;
}
return ret;
}
+function adjustCodeBlockLeftMargin(diffElt) {
+ let left = $(diffElt).find('.hljs-ln-numbers-container').width();
+ $(diffElt).find('.hljs-ln-code-container').css('margin-left', left + 'px');
+}
+
// to compute diff and process it for display
export function computeDiff(diffUrl, diffId) {
// force diff computation ?
let force = diffUrl.indexOf('force=true') !== -1;
// it no forced computation and diff already computed, do nothing
if (!force && computedDiffs.hasOwnProperty(diffId)) {
return;
}
// mark diff computation as already requested
computedDiffs[diffId] = true;
$(`#${diffId}-loading`).css('visibility', 'visible');
// set spinner visible while requesting diff
$(`#${diffId}-loading`).css('display', 'block');
$(`#${diffId}-highlightjs`).css('display', 'none');
// request diff computation and process it
fetch(diffUrl)
.then(response => response.json())
.then(data => {
// increment number of computed diffs
++nbDiffsComputed;
// toggle the 'Compute all diffs' button if all diffs have been computed
if (nbDiffsComputed === changes.length) {
$('#swh-compute-all-diffs').addClass('active');
}
// Large diff (> threshold) are not automatically computed,
// add a button to force its computation
if (data.diff_str.indexOf('Large diff') === 0) {
$(`#${diffId}`)[0].innerHTML = data.diff_str +
`
`;
setDiffVisible(diffId);
} else if (data.diff_str.indexOf('@@') !== 0) {
$(`#${diffId}`).text(data.diff_str);
setDiffVisible(diffId);
} else {
// prepare code highlighting
$(`.${diffId}`).removeClass('nohighlight');
$(`.${diffId}`).addClass(data.language);
// set unified diff text
$(`#${diffId}`).text(data.diff_str);
// code highlighting for unified diff
$(`#${diffId}`).each((i, block) => {
hljs.highlightBlock(block);
hljs.lineNumbersBlock(block);
});
// hljs.lineNumbersBlock is asynchronous so we have to postpone our
// next treatments by adding it at the end of the current js events queue
setTimeout(() => {
// process unified diff lines in order to generate side-by-side diffs text
// but also compute line numbers for unified and side-by-side diffs
let linesInfoRegExp = new RegExp(/^@@ -(\d+),(\d+) \+(\d+),(\d+) @@$/gm);
let baseFromLine = '';
let baseToLine = '';
let fromToLines = [];
let fromLines = [];
let toLines = [];
let maxNumberChars = 0;
let diffFromStr = '';
let diffToStr = '';
let linesOffset = 0;
+ let codeLineElts = $(`#${diffId} .hljs-ln-code-container`).children();
+
$(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
- let lnText = lnElt.nextSibling.innerText;
+ let lnText = $(codeLineElts[i]).text();
let linesInfo = linesInfoRegExp.exec(lnText);
let fromLine = '';
let toLine = '';
// parsed lines info from the diff output
if (linesInfo) {
baseFromLine = parseInt(linesInfo[1]) - 1;
baseToLine = parseInt(linesInfo[3]) - 1;
linesOffset = 0;
diffFromStr += (lnText + '\n');
diffToStr += (lnText + '\n');
fromLines.push('');
toLines.push('');
// line removed in the from file
} else if (lnText.length > 0 && lnText[0] === '-') {
baseFromLine = baseFromLine + 1;
fromLine = baseFromLine.toString();
fromLines.push(fromLine);
++nbDeletions;
diffFromStr += (lnText + '\n');
++linesOffset;
// line added in the from file
} else if (lnText.length > 0 && lnText[0] === '+') {
baseToLine = baseToLine + 1;
toLine = baseToLine.toString();
toLines.push(toLine);
++nbAdditions;
diffToStr += (lnText + '\n');
--linesOffset;
// line present in both files
} else {
baseFromLine = baseFromLine + 1;
baseToLine = baseToLine + 1;
fromLine = baseFromLine.toString();
toLine = baseToLine.toString();
for (let j = 0; j < Math.abs(linesOffset); ++j) {
if (linesOffset > 0) {
diffToStr += '\n';
toLines.push('');
} else {
diffFromStr += '\n';
fromLines.push('');
}
}
linesOffset = 0;
diffFromStr += (lnText + '\n');
diffToStr += (lnText + '\n');
toLines.push(toLine);
fromLines.push(fromLine);
}
if (!baseFromLine) {
fromLine = '';
}
if (!baseToLine) {
toLine = '';
}
fromToLines[i] = [fromLine, toLine];
maxNumberChars = Math.max(maxNumberChars, fromLine.length);
maxNumberChars = Math.max(maxNumberChars, toLine.length);
});
// set side-by-side diffs text
$(`#${diffId}-from`).text(diffFromStr);
$(`#${diffId}-to`).text(diffToStr);
// code highlighting for side-by-side diffs
$(`#${diffId}-from, #${diffId}-to`).each((i, block) => {
hljs.highlightBlock(block);
hljs.lineNumbersBlock(block);
});
- // hljs.lineNumbersBlock is asynchronous so we have to postpone our
- // next treatments by adding it at the end of the current js events queue
- setTimeout(() => {
- // diff highlighting for added/removed lines on top of code highlighting
- $(`.${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
- let lnText = lnElt.nextSibling.innerText;
+ function highlightDiffLines(diffId) {
+ let codeLineElts = $(`#${diffId} .hljs-ln-code-container`).children();
+ $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
+ let lnTextElt = codeLineElts[i];
+ let lnText = $(lnTextElt).text();
let linesInfo = linesInfoRegExp.exec(lnText);
if (linesInfo) {
- $(lnElt).parent().addClass('swh-diff-lines-info');
- let linesInfoText = $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').text();
- $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').children().remove();
- $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').text('');
- $(lnElt).parent().find('.hljs-ln-code .hljs-ln-line').append(` `);
+ $(lnElt).addClass('swh-diff-lines-info');
+ $(lnTextElt).addClass('swh-diff-lines-info');
+ $(lnTextElt).text('');
+ $(lnTextElt).append(` `);
} else if (lnText.length > 0 && lnText[0] === '-') {
- $(lnElt).parent().addClass('swh-diff-removed-line');
+ $(lnElt).addClass('swh-diff-removed-line');
+ $(lnTextElt).addClass('swh-diff-removed-line');
} else if (lnText.length > 0 && lnText[0] === '+') {
- $(lnElt).parent().addClass('swh-diff-added-line');
+ $(lnElt).addClass('swh-diff-added-line');
+ $(lnTextElt).addClass('swh-diff-added-line');
}
});
+ }
+
+ // hljs.lineNumbersBlock is asynchronous so we have to postpone our
+ // next treatments by adding it at the end of the current js events queue
+ setTimeout(() => {
+
+ // diff highlighting for added/removed lines on top of code highlighting
+ highlightDiffLines(diffId);
+ highlightDiffLines(`${diffId}-from`);
+ highlightDiffLines(`${diffId}-to`);
// set line numbers for unified diff
- $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
- $(lnElt).children().attr(
+ $(`#${diffId} .hljs-ln-numbers .hljs-ln-n`).each((i, lnElt) => {
+ $(lnElt).attr(
'data-line-number',
formatDiffLineNumbers(fromToLines[i][0], fromToLines[i][1],
maxNumberChars));
});
// set line numbers for the from side-by-side diff
- $(`#${diffId}-from .hljs-ln-numbers`).each((i, lnElt) => {
- $(lnElt).children().attr(
+ $(`#${diffId}-from .hljs-ln-numbers .hljs-ln-n`).each((i, lnElt) => {
+ $(lnElt).attr(
'data-line-number',
formatDiffLineNumbers(fromLines[i], null,
maxNumberChars));
});
// set line numbers for the to side-by-side diff
- $(`#${diffId}-to .hljs-ln-numbers`).each((i, lnElt) => {
- $(lnElt).children().attr(
+ $(`#${diffId}-to .hljs-ln-numbers .hljs-ln-n`).each((i, lnElt) => {
+ $(lnElt).attr(
'data-line-number',
formatDiffLineNumbers(null, toLines[i],
maxNumberChars));
});
// last processing:
// - remove the '+' and '-' at the beginning of the diff lines
// from code highlighting
// - add the "no new line at end of file marker" if needed
- $(`.${diffId} .hljs-ln-line`).each((i, lnElt) => {
+ $(`.${diffId} .hljs-ln-code`).each((i, lnElt) => {
if (lnElt.firstChild) {
if (lnElt.firstChild.nodeName !== '#text') {
let lineText = lnElt.firstChild.innerHTML;
if (lineText[0] === '-' || lineText[0] === '+') {
lnElt.firstChild.innerHTML = lineText.substr(1);
let newTextNode = document.createTextNode(lineText[0]);
$(lnElt).prepend(newTextNode);
}
}
$(lnElt).contents().filter((i, elt) => {
return elt.nodeType === 3; // Node.TEXT_NODE
}).each((i, textNode) => {
let swhNoNewLineMarker = '[swh-no-nl-marker]';
if (textNode.textContent.indexOf(swhNoNewLineMarker) !== -1) {
textNode.textContent = textNode.textContent.replace(swhNoNewLineMarker, '');
$(lnElt).append($(noNewLineMarker));
}
});
}
});
// hide the diff mode switch button in case of not generated diffs
if (data.diff_str.indexOf('Diffs are not generated for non textual content') !== 0) {
$(`#panel_${diffId} .diff-styles`).css('visibility', 'visible');
}
setDiffVisible(diffId);
+ adjustCodeBlockLeftMargin(`#${diffId}`);
+
});
});
}
});
}
function setDiffVisible(diffId) {
// set the unified diff visible by default
$(`#${diffId}-loading`).css('display', 'none');
$(`#${diffId}-highlightjs`).css('display', 'block');
// update displayed counters
$('#swh-revision-lines-added').text(`${nbAdditions} additions`);
$('#swh-revision-lines-deleted').text(`${nbDeletions} deletions`);
$('#swh-nb-diffs-computed').text(nbDiffsComputed);
// refresh the waypoints triggering diffs computation as
// the DOM layout has been updated
Waypoint.refreshAll();
}
// to compute all visible diffs in the viewport
function computeVisibleDiffs() {
$('.swh-file-diff-panel').each((i, elt) => {
if (isInViewport(elt)) {
let diffId = elt.id.replace('panel_', '');
computeDiff(diffsUrls[diffId], diffId);
}
});
}
function genDiffPanel(diffData) {
let diffPanelTitle = diffData.path;
if (diffData.type === 'rename') {
diffPanelTitle = `${diffData.from_path} → ${diffData.to_path}`;
}
let diffPanelHtml =
`
Loading diff ...