2016-03-13 21:23:11 -04:00
|
|
|
// 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, GrDiffGroup, GrDiffLine) {
|
|
|
|
'use strict';
|
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
const HTML_ENTITY_PATTERN = /[&<>"'`\/]/g;
|
|
|
|
const HTML_ENTITY_MAP = {
|
2017-01-30 16:42:46 -08:00
|
|
|
'&': '&',
|
|
|
|
'<': '<',
|
|
|
|
'>': '>',
|
|
|
|
'"': '"',
|
|
|
|
'\'': ''',
|
|
|
|
'/': '/',
|
|
|
|
'`': '`',
|
|
|
|
};
|
|
|
|
|
2016-06-27 09:36:36 -07:00
|
|
|
// Prevent redefinition.
|
|
|
|
if (window.GrDiffBuilder) { return; }
|
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
const REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
|
2016-07-11 17:32:53 -07:00
|
|
|
|
2017-07-31 14:33:16 -07:00
|
|
|
function GrDiffBuilder(diff, comments, prefs, projectName, outputEl, layers) {
|
2016-05-23 17:18:43 -07:00
|
|
|
this._diff = diff;
|
2016-03-20 14:14:20 -04:00
|
|
|
this._comments = comments;
|
2016-03-15 18:58:46 -04:00
|
|
|
this._prefs = prefs;
|
2017-07-31 14:33:16 -07:00
|
|
|
this._projectName = projectName;
|
2016-03-13 21:23:11 -04:00
|
|
|
this._outputEl = outputEl;
|
2016-06-27 12:19:21 -07:00
|
|
|
this.groups = [];
|
2016-07-14 12:31:09 -07:00
|
|
|
|
|
|
|
this.layers = layers || [];
|
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
for (const layer of this.layers) {
|
2016-11-17 12:03:51 -08:00
|
|
|
if (layer.addListener) {
|
|
|
|
layer.addListener(this._handleLayerUpdate.bind(this));
|
|
|
|
}
|
2017-05-16 13:45:17 -07:00
|
|
|
}
|
2016-03-13 21:23:11 -04:00
|
|
|
}
|
|
|
|
|
2016-03-17 14:39:42 -04:00
|
|
|
GrDiffBuilder.LESS_THAN_CODE = '<'.charCodeAt(0);
|
|
|
|
GrDiffBuilder.GREATER_THAN_CODE = '>'.charCodeAt(0);
|
|
|
|
GrDiffBuilder.AMPERSAND_CODE = '&'.charCodeAt(0);
|
|
|
|
GrDiffBuilder.SEMICOLON_CODE = ';'.charCodeAt(0);
|
|
|
|
|
|
|
|
GrDiffBuilder.LINE_FEED_HTML =
|
2016-03-25 16:48:13 -04:00
|
|
|
'<span class="style-scope gr-diff br"></span>';
|
2016-03-17 14:39:42 -04:00
|
|
|
|
2016-03-13 21:23:11 -04:00
|
|
|
GrDiffBuilder.GroupType = {
|
|
|
|
ADDED: 'b',
|
|
|
|
BOTH: 'ab',
|
|
|
|
REMOVED: 'a',
|
|
|
|
};
|
|
|
|
|
2016-03-25 09:02:30 -04:00
|
|
|
GrDiffBuilder.Highlights = {
|
|
|
|
ADDED: 'edit_b',
|
|
|
|
REMOVED: 'edit_a',
|
|
|
|
};
|
|
|
|
|
2016-03-20 14:14:20 -04:00
|
|
|
GrDiffBuilder.Side = {
|
|
|
|
LEFT: 'left',
|
|
|
|
RIGHT: 'right',
|
|
|
|
};
|
|
|
|
|
2016-06-01 15:13:33 -07:00
|
|
|
GrDiffBuilder.ContextButtonType = {
|
|
|
|
ABOVE: 'above',
|
|
|
|
BELOW: 'below',
|
|
|
|
ALL: 'all',
|
|
|
|
};
|
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
const PARTIAL_CONTEXT_AMOUNT = 10;
|
2016-06-01 15:13:33 -07:00
|
|
|
|
2016-10-31 14:35:35 -07:00
|
|
|
/**
|
|
|
|
* Abstract method
|
|
|
|
* @param {string} outputEl
|
|
|
|
* @param {number} fontSize
|
|
|
|
*/
|
|
|
|
GrDiffBuilder.prototype.addColumns = function() {
|
|
|
|
throw Error('Subclasses must implement addColumns');
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Abstract method
|
|
|
|
* @param {Object} group
|
|
|
|
*/
|
|
|
|
GrDiffBuilder.prototype.buildSectionElement = function() {
|
2016-05-23 15:31:21 -07:00
|
|
|
throw Error('Subclasses must implement buildGroupElement');
|
|
|
|
};
|
|
|
|
|
2016-03-16 18:23:56 -04:00
|
|
|
GrDiffBuilder.prototype.emitGroup = function(group, opt_beforeSection) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const element = this.buildSectionElement(group);
|
2016-05-23 15:31:21 -07:00
|
|
|
this._outputEl.insertBefore(element, opt_beforeSection);
|
|
|
|
group.element = element;
|
|
|
|
};
|
|
|
|
|
|
|
|
GrDiffBuilder.prototype.renderSection = function(element) {
|
2017-05-16 13:45:17 -07:00
|
|
|
for (let i = 0; i < this.groups.length; i++) {
|
|
|
|
const group = this.groups[i];
|
2016-05-23 15:31:21 -07:00
|
|
|
if (group.element === element) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const newElement = this.buildSectionElement(group);
|
2016-05-23 15:31:21 -07:00
|
|
|
group.element.parentElement.replaceChild(newElement, group.element);
|
|
|
|
group.element = newElement;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-06-09 16:08:04 -07:00
|
|
|
GrDiffBuilder.prototype.getGroupsByLineRange = function(
|
2016-05-23 15:31:21 -07:00
|
|
|
startLine, endLine, opt_side) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const groups = [];
|
|
|
|
for (let i = 0; i < this.groups.length; i++) {
|
|
|
|
const group = this.groups[i];
|
2016-05-23 15:31:21 -07:00
|
|
|
if (group.lines.length === 0) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-05-16 13:45:17 -07:00
|
|
|
let groupStartLine = 0;
|
|
|
|
let groupEndLine = 0;
|
2016-06-30 16:59:53 -07:00
|
|
|
if (opt_side) {
|
|
|
|
groupStartLine = group.lineRange[opt_side].start;
|
|
|
|
groupEndLine = group.lineRange[opt_side].end;
|
2016-06-09 16:08:04 -07:00
|
|
|
}
|
2016-06-30 16:59:53 -07:00
|
|
|
|
2016-06-09 16:08:04 -07:00
|
|
|
if (groupStartLine === 0) { // Line was removed or added.
|
|
|
|
groupStartLine = groupEndLine;
|
|
|
|
}
|
2017-07-05 11:17:26 -07:00
|
|
|
if (groupEndLine === 0) { // Line was removed or added.
|
2016-06-09 16:08:04 -07:00
|
|
|
groupEndLine = groupStartLine;
|
2016-05-23 15:31:21 -07:00
|
|
|
}
|
|
|
|
if (startLine <= groupEndLine && endLine >= groupStartLine) {
|
2016-06-09 16:08:04 -07:00
|
|
|
groups.push(group);
|
2016-05-23 15:31:21 -07:00
|
|
|
}
|
|
|
|
}
|
2016-06-09 16:08:04 -07:00
|
|
|
return groups;
|
|
|
|
};
|
|
|
|
|
2016-07-15 17:08:22 -07:00
|
|
|
GrDiffBuilder.prototype.getContentByLine = function(lineNumber, opt_side,
|
|
|
|
opt_root) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const root = Polymer.dom(opt_root || this._outputEl);
|
|
|
|
const sideSelector = opt_side ? ('.' + opt_side) : '';
|
2016-07-15 17:08:22 -07:00
|
|
|
return root.querySelector('td.lineNum[data-value="' + lineNumber +
|
|
|
|
'"]' + sideSelector + ' ~ td.content .contentText');
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find line elements or line objects by a range of line numbers and a side.
|
|
|
|
*
|
2017-08-11 16:32:47 -07:00
|
|
|
* @param {number} start The first line number
|
|
|
|
* @param {number} end The last line number
|
|
|
|
* @param {string} opt_side The side of the range. Either 'left' or 'right'.
|
|
|
|
* @param {!Array<GrDiffLine>} out_lines The output list of line objects. Use
|
2016-07-15 17:08:22 -07:00
|
|
|
* null if not desired.
|
2017-08-11 16:32:47 -07:00
|
|
|
* @param {!Array<HTMLElement>} out_elements The output list of line elements.
|
2016-07-15 17:08:22 -07:00
|
|
|
* Use null if not desired.
|
|
|
|
*/
|
|
|
|
GrDiffBuilder.prototype.findLinesByRange = function(start, end, opt_side,
|
|
|
|
out_lines, out_elements) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const groups = this.getGroupsByLineRange(start, end, opt_side);
|
|
|
|
for (const group of groups) {
|
|
|
|
let content = null;
|
|
|
|
for (const line of group.lines) {
|
2016-07-15 17:08:22 -07:00
|
|
|
if ((opt_side === 'left' && line.type === GrDiffLine.Type.ADD) ||
|
|
|
|
(opt_side === 'right' && line.type === GrDiffLine.Type.REMOVE)) {
|
2017-05-16 13:45:17 -07:00
|
|
|
continue;
|
2016-07-15 17:08:22 -07:00
|
|
|
}
|
2017-05-16 13:45:17 -07:00
|
|
|
const lineNumber = opt_side === 'left' ?
|
2016-07-15 17:08:22 -07:00
|
|
|
line.beforeNumber : line.afterNumber;
|
2017-05-16 13:45:17 -07:00
|
|
|
if (lineNumber < start || lineNumber > end) { continue; }
|
2016-07-15 17:08:22 -07:00
|
|
|
|
|
|
|
if (out_lines) { out_lines.push(line); }
|
|
|
|
if (out_elements) {
|
2016-07-21 22:19:25 -07:00
|
|
|
if (content) {
|
|
|
|
content = this._getNextContentOnSide(content, opt_side);
|
|
|
|
} else {
|
|
|
|
content = this.getContentByLine(lineNumber, opt_side,
|
|
|
|
group.element);
|
|
|
|
}
|
2016-07-15 17:08:22 -07:00
|
|
|
if (content) { out_elements.push(content); }
|
|
|
|
}
|
2017-05-16 13:45:17 -07:00
|
|
|
}
|
|
|
|
}
|
2016-07-15 17:08:22 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-09-13 15:49:58 -07:00
|
|
|
* Re-renders the DIV.contentText elements for the given side and range of
|
|
|
|
* diff content.
|
2016-07-15 17:08:22 -07:00
|
|
|
*/
|
|
|
|
GrDiffBuilder.prototype._renderContentByRange = function(start, end, side) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const lines = [];
|
|
|
|
const elements = [];
|
|
|
|
let line;
|
|
|
|
let el;
|
2016-07-15 17:08:22 -07:00
|
|
|
this.findLinesByRange(start, end, side, lines, elements);
|
2017-05-16 13:45:17 -07:00
|
|
|
for (let i = 0; i < lines.length; i++) {
|
2016-07-15 17:08:22 -07:00
|
|
|
line = lines[i];
|
|
|
|
el = elements[i];
|
2016-07-14 12:31:09 -07:00
|
|
|
el.parentElement.replaceChild(this._createTextEl(line, side).firstChild,
|
|
|
|
el);
|
2016-07-15 17:08:22 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-06-09 16:08:04 -07:00
|
|
|
GrDiffBuilder.prototype.getSectionsByLineRange = function(
|
|
|
|
startLine, endLine, opt_side) {
|
|
|
|
return this.getGroupsByLineRange(startLine, endLine, opt_side).map(
|
2017-05-16 13:45:17 -07:00
|
|
|
group => { return group.element; });
|
2016-04-29 22:25:51 +02:00
|
|
|
};
|
2016-03-13 21:23:11 -04:00
|
|
|
|
2016-03-22 16:34:03 -04:00
|
|
|
GrDiffBuilder.prototype._commentIsAtLineNum = function(side, lineNum) {
|
|
|
|
return this._commentLocations[side][lineNum] === true;
|
|
|
|
};
|
|
|
|
|
2016-06-28 10:33:33 -07:00
|
|
|
// TODO(wyatta): Move this completely into the processor.
|
2016-03-15 18:58:46 -04:00
|
|
|
GrDiffBuilder.prototype._insertContextGroups = function(groups, lines,
|
|
|
|
hiddenRange) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const linesBeforeCtx = lines.slice(0, hiddenRange[0]);
|
|
|
|
const hiddenLines = lines.slice(hiddenRange[0], hiddenRange[1]);
|
|
|
|
const linesAfterCtx = lines.slice(hiddenRange[1]);
|
2016-03-15 18:58:46 -04:00
|
|
|
|
|
|
|
if (linesBeforeCtx.length > 0) {
|
|
|
|
groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesBeforeCtx));
|
|
|
|
}
|
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
const ctxLine = new GrDiffLine(GrDiffLine.Type.CONTEXT_CONTROL);
|
2016-05-23 15:31:21 -07:00
|
|
|
ctxLine.contextGroup =
|
|
|
|
new GrDiffGroup(GrDiffGroup.Type.BOTH, hiddenLines);
|
2016-03-15 18:58:46 -04:00
|
|
|
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) {
|
2016-05-23 15:31:21 -07:00
|
|
|
if (!line.contextGroup || !line.contextGroup.lines.length) {
|
2016-03-15 18:58:46 -04:00
|
|
|
return null;
|
|
|
|
}
|
2016-06-01 15:13:33 -07:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
const td = this._createElement('td');
|
|
|
|
const showPartialLinks =
|
2016-06-01 15:13:33 -07:00
|
|
|
line.contextGroup.lines.length > PARTIAL_CONTEXT_AMOUNT;
|
|
|
|
|
|
|
|
if (showPartialLinks) {
|
|
|
|
td.appendChild(this._createContextButton(
|
|
|
|
GrDiffBuilder.ContextButtonType.ABOVE, section, line));
|
|
|
|
td.appendChild(document.createTextNode(' - '));
|
|
|
|
}
|
|
|
|
|
|
|
|
td.appendChild(this._createContextButton(
|
|
|
|
GrDiffBuilder.ContextButtonType.ALL, section, line));
|
|
|
|
|
|
|
|
if (showPartialLinks) {
|
|
|
|
td.appendChild(document.createTextNode(' - '));
|
|
|
|
td.appendChild(this._createContextButton(
|
|
|
|
GrDiffBuilder.ContextButtonType.BELOW, section, line));
|
|
|
|
}
|
|
|
|
|
|
|
|
return td;
|
|
|
|
};
|
|
|
|
|
|
|
|
GrDiffBuilder.prototype._createContextButton = function(type, section, line) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const contextLines = line.contextGroup.lines;
|
|
|
|
const context = PARTIAL_CONTEXT_AMOUNT;
|
2016-06-01 15:13:33 -07:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
const button = this._createElement('gr-button', 'showContext');
|
2016-03-15 18:58:46 -04:00
|
|
|
button.setAttribute('link', true);
|
2016-06-01 15:13:33 -07:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
let text;
|
|
|
|
const groups = []; // The groups that replace this one if tapped.
|
2016-06-01 15:13:33 -07:00
|
|
|
|
|
|
|
if (type === GrDiffBuilder.ContextButtonType.ALL) {
|
|
|
|
text = 'Show ' + contextLines.length + ' common line';
|
|
|
|
if (contextLines.length > 1) { text += 's'; }
|
|
|
|
groups.push(line.contextGroup);
|
|
|
|
} else if (type === GrDiffBuilder.ContextButtonType.ABOVE) {
|
|
|
|
text = '+' + context + '↑';
|
|
|
|
this._insertContextGroups(groups, contextLines,
|
|
|
|
[context, contextLines.length]);
|
|
|
|
} else if (type === GrDiffBuilder.ContextButtonType.BELOW) {
|
|
|
|
text = '+' + context + '↓';
|
|
|
|
this._insertContextGroups(groups, contextLines,
|
|
|
|
[0, contextLines.length - context]);
|
2016-03-15 18:58:46 -04:00
|
|
|
}
|
2016-06-01 15:13:33 -07:00
|
|
|
|
2016-03-15 18:58:46 -04:00
|
|
|
button.textContent = text;
|
2016-06-01 15:13:33 -07:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
button.addEventListener('tap', e => {
|
2016-03-16 18:23:56 -04:00
|
|
|
e.detail = {
|
2017-05-16 13:45:17 -07:00
|
|
|
groups,
|
|
|
|
section,
|
2016-03-16 18:23:56 -04:00
|
|
|
};
|
2016-03-15 18:58:46 -04:00
|
|
|
// Let it bubble up the DOM tree.
|
|
|
|
});
|
2016-06-01 15:13:33 -07:00
|
|
|
|
|
|
|
return button;
|
2016-03-15 18:58:46 -04:00
|
|
|
};
|
|
|
|
|
2016-03-20 14:14:20 -04:00
|
|
|
GrDiffBuilder.prototype._getCommentsForLine = function(comments, line,
|
|
|
|
opt_side) {
|
2016-03-24 17:59:35 -04:00
|
|
|
function byLineNum(lineNum) {
|
|
|
|
return function(c) {
|
|
|
|
return (c.line === lineNum) ||
|
2016-04-29 22:25:51 +02:00
|
|
|
(c.line === undefined && lineNum === GrDiffLine.FILE);
|
|
|
|
};
|
2016-03-24 17:59:35 -04:00
|
|
|
}
|
2017-05-16 13:45:17 -07:00
|
|
|
const leftComments =
|
2016-03-24 17:59:35 -04:00
|
|
|
comments[GrDiffBuilder.Side.LEFT].filter(byLineNum(line.beforeNumber));
|
2017-05-16 13:45:17 -07:00
|
|
|
const rightComments =
|
2016-03-24 17:59:35 -04:00
|
|
|
comments[GrDiffBuilder.Side.RIGHT].filter(byLineNum(line.afterNumber));
|
2016-03-20 14:14:20 -04:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
leftComments.forEach(c => { c.__commentSide = 'left'; });
|
|
|
|
rightComments.forEach(c => { c.__commentSide = 'right'; });
|
2017-01-30 12:03:13 -08:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
let result;
|
2016-03-20 14:14:20 -04:00
|
|
|
|
|
|
|
switch (opt_side) {
|
|
|
|
case GrDiffBuilder.Side.LEFT:
|
|
|
|
result = leftComments;
|
|
|
|
break;
|
|
|
|
case GrDiffBuilder.Side.RIGHT:
|
|
|
|
result = rightComments;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
result = leftComments.concat(rightComments);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2017-01-30 12:03:13 -08:00
|
|
|
GrDiffBuilder.prototype.createCommentThreadGroup = function(changeNum,
|
2017-07-31 14:33:16 -07:00
|
|
|
patchNum, path, isOnParent, range) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const threadGroupEl =
|
2017-01-27 15:33:08 -08:00
|
|
|
document.createElement('gr-diff-comment-thread-group');
|
|
|
|
threadGroupEl.changeNum = changeNum;
|
2017-02-09 07:34:23 -08:00
|
|
|
threadGroupEl.patchForNewThreads = patchNum;
|
2017-01-27 15:33:08 -08:00
|
|
|
threadGroupEl.path = path;
|
2017-03-13 16:40:01 -07:00
|
|
|
threadGroupEl.isOnParent = isOnParent;
|
2017-07-31 14:33:16 -07:00
|
|
|
threadGroupEl.projectName = this._projectName;
|
2017-01-27 15:33:08 -08:00
|
|
|
threadGroupEl.range = range;
|
|
|
|
return threadGroupEl;
|
2016-04-29 22:25:51 +02:00
|
|
|
};
|
2016-03-22 19:14:12 -04:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
GrDiffBuilder.prototype._commentThreadGroupForLine = function(line,
|
|
|
|
opt_side) {
|
|
|
|
const comments =
|
|
|
|
this._getCommentsForLine(this._comments, line, opt_side);
|
2016-03-20 14:14:20 -04:00
|
|
|
if (!comments || comments.length === 0) {
|
|
|
|
return null;
|
|
|
|
}
|
2016-03-22 19:14:12 -04:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
let patchNum = this._comments.meta.patchRange.patchNum;
|
|
|
|
let isOnParent = comments[0].side === 'PARENT' || false;
|
2016-03-22 19:14:12 -04:00
|
|
|
if (line.type === GrDiffLine.Type.REMOVE ||
|
2017-05-16 13:45:17 -07:00
|
|
|
opt_side === GrDiffBuilder.Side.LEFT) {
|
2016-03-22 19:14:12 -04:00
|
|
|
if (this._comments.meta.patchRange.basePatchNum === 'PARENT') {
|
2017-03-13 16:40:01 -07:00
|
|
|
isOnParent = true;
|
2016-03-22 19:14:12 -04:00
|
|
|
} else {
|
|
|
|
patchNum = this._comments.meta.patchRange.basePatchNum;
|
|
|
|
}
|
|
|
|
}
|
2017-05-16 13:45:17 -07:00
|
|
|
const threadGroupEl = this.createCommentThreadGroup(
|
2016-03-22 19:14:12 -04:00
|
|
|
this._comments.meta.changeNum,
|
|
|
|
patchNum,
|
|
|
|
this._comments.meta.path,
|
2017-07-31 14:33:16 -07:00
|
|
|
isOnParent);
|
2017-01-27 15:33:08 -08:00
|
|
|
threadGroupEl.comments = comments;
|
2016-08-24 14:44:21 -07:00
|
|
|
if (opt_side) {
|
2017-01-27 15:33:08 -08:00
|
|
|
threadGroupEl.setAttribute('data-side', opt_side);
|
2016-08-24 14:44:21 -07:00
|
|
|
}
|
2017-01-27 15:33:08 -08:00
|
|
|
return threadGroupEl;
|
2016-03-20 14:14:20 -04:00
|
|
|
};
|
|
|
|
|
2016-05-16 14:40:51 -07:00
|
|
|
GrDiffBuilder.prototype._createLineEl = function(line, number, type,
|
|
|
|
opt_class) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const td = this._createElement('td');
|
2016-05-16 14:40:51 -07:00
|
|
|
if (opt_class) {
|
|
|
|
td.classList.add(opt_class);
|
|
|
|
}
|
2017-06-06 16:59:22 -07:00
|
|
|
|
|
|
|
if (line.type === GrDiffLine.Type.REMOVE) {
|
|
|
|
td.setAttribute('aria-label', `${number} removed`);
|
|
|
|
} else if (line.type === GrDiffLine.Type.ADD) {
|
|
|
|
td.setAttribute('aria-label', `${number} added`);
|
|
|
|
}
|
|
|
|
|
2016-03-18 14:45:58 -04:00
|
|
|
if (line.type === GrDiffLine.Type.BLANK) {
|
|
|
|
return td;
|
|
|
|
} else if (line.type === GrDiffLine.Type.CONTEXT_CONTROL) {
|
2016-03-22 19:14:12 -04:00
|
|
|
td.classList.add('contextLineNum');
|
2016-03-15 18:58:46 -04:00
|
|
|
td.setAttribute('data-value', '@@');
|
2016-05-23 15:31:21 -07:00
|
|
|
} else if (line.type === GrDiffLine.Type.BOTH || line.type === type) {
|
2016-03-22 19:14:12 -04:00
|
|
|
td.classList.add('lineNum');
|
2016-03-15 18:58:46 -04:00
|
|
|
td.setAttribute('data-value', number);
|
2016-03-13 21:23:11 -04:00
|
|
|
}
|
|
|
|
return td;
|
|
|
|
};
|
|
|
|
|
2016-07-14 12:31:09 -07:00
|
|
|
GrDiffBuilder.prototype._createTextEl = function(line, opt_side) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const td = this._createElement('td');
|
|
|
|
const text = line.text;
|
2016-03-18 14:45:58 -04:00
|
|
|
if (line.type !== GrDiffLine.Type.BLANK) {
|
|
|
|
td.classList.add('content');
|
|
|
|
}
|
2016-03-13 21:23:11 -04:00
|
|
|
td.classList.add(line.type);
|
2017-05-16 13:45:17 -07:00
|
|
|
let html = this._escapeHTML(text);
|
2016-07-12 13:50:26 -07:00
|
|
|
html = this._addTabWrappers(html, this._prefs.tab_size);
|
2016-10-31 14:35:35 -07:00
|
|
|
if (!this._prefs.line_wrapping &&
|
|
|
|
this._textLength(text, this._prefs.tab_size) >
|
2016-04-11 21:17:38 -04:00
|
|
|
this._prefs.line_length) {
|
2016-03-17 14:39:42 -04:00
|
|
|
html = this._addNewlines(text, html);
|
|
|
|
}
|
2016-03-13 21:23:11 -04:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
const contentText = this._createElement('div', 'contentText');
|
2016-07-14 12:31:09 -07:00
|
|
|
if (opt_side) {
|
|
|
|
contentText.setAttribute('data-side', opt_side);
|
|
|
|
}
|
2016-07-01 09:30:44 -07:00
|
|
|
|
2016-03-13 21:23:11 -04:00
|
|
|
// If the html is equivalent to the text then it didn't get highlighted
|
|
|
|
// or escaped. Use textContent which is faster than innerHTML.
|
2016-04-11 21:17:38 -04:00
|
|
|
if (html === text) {
|
2016-07-01 09:30:44 -07:00
|
|
|
contentText.textContent = text;
|
2016-03-13 21:23:11 -04:00
|
|
|
} else {
|
2016-07-01 09:30:44 -07:00
|
|
|
contentText.innerHTML = html;
|
2016-03-13 21:23:11 -04:00
|
|
|
}
|
2016-07-01 09:30:44 -07:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
for (const layer of this.layers) {
|
2016-07-25 22:46:20 -07:00
|
|
|
layer.annotate(contentText, line);
|
2017-05-16 13:45:17 -07:00
|
|
|
}
|
2016-07-13 10:59:10 -07:00
|
|
|
|
2016-07-01 09:30:44 -07:00
|
|
|
td.appendChild(contentText);
|
|
|
|
|
2016-03-13 21:23:11 -04:00
|
|
|
return td;
|
|
|
|
};
|
|
|
|
|
2016-07-11 17:32:53 -07:00
|
|
|
/**
|
|
|
|
* Returns the text length after normalizing unicode and tabs.
|
2017-08-11 16:32:47 -07:00
|
|
|
* @return {number} The normalized length of the text.
|
2016-07-11 17:32:53 -07:00
|
|
|
*/
|
2016-04-11 21:17:38 -04:00
|
|
|
GrDiffBuilder.prototype._textLength = function(text, tabSize) {
|
2016-07-11 17:32:53 -07:00
|
|
|
text = text.replace(REGEX_ASTRAL_SYMBOL, '_');
|
2017-05-16 13:45:17 -07:00
|
|
|
let numChars = 0;
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
2016-04-11 21:17:38 -04:00
|
|
|
if (text[i] === '\t') {
|
2016-07-12 13:50:26 -07:00
|
|
|
numChars += tabSize - (numChars % tabSize);
|
2016-04-11 21:17:38 -04:00
|
|
|
} else {
|
|
|
|
numChars++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return numChars;
|
|
|
|
};
|
|
|
|
|
2016-03-17 14:39:42 -04:00
|
|
|
// Advance `index` by the appropriate number of characters that would
|
|
|
|
// represent one source code character and return that index. For
|
|
|
|
// example, for source code '<span>' the escaped html string is
|
|
|
|
// '<span>'. Advancing from index 0 on the prior html string would
|
|
|
|
// return 4, since < maps to one source code character ('<').
|
|
|
|
GrDiffBuilder.prototype._advanceChar = function(html, index) {
|
|
|
|
// TODO(andybons): Unicode is all kinds of messed up in JS. Account for it.
|
|
|
|
// https://mathiasbynens.be/notes/javascript-unicode
|
|
|
|
|
|
|
|
// Tags don't count as characters
|
|
|
|
while (index < html.length &&
|
2016-05-23 15:31:21 -07:00
|
|
|
html.charCodeAt(index) === GrDiffBuilder.LESS_THAN_CODE) {
|
2016-03-17 14:39:42 -04:00
|
|
|
while (index < html.length &&
|
2016-05-23 15:31:21 -07:00
|
|
|
html.charCodeAt(index) !== GrDiffBuilder.GREATER_THAN_CODE) {
|
2016-03-17 14:39:42 -04:00
|
|
|
index++;
|
|
|
|
}
|
2017-07-05 11:17:26 -07:00
|
|
|
index++; // skip the ">" itself
|
2016-03-17 14:39:42 -04:00
|
|
|
}
|
|
|
|
// An HTML entity (e.g., <) counts as one character.
|
|
|
|
if (index < html.length &&
|
2016-05-23 15:31:21 -07:00
|
|
|
html.charCodeAt(index) === GrDiffBuilder.AMPERSAND_CODE) {
|
2016-03-17 14:39:42 -04:00
|
|
|
while (index < html.length &&
|
2016-05-23 15:31:21 -07:00
|
|
|
html.charCodeAt(index) !== GrDiffBuilder.SEMICOLON_CODE) {
|
2016-03-17 14:39:42 -04:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return index + 1;
|
|
|
|
};
|
|
|
|
|
2017-02-21 15:45:46 -08:00
|
|
|
GrDiffBuilder.prototype._advancePastTagClose = function(html, index) {
|
|
|
|
while (index < html.length &&
|
|
|
|
html.charCodeAt(index) !== GrDiffBuilder.GREATER_THAN_CODE) {
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
return index + 1;
|
|
|
|
};
|
|
|
|
|
2016-03-17 14:39:42 -04:00
|
|
|
GrDiffBuilder.prototype._addNewlines = function(text, html) {
|
2017-05-16 13:45:17 -07:00
|
|
|
let htmlIndex = 0;
|
|
|
|
const indices = [];
|
|
|
|
let numChars = 0;
|
|
|
|
let prevHtmlIndex = 0;
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
2016-03-17 14:39:42 -04:00
|
|
|
if (numChars > 0 && numChars % this._prefs.line_length === 0) {
|
|
|
|
indices.push(htmlIndex);
|
|
|
|
}
|
|
|
|
htmlIndex = this._advanceChar(html, htmlIndex);
|
|
|
|
if (text[i] === '\t') {
|
2017-02-21 15:45:46 -08:00
|
|
|
// Advance past tab closing tag.
|
|
|
|
htmlIndex = this._advancePastTagClose(html, htmlIndex);
|
|
|
|
// ~~ is a faster Math.floor
|
|
|
|
if (~~(numChars / this._prefs.line_length) !==
|
|
|
|
~~((numChars + this._prefs.tab_size) / this._prefs.line_length)) {
|
|
|
|
// Tab crosses line limit - push it to the next line.
|
|
|
|
indices.push(prevHtmlIndex);
|
|
|
|
}
|
2016-03-17 14:39:42 -04:00
|
|
|
numChars += this._prefs.tab_size;
|
|
|
|
} else {
|
|
|
|
numChars++;
|
|
|
|
}
|
2017-02-21 15:45:46 -08:00
|
|
|
prevHtmlIndex = htmlIndex;
|
2016-03-17 14:39:42 -04:00
|
|
|
}
|
2017-05-16 13:45:17 -07:00
|
|
|
let result = html;
|
2016-03-17 14:39:42 -04:00
|
|
|
// Since the result string is being altered in place, start from the end
|
|
|
|
// of the string so that the insertion indices are not affected as the
|
|
|
|
// result string changes.
|
2017-05-16 13:45:17 -07:00
|
|
|
for (let i = indices.length - 1; i >= 0; i--) {
|
2016-03-17 14:39:42 -04:00
|
|
|
result = result.slice(0, indices[i]) + GrDiffBuilder.LINE_FEED_HTML +
|
|
|
|
result.slice(indices[i]);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2016-07-12 13:50:26 -07:00
|
|
|
/**
|
|
|
|
* Takes a string of text (not HTML) and returns a string of HTML with tab
|
|
|
|
* elements in place of tab characters. In each case tab elements are given
|
|
|
|
* the width needed to reach the next tab-stop.
|
|
|
|
*
|
2017-08-11 16:32:47 -07:00
|
|
|
* @param {string} A line of text potentially containing tab characters.
|
|
|
|
* @param {number} The width for tabs.
|
|
|
|
* @return {string} An HTML string potentially containing tab elements.
|
2016-07-12 13:50:26 -07:00
|
|
|
*/
|
|
|
|
GrDiffBuilder.prototype._addTabWrappers = function(line, tabSize) {
|
|
|
|
if (!line.length) { return ''; }
|
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
let result = '';
|
|
|
|
let offset = 0;
|
|
|
|
const split = line.split('\t');
|
|
|
|
let width;
|
2016-07-12 13:50:26 -07:00
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
for (let i = 0; i < split.length - 1; i++) {
|
2016-07-12 13:50:26 -07:00
|
|
|
offset += split[i].length;
|
|
|
|
width = tabSize - (offset % tabSize);
|
Improves visible tabs rendering
This change reverts visible tabs to use the » character. A few different
approaches have been used for rendering these tab indicators:
I: Before the Annotation Pipeline, tab indicators were configured by a
class that was optionally applied to tab elements by the diff
builder. The class was guarded by the show_tabs preference and a CSS
rule created a `::before` pseudo element to insert the character.
(Thus also preventing the » from being copyable text.)
II: When the Annotation Pipeline was implemented, the ordering of layers
called for intraline difference elements being rendered *inside* tab
indicators. As a result, the » indicator would sometimes have a
different background than the intraline difference, looking sloppy.
To solve this, the pseudo element was positioned using absolute,
allowing the pseudo element to consume no horizontal space and and
the intraline background to extend across the entire tab.
III:When performance tuning, it was discovered that the
absolute-positioned tab indicators were positioned incorrectly when
GPU acceleration was hinted for the diff, so the containing tab
elements were made relative.
IV: Continuing performance tuning, the tab indicators seemed to slow
scrolling on very large diffs with tabs. It was mistakenly assumed
(by me) that this was related to the pseudo-elements when it was
actually caused by the thousands of positioning contexts they
created using relative and absolute.
Instead they were modified to use strike-through instead of a pseudo
element, which was more-performant, but less-usable (it looked bad).
With this change, we roll-back the clock a little and solve a core
problem: namely that tab indicators were not annotated inside the
intraline differences. Fixing that, positioning tricks are no-longer
needed to make the background look right.
To do this, we decouple the tab elements (which control tab width) from
the tab indicators (which optionally make tabs visible). This is done
using an annotation layer that inserts tab indicator elements wherever
a tab character is used in the source content, and it does so after
intraline differences have been applied.
Bug: Issue 4441
Change-Id: I4fea2a3a20a7039bfeb746bd1e1c1004e43c4801
2016-08-25 11:31:42 -07:00
|
|
|
result += split[i] + this._getTabWrapper(width);
|
2016-07-12 13:50:26 -07:00
|
|
|
offset += width;
|
|
|
|
}
|
|
|
|
if (split.length) {
|
|
|
|
result += split[split.length - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2016-03-17 14:39:42 -04:00
|
|
|
};
|
|
|
|
|
Improves visible tabs rendering
This change reverts visible tabs to use the » character. A few different
approaches have been used for rendering these tab indicators:
I: Before the Annotation Pipeline, tab indicators were configured by a
class that was optionally applied to tab elements by the diff
builder. The class was guarded by the show_tabs preference and a CSS
rule created a `::before` pseudo element to insert the character.
(Thus also preventing the » from being copyable text.)
II: When the Annotation Pipeline was implemented, the ordering of layers
called for intraline difference elements being rendered *inside* tab
indicators. As a result, the » indicator would sometimes have a
different background than the intraline difference, looking sloppy.
To solve this, the pseudo element was positioned using absolute,
allowing the pseudo element to consume no horizontal space and and
the intraline background to extend across the entire tab.
III:When performance tuning, it was discovered that the
absolute-positioned tab indicators were positioned incorrectly when
GPU acceleration was hinted for the diff, so the containing tab
elements were made relative.
IV: Continuing performance tuning, the tab indicators seemed to slow
scrolling on very large diffs with tabs. It was mistakenly assumed
(by me) that this was related to the pseudo-elements when it was
actually caused by the thousands of positioning contexts they
created using relative and absolute.
Instead they were modified to use strike-through instead of a pseudo
element, which was more-performant, but less-usable (it looked bad).
With this change, we roll-back the clock a little and solve a core
problem: namely that tab indicators were not annotated inside the
intraline differences. Fixing that, positioning tricks are no-longer
needed to make the background look right.
To do this, we decouple the tab elements (which control tab width) from
the tab indicators (which optionally make tabs visible). This is done
using an annotation layer that inserts tab indicator elements wherever
a tab character is used in the source content, and it does so after
intraline differences have been applied.
Bug: Issue 4441
Change-Id: I4fea2a3a20a7039bfeb746bd1e1c1004e43c4801
2016-08-25 11:31:42 -07:00
|
|
|
GrDiffBuilder.prototype._getTabWrapper = function(tabSize) {
|
2016-03-17 14:39:42 -04:00
|
|
|
// Force this to be a number to prevent arbitrary injection.
|
|
|
|
tabSize = +tabSize;
|
|
|
|
if (isNaN(tabSize)) {
|
|
|
|
throw Error('Invalid tab size from preferences.');
|
|
|
|
}
|
|
|
|
|
2017-05-16 13:45:17 -07:00
|
|
|
let str = '<span class="style-scope gr-diff tab ';
|
2016-04-11 21:17:38 -04:00
|
|
|
str += '" style="';
|
2016-03-17 14:39:42 -04:00
|
|
|
// TODO(andybons): CSS tab-size is not supported in IE.
|
2016-04-11 21:17:38 -04:00
|
|
|
str += 'tab-size:' + tabSize + ';';
|
|
|
|
str += '-moz-tab-size:' + tabSize + ';';
|
2016-03-17 14:39:42 -04:00
|
|
|
str += '">\t</span>';
|
2016-03-18 16:06:37 -04:00
|
|
|
return str;
|
2016-03-17 14:39:42 -04:00
|
|
|
};
|
|
|
|
|
2016-03-13 21:23:11 -04:00
|
|
|
GrDiffBuilder.prototype._createElement = function(tagName, className) {
|
2017-05-16 13:45:17 -07:00
|
|
|
const el = document.createElement(tagName);
|
2016-03-13 21:23:11 -04:00
|
|
|
// When Shady DOM is being used, these classes are added to account for
|
|
|
|
// Polymer's polyfill behavior. In order to guarantee sufficient
|
|
|
|
// specificity within the CSS rules, these are added to every element.
|
|
|
|
// Since the Polymer DOM utility functions (which would do this
|
|
|
|
// automatically) are not being used for performance reasons, this is
|
|
|
|
// done manually.
|
2016-03-25 16:48:13 -04:00
|
|
|
el.classList.add('style-scope', 'gr-diff');
|
2017-05-16 13:45:17 -07:00
|
|
|
if (className) {
|
2016-03-13 21:23:11 -04:00
|
|
|
el.classList.add(className);
|
|
|
|
}
|
|
|
|
return el;
|
|
|
|
};
|
|
|
|
|
2016-07-14 12:31:09 -07:00
|
|
|
GrDiffBuilder.prototype._handleLayerUpdate = function(start, end, side) {
|
|
|
|
this._renderContentByRange(start, end, side);
|
|
|
|
};
|
|
|
|
|
2016-07-21 22:19:25 -07:00
|
|
|
/**
|
|
|
|
* Finds the next DIV.contentText element following the given element, and on
|
|
|
|
* the same side. Will only search within a group.
|
|
|
|
* @param {HTMLElement} content
|
2017-08-11 16:32:47 -07:00
|
|
|
* @param {string} side Either 'left' or 'right'
|
2016-07-21 22:19:25 -07:00
|
|
|
* @return {HTMLElement}
|
|
|
|
*/
|
|
|
|
GrDiffBuilder.prototype._getNextContentOnSide = function(content, side) {
|
|
|
|
throw Error('Subclasses must implement _getNextContentOnSide');
|
|
|
|
};
|
|
|
|
|
2016-12-21 12:55:21 -08:00
|
|
|
/**
|
|
|
|
* Determines whether the given group is either totally an addition or totally
|
|
|
|
* a removal.
|
2017-08-11 16:32:47 -07:00
|
|
|
* @param {!Object} group (GrDiffGroup)
|
|
|
|
* @return {boolean}
|
2016-12-21 12:55:21 -08:00
|
|
|
*/
|
|
|
|
GrDiffBuilder.prototype._isTotal = function(group) {
|
|
|
|
return group.type === GrDiffGroup.Type.DELTA &&
|
|
|
|
(!group.adds.length || !group.removes.length) &&
|
|
|
|
!(!group.adds.length && !group.removes.length);
|
2017-01-04 13:38:54 -08:00
|
|
|
};
|
2016-12-21 12:55:21 -08:00
|
|
|
|
2017-01-30 16:42:46 -08:00
|
|
|
GrDiffBuilder.prototype._escapeHTML = function(str) {
|
2017-05-16 13:45:17 -07:00
|
|
|
return str.replace(HTML_ENTITY_PATTERN, s => {
|
2017-01-30 16:42:46 -08:00
|
|
|
return HTML_ENTITY_MAP[s];
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-03-13 21:23:11 -04:00
|
|
|
window.GrDiffBuilder = GrDiffBuilder;
|
|
|
|
})(window, GrDiffGroup, GrDiffLine);
|