Merge "Converting to class and renaming gr-diff-builder element"
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<link rel="import" href="/bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../gr-coverage-layer/gr-coverage-layer.html">
|
||||
<link rel="import" href="../gr-diff-processor/gr-diff-processor.html">
|
||||
<link rel="import" href="../../../elements/shared/gr-hovercard/gr-hovercard.html">
|
||||
<link rel="import" href="../gr-ranged-comment-layer/gr-ranged-comment-layer.html">
|
||||
|
||||
<dom-module id="gr-diff-builder">
|
||||
<template>
|
||||
<div class="contentWrapper">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<gr-ranged-comment-layer
|
||||
id="rangeLayer"
|
||||
comment-ranges="[[commentRanges]]"></gr-ranged-comment-layer>
|
||||
<gr-coverage-layer
|
||||
id="coverageLayerLeft"
|
||||
coverage-ranges="[[_leftCoverageRanges]]"
|
||||
side="left"></gr-coverage-layer>
|
||||
<gr-coverage-layer
|
||||
id="coverageLayerRight"
|
||||
coverage-ranges="[[_rightCoverageRanges]]"
|
||||
side="right"></gr-coverage-layer>
|
||||
<gr-diff-processor
|
||||
id="processor"
|
||||
groups="{{_groups}}"></gr-diff-processor>
|
||||
</template>
|
||||
<script src="../../../scripts/util.js"></script>
|
||||
<script src="../gr-diff/gr-diff-line.js"></script>
|
||||
<script src="../gr-diff/gr-diff-group.js"></script>
|
||||
<script src="../gr-diff-highlight/gr-annotation.js"></script>
|
||||
<script src="gr-diff-builder.js"></script>
|
||||
<script src="gr-diff-builder-side-by-side.js"></script>
|
||||
<script src="gr-diff-builder-unified.js"></script>
|
||||
<script src="gr-diff-builder-image.js"></script>
|
||||
<script src="gr-diff-builder-binary.js"></script>
|
||||
<script src="gr-diff-builder-element.js"></script>
|
||||
</dom-module>
|
||||
@@ -0,0 +1,389 @@
|
||||
// Copyright (C) 2020 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() {
|
||||
'use strict';
|
||||
|
||||
const DiffViewMode = {
|
||||
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
|
||||
UNIFIED: 'UNIFIED_DIFF',
|
||||
};
|
||||
|
||||
const TRAILING_WHITESPACE_PATTERN = /\s+$/;
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
*/
|
||||
class GrDiffBuilderElement extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-diff-builder'; }
|
||||
/**
|
||||
* Fired when the diff begins rendering.
|
||||
*
|
||||
* @event render-start
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the diff finishes rendering text content.
|
||||
*
|
||||
* @event render-content
|
||||
*/
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
diff: Object,
|
||||
changeNum: String,
|
||||
patchNum: String,
|
||||
viewMode: String,
|
||||
isImageDiff: Boolean,
|
||||
baseImage: Object,
|
||||
revisionImage: Object,
|
||||
parentIndex: Number,
|
||||
path: String,
|
||||
projectName: String,
|
||||
|
||||
_builder: Object,
|
||||
_groups: Array,
|
||||
_layers: Array,
|
||||
_showTabs: Boolean,
|
||||
/** @type {!Array<!Gerrit.HoveredRange>} */
|
||||
commentRanges: {
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
/** @type {!Array<!Gerrit.CoverageRange>} */
|
||||
coverageRanges: {
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
_leftCoverageRanges: {
|
||||
type: Array,
|
||||
computed: '_computeLeftCoverageRanges(coverageRanges)',
|
||||
},
|
||||
_rightCoverageRanges: {
|
||||
type: Array,
|
||||
computed: '_computeRightCoverageRanges(coverageRanges)',
|
||||
},
|
||||
/**
|
||||
* The promise last returned from `render()` while the asynchronous
|
||||
* rendering is running - `null` otherwise. Provides a `cancel()`
|
||||
* method that rejects it with `{isCancelled: true}`.
|
||||
*
|
||||
* @type {?Object}
|
||||
*/
|
||||
_cancelableRenderPromise: Object,
|
||||
layers: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get diffElement() {
|
||||
return this.queryEffectiveChildren('#diffTable');
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_groupsChanged(_groups.splices)',
|
||||
];
|
||||
}
|
||||
|
||||
_computeLeftCoverageRanges(coverageRanges) {
|
||||
return coverageRanges.filter(range => range && range.side === 'left');
|
||||
}
|
||||
|
||||
_computeRightCoverageRanges(coverageRanges) {
|
||||
return coverageRanges.filter(range => range && range.side === 'right');
|
||||
}
|
||||
|
||||
render(keyLocations, prefs) {
|
||||
// Setting up annotation layers must happen after plugins are
|
||||
// installed, and |render| satisfies the requirement, however,
|
||||
// |attached| doesn't because in the diff view page, the element is
|
||||
// attached before plugins are installed.
|
||||
this._setupAnnotationLayers();
|
||||
|
||||
this._showTabs = !!prefs.show_tabs;
|
||||
this._showTrailingWhitespace = !!prefs.show_whitespace_errors;
|
||||
|
||||
// Stop the processor if it's running.
|
||||
this.cancel();
|
||||
|
||||
this._builder = this._getDiffBuilder(this.diff, prefs);
|
||||
|
||||
this.$.processor.context = prefs.context;
|
||||
this.$.processor.keyLocations = keyLocations;
|
||||
|
||||
this._clearDiffContent();
|
||||
this._builder.addColumns(this.diffElement, prefs.font_size);
|
||||
|
||||
const isBinary = !!(this.isImageDiff || this.diff.binary);
|
||||
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'render-start', {bubbles: true, composed: true}));
|
||||
this._cancelableRenderPromise = util.makeCancelable(
|
||||
this.$.processor.process(this.diff.content, isBinary)
|
||||
.then(() => {
|
||||
if (this.isImageDiff) {
|
||||
this._builder.renderDiff();
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('render-content',
|
||||
{bubbles: true, composed: true}));
|
||||
}));
|
||||
return this._cancelableRenderPromise
|
||||
.finally(() => { this._cancelableRenderPromise = null; })
|
||||
// Mocca testing does not like uncaught rejections, so we catch
|
||||
// the cancels which are expected and should not throw errors in
|
||||
// tests.
|
||||
.catch(e => { if (!e.isCanceled) return Promise.reject(e); });
|
||||
}
|
||||
|
||||
_setupAnnotationLayers() {
|
||||
const layers = [
|
||||
this._createTrailingWhitespaceLayer(),
|
||||
this._createIntralineLayer(),
|
||||
this._createTabIndicatorLayer(),
|
||||
this.$.rangeLayer,
|
||||
this.$.coverageLayerLeft,
|
||||
this.$.coverageLayerRight,
|
||||
];
|
||||
|
||||
if (this.layers) {
|
||||
layers.push(...this.layers);
|
||||
}
|
||||
this._layers = layers;
|
||||
}
|
||||
|
||||
getLineElByChild(node) {
|
||||
while (node) {
|
||||
if (node instanceof Element) {
|
||||
if (node.classList.contains('lineNum')) {
|
||||
return node;
|
||||
}
|
||||
if (node.classList.contains('section')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
node = node.previousSibling || node.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getLineNumberByChild(node) {
|
||||
const lineEl = this.getLineElByChild(node);
|
||||
return lineEl ?
|
||||
parseInt(lineEl.getAttribute('data-value'), 10) :
|
||||
null;
|
||||
}
|
||||
|
||||
getContentByLine(lineNumber, opt_side, opt_root) {
|
||||
return this._builder.getContentByLine(lineNumber, opt_side, opt_root);
|
||||
}
|
||||
|
||||
getContentByLineEl(lineEl) {
|
||||
const root = Polymer.dom(lineEl.parentElement);
|
||||
const side = this.getSideByLineEl(lineEl);
|
||||
const line = lineEl.getAttribute('data-value');
|
||||
return this.getContentByLine(line, side, root);
|
||||
}
|
||||
|
||||
getLineElByNumber(lineNumber, opt_side) {
|
||||
const sideSelector = opt_side ? ('.' + opt_side) : '';
|
||||
return this.diffElement.querySelector(
|
||||
'.lineNum[data-value="' + lineNumber + '"]' + sideSelector);
|
||||
}
|
||||
|
||||
getContentsByLineRange(startLine, endLine, opt_side) {
|
||||
const result = [];
|
||||
this._builder.findLinesByRange(startLine, endLine, opt_side, null,
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
getSideByLineEl(lineEl) {
|
||||
return lineEl.classList.contains(GrDiffBuilder.Side.RIGHT) ?
|
||||
GrDiffBuilder.Side.RIGHT : GrDiffBuilder.Side.LEFT;
|
||||
}
|
||||
|
||||
emitGroup(group, sectionEl) {
|
||||
this._builder.emitGroup(group, sectionEl);
|
||||
}
|
||||
|
||||
showContext(newGroups, sectionEl) {
|
||||
const groups = this._builder.groups;
|
||||
|
||||
const contextIndex = groups.findIndex(group =>
|
||||
group.element === sectionEl
|
||||
);
|
||||
groups.splice(contextIndex, 1, ...newGroups);
|
||||
|
||||
for (const newGroup of newGroups) {
|
||||
this._builder.emitGroup(newGroup, sectionEl);
|
||||
}
|
||||
sectionEl.parentNode.removeChild(sectionEl);
|
||||
|
||||
this.async(() => this.fire('render-content'), 1);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.$.processor.cancel();
|
||||
if (this._cancelableRenderPromise) {
|
||||
this._cancelableRenderPromise.cancel();
|
||||
this._cancelableRenderPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
_handlePreferenceError(pref) {
|
||||
const message = `The value of the '${pref}' user preference is ` +
|
||||
`invalid. Fix in diff preferences`;
|
||||
this.dispatchEvent(new CustomEvent('show-alert', {
|
||||
detail: {
|
||||
message,
|
||||
}, bubbles: true, composed: true}));
|
||||
throw Error(`Invalid preference value: ${pref}`);
|
||||
}
|
||||
|
||||
_getDiffBuilder(diff, prefs) {
|
||||
if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
|
||||
this._handlePreferenceError('tab size');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(prefs.line_length) || prefs.line_length <= 0) {
|
||||
this._handlePreferenceError('diff width');
|
||||
return;
|
||||
}
|
||||
|
||||
let builder = null;
|
||||
if (this.isImageDiff) {
|
||||
builder = new GrDiffBuilderImage(diff, prefs, this.diffElement,
|
||||
this.baseImage, this.revisionImage);
|
||||
} else if (diff.binary) {
|
||||
// If the diff is binary, but not an image.
|
||||
return new GrDiffBuilderBinary(diff, prefs, this.diffElement);
|
||||
} else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
|
||||
builder = new GrDiffBuilderSideBySide(diff, prefs, this.diffElement,
|
||||
this._layers);
|
||||
} else if (this.viewMode === DiffViewMode.UNIFIED) {
|
||||
builder = new GrDiffBuilderUnified(diff, prefs, this.diffElement,
|
||||
this._layers);
|
||||
}
|
||||
if (!builder) {
|
||||
throw Error('Unsupported diff view mode: ' + this.viewMode);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
_clearDiffContent() {
|
||||
this.diffElement.innerHTML = null;
|
||||
}
|
||||
|
||||
_groupsChanged(changeRecord) {
|
||||
if (!changeRecord) { return; }
|
||||
for (const splice of changeRecord.indexSplices) {
|
||||
let group;
|
||||
for (let i = 0; i < splice.addedCount; i++) {
|
||||
group = splice.object[splice.index + i];
|
||||
this._builder.groups.push(group);
|
||||
this._builder.emitGroup(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_createIntralineLayer() {
|
||||
return {
|
||||
// Take a DIV.contentText element and a line object with intraline
|
||||
// differences to highlight and apply them to the element as
|
||||
// annotations.
|
||||
annotate(contentEl, lineNumberEl, line) {
|
||||
const HL_CLASS = 'style-scope gr-diff intraline';
|
||||
for (const highlight of line.highlights) {
|
||||
// The start and end indices could be the same if a highlight is
|
||||
// meant to start at the end of a line and continue onto the
|
||||
// next one. Ignore it.
|
||||
if (highlight.startIndex === highlight.endIndex) { continue; }
|
||||
|
||||
// If endIndex isn't present, continue to the end of the line.
|
||||
const endIndex = highlight.endIndex === undefined ?
|
||||
line.text.length :
|
||||
highlight.endIndex;
|
||||
|
||||
GrAnnotation.annotateElement(
|
||||
contentEl,
|
||||
highlight.startIndex,
|
||||
endIndex - highlight.startIndex,
|
||||
HL_CLASS);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_createTabIndicatorLayer() {
|
||||
const show = () => this._showTabs;
|
||||
return {
|
||||
annotate(contentEl, lineNumberEl, line) {
|
||||
// If visible tabs are disabled, do nothing.
|
||||
if (!show()) { return; }
|
||||
|
||||
// Find and annotate the locations of tabs.
|
||||
const split = line.text.split('\t');
|
||||
if (!split) { return; }
|
||||
for (let i = 0, pos = 0; i < split.length - 1; i++) {
|
||||
// Skip forward by the length of the content
|
||||
pos += split[i].length;
|
||||
|
||||
GrAnnotation.annotateElement(contentEl, pos, 1,
|
||||
'style-scope gr-diff tab-indicator');
|
||||
|
||||
// Skip forward by one tab character.
|
||||
pos++;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_createTrailingWhitespaceLayer() {
|
||||
const show = function() {
|
||||
return this._showTrailingWhitespace;
|
||||
}.bind(this);
|
||||
|
||||
return {
|
||||
annotate(contentEl, lineNumberEl, line) {
|
||||
if (!show()) { return; }
|
||||
|
||||
const match = line.text.match(TRAILING_WHITESPACE_PATTERN);
|
||||
if (match) {
|
||||
// Normalize string positions in case there is unicode before or
|
||||
// within the match.
|
||||
const index = GrAnnotation.getStringLength(
|
||||
line.text.substr(0, match.index));
|
||||
const length = GrAnnotation.getStringLength(match[0]);
|
||||
GrAnnotation.annotateElement(contentEl, index, length,
|
||||
'style-scope gr-diff trailing-whitespace');
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setBlame(blame) {
|
||||
if (!this._builder || !blame) { return; }
|
||||
this._builder.setBlame(blame);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrDiffBuilderElement.is, GrDiffBuilderElement);
|
||||
})();
|
||||
@@ -33,7 +33,7 @@ limitations under the License.
|
||||
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
|
||||
<link rel="import" href="gr-diff-builder.html">
|
||||
<link rel="import" href="gr-diff-builder-element.html">
|
||||
|
||||
<script>void(0);</script>
|
||||
|
||||
@@ -1,423 +0,0 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<link rel="import" href="/bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../gr-coverage-layer/gr-coverage-layer.html">
|
||||
<link rel="import" href="../gr-diff-processor/gr-diff-processor.html">
|
||||
<link rel="import" href="../../../elements/shared/gr-hovercard/gr-hovercard.html">
|
||||
<link rel="import" href="../gr-ranged-comment-layer/gr-ranged-comment-layer.html">
|
||||
|
||||
<dom-module id="gr-diff-builder">
|
||||
<template>
|
||||
<div class="contentWrapper">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<gr-ranged-comment-layer
|
||||
id="rangeLayer"
|
||||
comment-ranges="[[commentRanges]]"></gr-ranged-comment-layer>
|
||||
<gr-coverage-layer
|
||||
id="coverageLayerLeft"
|
||||
coverage-ranges="[[_leftCoverageRanges]]"
|
||||
side="left"></gr-coverage-layer>
|
||||
<gr-coverage-layer
|
||||
id="coverageLayerRight"
|
||||
coverage-ranges="[[_rightCoverageRanges]]"
|
||||
side="right"></gr-coverage-layer>
|
||||
<gr-diff-processor
|
||||
id="processor"
|
||||
groups="{{_groups}}"></gr-diff-processor>
|
||||
</template>
|
||||
<script src="../../../scripts/util.js"></script>
|
||||
<script src="../gr-diff/gr-diff-line.js"></script>
|
||||
<script src="../gr-diff/gr-diff-group.js"></script>
|
||||
<script src="../gr-diff-highlight/gr-annotation.js"></script>
|
||||
<script src="gr-diff-builder.js"></script>
|
||||
<script src="gr-diff-builder-side-by-side.js"></script>
|
||||
<script src="gr-diff-builder-unified.js"></script>
|
||||
<script src="gr-diff-builder-image.js"></script>
|
||||
<script src="gr-diff-builder-binary.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const DiffViewMode = {
|
||||
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
|
||||
UNIFIED: 'UNIFIED_DIFF',
|
||||
};
|
||||
|
||||
const TRAILING_WHITESPACE_PATTERN = /\s+$/;
|
||||
|
||||
Polymer({
|
||||
is: 'gr-diff-builder',
|
||||
|
||||
/**
|
||||
* Fired when the diff begins rendering.
|
||||
*
|
||||
* @event render-start
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the diff finishes rendering text content.
|
||||
*
|
||||
* @event render-content
|
||||
*/
|
||||
|
||||
properties: {
|
||||
diff: Object,
|
||||
changeNum: String,
|
||||
patchNum: String,
|
||||
viewMode: String,
|
||||
isImageDiff: Boolean,
|
||||
baseImage: Object,
|
||||
revisionImage: Object,
|
||||
parentIndex: Number,
|
||||
path: String,
|
||||
projectName: String,
|
||||
|
||||
_builder: Object,
|
||||
_groups: Array,
|
||||
_layers: Array,
|
||||
_showTabs: Boolean,
|
||||
/** @type {!Array<!Gerrit.HoveredRange>} */
|
||||
commentRanges: {
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
/** @type {!Array<!Gerrit.CoverageRange>} */
|
||||
coverageRanges: {
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
_leftCoverageRanges: {
|
||||
type: Array,
|
||||
computed: '_computeLeftCoverageRanges(coverageRanges)',
|
||||
},
|
||||
_rightCoverageRanges: {
|
||||
type: Array,
|
||||
computed: '_computeRightCoverageRanges(coverageRanges)',
|
||||
},
|
||||
/**
|
||||
* The promise last returned from `render()` while the asynchronous
|
||||
* rendering is running - `null` otherwise. Provides a `cancel()`
|
||||
* method that rejects it with `{isCancelled: true}`.
|
||||
*
|
||||
* @type {?Object}
|
||||
*/
|
||||
_cancelableRenderPromise: Object,
|
||||
layers: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
},
|
||||
|
||||
behaviors: [
|
||||
Gerrit.FireBehavior,
|
||||
],
|
||||
|
||||
get diffElement() {
|
||||
return this.queryEffectiveChildren('#diffTable');
|
||||
},
|
||||
|
||||
observers: [
|
||||
'_groupsChanged(_groups.splices)',
|
||||
],
|
||||
|
||||
_computeLeftCoverageRanges(coverageRanges) {
|
||||
return coverageRanges.filter(range => range && range.side === 'left');
|
||||
},
|
||||
|
||||
_computeRightCoverageRanges(coverageRanges) {
|
||||
return coverageRanges.filter(range => range && range.side === 'right');
|
||||
},
|
||||
|
||||
render(keyLocations, prefs) {
|
||||
// Setting up annotation layers must happen after plugins are
|
||||
// installed, and |render| satisfies the requirement, however,
|
||||
// |attached| doesn't because in the diff view page, the element is
|
||||
// attached before plugins are installed.
|
||||
this._setupAnnotationLayers();
|
||||
|
||||
this._showTabs = !!prefs.show_tabs;
|
||||
this._showTrailingWhitespace = !!prefs.show_whitespace_errors;
|
||||
|
||||
// Stop the processor if it's running.
|
||||
this.cancel();
|
||||
|
||||
this._builder = this._getDiffBuilder(this.diff, prefs);
|
||||
|
||||
this.$.processor.context = prefs.context;
|
||||
this.$.processor.keyLocations = keyLocations;
|
||||
|
||||
this._clearDiffContent();
|
||||
this._builder.addColumns(this.diffElement, prefs.font_size);
|
||||
|
||||
const isBinary = !!(this.isImageDiff || this.diff.binary);
|
||||
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'render-start', {bubbles: true, composed: true}));
|
||||
this._cancelableRenderPromise = util.makeCancelable(
|
||||
this.$.processor.process(this.diff.content, isBinary)
|
||||
.then(() => {
|
||||
if (this.isImageDiff) {
|
||||
this._builder.renderDiff();
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent('render-content',
|
||||
{bubbles: true, composed: true}));
|
||||
}));
|
||||
return this._cancelableRenderPromise
|
||||
.finally(() => { this._cancelableRenderPromise = null; })
|
||||
// Mocca testing does not like uncaught rejections, so we catch
|
||||
// the cancels which are expected and should not throw errors in
|
||||
// tests.
|
||||
.catch(e => { if (!e.isCanceled) return Promise.reject(e); });
|
||||
},
|
||||
|
||||
_setupAnnotationLayers() {
|
||||
const layers = [
|
||||
this._createTrailingWhitespaceLayer(),
|
||||
this._createIntralineLayer(),
|
||||
this._createTabIndicatorLayer(),
|
||||
this.$.rangeLayer,
|
||||
this.$.coverageLayerLeft,
|
||||
this.$.coverageLayerRight,
|
||||
];
|
||||
|
||||
if (this.layers) {
|
||||
layers.push(...this.layers);
|
||||
}
|
||||
this._layers = layers;
|
||||
},
|
||||
|
||||
getLineElByChild(node) {
|
||||
while (node) {
|
||||
if (node instanceof Element) {
|
||||
if (node.classList.contains('lineNum')) {
|
||||
return node;
|
||||
}
|
||||
if (node.classList.contains('section')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
node = node.previousSibling || node.parentElement;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getLineNumberByChild(node) {
|
||||
const lineEl = this.getLineElByChild(node);
|
||||
return lineEl ?
|
||||
parseInt(lineEl.getAttribute('data-value'), 10) :
|
||||
null;
|
||||
},
|
||||
|
||||
getContentByLine(lineNumber, opt_side, opt_root) {
|
||||
return this._builder.getContentByLine(lineNumber, opt_side, opt_root);
|
||||
},
|
||||
|
||||
getContentByLineEl(lineEl) {
|
||||
const root = Polymer.dom(lineEl.parentElement);
|
||||
const side = this.getSideByLineEl(lineEl);
|
||||
const line = lineEl.getAttribute('data-value');
|
||||
return this.getContentByLine(line, side, root);
|
||||
},
|
||||
|
||||
getLineElByNumber(lineNumber, opt_side) {
|
||||
const sideSelector = opt_side ? ('.' + opt_side) : '';
|
||||
return this.diffElement.querySelector(
|
||||
'.lineNum[data-value="' + lineNumber + '"]' + sideSelector);
|
||||
},
|
||||
|
||||
getContentsByLineRange(startLine, endLine, opt_side) {
|
||||
const result = [];
|
||||
this._builder.findLinesByRange(startLine, endLine, opt_side, null,
|
||||
result);
|
||||
return result;
|
||||
},
|
||||
|
||||
getSideByLineEl(lineEl) {
|
||||
return lineEl.classList.contains(GrDiffBuilder.Side.RIGHT) ?
|
||||
GrDiffBuilder.Side.RIGHT : GrDiffBuilder.Side.LEFT;
|
||||
},
|
||||
|
||||
emitGroup(group, sectionEl) {
|
||||
this._builder.emitGroup(group, sectionEl);
|
||||
},
|
||||
|
||||
showContext(newGroups, sectionEl) {
|
||||
const groups = this._builder.groups;
|
||||
|
||||
const contextIndex = groups.findIndex(group =>
|
||||
group.element === sectionEl
|
||||
);
|
||||
groups.splice(contextIndex, 1, ...newGroups);
|
||||
|
||||
for (const newGroup of newGroups) {
|
||||
this._builder.emitGroup(newGroup, sectionEl);
|
||||
}
|
||||
sectionEl.parentNode.removeChild(sectionEl);
|
||||
|
||||
this.async(() => this.fire('render-content'), 1);
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.$.processor.cancel();
|
||||
if (this._cancelableRenderPromise) {
|
||||
this._cancelableRenderPromise.cancel();
|
||||
this._cancelableRenderPromise = null;
|
||||
}
|
||||
},
|
||||
|
||||
_handlePreferenceError(pref) {
|
||||
const message = `The value of the '${pref}' user preference is ` +
|
||||
`invalid. Fix in diff preferences`;
|
||||
this.dispatchEvent(new CustomEvent('show-alert', {
|
||||
detail: {
|
||||
message,
|
||||
}, bubbles: true, composed: true}));
|
||||
throw Error(`Invalid preference value: ${pref}`);
|
||||
},
|
||||
|
||||
_getDiffBuilder(diff, prefs) {
|
||||
if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
|
||||
this._handlePreferenceError('tab size');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(prefs.line_length) || prefs.line_length <= 0) {
|
||||
this._handlePreferenceError('diff width');
|
||||
return;
|
||||
}
|
||||
|
||||
let builder = null;
|
||||
if (this.isImageDiff) {
|
||||
builder = new GrDiffBuilderImage(diff, prefs, this.diffElement,
|
||||
this.baseImage, this.revisionImage);
|
||||
} else if (diff.binary) {
|
||||
// If the diff is binary, but not an image.
|
||||
return new GrDiffBuilderBinary(diff, prefs, this.diffElement);
|
||||
} else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
|
||||
builder = new GrDiffBuilderSideBySide(diff, prefs, this.diffElement,
|
||||
this._layers);
|
||||
} else if (this.viewMode === DiffViewMode.UNIFIED) {
|
||||
builder = new GrDiffBuilderUnified(diff, prefs, this.diffElement,
|
||||
this._layers);
|
||||
}
|
||||
if (!builder) {
|
||||
throw Error('Unsupported diff view mode: ' + this.viewMode);
|
||||
}
|
||||
return builder;
|
||||
},
|
||||
|
||||
_clearDiffContent() {
|
||||
this.diffElement.innerHTML = null;
|
||||
},
|
||||
|
||||
_groupsChanged(changeRecord) {
|
||||
if (!changeRecord) { return; }
|
||||
for (const splice of changeRecord.indexSplices) {
|
||||
let group;
|
||||
for (let i = 0; i < splice.addedCount; i++) {
|
||||
group = splice.object[splice.index + i];
|
||||
this._builder.groups.push(group);
|
||||
this._builder.emitGroup(group);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_createIntralineLayer() {
|
||||
return {
|
||||
// Take a DIV.contentText element and a line object with intraline
|
||||
// differences to highlight and apply them to the element as
|
||||
// annotations.
|
||||
annotate(contentEl, lineNumberEl, line) {
|
||||
const HL_CLASS = 'style-scope gr-diff intraline';
|
||||
for (const highlight of line.highlights) {
|
||||
// The start and end indices could be the same if a highlight is
|
||||
// meant to start at the end of a line and continue onto the
|
||||
// next one. Ignore it.
|
||||
if (highlight.startIndex === highlight.endIndex) { continue; }
|
||||
|
||||
// If endIndex isn't present, continue to the end of the line.
|
||||
const endIndex = highlight.endIndex === undefined ?
|
||||
line.text.length :
|
||||
highlight.endIndex;
|
||||
|
||||
GrAnnotation.annotateElement(
|
||||
contentEl,
|
||||
highlight.startIndex,
|
||||
endIndex - highlight.startIndex,
|
||||
HL_CLASS);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
_createTabIndicatorLayer() {
|
||||
const show = () => this._showTabs;
|
||||
return {
|
||||
annotate(contentEl, lineNumberEl, line) {
|
||||
// If visible tabs are disabled, do nothing.
|
||||
if (!show()) { return; }
|
||||
|
||||
// Find and annotate the locations of tabs.
|
||||
const split = line.text.split('\t');
|
||||
if (!split) { return; }
|
||||
for (let i = 0, pos = 0; i < split.length - 1; i++) {
|
||||
// Skip forward by the length of the content
|
||||
pos += split[i].length;
|
||||
|
||||
GrAnnotation.annotateElement(contentEl, pos, 1,
|
||||
'style-scope gr-diff tab-indicator');
|
||||
|
||||
// Skip forward by one tab character.
|
||||
pos++;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
_createTrailingWhitespaceLayer() {
|
||||
const show = function() {
|
||||
return this._showTrailingWhitespace;
|
||||
}.bind(this);
|
||||
|
||||
return {
|
||||
annotate(contentEl, lineNumberEl, line) {
|
||||
if (!show()) { return; }
|
||||
|
||||
const match = line.text.match(TRAILING_WHITESPACE_PATTERN);
|
||||
if (match) {
|
||||
// Normalize string positions in case there is unicode before or
|
||||
// within the match.
|
||||
const index = GrAnnotation.getStringLength(
|
||||
line.text.substr(0, match.index));
|
||||
const length = GrAnnotation.getStringLength(match[0]);
|
||||
GrAnnotation.annotateElement(contentEl, index, length,
|
||||
'style-scope gr-diff trailing-whitespace');
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
setBlame(blame) {
|
||||
if (!this._builder || !blame) { return; }
|
||||
this._builder.setBlame(blame);
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</dom-module>
|
||||
@@ -20,7 +20,7 @@ limitations under the License.
|
||||
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../gr-diff-builder/gr-diff-builder.html">
|
||||
<link rel="import" href="../gr-diff-builder/gr-diff-builder-element.html">
|
||||
<link rel="import" href="../gr-diff-highlight/gr-diff-highlight.html">
|
||||
<link rel="import" href="../gr-diff-selection/gr-diff-selection.html">
|
||||
<link rel="import" href="../gr-syntax-themes/gr-syntax-theme.html">
|
||||
|
||||
@@ -112,7 +112,7 @@ limitations under the License.
|
||||
'core/gr-smart-search/gr-smart-search_test.html',
|
||||
'diff/gr-comment-api/gr-comment-api_test.html',
|
||||
'diff/gr-coverage-layer/gr-coverage-layer_test.html',
|
||||
'diff/gr-diff-builder/gr-diff-builder_test.html',
|
||||
'diff/gr-diff-builder/gr-diff-builder-element_test.html',
|
||||
'diff/gr-diff-builder/gr-diff-builder-unified_test.html',
|
||||
'diff/gr-diff-cursor/gr-diff-cursor_test.html',
|
||||
'diff/gr-diff-highlight/gr-annotation_test.html',
|
||||
|
||||
Reference in New Issue
Block a user