Merge changes Ic301cc54,I828c4f89
* changes: Show but don't highlight ignored whitespace Make context collapsing work for multi section
This commit is contained in:
@@ -35,6 +35,9 @@
|
||||
if (group.dueToRebase) {
|
||||
sectionEl.classList.add('dueToRebase');
|
||||
}
|
||||
if (group.ignoredWhitespaceOnly) {
|
||||
sectionEl.classList.add('ignoredWhitespaceOnly');
|
||||
}
|
||||
const pairs = group.getSideBySidePairs();
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
sectionEl.appendChild(this._createRow(sectionEl, pairs[i].left,
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
if (group.dueToRebase) {
|
||||
sectionEl.classList.add('dueToRebase');
|
||||
}
|
||||
if (group.ignoredWhitespaceOnly) {
|
||||
sectionEl.classList.add('ignoredWhitespaceOnly');
|
||||
}
|
||||
|
||||
for (let i = 0; i < group.lines.length; ++i) {
|
||||
sectionEl.appendChild(this._createRow(sectionEl, group.lines[i]));
|
||||
|
||||
@@ -289,7 +289,7 @@ limitations under the License.
|
||||
const contextIndex = groups.findIndex(group =>
|
||||
group.element === sectionEl
|
||||
);
|
||||
groups.splice(...[contextIndex, 1].concat(newGroups));
|
||||
groups.splice(contextIndex, 1, ...newGroups);
|
||||
|
||||
for (const newGroup of newGroups) {
|
||||
this._builder.emitGroup(newGroup, sectionEl);
|
||||
|
||||
@@ -231,33 +231,12 @@
|
||||
group => { return group.element; });
|
||||
};
|
||||
|
||||
// TODO(wyatta): Move this completely into the processor.
|
||||
GrDiffBuilder.prototype._insertContextGroups = function(groups, lines,
|
||||
hiddenRange) {
|
||||
const linesBeforeCtx = lines.slice(0, hiddenRange[0]);
|
||||
const hiddenLines = lines.slice(hiddenRange[0], hiddenRange[1]);
|
||||
const linesAfterCtx = lines.slice(hiddenRange[1]);
|
||||
|
||||
if (linesBeforeCtx.length > 0) {
|
||||
groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesBeforeCtx));
|
||||
}
|
||||
|
||||
const ctxLine = new GrDiffLine(GrDiffLine.Type.CONTEXT_CONTROL);
|
||||
ctxLine.contextGroups =
|
||||
[new GrDiffGroup(GrDiffGroup.Type.BOTH, hiddenLines)];
|
||||
groups.push(new GrDiffGroup(GrDiffGroup.Type.CONTEXT_CONTROL,
|
||||
[ctxLine]));
|
||||
|
||||
if (linesAfterCtx.length > 0) {
|
||||
groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesAfterCtx));
|
||||
}
|
||||
};
|
||||
|
||||
GrDiffBuilder.prototype._createContextControl = function(section, line) {
|
||||
if (!line.contextGroups) return null;
|
||||
|
||||
const numLines = line.contextGroups.reduce(
|
||||
(sum, contextGroup) => sum + contextGroup.lines.length, 0);
|
||||
const numLines =
|
||||
line.contextGroups[line.contextGroups.length - 1].lineRange.left.end -
|
||||
line.contextGroups[0].lineRange.left.start + 1;
|
||||
|
||||
if (numLines === 0) return null;
|
||||
|
||||
@@ -266,24 +245,24 @@
|
||||
|
||||
if (showPartialLinks) {
|
||||
td.appendChild(this._createContextButton(
|
||||
GrDiffBuilder.ContextButtonType.ABOVE, section, line));
|
||||
GrDiffBuilder.ContextButtonType.ABOVE, section, line, numLines));
|
||||
td.appendChild(document.createTextNode(' - '));
|
||||
}
|
||||
|
||||
td.appendChild(this._createContextButton(
|
||||
GrDiffBuilder.ContextButtonType.ALL, section, line));
|
||||
GrDiffBuilder.ContextButtonType.ALL, section, line, numLines));
|
||||
|
||||
if (showPartialLinks) {
|
||||
td.appendChild(document.createTextNode(' - '));
|
||||
td.appendChild(this._createContextButton(
|
||||
GrDiffBuilder.ContextButtonType.BELOW, section, line));
|
||||
GrDiffBuilder.ContextButtonType.BELOW, section, line, numLines));
|
||||
}
|
||||
|
||||
return td;
|
||||
};
|
||||
|
||||
GrDiffBuilder.prototype._createContextButton = function(type, section, line) {
|
||||
const contextLines = line.contextGroups[0].lines;
|
||||
GrDiffBuilder.prototype._createContextButton = function(type, section, line,
|
||||
numLines) {
|
||||
const context = PARTIAL_CONTEXT_AMOUNT;
|
||||
|
||||
const button = this._createElement('gr-button', 'showContext');
|
||||
@@ -291,20 +270,20 @@
|
||||
button.setAttribute('no-uppercase', true);
|
||||
|
||||
let text;
|
||||
const groups = []; // The groups that replace this one if tapped.
|
||||
let groups = []; // The groups that replace this one if tapped.
|
||||
|
||||
if (type === GrDiffBuilder.ContextButtonType.ALL) {
|
||||
text = 'Show ' + contextLines.length + ' common line';
|
||||
if (contextLines.length > 1) { text += 's'; }
|
||||
text = 'Show ' + numLines + ' common line';
|
||||
if (numLines > 1) { text += 's'; }
|
||||
groups.push(...line.contextGroups);
|
||||
} else if (type === GrDiffBuilder.ContextButtonType.ABOVE) {
|
||||
text = '+' + context + '↑';
|
||||
this._insertContextGroups(groups, contextLines,
|
||||
[context, contextLines.length]);
|
||||
groups = GrDiffGroup.hideInContextControl(line.contextGroups,
|
||||
context, undefined);
|
||||
} else if (type === GrDiffBuilder.ContextButtonType.BELOW) {
|
||||
text = '+' + context + '↓';
|
||||
this._insertContextGroups(groups, contextLines,
|
||||
[0, contextLines.length - context]);
|
||||
groups = GrDiffGroup.hideInContextControl(line.contextGroups,
|
||||
0, numLines - context);
|
||||
}
|
||||
|
||||
Polymer.dom(button).textContent = text;
|
||||
|
||||
@@ -90,26 +90,37 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('context control buttons', () => {
|
||||
const section = {};
|
||||
const line = {contextGroups: [{lines: []}]};
|
||||
|
||||
// Create 10 lines.
|
||||
const lines = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
line.contextGroups[0].lines.push('lorem upsum');
|
||||
const line = new GrDiffLine(GrDiffLine.Type.BOTH);
|
||||
line.beforeNumber = i + 1;
|
||||
line.afterNumber = i + 1;
|
||||
line.text = 'lorem upsum';
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
const contextLine = {
|
||||
contextGroups: [new GrDiffGroup(GrDiffGroup.Type.BOTH, lines)],
|
||||
};
|
||||
|
||||
const section = {};
|
||||
// Does not include +10 buttons when there are fewer than 11 lines.
|
||||
let td = builder._createContextControl(section, line);
|
||||
let td = builder._createContextControl(section, contextLine);
|
||||
let buttons = td.querySelectorAll('gr-button.showContext');
|
||||
|
||||
assert.equal(buttons.length, 1);
|
||||
assert.equal(Polymer.dom(buttons[0]).textContent, 'Show 10 common lines');
|
||||
|
||||
// Add another line.
|
||||
line.contextGroups[0].lines.push('lorem upsum');
|
||||
const line = new GrDiffLine(GrDiffLine.Type.BOTH);
|
||||
line.text = 'lorem upsum';
|
||||
line.beforeNumber = 11;
|
||||
line.afterNumber = 11;
|
||||
contextLine.contextGroups[0].addLine(line);
|
||||
|
||||
// Includes +10 buttons when there are at least 11 lines.
|
||||
td = builder._createContextControl(section, line);
|
||||
td = builder._createContextControl(section, contextLine);
|
||||
buttons = td.querySelectorAll('gr-button.showContext');
|
||||
|
||||
assert.equal(buttons.length, 3);
|
||||
|
||||
@@ -271,9 +271,6 @@
|
||||
.then(diff => {
|
||||
this._loadedWhitespaceLevel = whitespaceLevel;
|
||||
this._reportDiff(diff);
|
||||
if (this._getIgnoreWhitespace() !== WHITESPACE_IGNORE_NONE) {
|
||||
return this._translateChunksToIgnore(diff);
|
||||
}
|
||||
return diff;
|
||||
})
|
||||
.catch(e => {
|
||||
@@ -734,49 +731,6 @@
|
||||
matchers.some(matcher => matcher(threadEl)));
|
||||
},
|
||||
|
||||
/**
|
||||
* Take a diff that was loaded with a ignore-whitespace other than
|
||||
* IGNORE_NONE, and convert delta chunks labeled as common into shared
|
||||
* chunks.
|
||||
* @param {!Object} diff
|
||||
* @returns {!Object}
|
||||
*/
|
||||
_translateChunksToIgnore(diff) {
|
||||
const newDiff = Object.assign({}, diff);
|
||||
const mergedContent = [];
|
||||
|
||||
// Was the last chunk visited a shared chunk?
|
||||
let lastWasShared = false;
|
||||
|
||||
for (const chunk of diff.content) {
|
||||
if (lastWasShared && chunk.common && chunk.b) {
|
||||
// The last chunk was shared and this chunk should be ignored, so
|
||||
// add its revision content to the previous chunk.
|
||||
mergedContent[mergedContent.length - 1].ab.push(...chunk.b);
|
||||
} else if (chunk.common && !chunk.b) {
|
||||
// If the chunk should be ignored, but it doesn't have revision
|
||||
// content, then drop it and continue without updating lastWasShared.
|
||||
continue;
|
||||
} else if (lastWasShared && chunk.ab) {
|
||||
// Both the last chunk and the current chunk are shared. Merge this
|
||||
// chunk's shared content into the previous shared content.
|
||||
mergedContent[mergedContent.length - 1].ab.push(...chunk.ab);
|
||||
} else if (!lastWasShared && chunk.common && chunk.b) {
|
||||
// If the previous chunk was not shared, but this one should be
|
||||
// ignored, then add it as a shared chunk.
|
||||
mergedContent.push({ab: chunk.b});
|
||||
} else {
|
||||
// Otherwise add the chunk as is.
|
||||
mergedContent.push(chunk);
|
||||
}
|
||||
|
||||
lastWasShared = !!mergedContent[mergedContent.length - 1].ab;
|
||||
}
|
||||
|
||||
newDiff.content = mergedContent;
|
||||
return newDiff;
|
||||
},
|
||||
|
||||
_getIgnoreWhitespace() {
|
||||
if (!this.prefs || !this.prefs.ignore_whitespace) {
|
||||
return WHITESPACE_IGNORE_NONE;
|
||||
|
||||
@@ -1257,89 +1257,5 @@ limitations under the License.
|
||||
assert.deepEqual(element._filterThreadElsForLocation(threadEls, line,
|
||||
Gerrit.DiffSide.RIGHT), [r]);
|
||||
});
|
||||
|
||||
suite('_translateChunksToIgnore', () => {
|
||||
let content;
|
||||
|
||||
setup(() => {
|
||||
content = [
|
||||
{ab: ['one', 'two']},
|
||||
{a: ['three'], b: ['different three']},
|
||||
{b: ['four']},
|
||||
{ab: ['five', 'six']},
|
||||
{a: ['seven']},
|
||||
{ab: ['eight', 'nine']},
|
||||
];
|
||||
});
|
||||
|
||||
test('does nothing to unmarked diff', () => {
|
||||
assert.deepEqual(element._translateChunksToIgnore({content}),
|
||||
{content});
|
||||
});
|
||||
|
||||
test('merges marked delta chunk', () => {
|
||||
content[1].common = true;
|
||||
assert.deepEqual(element._translateChunksToIgnore({content}), {
|
||||
content: [
|
||||
{ab: ['one', 'two', 'different three']},
|
||||
{b: ['four']},
|
||||
{ab: ['five', 'six']},
|
||||
{a: ['seven']},
|
||||
{ab: ['eight', 'nine']},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('merges marked addition chunk', () => {
|
||||
content[2].common = true;
|
||||
assert.deepEqual(element._translateChunksToIgnore({content}), {
|
||||
content: [
|
||||
{ab: ['one', 'two']},
|
||||
{a: ['three'], b: ['different three']},
|
||||
{ab: ['four', 'five', 'six']},
|
||||
{a: ['seven']},
|
||||
{ab: ['eight', 'nine']},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('merges multiple marked delta', () => {
|
||||
content[1].common = true;
|
||||
content[2].common = true;
|
||||
assert.deepEqual(element._translateChunksToIgnore({content}), {
|
||||
content: [
|
||||
{ab: ['one', 'two', 'different three', 'four', 'five', 'six']},
|
||||
{a: ['seven']},
|
||||
{ab: ['eight', 'nine']},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('marked deletion chunks are omitted', () => {
|
||||
content[4].common = true;
|
||||
assert.deepEqual(element._translateChunksToIgnore({content}), {
|
||||
content: [
|
||||
{ab: ['one', 'two']},
|
||||
{a: ['three'], b: ['different three']},
|
||||
{b: ['four']},
|
||||
{ab: ['five', 'six', 'eight', 'nine']},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('marked deltas can start shared chunks', () => {
|
||||
content[0] = {a: ['one'], b: ['two'], common: true};
|
||||
assert.deepEqual(element._translateChunksToIgnore({content}), {
|
||||
content: [
|
||||
{ab: ['two']},
|
||||
{a: ['three'], b: ['different three']},
|
||||
{b: ['four']},
|
||||
{ab: ['five', 'six']},
|
||||
{a: ['seven']},
|
||||
{ab: ['eight', 'nine']},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -162,8 +162,7 @@
|
||||
}
|
||||
|
||||
// Process the next section and incorporate the result.
|
||||
const result = this._processNext(
|
||||
state, content[state.sectionIndex], content.length);
|
||||
const result = this._processNext(state, content);
|
||||
for (const group of result.groups) {
|
||||
this.push('groups', group);
|
||||
currentBatch += group.lines.length;
|
||||
@@ -172,7 +171,7 @@
|
||||
state.lineNums.right += result.lineDelta.right;
|
||||
|
||||
// Increment the index and recurse.
|
||||
state.sectionIndex++;
|
||||
state.sectionIndex = result.newSectionIndex;
|
||||
if (currentBatch >= this._asyncThreshold) {
|
||||
currentBatch = 0;
|
||||
this._nextStepHandle = this.async(nextStep, 1);
|
||||
@@ -201,41 +200,111 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the next section of the diff.
|
||||
* Process the next uncommon section, or the next common sections.
|
||||
*
|
||||
* @param {!Object} state
|
||||
* @param {!Object} section
|
||||
* @param {number} numSections
|
||||
* @param {!Array<!Object>} sections
|
||||
*/
|
||||
_processNext(state, section, numSections) {
|
||||
const lines = this._linesFromSection(
|
||||
section, state.lineNums.left + 1, state.lineNums.right + 1);
|
||||
const lineDelta = {
|
||||
left: section.ab ? section.ab.length : section.a ? section.a.length : 0,
|
||||
right: section.ab ? section.ab.length :
|
||||
section.b ? section.b.length : 0,
|
||||
};
|
||||
let groups;
|
||||
if (section.ab) { // If it's a shared section.
|
||||
let sectionEnd = null;
|
||||
if (state.sectionIndex === 0) {
|
||||
sectionEnd = 'first';
|
||||
} else if (state.sectionIndex === numSections - 1) {
|
||||
sectionEnd = 'last';
|
||||
}
|
||||
groups = this._sharedGroupsFromLines(
|
||||
lines,
|
||||
lineDelta.left,
|
||||
numSections > 1 ? this.context : WHOLE_FILE,
|
||||
state.lineNums.left,
|
||||
state.lineNums.right,
|
||||
sectionEnd);
|
||||
} else { // Otherwise it's a delta section.
|
||||
const deltaGroup = new GrDiffGroup(GrDiffGroup.Type.DELTA, lines);
|
||||
deltaGroup.dueToRebase = section.due_to_rebase;
|
||||
groups = [deltaGroup];
|
||||
_processNext(state, sections) {
|
||||
const firstUncommonSectionIndex = this._firstUncommonSectionIndex(
|
||||
sections, state.sectionIndex);
|
||||
if (firstUncommonSectionIndex === state.sectionIndex) {
|
||||
const section = sections[state.sectionIndex];
|
||||
return {
|
||||
lineDelta: {
|
||||
left: section.a ? section.a.length : 0,
|
||||
right: section.b ? section.b.length : 0,
|
||||
},
|
||||
groups: [this._sectionToGroup(
|
||||
section, state.lineNums.left + 1, state.lineNums.right + 1)],
|
||||
newSectionIndex: state.sectionIndex + 1,
|
||||
};
|
||||
}
|
||||
return {lineDelta, groups};
|
||||
|
||||
return this._processCommonSections(
|
||||
state, sections, firstUncommonSectionIndex);
|
||||
},
|
||||
|
||||
_firstUncommonSectionIndex(sections, offset) {
|
||||
let sectionIndex = offset;
|
||||
while (sectionIndex < sections.length &&
|
||||
this._isCommonSection(sections[sectionIndex])) {
|
||||
sectionIndex++;
|
||||
}
|
||||
return sectionIndex;
|
||||
},
|
||||
|
||||
_isCommonSection(section) {
|
||||
return section.ab || section.common;
|
||||
},
|
||||
|
||||
/**
|
||||
* Process a stretch of common sections.
|
||||
*
|
||||
* Outputs up to three groups:
|
||||
* 1) Visible context before the hidden common code, unless it's the
|
||||
* very beginning of the file.
|
||||
* 2) Context hidden behind a context bar, unless empty.
|
||||
* 3) Visible context after the hidden common code, unless it's the very
|
||||
* end of the file.
|
||||
*
|
||||
* @param {!Object} state
|
||||
* @param {!Array<Object>} sections
|
||||
* @param {number} firstUncommonSectionIndex
|
||||
* @return {!Object}
|
||||
*/
|
||||
_processCommonSections(state, sections, firstUncommonSectionIndex) {
|
||||
const commonSections = sections.slice(
|
||||
state.sectionIndex, firstUncommonSectionIndex);
|
||||
const lineCount = commonSections.reduce(
|
||||
(sum, section) => sum + this._commonSectionLength(section), 0);
|
||||
|
||||
let groups = this._sectionsToGroups(
|
||||
commonSections, state.lineNums.left + 1, state.lineNums.right + 1);
|
||||
|
||||
if (this.context !== WHOLE_FILE) {
|
||||
const hiddenStart = state.sectionIndex === 0 ? 0 : this.context;
|
||||
const hiddenEnd = lineCount - (
|
||||
firstUncommonSectionIndex === sections.length ?
|
||||
0 : this.context);
|
||||
groups = GrDiffGroup.hideInContextControl(
|
||||
groups, hiddenStart, hiddenEnd);
|
||||
}
|
||||
|
||||
return {
|
||||
lineDelta: {
|
||||
left: lineCount,
|
||||
right: lineCount,
|
||||
},
|
||||
groups,
|
||||
newSectionIndex: firstUncommonSectionIndex,
|
||||
};
|
||||
},
|
||||
|
||||
_commonSectionLength(section) {
|
||||
console.assert(section.ab || section.common);
|
||||
console.assert(
|
||||
!section.a || (section.b && section.a.length === section.b.length));
|
||||
return (section.ab || section.a).length;
|
||||
},
|
||||
|
||||
_sectionsToGroups(sections, offsetLeft, offsetRight) {
|
||||
return sections.map(section => {
|
||||
const group = this._sectionToGroup(section, offsetLeft, offsetRight);
|
||||
const sectionLength = this._commonSectionLength(section);
|
||||
offsetLeft += sectionLength;
|
||||
offsetRight += sectionLength;
|
||||
return group;
|
||||
});
|
||||
},
|
||||
|
||||
_sectionToGroup(section, offsetLeft, offsetRight) {
|
||||
const type = section.ab ? GrDiffGroup.Type.BOTH : GrDiffGroup.Type.DELTA;
|
||||
const lines = this._linesFromSection(section, offsetLeft, offsetRight);
|
||||
const group = new GrDiffGroup(type, lines);
|
||||
group.dueToRebase = section.due_to_rebase;
|
||||
group.ignoredWhitespaceOnly = section.common;
|
||||
return group;
|
||||
},
|
||||
|
||||
_linesFromSection(section, offsetLeft, offsetRight) {
|
||||
@@ -247,14 +316,14 @@
|
||||
if (section.a) {
|
||||
// Avoiding a.push(...b) because that causes callstack overflows for
|
||||
// large b, which can occur when large files are added removed.
|
||||
lines = lines.concat(this._deltaLinesFromRows(
|
||||
lines = lines.concat(this._linesFromRows(
|
||||
GrDiffLine.Type.REMOVE, section.a, offsetLeft,
|
||||
section[DiffHighlights.REMOVED]));
|
||||
}
|
||||
if (section.b) {
|
||||
// Avoiding a.push(...b) because that causes callstack overflows for
|
||||
// large b, which can occur when large files are added removed.
|
||||
lines = lines.concat(this._deltaLinesFromRows(
|
||||
lines = lines.concat(this._linesFromRows(
|
||||
GrDiffLine.Type.ADD, section.b, offsetRight,
|
||||
section[DiffHighlights.ADDED]));
|
||||
}
|
||||
@@ -264,7 +333,7 @@
|
||||
/**
|
||||
* @return {!Array<!Object>} Array of GrDiffLines
|
||||
*/
|
||||
_deltaLinesFromRows(lineType, rows, offset, opt_highlights) {
|
||||
_linesFromRows(lineType, rows, offset, opt_highlights) {
|
||||
// Normalize highlights if they have been passed.
|
||||
if (opt_highlights) {
|
||||
opt_highlights = this._normalizeIntralineHighlights(rows,
|
||||
@@ -294,67 +363,6 @@
|
||||
return line;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Take rows of a shared diff section and produce an array of corresponding
|
||||
* (potentially collapsed) groups.
|
||||
* @param {!Array<string>} lines
|
||||
* @param {number} numLines
|
||||
* @param {number} context
|
||||
* @param {number} startLineNumLeft
|
||||
* @param {number} startLineNumRight
|
||||
* @param {?string=} opt_sectionEnd String representing whether this is the
|
||||
* first section or the last section or neither. Use the values 'first',
|
||||
* 'last' and null respectively.
|
||||
* @return {!Array<!Object>} Array of GrDiffGroup
|
||||
*/
|
||||
_sharedGroupsFromLines(lines, numLines, context, startLineNumLeft,
|
||||
startLineNumRight, opt_sectionEnd) {
|
||||
// Find the hidden range based on the user's context preference. If this
|
||||
// is the first or the last section of the diff, make sure the collapsed
|
||||
// part of the section extends to the edge of the file.
|
||||
const hiddenRangeStart = opt_sectionEnd === 'first' ? 0 : context;
|
||||
const hiddenRangeEnd = opt_sectionEnd === 'last' ?
|
||||
numLines : numLines - context;
|
||||
|
||||
const result = [];
|
||||
// If there is a range to hide.
|
||||
if (context !== WHOLE_FILE && hiddenRangeEnd - hiddenRangeStart > 1) {
|
||||
const linesBeforeCtx = [];
|
||||
const hiddenLines = [];
|
||||
const linesAfterCtx = [];
|
||||
for (const line of lines) {
|
||||
// In the case there are no changes, these are the same.
|
||||
// In the case of ignored whitespace changes, either only one is set,
|
||||
// or the are the same.
|
||||
const lineOffset = line.beforeNumber ?
|
||||
line.beforeNumber - startLineNumLeft - 1 :
|
||||
line.afterNumber - startLineNumRight - 1;
|
||||
if (lineOffset < hiddenRangeStart) linesBeforeCtx.push(line);
|
||||
else if (hiddenRangeEnd <= lineOffset) linesAfterCtx.push(line);
|
||||
else hiddenLines.push(line);
|
||||
}
|
||||
|
||||
if (linesBeforeCtx.length > 0) {
|
||||
result.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesBeforeCtx));
|
||||
}
|
||||
|
||||
const ctxLine = new GrDiffLine(GrDiffLine.Type.CONTEXT_CONTROL);
|
||||
ctxLine.contextGroups =
|
||||
[new GrDiffGroup(GrDiffGroup.Type.BOTH, hiddenLines)];
|
||||
result.push(new GrDiffGroup(GrDiffGroup.Type.CONTEXT_CONTROL,
|
||||
[ctxLine]));
|
||||
|
||||
if (linesAfterCtx.length > 0) {
|
||||
result.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesAfterCtx));
|
||||
}
|
||||
} else {
|
||||
result.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, lines));
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
_makeFileComments() {
|
||||
const line = new GrDiffLine(GrDiffLine.Type.BOTH);
|
||||
line.beforeNumber = GrDiffLine.FILE;
|
||||
|
||||
@@ -264,6 +264,110 @@ limitations under the License.
|
||||
});
|
||||
});
|
||||
|
||||
test('for interleaved ab and common: true chunks', () => {
|
||||
element.context = 10;
|
||||
const content = [
|
||||
{a: ['all work and no play make andybons a dull boy']},
|
||||
{ab: new Array(3)
|
||||
.fill('all work and no play make jill a dull girl')},
|
||||
{
|
||||
a: new Array(3).fill(
|
||||
'all work and no play make jill a dull girl'),
|
||||
b: new Array(3).fill(
|
||||
' all work and no play make jill a dull girl'),
|
||||
common: true,
|
||||
},
|
||||
{ab: new Array(3)
|
||||
.fill('all work and no play make jill a dull girl')},
|
||||
{
|
||||
a: new Array(3).fill(
|
||||
'all work and no play make jill a dull girl'),
|
||||
b: new Array(3).fill(
|
||||
' all work and no play make jill a dull girl'),
|
||||
common: true,
|
||||
},
|
||||
{ab: new Array(3)
|
||||
.fill('all work and no play make jill a dull girl')},
|
||||
];
|
||||
|
||||
return element.process(content).then(() => {
|
||||
const groups = element.groups;
|
||||
|
||||
// group[0] is the file group
|
||||
// group[1] is the "a" group
|
||||
|
||||
// The first three interleaved chunks are completely shown because
|
||||
// they are part of the context (3 * 3 <= 10)
|
||||
|
||||
assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
|
||||
assert.equal(groups[2].lines.length, 3);
|
||||
for (const l of groups[2].lines) {
|
||||
assert.equal(
|
||||
l.text, 'all work and no play make jill a dull girl');
|
||||
}
|
||||
|
||||
assert.equal(groups[3].type, GrDiffGroup.Type.DELTA);
|
||||
assert.equal(groups[3].lines.length, 6);
|
||||
assert.equal(groups[3].adds.length, 3);
|
||||
assert.equal(groups[3].removes.length, 3);
|
||||
for (const l of groups[3].removes) {
|
||||
assert.equal(
|
||||
l.text, 'all work and no play make jill a dull girl');
|
||||
}
|
||||
for (const l of groups[3].adds) {
|
||||
assert.equal(
|
||||
l.text, ' all work and no play make jill a dull girl');
|
||||
}
|
||||
|
||||
assert.equal(groups[4].type, GrDiffGroup.Type.BOTH);
|
||||
assert.equal(groups[4].lines.length, 3);
|
||||
for (const l of groups[4].lines) {
|
||||
assert.equal(
|
||||
l.text, 'all work and no play make jill a dull girl');
|
||||
}
|
||||
|
||||
// The next chunk is partially shown, so it results in two groups
|
||||
|
||||
assert.equal(groups[5].type, GrDiffGroup.Type.DELTA);
|
||||
assert.equal(groups[5].lines.length, 2);
|
||||
assert.equal(groups[5].adds.length, 1);
|
||||
assert.equal(groups[5].removes.length, 1);
|
||||
for (const l of groups[5].removes) {
|
||||
assert.equal(
|
||||
l.text, 'all work and no play make jill a dull girl');
|
||||
}
|
||||
for (const l of groups[5].adds) {
|
||||
assert.equal(
|
||||
l.text, ' all work and no play make jill a dull girl');
|
||||
}
|
||||
|
||||
assert.equal(groups[6].type, GrDiffGroup.Type.CONTEXT_CONTROL);
|
||||
assert.equal(groups[6].lines[0].contextGroups.length, 2);
|
||||
|
||||
assert.equal(groups[6].lines[0].contextGroups[0].lines.length, 4);
|
||||
assert.equal(groups[6].lines[0].contextGroups[0].removes.length, 2);
|
||||
assert.equal(groups[6].lines[0].contextGroups[0].adds.length, 2);
|
||||
for (const l of groups[6].lines[0].contextGroups[0].removes) {
|
||||
assert.equal(
|
||||
l.text, 'all work and no play make jill a dull girl');
|
||||
}
|
||||
for (const l of groups[6].lines[0].contextGroups[0].adds) {
|
||||
assert.equal(
|
||||
l.text, ' all work and no play make jill a dull girl');
|
||||
}
|
||||
|
||||
// The final chunk is completely hidden
|
||||
assert.equal(
|
||||
groups[6].lines[0].contextGroups[1].type,
|
||||
GrDiffGroup.Type.BOTH);
|
||||
assert.equal(groups[6].lines[0].contextGroups[1].lines.length, 3);
|
||||
for (const l of groups[6].lines[0].contextGroups[1].lines) {
|
||||
assert.equal(
|
||||
l.text, 'all work and no play make jill a dull girl');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('in the middle, larger than context', () => {
|
||||
element.context = 10;
|
||||
const content = [
|
||||
@@ -506,10 +610,13 @@ limitations under the License.
|
||||
sandbox.stub(element, 'async');
|
||||
element._isScrolling = true;
|
||||
element.process(content);
|
||||
// Just the files group - no more processing during scrolling.
|
||||
assert.equal(element.groups.length, 1);
|
||||
|
||||
element._isScrolling = false;
|
||||
element.process(content);
|
||||
assert.equal(element.groups.length, 33);
|
||||
// More groups have been processed. How many does not matter here.
|
||||
assert.isAtLeast(element.groups.length, 2);
|
||||
});
|
||||
|
||||
test('image diffs', () => {
|
||||
@@ -528,22 +635,25 @@ limitations under the License.
|
||||
assert.equal(element.groups[0].lines.length, 1);
|
||||
});
|
||||
|
||||
|
||||
suite('gr-diff-processor helpers', () => {
|
||||
suite('_processNext', () => {
|
||||
let rows;
|
||||
|
||||
setup(() => {
|
||||
rows = loremIpsum.split(' ');
|
||||
});
|
||||
|
||||
test('_processNext WHOLE_FILE', () => {
|
||||
test('WHOLE_FILE', () => {
|
||||
element.context = WHOLE_FILE;
|
||||
const state = {
|
||||
lineNums: {left: 10, right: 100},
|
||||
sectionIndex: 1,
|
||||
};
|
||||
const result = element._processNext(
|
||||
state, {ab: rows}, 3);
|
||||
const sections = [
|
||||
{a: ['foo']},
|
||||
{ab: rows},
|
||||
{a: ['bar']},
|
||||
];
|
||||
const result = element._processNext(state, sections);
|
||||
|
||||
// Results in one, uncollapsed group with all rows.
|
||||
assert.equal(result.groups.length, 1);
|
||||
@@ -564,14 +674,18 @@ limitations under the License.
|
||||
state.lineNums.right + rows.length);
|
||||
});
|
||||
|
||||
test('_processNext context', () => {
|
||||
test('with context', () => {
|
||||
element.context = 10;
|
||||
const state = {
|
||||
lineNums: {left: 10, right: 100},
|
||||
sectionIndex: 1,
|
||||
};
|
||||
const result = element._processNext(
|
||||
state, {ab: rows}, 3);
|
||||
const sections = [
|
||||
{a: ['foo']},
|
||||
{ab: rows},
|
||||
{a: ['bar']},
|
||||
];
|
||||
const result = element._processNext(state, sections);
|
||||
const expectedCollapseSize = rows.length - 2 * element.context;
|
||||
|
||||
assert.equal(result.groups.length, 3, 'Results in three groups');
|
||||
@@ -587,14 +701,18 @@ limitations under the License.
|
||||
expectedCollapseSize);
|
||||
});
|
||||
|
||||
test('_processNext first', () => {
|
||||
test('first', () => {
|
||||
element.context = 10;
|
||||
const state = {
|
||||
lineNums: {left: 10, right: 100},
|
||||
sectionIndex: 0,
|
||||
};
|
||||
const result = element._processNext(
|
||||
state, {ab: rows}, 3);
|
||||
const sections = [
|
||||
{ab: rows},
|
||||
{a: ['foo']},
|
||||
{a: ['bar']},
|
||||
];
|
||||
const result = element._processNext(state, sections);
|
||||
const expectedCollapseSize = rows.length - element.context;
|
||||
|
||||
assert.equal(result.groups.length, 2, 'Results in two groups');
|
||||
@@ -608,7 +726,7 @@ limitations under the License.
|
||||
expectedCollapseSize);
|
||||
});
|
||||
|
||||
test('_processNext few-rows', () => {
|
||||
test('few-rows', () => {
|
||||
// Only ten rows.
|
||||
rows = rows.slice(0, 10);
|
||||
element.context = 10;
|
||||
@@ -616,32 +734,48 @@ limitations under the License.
|
||||
lineNums: {left: 10, right: 100},
|
||||
sectionIndex: 0,
|
||||
};
|
||||
const result = element._processNext(
|
||||
state, {ab: rows}, 3);
|
||||
const sections = [
|
||||
{ab: rows},
|
||||
{a: ['foo']},
|
||||
{a: ['bar']},
|
||||
];
|
||||
const result = element._processNext(state, sections);
|
||||
|
||||
// Results in one uncollapsed group with all rows.
|
||||
assert.equal(result.groups.length, 1, 'Results in one group');
|
||||
assert.equal(result.groups[0].lines.length, rows.length);
|
||||
});
|
||||
|
||||
test('_processNext no single line collapse', () => {
|
||||
test('no single line collapse', () => {
|
||||
rows = rows.slice(0, 7);
|
||||
element.context = 3;
|
||||
const state = {
|
||||
lineNums: {left: 10, right: 100},
|
||||
sectionIndex: 1,
|
||||
};
|
||||
const result = element._processNext(
|
||||
state, {ab: rows}, 3);
|
||||
const sections = [
|
||||
{a: ['foo']},
|
||||
{ab: rows},
|
||||
{a: ['bar']},
|
||||
];
|
||||
const result = element._processNext(state, sections);
|
||||
|
||||
// Results in one uncollapsed group with all rows.
|
||||
assert.equal(result.groups.length, 1, 'Results in one group');
|
||||
assert.equal(result.groups[0].lines.length, rows.length);
|
||||
});
|
||||
});
|
||||
|
||||
test('_deltaLinesFromRows', () => {
|
||||
suite('gr-diff-processor helpers', () => {
|
||||
let rows;
|
||||
|
||||
setup(() => {
|
||||
rows = loremIpsum.split(' ');
|
||||
});
|
||||
|
||||
test('_linesFromRows', () => {
|
||||
const startLineNum = 10;
|
||||
let result = element._deltaLinesFromRows(GrDiffLine.Type.ADD, rows,
|
||||
let result = element._linesFromRows(GrDiffLine.Type.ADD, rows,
|
||||
startLineNum + 1);
|
||||
|
||||
assert.equal(result.length, rows.length);
|
||||
@@ -652,7 +786,7 @@ limitations under the License.
|
||||
startLineNum + rows.length);
|
||||
assert.notOk(result[result.length - 1].beforeNumber);
|
||||
|
||||
result = element._deltaLinesFromRows(GrDiffLine.Type.REMOVE, rows,
|
||||
result = element._linesFromRows(GrDiffLine.Type.REMOVE, rows,
|
||||
startLineNum + 1);
|
||||
|
||||
assert.equal(result.length, rows.length);
|
||||
|
||||
@@ -22,20 +22,35 @@
|
||||
|
||||
/**
|
||||
* 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{!Array<!GrDiffLine>} */
|
||||
/** @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>} */
|
||||
/** @type {!Array<!GrDiffLine>} */
|
||||
this.adds = [];
|
||||
/** @type{!Array<!GrDiffLine>} */
|
||||
/** @type {!Array<!GrDiffLine>} */
|
||||
this.removes = [];
|
||||
|
||||
/** @type{boolean|undefined} */
|
||||
this.dueToRebase = undefined;
|
||||
|
||||
/** Both start and end line are inclusive. */
|
||||
this.lineRange = {
|
||||
left: {start: null, end: null},
|
||||
right: {start: null, end: null},
|
||||
@@ -46,8 +61,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
GrDiffGroup.prototype.element = null;
|
||||
|
||||
/** @enum {string} */
|
||||
GrDiffGroup.Type = {
|
||||
/** Unchanged context. */
|
||||
BOTH: 'both',
|
||||
@@ -59,6 +73,139 @@
|
||||
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);
|
||||
|
||||
@@ -77,6 +224,7 @@
|
||||
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) {
|
||||
|
||||
@@ -30,12 +30,9 @@ limitations under the License.
|
||||
suite('gr-diff-group tests', () => {
|
||||
test('delta line pairs', () => {
|
||||
let group = new GrDiffGroup(GrDiffGroup.Type.DELTA);
|
||||
const l1 = new GrDiffLine(GrDiffLine.Type.ADD);
|
||||
const l2 = new GrDiffLine(GrDiffLine.Type.ADD);
|
||||
const l3 = new GrDiffLine(GrDiffLine.Type.REMOVE);
|
||||
l1.afterNumber = 128;
|
||||
l2.afterNumber = 129;
|
||||
l3.beforeNumber = 64;
|
||||
const l1 = new GrDiffLine(GrDiffLine.Type.ADD, 0, 128);
|
||||
const l2 = new GrDiffLine(GrDiffLine.Type.ADD, 0, 129);
|
||||
const l3 = new GrDiffLine(GrDiffLine.Type.REMOVE, 64, 0);
|
||||
group.addLine(l1);
|
||||
group.addLine(l2);
|
||||
group.addLine(l3);
|
||||
@@ -66,17 +63,9 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('group/header line pairs', () => {
|
||||
const l1 = new GrDiffLine(GrDiffLine.Type.BOTH);
|
||||
l1.beforeNumber = 64;
|
||||
l1.afterNumber = 128;
|
||||
|
||||
const l2 = new GrDiffLine(GrDiffLine.Type.BOTH);
|
||||
l2.beforeNumber = 65;
|
||||
l2.afterNumber = 129;
|
||||
|
||||
const l3 = new GrDiffLine(GrDiffLine.Type.BOTH);
|
||||
l3.beforeNumber = 66;
|
||||
l3.afterNumber = 130;
|
||||
const l1 = new GrDiffLine(GrDiffLine.Type.BOTH, 64, 128);
|
||||
const l2 = new GrDiffLine(GrDiffLine.Type.BOTH, 65, 129);
|
||||
const l3 = new GrDiffLine(GrDiffLine.Type.BOTH, 66, 130);
|
||||
|
||||
let group = new GrDiffGroup(GrDiffGroup.Type.BOTH, [l1, l2, l3]);
|
||||
|
||||
@@ -124,6 +113,97 @@ limitations under the License.
|
||||
assert.throws(group.addLine.bind(group, l2));
|
||||
assert.doesNotThrow(group.addLine.bind(group, l3));
|
||||
});
|
||||
|
||||
suite('hideInContextControl', () => {
|
||||
let groups;
|
||||
setup(() => {
|
||||
groups = [
|
||||
new GrDiffGroup(GrDiffGroup.Type.BOTH, [
|
||||
new GrDiffLine(GrDiffLine.Type.BOTH, 5, 7),
|
||||
new GrDiffLine(GrDiffLine.Type.BOTH, 6, 8),
|
||||
new GrDiffLine(GrDiffLine.Type.BOTH, 7, 9),
|
||||
]),
|
||||
new GrDiffGroup(GrDiffGroup.Type.DELTA, [
|
||||
new GrDiffLine(GrDiffLine.Type.REMOVE, 8),
|
||||
new GrDiffLine(GrDiffLine.Type.ADD, 0, 10),
|
||||
new GrDiffLine(GrDiffLine.Type.REMOVE, 9),
|
||||
new GrDiffLine(GrDiffLine.Type.ADD, 0, 11),
|
||||
new GrDiffLine(GrDiffLine.Type.REMOVE, 10),
|
||||
new GrDiffLine(GrDiffLine.Type.ADD, 0, 12),
|
||||
]),
|
||||
new GrDiffGroup(GrDiffGroup.Type.BOTH, [
|
||||
new GrDiffLine(GrDiffLine.Type.BOTH, 11, 13),
|
||||
new GrDiffLine(GrDiffLine.Type.BOTH, 12, 14),
|
||||
new GrDiffLine(GrDiffLine.Type.BOTH, 13, 15),
|
||||
]),
|
||||
];
|
||||
});
|
||||
|
||||
test('hides hidden groups in context control', () => {
|
||||
const collapsedGroups = GrDiffGroup.hideInContextControl(groups, 3, 6);
|
||||
assert.equal(collapsedGroups.length, 3);
|
||||
|
||||
assert.equal(collapsedGroups[0], groups[0]);
|
||||
|
||||
assert.equal(collapsedGroups[1].type, GrDiffGroup.Type.CONTEXT_CONTROL);
|
||||
assert.equal(collapsedGroups[1].lines.length, 1);
|
||||
assert.equal(
|
||||
collapsedGroups[1].lines[0].type, GrDiffLine.Type.CONTEXT_CONTROL);
|
||||
assert.equal(
|
||||
collapsedGroups[1].lines[0].contextGroups.length, 1);
|
||||
assert.equal(
|
||||
collapsedGroups[1].lines[0].contextGroups[0], groups[1]);
|
||||
|
||||
assert.equal(collapsedGroups[2], groups[2]);
|
||||
});
|
||||
|
||||
test('splits partially hidden groups', () => {
|
||||
const collapsedGroups = GrDiffGroup.hideInContextControl(groups, 4, 7);
|
||||
assert.equal(collapsedGroups.length, 4);
|
||||
assert.equal(collapsedGroups[0], groups[0]);
|
||||
|
||||
assert.equal(collapsedGroups[1].type, GrDiffGroup.Type.DELTA);
|
||||
assert.deepEqual(collapsedGroups[1].adds, [groups[1].adds[0]]);
|
||||
assert.deepEqual(collapsedGroups[1].removes, [groups[1].removes[0]]);
|
||||
|
||||
assert.equal(collapsedGroups[2].type, GrDiffGroup.Type.CONTEXT_CONTROL);
|
||||
assert.equal(collapsedGroups[2].lines.length, 1);
|
||||
assert.equal(
|
||||
collapsedGroups[2].lines[0].type, GrDiffLine.Type.CONTEXT_CONTROL);
|
||||
assert.equal(
|
||||
collapsedGroups[2].lines[0].contextGroups.length, 2);
|
||||
|
||||
assert.equal(
|
||||
collapsedGroups[2].lines[0].contextGroups[0].type,
|
||||
GrDiffGroup.Type.DELTA);
|
||||
assert.deepEqual(
|
||||
collapsedGroups[2].lines[0].contextGroups[0].adds,
|
||||
groups[1].adds.slice(1));
|
||||
assert.deepEqual(
|
||||
collapsedGroups[2].lines[0].contextGroups[0].removes,
|
||||
groups[1].removes.slice(1));
|
||||
|
||||
assert.equal(
|
||||
collapsedGroups[2].lines[0].contextGroups[1].type,
|
||||
GrDiffGroup.Type.BOTH);
|
||||
assert.deepEqual(
|
||||
collapsedGroups[2].lines[0].contextGroups[1].lines,
|
||||
[groups[2].lines[0]]);
|
||||
|
||||
assert.equal(collapsedGroups[3].type, GrDiffGroup.Type.BOTH);
|
||||
assert.deepEqual(collapsedGroups[3].lines, groups[2].lines.slice(1));
|
||||
});
|
||||
|
||||
test('groups unchanged if the hidden range is empty', () => {
|
||||
assert.deepEqual(
|
||||
GrDiffGroup.hideInContextControl(groups, 0, 0), groups);
|
||||
});
|
||||
|
||||
test('groups unchanged if there is only 1 line to hide', () => {
|
||||
assert.deepEqual(
|
||||
GrDiffGroup.hideInContextControl(groups, 3, 4), groups);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -20,21 +20,27 @@
|
||||
// Prevent redefinition.
|
||||
if (window.GrDiffLine) { return; }
|
||||
|
||||
function GrDiffLine(type) {
|
||||
/**
|
||||
* @param {GrDiffLine.Type} type
|
||||
* @param {number|string=} opt_beforeLine
|
||||
* @param {number|string=} opt_afterLine
|
||||
*/
|
||||
function GrDiffLine(type, opt_beforeLine, opt_afterLine) {
|
||||
this.type = type;
|
||||
|
||||
/** @type {number|string} */
|
||||
this.beforeNumber = opt_beforeLine || 0;
|
||||
/** @type {number|string} */
|
||||
this.afterNumber = opt_afterLine || 0;
|
||||
|
||||
this.highlights = [];
|
||||
|
||||
/** @type {?Array<Object>} ?Array<!GrDiffGroup> */
|
||||
this.contextGroups = null;
|
||||
|
||||
this.text = '';
|
||||
}
|
||||
|
||||
/** @type {number|string} */
|
||||
GrDiffLine.prototype.afterNumber = 0;
|
||||
|
||||
/** @type {number|string} */
|
||||
GrDiffLine.prototype.beforeNumber = 0;
|
||||
|
||||
/** @type {?Array<Object>} ?Array<!GrDiffLine> */
|
||||
GrDiffLine.prototype.contextGroups = null;
|
||||
|
||||
GrDiffLine.prototype.text = '';
|
||||
|
||||
GrDiffLine.Type = {
|
||||
ADD: 'add',
|
||||
|
||||
@@ -136,6 +136,8 @@ limitations under the License.
|
||||
.content.remove {
|
||||
background-color: var(--light-remove-highlight-color);
|
||||
}
|
||||
|
||||
/* dueToRebase */
|
||||
.dueToRebase .content.add .intraline,
|
||||
.delta.total.dueToRebase .content.add {
|
||||
background-color: var(--dark-rebased-add-highlight-color);
|
||||
@@ -150,6 +152,17 @@ limitations under the License.
|
||||
.dueToRebase .content.remove {
|
||||
background-color: var(--light-remove-add-highlight-color);
|
||||
}
|
||||
|
||||
/* ignoredWhitespaceOnly */
|
||||
.ignoredWhitespaceOnly .content.add .intraline,
|
||||
.delta.total.ignoredWhitespaceOnly .content.add,
|
||||
.ignoredWhitespaceOnly .content.add,
|
||||
.ignoredWhitespaceOnly .content.remove .intraline,
|
||||
.delta.total.ignoredWhitespaceOnly .content.remove,
|
||||
.ignoredWhitespaceOnly .content.remove {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.content .contentText:empty:after {
|
||||
/* Newline, to ensure empty lines are one line-height tall. */
|
||||
content: '\A';
|
||||
|
||||
Reference in New Issue
Block a user