diff --git a/swh/web/assets/src/bundles/revision/diff-utils.js b/swh/web/assets/src/bundles/revision/diff-utils.js
index 3aeb9a8b..835d4503 100644
--- a/swh/web/assets/src/bundles/revision/diff-utils.js
+++ b/swh/web/assets/src/bundles/revision/diff-utils.js
@@ -1,546 +1,536 @@
/**
* Copyright (C) 2018-2020 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 {swhSpinnerSrc} from 'utils/constants';
// 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 parseDiffHunkRangeIfAny(lineText) {
let baseFromLine, baseToLine;
if (lineText.startsWith('@@')) {
let linesInfoRegExp = new RegExp(/^@@ -(\d+),(\d+) \+(\d+),(\d+) @@$/gm);
let linesInfoRegExp2 = new RegExp(/^@@ -(\d+) \+(\d+),(\d+) @@$/gm);
let linesInfoRegExp3 = new RegExp(/^@@ -(\d+),(\d+) \+(\d+) @@$/gm);
let linesInfoRegExp4 = new RegExp(/^@@ -(\d+) \+(\d+) @@$/gm);
let linesInfo = linesInfoRegExp.exec(lineText);
let linesInfo2 = linesInfoRegExp2.exec(lineText);
let linesInfo3 = linesInfoRegExp3.exec(lineText);
let linesInfo4 = linesInfoRegExp4.exec(lineText);
if (linesInfo) {
baseFromLine = parseInt(linesInfo[1]) - 1;
baseToLine = parseInt(linesInfo[3]) - 1;
} else if (linesInfo2) {
baseFromLine = parseInt(linesInfo2[1]) - 1;
baseToLine = parseInt(linesInfo2[2]) - 1;
} else if (linesInfo3) {
baseFromLine = parseInt(linesInfo3[1]) - 1;
baseToLine = parseInt(linesInfo3[3]) - 1;
} else if (linesInfo4) {
baseFromLine = parseInt(linesInfo4[1]) - 1;
baseToLine = parseInt(linesInfo4[2]) - 1;
}
}
if (baseFromLine !== undefined) {
return [baseFromLine, baseToLine];
} else {
return null;
}
}
// 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.lineNumbersBlockSync(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 baseFromLine = '';
- let baseToLine = '';
- let fromToLines = [];
- let fromLines = [];
- let toLines = [];
- let maxNumberChars = 0;
- let diffFromStr = '';
- let diffToStr = '';
- let linesOffset = 0;
-
- $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
- let lnText = lnElt.nextSibling.innerText;
- let linesInfo = parseDiffHunkRangeIfAny(lnText);
- let fromLine = '';
- let toLine = '';
- // parsed lines info from the diff output
- if (linesInfo) {
- baseFromLine = linesInfo[0];
- baseToLine = linesInfo[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 to 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('');
- }
+ // 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 baseFromLine = '';
+ let baseToLine = '';
+ let fromToLines = [];
+ let fromLines = [];
+ let toLines = [];
+ let maxNumberChars = 0;
+ let diffFromStr = '';
+ let diffToStr = '';
+ let linesOffset = 0;
+
+ $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
+ let lnText = lnElt.nextSibling.innerText;
+ let linesInfo = parseDiffHunkRangeIfAny(lnText);
+ let fromLine = '';
+ let toLine = '';
+ // parsed lines info from the diff output
+ if (linesInfo) {
+ baseFromLine = linesInfo[0];
+ baseToLine = linesInfo[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 to 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;
- if (lnText.startsWith('@@')) {
- $(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(` `);
- } else if (lnText.length > 0 && lnText[0] === '-') {
- $(lnElt).parent().addClass('swh-diff-removed-line');
- } else if (lnText.length > 0 && lnText[0] === '+') {
- $(lnElt).parent().addClass('swh-diff-added-line');
- }
- });
+ 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 line numbers for unified diff
- $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
- $(lnElt).children().attr(
- 'data-line-number',
- formatDiffLineNumbers(fromToLines[i][0], fromToLines[i][1],
- maxNumberChars));
- });
+ // set side-by-side diffs text
+ $(`#${diffId}-from`).text(diffFromStr);
+ $(`#${diffId}-to`).text(diffToStr);
- // set line numbers for the from side-by-side diff
- $(`#${diffId}-from .hljs-ln-numbers`).each((i, lnElt) => {
- $(lnElt).children().attr(
- 'data-line-number',
- formatDiffLineNumbers(fromLines[i], null,
- maxNumberChars));
- });
+ // code highlighting for side-by-side diffs
+ $(`#${diffId}-from, #${diffId}-to`).each((i, block) => {
+ hljs.highlightBlock(block);
+ hljs.lineNumbersBlockSync(block);
+ });
- // set line numbers for the to side-by-side diff
- $(`#${diffId}-to .hljs-ln-numbers`).each((i, lnElt) => {
- $(lnElt).children().attr(
- 'data-line-number',
- formatDiffLineNumbers(null, toLines[i],
- maxNumberChars));
- });
+ // diff highlighting for added/removed lines on top of code highlighting
+ $(`.${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
+ let lnText = lnElt.nextSibling.innerText;
+ if (lnText.startsWith('@@')) {
+ $(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(` `);
+ } else if (lnText.length > 0 && lnText[0] === '-') {
+ $(lnElt).parent().addClass('swh-diff-removed-line');
+ } else if (lnText.length > 0 && lnText[0] === '+') {
+ $(lnElt).parent().addClass('swh-diff-added-line');
+ }
+ });
- // 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-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));
- }
- });
- }
- });
+ // set line numbers for unified diff
+ $(`#${diffId} .hljs-ln-numbers`).each((i, lnElt) => {
+ $(lnElt).children().attr(
+ 'data-line-number',
+ formatDiffLineNumbers(fromToLines[i][0], fromToLines[i][1],
+ maxNumberChars));
+ });
- // 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');
- }
+ // set line numbers for the from side-by-side diff
+ $(`#${diffId}-from .hljs-ln-numbers`).each((i, lnElt) => {
+ $(lnElt).children().attr(
+ 'data-line-number',
+ formatDiffLineNumbers(fromLines[i], null,
+ maxNumberChars));
+ });
- setDiffVisible(diffId);
+ // set line numbers for the to side-by-side diff
+ $(`#${diffId}-to .hljs-ln-numbers`).each((i, lnElt) => {
+ $(lnElt).children().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-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);
}
});
}
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 ...