| // 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() { |
| 'use strict'; |
| |
| var HOVER_PATH_PATTERN = /^comments\.(left|right)\.\#(\d+)\.__hovering$/; |
| var SPLICE_PATH_PATTERN = /^comments\.(left|right)\.splices$/; |
| |
| var RANGE_HIGHLIGHT = 'range'; |
| var HOVER_HIGHLIGHT = 'rangeHighlight'; |
| |
| Polymer({ |
| is: 'gr-ranged-comment-layer', |
| |
| properties: { |
| comments: Object, |
| _listeners: { |
| type: Array, |
| value: function() { return []; }, |
| }, |
| _commentMap: { |
| type: Object, |
| value: function() { return {left: [], right: []}; }, |
| } |
| }, |
| |
| observers: [ |
| '_handleCommentChange(comments.*)', |
| ], |
| |
| /** |
| * Layer method to add annotations to a line. |
| * @param {HTMLElement} el The DIV.contentText element to apply the |
| * annotation to. |
| * @param {GrDiffLine} line The line object. |
| */ |
| annotate: function(el, line) { |
| var ranges = []; |
| if (line.type === GrDiffLine.Type.REMOVE || ( |
| line.type === GrDiffLine.Type.BOTH && |
| el.getAttribute('data-side') !== 'right')) { |
| ranges = ranges.concat(this._getRangesForLine(line, 'left')); |
| } |
| if (line.type === GrDiffLine.Type.ADD || ( |
| line.type === GrDiffLine.Type.BOTH && |
| el.getAttribute('data-side') !== 'left')) { |
| ranges = ranges.concat(this._getRangesForLine(line, 'right')); |
| } |
| |
| ranges.forEach(function(range) { |
| GrAnnotation.annotateElement(el, range.start, |
| range.end - range.start, |
| range.hovering ? HOVER_HIGHLIGHT : RANGE_HIGHLIGHT); |
| }); |
| }, |
| |
| /** |
| * Register a listener for layer updates. |
| * @param {Function<Number, Number, String>} fn The update handler function. |
| * Should accept as arguments the line numbers for the start and end of |
| * the update and the side as a string. |
| */ |
| addListener: function(fn) { |
| this._listeners.push(fn); |
| }, |
| |
| /** |
| * Notify Layer listeners of changes to annotations. |
| * @param {Number} start The line where the update starts. |
| * @param {Number} end The line where the update ends. |
| * @param {String} side The side of the update. ('left' or 'right') |
| */ |
| _notifyUpdateRange: function(start, end, side) { |
| this._listeners.forEach(function(listener) { |
| listener(start, end, side); |
| }); |
| }, |
| |
| /** |
| * Handle change in the comments by updating the comment maps and by |
| * emitting appropriate update notifications. |
| * @param {Object} record The change record. |
| */ |
| _handleCommentChange: function(record) { |
| if (!record.path) { return; } |
| |
| // If the entire set of comments was changed. |
| if (record.path === 'comments') { |
| this._commentMap.left = this._computeCommentMap(this.comments.left); |
| this._commentMap.right = this._computeCommentMap(this.comments.right); |
| return; |
| } |
| |
| // If the change only changed the `hovering` property of a comment. |
| var match = record.path.match(HOVER_PATH_PATTERN); |
| if (match) { |
| var side = match[1]; |
| var index = match[2]; |
| var comment = this.comments[side][index]; |
| if (comment && comment.range) { |
| this._commentMap[side] = this._computeCommentMap(this.comments[side]); |
| this._notifyUpdateRange( |
| comment.range.start_line, comment.range.end_line, side); |
| } |
| return; |
| } |
| |
| // If comments were spliced in or out. |
| match = record.path.match(SPLICE_PATH_PATTERN); |
| if (match) { |
| var side = match[1]; |
| this._commentMap[side] = this._computeCommentMap(this.comments[side]); |
| this._handleCommentSplice(record.value, side); |
| } |
| }, |
| |
| /** |
| * Take a list of comments and return a sparse list mapping line numbers to |
| * partial ranges. Uses an end-character-index of -1 to indicate the end of |
| * the line. |
| * @param {Array<Object>} commentList The list of comments. |
| * @return {Object} The sparse list. |
| */ |
| _computeCommentMap: function(commentList) { |
| var result = {}; |
| commentList.forEach(function(comment) { |
| if (!comment.range) { return; } |
| var range = comment.range; |
| for (var line = range.start_line; line <= range.end_line; line++) { |
| if (!result[line]) { result[line] = []; } |
| result[line].push({ |
| comment: comment, |
| start: line === range.start_line ? range.start_character : 0, |
| end: line === range.end_line ? range.end_character : -1, |
| }); |
| } |
| }); |
| return result; |
| }, |
| |
| /** |
| * Translate a splice record into range update notifications. |
| */ |
| _handleCommentSplice: function(record, side) { |
| if (!record || !record.indexSplices) { return; } |
| record.indexSplices.forEach(function(splice) { |
| var ranges = splice.removed.length ? |
| splice.removed.map(function(c) { return c.range; }) : |
| [splice.object[splice.index].range]; |
| ranges.forEach(function(range) { |
| if (!range) { return; } |
| this._notifyUpdateRange(range.start_line, range.end_line, side); |
| }.bind(this)); |
| }.bind(this)); |
| }, |
| |
| _getRangesForLine: function(line, side) { |
| var lineNum = side === 'left' ? line.beforeNumber : line.afterNumber; |
| var ranges = this.get(['_commentMap', side, lineNum]) || []; |
| return ranges |
| .map(function(range) { |
| return { |
| start: range.start, |
| end: range.end === -1 ? line.text.length : range.end, |
| hovering: !!range.comment.__hovering, |
| }; |
| }) |
| .sort(function(a, b) { |
| // Sort the ranges so that hovering highlights are on top. |
| return a.hovering && !b.hovering ? 1 : 0; |
| }); |
| }, |
| }); |
| })(); |