diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html
new file mode 100644
index 0000000000..56a6fb9b1a
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js
new file mode 100644
index 0000000000..5e7cc26932
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js
@@ -0,0 +1,129 @@
+/**
+ * @license
+ * Copyright (C) 2019 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';
+
+ /** @enum {string} */
+ Gerrit.CoverageType = {
+ /**
+ * start_character and end_character of the range will be ignored for this
+ * type.
+ */
+ COVERED: 'COVERED',
+ /**
+ * start_character and end_character of the range will be ignored for this
+ * type.
+ */
+ NOT_COVERED: 'NOT_COVERED',
+ PARTIALLY_COVERED: 'PARTIALLY_COVERED',
+ /**
+ * You don't have to use this. If there is no coverage information for a
+ * range, then it implicitly means NOT_INSTRUMENTED. start_character and
+ * end_character of the range will be ignored for this type.
+ */
+ NOT_INSTRUMENTED: 'NOT_INSTRUMENTED',
+ };
+
+ /**
+ * @typedef {{
+ * side: string,
+ * type: Gerrit.CoverageType,
+ * code_range: Gerrit.Range,
+ * }}
+ */
+ Gerrit.CoverageRange;
+
+ Polymer({
+ is: 'gr-coverage-layer',
+
+ properties: {
+ /**
+ * Must be sorted by code_range.start_line.
+ * Must only contain ranges that match the side.
+ *
+ * @type {!Array}
+ */
+ coverageRanges: Array,
+ side: String,
+
+ /**
+ * We keep track of the line number from the previous annotate() call,
+ * and also of the index of the coverage range that had matched.
+ * annotate() calls are coming in with increasing line numbers and
+ * coverage ranges are sorted by line number. So this is a very simple
+ * and efficient way for finding the coverage range that matches a given
+ * line number.
+ */
+ _lineNumber: {
+ type: Number,
+ value: 0,
+ },
+ _index: {
+ type: Number,
+ value: 0,
+ },
+ },
+
+ /**
+ * Layer method to add annotations to a line.
+ *
+ * @param {!HTMLElement} el Not used for this layer.
+ * @param {!HTMLElement} lineNumberEl The
element with the line number.
+ * @param {!Object} line Not used for this layer.
+ */
+ annotate(el, lineNumberEl, line) {
+ if (!lineNumberEl || !lineNumberEl.classList.contains(this.side)) {
+ return;
+ }
+ const elementLineNumber = parseInt(
+ lineNumberEl.getAttribute('data-value'), 10);
+ if (!elementLineNumber || elementLineNumber < 1) return;
+
+ // If the line number is smaller than before, then we have to reset our
+ // algorithm and start searching the coverage ranges from the beginning.
+ // That happens for example when you expand diff sections.
+ if (elementLineNumber < this._lineNumber) {
+ this._index = 0;
+ }
+ this._lineNumber = elementLineNumber;
+
+ // We simply loop through all the coverage ranges until we find one that
+ // matches the line number.
+ while (this._index < this.coverageRanges.length) {
+ const coverageRange = this.coverageRanges[this._index];
+
+ // If the line number has moved past the current coverage range, then
+ // try the next coverage range.
+ if (this._lineNumber > coverageRange.code_range.end_line) {
+ this._index++;
+ continue;
+ }
+
+ // If the line number has not reached the next coverage range (and the
+ // range before also did not match), then this line has not been
+ // instrumented. Nothing to do for this line.
+ if (this._lineNumber < coverageRange.code_range.start_line) {
+ return;
+ }
+
+ // The line number is within the current coverage range. Style it!
+ lineNumberEl.classList.add(coverageRange.type);
+ return;
+ }
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
new file mode 100644
index 0000000000..edd88a24e1
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
@@ -0,0 +1,138 @@
+
+
+
+
+gr-coverage-layer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index b5f21b6b91..7bfec005db 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -16,6 +16,7 @@ limitations under the License.
-->
+
@@ -31,6 +32,14 @@ limitations under the License.
+
+
@@ -108,6 +117,19 @@ limitations under the License.
type: Array,
value: () => [],
},
+ /** @type {!Array} */
+ 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()`
@@ -125,6 +147,14 @@ limitations under the License.
'_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,
@@ -184,6 +214,8 @@ limitations under the License.
this._createIntralineLayer(),
this._createTabIndicatorLayer(),
this.$.rangeLayer,
+ this.$.coverageLayerLeft,
+ this.$.coverageLayerRight,
];
// Get layers from plugins (if any).
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 61e8603ed0..8a117af73b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -270,6 +270,15 @@ limitations under the License.
.newlineWarning.hidden {
display: none;
}
+ .lineNum.COVERED {
+ background-color: #E0F2F1;
+ }
+ .lineNum.NOT_COVERED {
+ background-color: #FFD1A4;
+ }
+ .lineNum.PARTIALLY_COVERED {
+ background: linear-gradient(to right bottom, #FFD1A4 0%, #FFD1A4 50%, #E0F2F1 50%, #E0F2F1 100%);
+ }