Files
gerrit/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js
Ole Rehmsen 80df1f3f0f Show but don't highlight ignored whitespace
When a line had only whitespace changes that the user had configured to
be ignored, the previous behavior was to not show those at all, and if
the line showed up as context, render the right-side version of that
line both left and right. That is very confusing.

The new behavior is to not use colors to highlight those changes, but
still render the left and right as the files actually are.

Bug: Issue 1062
Change-Id: Ic301cc5445d33ee6e0a780178a189386669f8a7a
2019-06-06 18:06:50 +02:00

283 lines
8.9 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, GrDiffLine) {
'use strict';
// Prevent redefinition.
if (window.GrDiffGroup) { return; }
/**
* A chunk of the diff that should be rendered together.
*
* @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;
/** @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, GrDiffLine);