Get rid of global GrDiffBuilder
* Replace the global GrDiffBuilder variable with named imports.
* Update gr-app-global-var-init.js
Change-Id: I0d07cd3ef56bf2fefebbfc29d31ed515e13b9d27
diff --git a/polygerrit-ui/app/.eslintrc.js b/polygerrit-ui/app/.eslintrc.js
index cd290a1..8e10d1e 100644
--- a/polygerrit-ui/app/.eslintrc.js
+++ b/polygerrit-ui/app/.eslintrc.js
@@ -177,7 +177,6 @@
"GrChangeReplyInterface": "readonly",
"GrChangeViewApi": "readonly",
"GrCountStringFormatter": "readonly",
- "GrDiffBuilder": "readonly",
"GrDiffBuilderBinary": "readonly",
"GrDiffBuilderImage": "readonly",
"GrDiffBuilderSideBySide": "readonly",
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
index 1f96cec..217ee19 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
@@ -14,7 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-(function(window, GrDiffBuilder) {
+
+import {GrDiffBuilder} from './gr-diff-builder.js';
+
+(function(window) {
'use strict';
// Prevent redefinition.
@@ -44,4 +47,4 @@
};
window.GrDiffBuilderBinary = GrDiffBuilderBinary;
-})(window, GrDiffBuilder);
+})(window);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
index 19e4c8f..f4e9de4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
@@ -21,7 +21,6 @@
import '../../shared/gr-hovercard/gr-hovercard.js';
import '../gr-ranged-comment-layer/gr-ranged-comment-layer.js';
import '../../../scripts/util.js';
-import './gr-diff-builder.js';
import './gr-diff-builder-side-by-side.js';
import './gr-diff-builder-unified.js';
import './gr-diff-builder-image.js';
@@ -32,6 +31,7 @@
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {htmlTemplate} from './gr-diff-builder-element_html.js';
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation.js';
+import {GrDiffBuilder} from './gr-diff-builder.js';
const DiffViewMode = {
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
index 4445504..da4c2a4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
@@ -58,6 +58,7 @@
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation.js';
import {GrDiffLine} from '../gr-diff/gr-diff-line.js';
import {GrDiffGroup} from '../gr-diff/gr-diff-group.js';
+import {GrDiffBuilder} from './gr-diff-builder.js';
const DiffViewMode = {
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
index 5fe9f3a..8fc214d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
@@ -14,7 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-(function(window, GrDiffBuilder) {
+
+import {GrDiffBuilder} from './gr-diff-builder.js';
+
+(function(window) {
'use strict';
// Prevent redefinition.
@@ -116,4 +119,4 @@
};
window.GrDiffBuilderSideBySide = GrDiffBuilderSideBySide;
-})(window, GrDiffBuilder);
+})(window);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
index 240e85b..7b0ac78 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
@@ -15,8 +15,9 @@
* limitations under the License.
*/
import {GrDiffLine} from '../gr-diff/gr-diff-line.js';
+import {GrDiffBuilder} from './gr-diff-builder.js';
-(function(window, GrDiffBuilder) {
+(function(window) {
'use strict';
// Prevent redefinition.
@@ -114,4 +115,4 @@
};
window.GrDiffBuilderUnified = GrDiffBuilderUnified;
-})(window, GrDiffBuilder);
+})(window);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index cc05139..a9482c9 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -17,612 +17,603 @@
import {GrDiffLine} from '../gr-diff/gr-diff-line.js';
import {GrDiffGroup} from '../gr-diff/gr-diff-group.js';
-(function(window) {
- 'use strict';
+/**
+ * In JS, unicode code points above 0xFFFF occupy two elements of a string.
+ * For example '𐀏'.length is 2. An occurence of such a code point is called a
+ * surrogate pair.
+ *
+ * This regex segments a string along tabs ('\t') and surrogate pairs, since
+ * these are two cases where '1 char' does not automatically imply '1 column'.
+ *
+ * TODO: For human languages whose orthographies use combining marks, this
+ * approach won't correctly identify the grapheme boundaries. In those cases,
+ * a grapheme consists of multiple code points that should count as only one
+ * character against the column limit. Getting that correct (if it's desired)
+ * is probably beyond the limits of a regex, but there are nonstandard APIs to
+ * do this, and proposed (but, as of Nov 2017, unimplemented) standard APIs.
+ *
+ * Further reading:
+ * On Unicode in JS: https://mathiasbynens.be/notes/javascript-unicode
+ * Graphemes: http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
+ * A proposed JS API: https://github.com/tc39/proposal-intl-segmenter
+ */
+const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
- // Prevent redefinition.
- if (window.GrDiffBuilder) { return; }
+export function GrDiffBuilder(diff, prefs, outputEl, layers) {
+ this._diff = diff;
+ this._prefs = prefs;
+ this._outputEl = outputEl;
+ this.groups = [];
+ this._blameInfo = null;
- /**
- * In JS, unicode code points above 0xFFFF occupy two elements of a string.
- * For example '𐀏'.length is 2. An occurence of such a code point is called a
- * surrogate pair.
- *
- * This regex segments a string along tabs ('\t') and surrogate pairs, since
- * these are two cases where '1 char' does not automatically imply '1 column'.
- *
- * TODO: For human languages whose orthographies use combining marks, this
- * approach won't correctly identify the grapheme boundaries. In those cases,
- * a grapheme consists of multiple code points that should count as only one
- * character against the column limit. Getting that correct (if it's desired)
- * is probably beyond the limits of a regex, but there are nonstandard APIs to
- * do this, and proposed (but, as of Nov 2017, unimplemented) standard APIs.
- *
- * Further reading:
- * On Unicode in JS: https://mathiasbynens.be/notes/javascript-unicode
- * Graphemes: http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
- * A proposed JS API: https://github.com/tc39/proposal-intl-segmenter
- */
- const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
+ this.layers = layers || [];
- function GrDiffBuilder(diff, prefs, outputEl, layers) {
- this._diff = diff;
- this._prefs = prefs;
- this._outputEl = outputEl;
- this.groups = [];
- this._blameInfo = null;
+ if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
+ throw Error('Invalid tab size from preferences.');
+ }
- this.layers = layers || [];
+ if (isNaN(prefs.line_length) || prefs.line_length <= 0) {
+ throw Error('Invalid line length from preferences.');
+ }
- if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
- throw Error('Invalid tab size from preferences.');
+ for (const layer of this.layers) {
+ if (layer.addListener) {
+ layer.addListener(this._handleLayerUpdate.bind(this));
+ }
+ }
+}
+
+GrDiffBuilder.GroupType = {
+ ADDED: 'b',
+ BOTH: 'ab',
+ REMOVED: 'a',
+};
+
+GrDiffBuilder.Highlights = {
+ ADDED: 'edit_b',
+ REMOVED: 'edit_a',
+};
+
+GrDiffBuilder.Side = {
+ LEFT: 'left',
+ RIGHT: 'right',
+};
+
+GrDiffBuilder.ContextButtonType = {
+ ABOVE: 'above',
+ BELOW: 'below',
+ ALL: 'all',
+};
+
+const PARTIAL_CONTEXT_AMOUNT = 10;
+
+/**
+ * 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() {
+ throw Error('Subclasses must implement buildSectionElement');
+};
+
+GrDiffBuilder.prototype.emitGroup = function(group, opt_beforeSection) {
+ const element = this.buildSectionElement(group);
+ this._outputEl.insertBefore(element, opt_beforeSection);
+ group.element = element;
+};
+
+GrDiffBuilder.prototype.getGroupsByLineRange = function(
+ startLine, endLine, opt_side) {
+ const groups = [];
+ for (let i = 0; i < this.groups.length; i++) {
+ const group = this.groups[i];
+ if (group.lines.length === 0) {
+ continue;
+ }
+ let groupStartLine = 0;
+ let groupEndLine = 0;
+ if (opt_side) {
+ groupStartLine = group.lineRange[opt_side].start;
+ groupEndLine = group.lineRange[opt_side].end;
}
- if (isNaN(prefs.line_length) || prefs.line_length <= 0) {
- throw Error('Invalid line length from preferences.');
+ if (groupStartLine === 0) { // Line was removed or added.
+ groupStartLine = groupEndLine;
}
+ if (groupEndLine === 0) { // Line was removed or added.
+ groupEndLine = groupStartLine;
+ }
+ if (startLine <= groupEndLine && endLine >= groupStartLine) {
+ groups.push(group);
+ }
+ }
+ return groups;
+};
- for (const layer of this.layers) {
- if (layer.addListener) {
- layer.addListener(this._handleLayerUpdate.bind(this));
+GrDiffBuilder.prototype.getContentByLine = function(lineNumber, opt_side,
+ opt_root) {
+ const root = Polymer.dom(opt_root || this._outputEl);
+ const sideSelector = opt_side ? ('.' + opt_side) : '';
+ 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.
+ *
+ * @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
+ * null if not desired.
+ * @param {!Array<HTMLElement>} out_elements The output list of line elements.
+ * Use null if not desired.
+ */
+GrDiffBuilder.prototype.findLinesByRange = function(start, end, opt_side,
+ out_lines, out_elements) {
+ const groups = this.getGroupsByLineRange(start, end, opt_side);
+ for (const group of groups) {
+ let content = null;
+ for (const line of group.lines) {
+ if ((opt_side === 'left' && line.type === GrDiffLine.Type.ADD) ||
+ (opt_side === 'right' && line.type === GrDiffLine.Type.REMOVE)) {
+ continue;
+ }
+ const lineNumber = opt_side === 'left' ?
+ line.beforeNumber : line.afterNumber;
+ if (lineNumber < start || lineNumber > end) { continue; }
+
+ if (out_lines) { out_lines.push(line); }
+ if (out_elements) {
+ if (content) {
+ content = this._getNextContentOnSide(content, opt_side);
+ } else {
+ content = this.getContentByLine(lineNumber, opt_side,
+ group.element);
+ }
+ if (content) { out_elements.push(content); }
}
}
}
+};
- GrDiffBuilder.GroupType = {
- ADDED: 'b',
- BOTH: 'ab',
- REMOVED: 'a',
- };
-
- GrDiffBuilder.Highlights = {
- ADDED: 'edit_b',
- REMOVED: 'edit_a',
- };
-
- GrDiffBuilder.Side = {
- LEFT: 'left',
- RIGHT: 'right',
- };
-
- GrDiffBuilder.ContextButtonType = {
- ABOVE: 'above',
- BELOW: 'below',
- ALL: 'all',
- };
-
- const PARTIAL_CONTEXT_AMOUNT = 10;
-
- /**
- * 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() {
- throw Error('Subclasses must implement buildSectionElement');
- };
-
- GrDiffBuilder.prototype.emitGroup = function(group, opt_beforeSection) {
- const element = this.buildSectionElement(group);
- this._outputEl.insertBefore(element, opt_beforeSection);
- group.element = element;
- };
-
- GrDiffBuilder.prototype.getGroupsByLineRange = function(
- startLine, endLine, opt_side) {
- const groups = [];
- for (let i = 0; i < this.groups.length; i++) {
- const group = this.groups[i];
- if (group.lines.length === 0) {
- continue;
- }
- let groupStartLine = 0;
- let groupEndLine = 0;
- if (opt_side) {
- groupStartLine = group.lineRange[opt_side].start;
- groupEndLine = group.lineRange[opt_side].end;
- }
-
- if (groupStartLine === 0) { // Line was removed or added.
- groupStartLine = groupEndLine;
- }
- if (groupEndLine === 0) { // Line was removed or added.
- groupEndLine = groupStartLine;
- }
- if (startLine <= groupEndLine && endLine >= groupStartLine) {
- groups.push(group);
- }
+/**
+ * Re-renders the DIV.contentText elements for the given side and range of
+ * diff content.
+ */
+GrDiffBuilder.prototype._renderContentByRange = function(start, end, side) {
+ const lines = [];
+ const elements = [];
+ let line;
+ let el;
+ this.findLinesByRange(start, end, side, lines, elements);
+ for (let i = 0; i < lines.length; i++) {
+ line = lines[i];
+ el = elements[i];
+ if (!el) {
+ // Cannot re-render an element if it does not exist. This can happen
+ // if lines are collapsed and not visible on the page yet.
+ continue;
}
- return groups;
- };
+ const lineNumberEl = this._getLineNumberEl(el, side);
+ el.parentElement.replaceChild(
+ this._createTextEl(lineNumberEl, line, side).firstChild,
+ el);
+ }
+};
- GrDiffBuilder.prototype.getContentByLine = function(lineNumber, opt_side,
- opt_root) {
- const root = Polymer.dom(opt_root || this._outputEl);
- const sideSelector = opt_side ? ('.' + opt_side) : '';
- return root.querySelector('td.lineNum[data-value="' + lineNumber +
- '"]' + sideSelector + ' ~ td.content .contentText');
- };
+GrDiffBuilder.prototype.getSectionsByLineRange = function(
+ startLine, endLine, opt_side) {
+ return this.getGroupsByLineRange(startLine, endLine, opt_side).map(
+ group => group.element);
+};
- /**
- * Find line elements or line objects by a range of line numbers and a side.
- *
- * @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
- * null if not desired.
- * @param {!Array<HTMLElement>} out_elements The output list of line elements.
- * Use null if not desired.
- */
- GrDiffBuilder.prototype.findLinesByRange = function(start, end, opt_side,
- out_lines, out_elements) {
- const groups = this.getGroupsByLineRange(start, end, opt_side);
- for (const group of groups) {
- let content = null;
- for (const line of group.lines) {
- if ((opt_side === 'left' && line.type === GrDiffLine.Type.ADD) ||
- (opt_side === 'right' && line.type === GrDiffLine.Type.REMOVE)) {
- continue;
- }
- const lineNumber = opt_side === 'left' ?
- line.beforeNumber : line.afterNumber;
- if (lineNumber < start || lineNumber > end) { continue; }
+GrDiffBuilder.prototype._createContextControl = function(section, line) {
+ if (!line.contextGroups) return null;
- if (out_lines) { out_lines.push(line); }
- if (out_elements) {
- if (content) {
- content = this._getNextContentOnSide(content, opt_side);
- } else {
- content = this.getContentByLine(lineNumber, opt_side,
- group.element);
- }
- if (content) { out_elements.push(content); }
- }
- }
- }
- };
+ const numLines =
+ line.contextGroups[line.contextGroups.length - 1].lineRange.left.end -
+ line.contextGroups[0].lineRange.left.start + 1;
- /**
- * Re-renders the DIV.contentText elements for the given side and range of
- * diff content.
- */
- GrDiffBuilder.prototype._renderContentByRange = function(start, end, side) {
- const lines = [];
- const elements = [];
- let line;
- let el;
- this.findLinesByRange(start, end, side, lines, elements);
- for (let i = 0; i < lines.length; i++) {
- line = lines[i];
- el = elements[i];
- if (!el) {
- // Cannot re-render an element if it does not exist. This can happen
- // if lines are collapsed and not visible on the page yet.
- continue;
- }
- const lineNumberEl = this._getLineNumberEl(el, side);
- el.parentElement.replaceChild(
- this._createTextEl(lineNumberEl, line, side).firstChild,
- el);
- }
- };
+ if (numLines === 0) return null;
- GrDiffBuilder.prototype.getSectionsByLineRange = function(
- startLine, endLine, opt_side) {
- return this.getGroupsByLineRange(startLine, endLine, opt_side).map(
- group => group.element);
- };
+ const td = this._createElement('td');
+ const showPartialLinks = numLines > PARTIAL_CONTEXT_AMOUNT;
- GrDiffBuilder.prototype._createContextControl = function(section, line) {
- if (!line.contextGroups) return null;
-
- const numLines =
- line.contextGroups[line.contextGroups.length - 1].lineRange.left.end -
- line.contextGroups[0].lineRange.left.start + 1;
-
- if (numLines === 0) return null;
-
- const td = this._createElement('td');
- const showPartialLinks = numLines > PARTIAL_CONTEXT_AMOUNT;
-
- if (showPartialLinks) {
- td.appendChild(this._createContextButton(
- GrDiffBuilder.ContextButtonType.ABOVE, section, line, numLines));
- }
-
+ if (showPartialLinks) {
td.appendChild(this._createContextButton(
- GrDiffBuilder.ContextButtonType.ALL, section, line, numLines));
+ GrDiffBuilder.ContextButtonType.ABOVE, section, line, numLines));
+ }
- if (showPartialLinks) {
- td.appendChild(this._createContextButton(
- GrDiffBuilder.ContextButtonType.BELOW, section, line, numLines));
+ td.appendChild(this._createContextButton(
+ GrDiffBuilder.ContextButtonType.ALL, section, line, numLines));
+
+ if (showPartialLinks) {
+ td.appendChild(this._createContextButton(
+ GrDiffBuilder.ContextButtonType.BELOW, section, line, numLines));
+ }
+
+ return td;
+};
+
+GrDiffBuilder.prototype._createContextButton = function(type, section, line,
+ numLines) {
+ const context = PARTIAL_CONTEXT_AMOUNT;
+
+ const button = this._createElement('gr-button', 'showContext');
+ button.setAttribute('link', true);
+ button.setAttribute('no-uppercase', true);
+
+ let text;
+ let groups = []; // The groups that replace this one if tapped.
+ if (type === GrDiffBuilder.ContextButtonType.ALL) {
+ const icon = this._createElement('iron-icon', 'showContext');
+ icon.setAttribute('icon', 'gr-icons:unfold-more');
+ Polymer.dom(button).appendChild(icon);
+
+ text = 'Show ' + numLines + ' common line';
+ if (numLines > 1) { text += 's'; }
+ groups.push(...line.contextGroups);
+ } else if (type === GrDiffBuilder.ContextButtonType.ABOVE) {
+ text = '+' + context + ' above';
+ groups = GrDiffGroup.hideInContextControl(line.contextGroups,
+ context, numLines);
+ } else if (type === GrDiffBuilder.ContextButtonType.BELOW) {
+ text = '+' + context + ' below';
+ groups = GrDiffGroup.hideInContextControl(line.contextGroups,
+ 0, numLines - context);
+ }
+ const textSpan = this._createElement('span', 'showContext');
+ Polymer.dom(textSpan).textContent = text;
+ Polymer.dom(button).appendChild(textSpan);
+
+ button.addEventListener('tap', e => {
+ e.detail = {
+ groups,
+ section,
+ numLines,
+ };
+ // Let it bubble up the DOM tree.
+ });
+
+ return button;
+};
+
+GrDiffBuilder.prototype._createLineEl = function(
+ line, number, type, side) {
+ const td = this._createElement('td');
+ if (side) {
+ td.classList.add(side);
+ }
+
+ // Add aria-labels for valid line numbers.
+ // For unified diff, this method will be called with number set to 0 for
+ // the empty line number column for added/removed lines. This should not
+ // be announced to the screenreader.
+ if (number > 0) {
+ 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`);
}
+ }
+ if (line.type === GrDiffLine.Type.BLANK) {
return td;
- };
+ } else if (line.type === GrDiffLine.Type.CONTEXT_CONTROL) {
+ td.classList.add('contextLineNum');
+ } else if (line.type === GrDiffLine.Type.BOTH || line.type === type) {
+ td.classList.add('lineNum');
+ td.setAttribute('data-value', number);
+ td.textContent = number === 'FILE' ? 'File' : number;
+ }
+ return td;
+};
- GrDiffBuilder.prototype._createContextButton = function(type, section, line,
- numLines) {
- const context = PARTIAL_CONTEXT_AMOUNT;
+GrDiffBuilder.prototype._createTextEl = function(
+ lineNumberEl, line, opt_side) {
+ const td = this._createElement('td');
+ if (line.type !== GrDiffLine.Type.BLANK) {
+ td.classList.add('content');
+ }
- const button = this._createElement('gr-button', 'showContext');
- button.setAttribute('link', true);
- button.setAttribute('no-uppercase', true);
+ // If intraline info is not available, the entire line will be
+ // considered as changed and marked as dark red / green color
+ if (!line.hasIntralineInfo) {
+ td.classList.add('no-intraline-info');
+ }
+ td.classList.add(line.type);
- let text;
- let groups = []; // The groups that replace this one if tapped.
- if (type === GrDiffBuilder.ContextButtonType.ALL) {
- const icon = this._createElement('iron-icon', 'showContext');
- icon.setAttribute('icon', 'gr-icons:unfold-more');
- Polymer.dom(button).appendChild(icon);
+ const lineLimit =
+ !this._prefs.line_wrapping ? this._prefs.line_length : Infinity;
- text = 'Show ' + numLines + ' common line';
- if (numLines > 1) { text += 's'; }
- groups.push(...line.contextGroups);
- } else if (type === GrDiffBuilder.ContextButtonType.ABOVE) {
- text = '+' + context + ' above';
- groups = GrDiffGroup.hideInContextControl(line.contextGroups,
- context, numLines);
- } else if (type === GrDiffBuilder.ContextButtonType.BELOW) {
- text = '+' + context + ' below';
- groups = GrDiffGroup.hideInContextControl(line.contextGroups,
- 0, numLines - context);
+ const contentText =
+ this._formatText(line.text, this._prefs.tab_size, lineLimit);
+ if (opt_side) {
+ contentText.setAttribute('data-side', opt_side);
+ }
+
+ for (const layer of this.layers) {
+ if (typeof layer.annotate == 'function') {
+ layer.annotate(contentText, lineNumberEl, line);
}
- const textSpan = this._createElement('span', 'showContext');
- Polymer.dom(textSpan).textContent = text;
- Polymer.dom(button).appendChild(textSpan);
+ }
- button.addEventListener('tap', e => {
- e.detail = {
- groups,
- section,
- numLines,
- };
- // Let it bubble up the DOM tree.
- });
+ td.appendChild(contentText);
- return button;
- };
+ return td;
+};
- GrDiffBuilder.prototype._createLineEl = function(
- line, number, type, side) {
- const td = this._createElement('td');
- if (side) {
- td.classList.add(side);
- }
+/**
+ * Returns a 'div' element containing the supplied |text| as its innerText,
+ * with '\t' characters expanded to a width determined by |tabSize|, and the
+ * text wrapped at column |lineLimit|, which may be Infinity if no wrapping is
+ * desired.
+ *
+ * @param {string} text The text to be formatted.
+ * @param {number} tabSize The width of each tab stop.
+ * @param {number} lineLimit The column after which to wrap lines.
+ * @return {HTMLElement}
+ */
+GrDiffBuilder.prototype._formatText = function(text, tabSize, lineLimit) {
+ const contentText = this._createElement('div', 'contentText');
- // Add aria-labels for valid line numbers.
- // For unified diff, this method will be called with number set to 0 for
- // the empty line number column for added/removed lines. This should not
- // be announced to the screenreader.
- if (number > 0) {
- 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`);
+ let columnPos = 0;
+ let textOffset = 0;
+ for (const segment of text.split(REGEX_TAB_OR_SURROGATE_PAIR)) {
+ if (segment) {
+ // |segment| contains only normal characters. If |segment| doesn't fit
+ // entirely on the current line, append chunks of |segment| followed by
+ // line breaks.
+ let rowStart = 0;
+ let rowEnd = lineLimit - columnPos;
+ while (rowEnd < segment.length) {
+ contentText.appendChild(
+ document.createTextNode(segment.substring(rowStart, rowEnd)));
+ contentText.appendChild(this._createElement('span', 'br'));
+ columnPos = 0;
+ rowStart = rowEnd;
+ rowEnd += lineLimit;
}
+ // Append the last part of |segment|, which fits on the current line.
+ contentText.appendChild(
+ document.createTextNode(segment.substring(rowStart)));
+ columnPos += (segment.length - rowStart);
+ textOffset += segment.length;
}
-
- if (line.type === GrDiffLine.Type.BLANK) {
- return td;
- } else if (line.type === GrDiffLine.Type.CONTEXT_CONTROL) {
- td.classList.add('contextLineNum');
- } else if (line.type === GrDiffLine.Type.BOTH || line.type === type) {
- td.classList.add('lineNum');
- td.setAttribute('data-value', number);
- td.textContent = number === 'FILE' ? 'File' : number;
- }
- return td;
- };
-
- GrDiffBuilder.prototype._createTextEl = function(
- lineNumberEl, line, opt_side) {
- const td = this._createElement('td');
- if (line.type !== GrDiffLine.Type.BLANK) {
- td.classList.add('content');
- }
-
- // If intraline info is not available, the entire line will be
- // considered as changed and marked as dark red / green color
- if (!line.hasIntralineInfo) {
- td.classList.add('no-intraline-info');
- }
- td.classList.add(line.type);
-
- const lineLimit =
- !this._prefs.line_wrapping ? this._prefs.line_length : Infinity;
-
- const contentText =
- this._formatText(line.text, this._prefs.tab_size, lineLimit);
- if (opt_side) {
- contentText.setAttribute('data-side', opt_side);
- }
-
- for (const layer of this.layers) {
- if (typeof layer.annotate == 'function') {
- layer.annotate(contentText, lineNumberEl, line);
- }
- }
-
- td.appendChild(contentText);
-
- return td;
- };
-
- /**
- * Returns a 'div' element containing the supplied |text| as its innerText,
- * with '\t' characters expanded to a width determined by |tabSize|, and the
- * text wrapped at column |lineLimit|, which may be Infinity if no wrapping is
- * desired.
- *
- * @param {string} text The text to be formatted.
- * @param {number} tabSize The width of each tab stop.
- * @param {number} lineLimit The column after which to wrap lines.
- * @return {HTMLElement}
- */
- GrDiffBuilder.prototype._formatText = function(text, tabSize, lineLimit) {
- const contentText = this._createElement('div', 'contentText');
-
- let columnPos = 0;
- let textOffset = 0;
- for (const segment of text.split(REGEX_TAB_OR_SURROGATE_PAIR)) {
- if (segment) {
- // |segment| contains only normal characters. If |segment| doesn't fit
- // entirely on the current line, append chunks of |segment| followed by
- // line breaks.
- let rowStart = 0;
- let rowEnd = lineLimit - columnPos;
- while (rowEnd < segment.length) {
- contentText.appendChild(
- document.createTextNode(segment.substring(rowStart, rowEnd)));
+ if (textOffset < text.length) {
+ // Handle the special character at |textOffset|.
+ if (text.startsWith('\t', textOffset)) {
+ // Append a single '\t' character.
+ let effectiveTabSize = tabSize - (columnPos % tabSize);
+ if (columnPos + effectiveTabSize > lineLimit) {
contentText.appendChild(this._createElement('span', 'br'));
columnPos = 0;
- rowStart = rowEnd;
- rowEnd += lineLimit;
+ effectiveTabSize = tabSize;
}
- // Append the last part of |segment|, which fits on the current line.
- contentText.appendChild(
- document.createTextNode(segment.substring(rowStart)));
- columnPos += (segment.length - rowStart);
- textOffset += segment.length;
- }
- if (textOffset < text.length) {
- // Handle the special character at |textOffset|.
- if (text.startsWith('\t', textOffset)) {
- // Append a single '\t' character.
- let effectiveTabSize = tabSize - (columnPos % tabSize);
- if (columnPos + effectiveTabSize > lineLimit) {
- contentText.appendChild(this._createElement('span', 'br'));
- columnPos = 0;
- effectiveTabSize = tabSize;
- }
- contentText.appendChild(this._getTabWrapper(effectiveTabSize));
- columnPos += effectiveTabSize;
- textOffset++;
- } else {
- // Append a single surrogate pair.
- if (columnPos >= lineLimit) {
- contentText.appendChild(this._createElement('span', 'br'));
- columnPos = 0;
- }
- contentText.appendChild(document.createTextNode(
- text.substring(textOffset, textOffset + 2)));
- textOffset += 2;
- columnPos += 1;
+ contentText.appendChild(this._getTabWrapper(effectiveTabSize));
+ columnPos += effectiveTabSize;
+ textOffset++;
+ } else {
+ // Append a single surrogate pair.
+ if (columnPos >= lineLimit) {
+ contentText.appendChild(this._createElement('span', 'br'));
+ columnPos = 0;
}
+ contentText.appendChild(document.createTextNode(
+ text.substring(textOffset, textOffset + 2)));
+ textOffset += 2;
+ columnPos += 1;
}
}
- return contentText;
- };
+ }
+ return contentText;
+};
- /**
- * Returns a <span> element holding a '\t' character, that will visually
- * occupy |tabSize| many columns.
- *
- * @param {number} tabSize The effective size of this tab stop.
- * @return {HTMLElement}
- */
- GrDiffBuilder.prototype._getTabWrapper = function(tabSize) {
- // Force this to be a number to prevent arbitrary injection.
- const result = this._createElement('span', 'tab');
- result.style['tab-size'] = tabSize;
- result.style['-moz-tab-size'] = tabSize;
- result.innerText = '\t';
- return result;
- };
+/**
+ * Returns a <span> element holding a '\t' character, that will visually
+ * occupy |tabSize| many columns.
+ *
+ * @param {number} tabSize The effective size of this tab stop.
+ * @return {HTMLElement}
+ */
+GrDiffBuilder.prototype._getTabWrapper = function(tabSize) {
+ // Force this to be a number to prevent arbitrary injection.
+ const result = this._createElement('span', 'tab');
+ result.style['tab-size'] = tabSize;
+ result.style['-moz-tab-size'] = tabSize;
+ result.innerText = '\t';
+ return result;
+};
- GrDiffBuilder.prototype._createElement = function(tagName, classStr) {
- const el = document.createElement(tagName);
- // 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.
- el.classList.add('style-scope', 'gr-diff');
- if (classStr) {
- for (const className of classStr.split(' ')) {
- el.classList.add(className);
- }
+GrDiffBuilder.prototype._createElement = function(tagName, classStr) {
+ const el = document.createElement(tagName);
+ // 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.
+ el.classList.add('style-scope', 'gr-diff');
+ if (classStr) {
+ for (const className of classStr.split(' ')) {
+ el.classList.add(className);
}
- return el;
- };
+ }
+ return el;
+};
- GrDiffBuilder.prototype._handleLayerUpdate = function(start, end, side) {
- this._renderContentByRange(start, end, side);
- };
+GrDiffBuilder.prototype._handleLayerUpdate = function(start, end, side) {
+ this._renderContentByRange(start, end, side);
+};
- /**
- * Finds the next DIV.contentText element following the given element, and on
- * the same side. Will only search within a group.
- *
- * @param {HTMLElement} content
- * @param {string} side Either 'left' or 'right'
- * @return {HTMLElement}
- */
- GrDiffBuilder.prototype._getNextContentOnSide = function(content, side) {
- throw Error('Subclasses must implement _getNextContentOnSide');
- };
+/**
+ * Finds the next DIV.contentText element following the given element, and on
+ * the same side. Will only search within a group.
+ *
+ * @param {HTMLElement} content
+ * @param {string} side Either 'left' or 'right'
+ * @return {HTMLElement}
+ */
+GrDiffBuilder.prototype._getNextContentOnSide = function(content, side) {
+ throw Error('Subclasses must implement _getNextContentOnSide');
+};
- /**
- * Determines whether the given group is either totally an addition or totally
- * a removal.
- *
- * @param {!Object} group (GrDiffGroup)
- * @return {boolean}
- */
- GrDiffBuilder.prototype._isTotal = function(group) {
- return group.type === GrDiffGroup.Type.DELTA &&
- (!group.adds.length || !group.removes.length) &&
- !(!group.adds.length && !group.removes.length);
- };
+/**
+ * Determines whether the given group is either totally an addition or totally
+ * a removal.
+ *
+ * @param {!Object} group (GrDiffGroup)
+ * @return {boolean}
+ */
+GrDiffBuilder.prototype._isTotal = function(group) {
+ return group.type === GrDiffGroup.Type.DELTA &&
+ (!group.adds.length || !group.removes.length) &&
+ !(!group.adds.length && !group.removes.length);
+};
- /**
- * Set the blame information for the diff. For any already-rendered line,
- * re-render its blame cell content.
- *
- * @param {Object} blame
- */
- GrDiffBuilder.prototype.setBlame = function(blame) {
- this._blameInfo = blame;
+/**
+ * Set the blame information for the diff. For any already-rendered line,
+ * re-render its blame cell content.
+ *
+ * @param {Object} blame
+ */
+GrDiffBuilder.prototype.setBlame = function(blame) {
+ this._blameInfo = blame;
- // TODO(wyatta): make this loop asynchronous.
- for (const commit of blame) {
- for (const range of commit.ranges) {
- for (let i = range.start; i <= range.end; i++) {
- // TODO(wyatta): this query is expensive, but, when traversing a
- // range, the lines are consecutive, and given the previous blame
- // cell, the next one can be reached cheaply.
- const el = this._getBlameByLineNum(i);
- if (!el) { continue; }
- // Remove the element's children (if any).
- while (el.hasChildNodes()) {
- el.removeChild(el.lastChild);
- }
- const blame = this._getBlameForBaseLine(i, commit);
- el.appendChild(blame);
+ // TODO(wyatta): make this loop asynchronous.
+ for (const commit of blame) {
+ for (const range of commit.ranges) {
+ for (let i = range.start; i <= range.end; i++) {
+ // TODO(wyatta): this query is expensive, but, when traversing a
+ // range, the lines are consecutive, and given the previous blame
+ // cell, the next one can be reached cheaply.
+ const el = this._getBlameByLineNum(i);
+ if (!el) { continue; }
+ // Remove the element's children (if any).
+ while (el.hasChildNodes()) {
+ el.removeChild(el.lastChild);
}
+ const blame = this._getBlameForBaseLine(i, commit);
+ el.appendChild(blame);
}
}
- };
+ }
+};
- /**
- * Find the blame cell for a given line number.
- *
- * @param {number} lineNum
- * @return {HTMLTableDataCellElement}
- */
- GrDiffBuilder.prototype._getBlameByLineNum = function(lineNum) {
- const root = Polymer.dom(this._outputEl);
- return root.querySelector(`td.blame[data-line-number="${lineNum}"]`);
- };
+/**
+ * Find the blame cell for a given line number.
+ *
+ * @param {number} lineNum
+ * @return {HTMLTableDataCellElement}
+ */
+GrDiffBuilder.prototype._getBlameByLineNum = function(lineNum) {
+ const root = Polymer.dom(this._outputEl);
+ return root.querySelector(`td.blame[data-line-number="${lineNum}"]`);
+};
- /**
- * Given a base line number, return the commit containing that line in the
- * current set of blame information. If no blame information has been
- * provided, null is returned.
- *
- * @param {number} lineNum
- * @return {Object} The commit information.
- */
- GrDiffBuilder.prototype._getBlameCommitForBaseLine = function(lineNum) {
- if (!this._blameInfo) { return null; }
+/**
+ * Given a base line number, return the commit containing that line in the
+ * current set of blame information. If no blame information has been
+ * provided, null is returned.
+ *
+ * @param {number} lineNum
+ * @return {Object} The commit information.
+ */
+GrDiffBuilder.prototype._getBlameCommitForBaseLine = function(lineNum) {
+ if (!this._blameInfo) { return null; }
- for (const blameCommit of this._blameInfo) {
- for (const range of blameCommit.ranges) {
- if (range.start <= lineNum && range.end >= lineNum) {
- return blameCommit;
- }
+ for (const blameCommit of this._blameInfo) {
+ for (const range of blameCommit.ranges) {
+ if (range.start <= lineNum && range.end >= lineNum) {
+ return blameCommit;
}
}
- return null;
- };
+ }
+ return null;
+};
- /**
- * Given the number of a base line, get the content for the blame cell of that
- * line. If there is no blame information for that line, returns null.
- *
- * @param {number} lineNum
- * @param {Object=} opt_commit Optionally provide the commit object, so that
- * it does not need to be searched.
- * @return {HTMLSpanElement}
- */
- GrDiffBuilder.prototype._getBlameForBaseLine = function(lineNum, opt_commit) {
- const commit = opt_commit || this._getBlameCommitForBaseLine(lineNum);
- if (!commit) { return null; }
+/**
+ * Given the number of a base line, get the content for the blame cell of that
+ * line. If there is no blame information for that line, returns null.
+ *
+ * @param {number} lineNum
+ * @param {Object=} opt_commit Optionally provide the commit object, so that
+ * it does not need to be searched.
+ * @return {HTMLSpanElement}
+ */
+GrDiffBuilder.prototype._getBlameForBaseLine = function(lineNum, opt_commit) {
+ const commit = opt_commit || this._getBlameCommitForBaseLine(lineNum);
+ if (!commit) { return null; }
- const isStartOfRange = commit.ranges.some(r => r.start === lineNum);
+ const isStartOfRange = commit.ranges.some(r => r.start === lineNum);
- const date = (new Date(commit.time * 1000)).toLocaleDateString();
- const blameNode = this._createElement('span',
- isStartOfRange ? 'startOfRange' : '');
+ const date = (new Date(commit.time * 1000)).toLocaleDateString();
+ const blameNode = this._createElement('span',
+ isStartOfRange ? 'startOfRange' : '');
- const shaNode = this._createElement('a', 'blameDate');
- shaNode.innerText = `${date}`;
- shaNode.setAttribute('href', `/q/${commit.id}`);
- blameNode.appendChild(shaNode);
+ const shaNode = this._createElement('a', 'blameDate');
+ shaNode.innerText = `${date}`;
+ shaNode.setAttribute('href', `/q/${commit.id}`);
+ blameNode.appendChild(shaNode);
- const shortName = commit.author.split(' ')[0];
- const authorNode = this._createElement('span', 'blameAuthor');
- authorNode.innerText = ` ${shortName}`;
- blameNode.appendChild(authorNode);
+ const shortName = commit.author.split(' ')[0];
+ const authorNode = this._createElement('span', 'blameAuthor');
+ authorNode.innerText = ` ${shortName}`;
+ blameNode.appendChild(authorNode);
- const hoverCardFragment = this._createElement('span', 'blameHoverCard');
- hoverCardFragment.innerText =
- `Commit ${commit.id}
+ const hoverCardFragment = this._createElement('span', 'blameHoverCard');
+ hoverCardFragment.innerText =
+ `Commit ${commit.id}
Author: ${commit.author}
Date: ${date}
${commit.commit_msg}`;
- const hovercard = this._createElement('gr-hovercard');
- hovercard.appendChild(hoverCardFragment);
- blameNode.appendChild(hovercard);
+ const hovercard = this._createElement('gr-hovercard');
+ hovercard.appendChild(hoverCardFragment);
+ blameNode.appendChild(hovercard);
- return blameNode;
- };
+ return blameNode;
+};
- /**
- * Create a blame cell for the given base line. Blame information will be
- * included in the cell if available.
- *
- * @param {GrDiffLine} line
- * @return {HTMLTableDataCellElement}
- */
- GrDiffBuilder.prototype._createBlameCell = function(line) {
- const blameTd = this._createElement('td', 'blame');
- blameTd.setAttribute('data-line-number', line.beforeNumber);
- if (line.beforeNumber) {
- const content = this._getBlameForBaseLine(line.beforeNumber);
- if (content) {
- blameTd.appendChild(content);
- }
+/**
+ * Create a blame cell for the given base line. Blame information will be
+ * included in the cell if available.
+ *
+ * @param {GrDiffLine} line
+ * @return {HTMLTableDataCellElement}
+ */
+GrDiffBuilder.prototype._createBlameCell = function(line) {
+ const blameTd = this._createElement('td', 'blame');
+ blameTd.setAttribute('data-line-number', line.beforeNumber);
+ if (line.beforeNumber) {
+ const content = this._getBlameForBaseLine(line.beforeNumber);
+ if (content) {
+ blameTd.appendChild(content);
}
- return blameTd;
- };
+ }
+ return blameTd;
+};
- /**
- * Finds the line number element given the content element by walking up the
- * DOM tree to the diff row and then querying for a .lineNum element on the
- * requested side.
- *
- * TODO(brohlfs): Consolidate this with getLineEl... methods in html file.
- */
- GrDiffBuilder.prototype._getLineNumberEl = function(content, side) {
- let row = content;
- while (row && !row.classList.contains('diff-row')) row = row.parentElement;
- return row ? row.querySelector('.lineNum.' + side) : null;
- };
-
- window.GrDiffBuilder = GrDiffBuilder;
-})(window);
+/**
+ * Finds the line number element given the content element by walking up the
+ * DOM tree to the diff row and then querying for a .lineNum element on the
+ * requested side.
+ *
+ * TODO(brohlfs): Consolidate this with getLineEl... methods in html file.
+ */
+GrDiffBuilder.prototype._getLineNumberEl = function(content, side) {
+ let row = content;
+ while (row && !row.classList.contains('diff-row')) row = row.parentElement;
+ return row ? row.querySelector('.lineNum.' + side) : null;
+};
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
index 6936957..f281785 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -29,6 +29,7 @@
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {htmlTemplate} from './gr-diff-host_html.js';
import {PatchSetBehavior} from '../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.js';
+import {GrDiffBuilder} from '../gr-diff-builder/gr-diff-builder.js';
const MSG_EMPTY_BLAME = 'No blame information for this diff.';
diff --git a/polygerrit-ui/app/elements/gr-app-global-var-init.js b/polygerrit-ui/app/elements/gr-app-global-var-init.js
index 3e32ef4..43fbf10 100644
--- a/polygerrit-ui/app/elements/gr-app-global-var-init.js
+++ b/polygerrit-ui/app/elements/gr-app-global-var-init.js
@@ -27,6 +27,7 @@
import {GrAttributeHelper} from './plugins/gr-attribute-helper/gr-attribute-helper.js';
import {GrDiffLine} from './diff/gr-diff/gr-diff-line.js';
import {GrDiffGroup} from './diff/gr-diff/gr-diff-group.js';
+import {GrDiffBuilder} from './diff/gr-diff-builder/gr-diff-builder.js';
export function initGlobalVariables() {
window.GrDisplayNameUtils = GrDisplayNameUtils;
@@ -34,4 +35,5 @@
window.GrAttributeHelper = GrAttributeHelper;
window.GrDiffLine = GrDiffLine;
window.GrDiffGroup = GrDiffGroup;
+ window.GrDiffBuilder = GrDiffBuilder;
}