290 lines
9.0 KiB
JavaScript
290 lines
9.0 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
(function(window) {
|
|
'use strict';
|
|
|
|
// Prevent redefinition.
|
|
if (window.GrDiffGroup) { return; }
|
|
|
|
/**
|
|
* A chunk of the diff that should be rendered together.
|
|
*
|
|
* @constructor
|
|
* @param {!GrDiffGroup.Type} type
|
|
* @param {!Array<!GrDiffLine>=} opt_lines
|
|
*/
|
|
function GrDiffGroup(type, opt_lines) {
|
|
/** @type {!GrDiffGroup.Type} */
|
|
this.type = type;
|
|
|
|
/** @type {boolean} */
|
|
this.dueToRebase = false;
|
|
|
|
/**
|
|
* True means all changes in this line are whitespace changes that should
|
|
* not be highlighted as changed as per the user settings.
|
|
*
|
|
* @type{boolean}
|
|
*/
|
|
this.ignoredWhitespaceOnly = false;
|
|
|
|
/**
|
|
* True means it should not be collapsed (because it was in the URL, or
|
|
* there is a comment on that line)
|
|
*/
|
|
this.keyLocation = false;
|
|
|
|
/** @type {?HTMLElement} */
|
|
this.element = null;
|
|
|
|
/** @type {!Array<!GrDiffLine>} */
|
|
this.lines = [];
|
|
/** @type {!Array<!GrDiffLine>} */
|
|
this.adds = [];
|
|
/** @type {!Array<!GrDiffLine>} */
|
|
this.removes = [];
|
|
|
|
/** Both start and end line are inclusive. */
|
|
this.lineRange = {
|
|
left: {start: null, end: null},
|
|
right: {start: null, end: null},
|
|
};
|
|
|
|
if (opt_lines) {
|
|
opt_lines.forEach(this.addLine, this);
|
|
}
|
|
}
|
|
|
|
/** @enum {string} */
|
|
GrDiffGroup.Type = {
|
|
/** Unchanged context. */
|
|
BOTH: 'both',
|
|
|
|
/** A widget used to show more context. */
|
|
CONTEXT_CONTROL: 'contextControl',
|
|
|
|
/** Added, removed or modified chunk. */
|
|
DELTA: 'delta',
|
|
};
|
|
|
|
/**
|
|
* Hides lines in the given range behind a context control group.
|
|
*
|
|
* Groups that would be partially visible are split into their visible and
|
|
* hidden parts, respectively.
|
|
* The groups need to be "common groups", meaning they have to have either
|
|
* originated from an `ab` chunk, or from an `a`+`b` chunk with
|
|
* `common: true`.
|
|
*
|
|
* If the hidden range is 1 line or less, nothing is hidden and no context
|
|
* control group is created.
|
|
*
|
|
* @param {!Array<!GrDiffGroup>} groups Common groups, ordered by their line
|
|
* ranges.
|
|
* @param {number} hiddenStart The first element to be hidden, as a
|
|
* non-negative line number offset relative to the first group's start
|
|
* line, left and right respectively.
|
|
* @param {number} hiddenEnd The first visible element after the hidden range,
|
|
* as a non-negative line number offset relative to the first group's
|
|
* start line, left and right respectively.
|
|
* @return {!Array<!GrDiffGroup>}
|
|
*/
|
|
GrDiffGroup.hideInContextControl = function(groups, hiddenStart, hiddenEnd) {
|
|
if (groups.length === 0) return [];
|
|
// Clamp hiddenStart and hiddenEnd - inspired by e.g. substring
|
|
hiddenStart = Math.max(hiddenStart, 0);
|
|
hiddenEnd = Math.max(hiddenEnd, hiddenStart);
|
|
|
|
let before = [];
|
|
let hidden = groups;
|
|
let after = [];
|
|
|
|
const numHidden = hiddenEnd - hiddenStart;
|
|
|
|
// Only collapse if there is more than 1 line to be hidden.
|
|
if (numHidden > 1) {
|
|
if (hiddenStart) {
|
|
[before, hidden] = GrDiffGroup._splitCommonGroups(hidden, hiddenStart);
|
|
}
|
|
if (hiddenEnd) {
|
|
[hidden, after] = GrDiffGroup._splitCommonGroups(
|
|
hidden, hiddenEnd - hiddenStart);
|
|
}
|
|
} else {
|
|
[hidden, after] = [[], hidden];
|
|
}
|
|
|
|
const result = [...before];
|
|
if (hidden.length) {
|
|
const ctxLine = new GrDiffLine(GrDiffLine.Type.CONTEXT_CONTROL);
|
|
ctxLine.contextGroups = hidden;
|
|
const ctxGroup = new GrDiffGroup(
|
|
GrDiffGroup.Type.CONTEXT_CONTROL, [ctxLine]);
|
|
result.push(ctxGroup);
|
|
}
|
|
result.push(...after);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Splits a list of common groups into two lists of groups.
|
|
*
|
|
* Groups where all lines are before or all lines are after the split will be
|
|
* retained as is and put into the first or second list respectively. Groups
|
|
* with some lines before and some lines after the split will be split into
|
|
* two groups, which will be put into the first and second list.
|
|
*
|
|
* @param {!Array<!GrDiffGroup>} groups
|
|
* @param {number} split A line number offset relative to the first group's
|
|
* start line at which the groups should be split.
|
|
* @return {!Array<!Array<!GrDiffGroup>>} The outer array has 2 elements, the
|
|
* list of groups before and the list of groups after the split.
|
|
*/
|
|
GrDiffGroup._splitCommonGroups = function(groups, split) {
|
|
if (groups.length === 0) return [[], []];
|
|
const leftSplit = groups[0].lineRange.left.start + split;
|
|
const rightSplit = groups[0].lineRange.right.start + split;
|
|
|
|
const beforeGroups = [];
|
|
const afterGroups = [];
|
|
for (const group of groups) {
|
|
if (group.lineRange.left.end < leftSplit ||
|
|
group.lineRange.right.end < rightSplit) {
|
|
beforeGroups.push(group);
|
|
continue;
|
|
}
|
|
if (leftSplit <= group.lineRange.left.start ||
|
|
rightSplit <= group.lineRange.right.start) {
|
|
afterGroups.push(group);
|
|
continue;
|
|
}
|
|
|
|
const before = [];
|
|
const after = [];
|
|
for (const line of group.lines) {
|
|
if ((line.beforeNumber && line.beforeNumber < leftSplit) ||
|
|
(line.afterNumber && line.afterNumber < rightSplit)) {
|
|
before.push(line);
|
|
} else {
|
|
after.push(line);
|
|
}
|
|
}
|
|
|
|
if (before.length) {
|
|
beforeGroups.push(before.length === group.lines.length ?
|
|
group : group.cloneWithLines(before));
|
|
}
|
|
if (after.length) {
|
|
afterGroups.push(after.length === group.lines.length ?
|
|
group : group.cloneWithLines(after));
|
|
}
|
|
}
|
|
return [beforeGroups, afterGroups];
|
|
};
|
|
|
|
/**
|
|
* Creates a new group with the same properties but different lines.
|
|
*
|
|
* The element property is not copied, because the original element is still a
|
|
* rendering of the old lines, so that would not make sense.
|
|
*
|
|
* @param {!Array<!GrDiffLine>} lines
|
|
* @return {!GrDiffGroup}
|
|
*/
|
|
GrDiffGroup.prototype.cloneWithLines = function(lines) {
|
|
const group = new GrDiffGroup(this.type, lines);
|
|
group.dueToRebase = this.dueToRebase;
|
|
group.ignoredWhitespaceOnly = this.ignoredWhitespaceOnly;
|
|
return group;
|
|
};
|
|
|
|
/** @param {!GrDiffLine} line */
|
|
GrDiffGroup.prototype.addLine = function(line) {
|
|
this.lines.push(line);
|
|
|
|
const notDelta = (this.type === GrDiffGroup.Type.BOTH ||
|
|
this.type === GrDiffGroup.Type.CONTEXT_CONTROL);
|
|
if (notDelta && (line.type === GrDiffLine.Type.ADD ||
|
|
line.type === GrDiffLine.Type.REMOVE)) {
|
|
throw Error('Cannot add delta line to a non-delta group.');
|
|
}
|
|
|
|
if (line.type === GrDiffLine.Type.ADD) {
|
|
this.adds.push(line);
|
|
} else if (line.type === GrDiffLine.Type.REMOVE) {
|
|
this.removes.push(line);
|
|
}
|
|
this._updateRange(line);
|
|
};
|
|
|
|
/** @return {!Array<{left: GrDiffLine, right: GrDiffLine}>} */
|
|
GrDiffGroup.prototype.getSideBySidePairs = function() {
|
|
if (this.type === GrDiffGroup.Type.BOTH ||
|
|
this.type === GrDiffGroup.Type.CONTEXT_CONTROL) {
|
|
return this.lines.map(line => {
|
|
return {
|
|
left: line,
|
|
right: line,
|
|
};
|
|
});
|
|
}
|
|
|
|
const pairs = [];
|
|
let i = 0;
|
|
let j = 0;
|
|
while (i < this.removes.length || j < this.adds.length) {
|
|
pairs.push({
|
|
left: this.removes[i] || GrDiffLine.BLANK_LINE,
|
|
right: this.adds[j] || GrDiffLine.BLANK_LINE,
|
|
});
|
|
i++;
|
|
j++;
|
|
}
|
|
return pairs;
|
|
};
|
|
|
|
GrDiffGroup.prototype._updateRange = function(line) {
|
|
if (line.beforeNumber === 'FILE' || line.afterNumber === 'FILE') { return; }
|
|
|
|
if (line.type === GrDiffLine.Type.ADD ||
|
|
line.type === GrDiffLine.Type.BOTH) {
|
|
if (this.lineRange.right.start === null ||
|
|
line.afterNumber < this.lineRange.right.start) {
|
|
this.lineRange.right.start = line.afterNumber;
|
|
}
|
|
if (this.lineRange.right.end === null ||
|
|
line.afterNumber > this.lineRange.right.end) {
|
|
this.lineRange.right.end = line.afterNumber;
|
|
}
|
|
}
|
|
|
|
if (line.type === GrDiffLine.Type.REMOVE ||
|
|
line.type === GrDiffLine.Type.BOTH) {
|
|
if (this.lineRange.left.start === null ||
|
|
line.beforeNumber < this.lineRange.left.start) {
|
|
this.lineRange.left.start = line.beforeNumber;
|
|
}
|
|
if (this.lineRange.left.end === null ||
|
|
line.beforeNumber > this.lineRange.left.end) {
|
|
this.lineRange.left.end = line.beforeNumber;
|
|
}
|
|
}
|
|
};
|
|
|
|
window.GrDiffGroup = GrDiffGroup;
|
|
})(window);
|