Add intraline highlights to gr-new-diff
Change-Id: Ia797012253a537552a9640f6eaa78b6fcb10c02b
This commit is contained in:
		@@ -40,6 +40,11 @@
 | 
				
			|||||||
    REMOVED: 'a',
 | 
					    REMOVED: 'a',
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  GrDiffBuilder.Highlights = {
 | 
				
			||||||
 | 
					    ADDED: 'edit_b',
 | 
				
			||||||
 | 
					    REMOVED: 'edit_a',
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GrDiffBuilder.Side = {
 | 
					  GrDiffBuilder.Side = {
 | 
				
			||||||
    LEFT: 'left',
 | 
					    LEFT: 'left',
 | 
				
			||||||
    RIGHT: 'right',
 | 
					    RIGHT: 'right',
 | 
				
			||||||
@@ -90,12 +95,25 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (group[GrDiffBuilder.GroupType.REMOVED] !== undefined) {
 | 
					      if (group[GrDiffBuilder.GroupType.REMOVED] !== undefined) {
 | 
				
			||||||
 | 
					        var highlights;
 | 
				
			||||||
 | 
					        if (group[GrDiffBuilder.Highlights.REMOVED] !== undefined) {
 | 
				
			||||||
 | 
					          highlights = this._normalizeIntralineHighlights(
 | 
				
			||||||
 | 
					              group[GrDiffBuilder.GroupType.REMOVED],
 | 
				
			||||||
 | 
					              group[GrDiffBuilder.Highlights.REMOVED]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        this._appendRemovedLines(group[GrDiffBuilder.GroupType.REMOVED], lines,
 | 
					        this._appendRemovedLines(group[GrDiffBuilder.GroupType.REMOVED], lines,
 | 
				
			||||||
            lineNums);
 | 
					            lineNums, highlights);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (group[GrDiffBuilder.GroupType.ADDED] !== undefined) {
 | 
					      if (group[GrDiffBuilder.GroupType.ADDED] !== undefined) {
 | 
				
			||||||
 | 
					        var highlights;
 | 
				
			||||||
 | 
					        if (group[GrDiffBuilder.Highlights.ADDED] !== undefined) {
 | 
				
			||||||
 | 
					          highlights = this._normalizeIntralineHighlights(
 | 
				
			||||||
 | 
					            group[GrDiffBuilder.GroupType.ADDED],
 | 
				
			||||||
 | 
					            group[GrDiffBuilder.Highlights.ADDED]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        this._appendAddedLines(group[GrDiffBuilder.GroupType.ADDED], lines,
 | 
					        this._appendAddedLines(group[GrDiffBuilder.GroupType.ADDED], lines,
 | 
				
			||||||
            lineNums);
 | 
					            lineNums, highlights);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      groups.push(new GrDiffGroup(GrDiffGroup.Type.DELTA, lines));
 | 
					      groups.push(new GrDiffGroup(GrDiffGroup.Type.DELTA, lines));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -171,6 +189,66 @@
 | 
				
			|||||||
    return result;
 | 
					    return result;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // The `highlights` array consists of a list of <skip length, mark length>
 | 
				
			||||||
 | 
					  // pairs, where the skip length is the number of characters between the
 | 
				
			||||||
 | 
					  // end of the previous edit and the start of this edit, and the mark
 | 
				
			||||||
 | 
					  // length is the number of edited characters following the skip. The start
 | 
				
			||||||
 | 
					  // of the edits is from the beginning of the related diff content lines.
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					  // Note that the implied newline character at the end of each line is
 | 
				
			||||||
 | 
					  // included in the length calculation, and thus it is possible for the
 | 
				
			||||||
 | 
					  // edits to span newlines.
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					  // A line highlight object consists of three fields:
 | 
				
			||||||
 | 
					  // - contentIndex: The index of the diffChunk `content` field (the line
 | 
				
			||||||
 | 
					  //   being referred to).
 | 
				
			||||||
 | 
					  // - startIndex: Where the highlight should begin.
 | 
				
			||||||
 | 
					  // - endIndex: (optional) Where the highlight should end. If omitted, the
 | 
				
			||||||
 | 
					  //   highlight is meant to be a continuation onto the next line.
 | 
				
			||||||
 | 
					  GrDiffBuilder.prototype._normalizeIntralineHighlights = function(content,
 | 
				
			||||||
 | 
					      highlights) {
 | 
				
			||||||
 | 
					    var contentIndex = 0;
 | 
				
			||||||
 | 
					    var idx = 0;
 | 
				
			||||||
 | 
					    var normalized = [];
 | 
				
			||||||
 | 
					    for (var i = 0; i < highlights.length; i++) {
 | 
				
			||||||
 | 
					      var line = content[contentIndex] + '\n';
 | 
				
			||||||
 | 
					      var hl = highlights[i];
 | 
				
			||||||
 | 
					      var j = 0;
 | 
				
			||||||
 | 
					      while (j < hl[0]) {
 | 
				
			||||||
 | 
					        if (idx === line.length) {
 | 
				
			||||||
 | 
					          idx = 0;
 | 
				
			||||||
 | 
					          line = content[++contentIndex] + '\n';
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        idx++;
 | 
				
			||||||
 | 
					        j++;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var lineHighlight = {
 | 
				
			||||||
 | 
					        contentIndex: contentIndex,
 | 
				
			||||||
 | 
					        startIndex: idx,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      j = 0;
 | 
				
			||||||
 | 
					      while (line && j < hl[1]) {
 | 
				
			||||||
 | 
					        if (idx === line.length) {
 | 
				
			||||||
 | 
					          idx = 0;
 | 
				
			||||||
 | 
					          line = content[++contentIndex] + '\n';
 | 
				
			||||||
 | 
					          normalized.push(lineHighlight);
 | 
				
			||||||
 | 
					          lineHighlight = {
 | 
				
			||||||
 | 
					            contentIndex: contentIndex,
 | 
				
			||||||
 | 
					            startIndex: idx,
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        idx++;
 | 
				
			||||||
 | 
					        j++;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      lineHighlight.endIndex = idx;
 | 
				
			||||||
 | 
					      normalized.push(lineHighlight);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return normalized;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GrDiffBuilder.prototype._insertContextGroups = function(groups, lines,
 | 
					  GrDiffBuilder.prototype._insertContextGroups = function(groups, lines,
 | 
				
			||||||
      hiddenRange) {
 | 
					      hiddenRange) {
 | 
				
			||||||
    var linesBeforeCtx = lines.slice(0, hiddenRange[0]);
 | 
					    var linesBeforeCtx = lines.slice(0, hiddenRange[0]);
 | 
				
			||||||
@@ -201,21 +279,32 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GrDiffBuilder.prototype._appendRemovedLines = function(rows, lines,
 | 
					  GrDiffBuilder.prototype._appendRemovedLines = function(rows, lines, lineNums,
 | 
				
			||||||
      lineNums) {
 | 
					      opt_highlights) {
 | 
				
			||||||
    for (var i = 0; i < rows.length; i++) {
 | 
					    for (var i = 0; i < rows.length; i++) {
 | 
				
			||||||
      var line = new GrDiffLine(GrDiffLine.Type.REMOVE);
 | 
					      var line = new GrDiffLine(GrDiffLine.Type.REMOVE);
 | 
				
			||||||
      line.text = rows[i];
 | 
					      line.text = rows[i];
 | 
				
			||||||
      line.beforeNumber = ++lineNums.left;
 | 
					      line.beforeNumber = ++lineNums.left;
 | 
				
			||||||
 | 
					      if (opt_highlights) {
 | 
				
			||||||
 | 
					        line.highlights = opt_highlights.filter(function(hl) {
 | 
				
			||||||
 | 
					          return hl.contentIndex === i;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      lines.push(line);
 | 
					      lines.push(line);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GrDiffBuilder.prototype._appendAddedLines = function(rows, lines, lineNums) {
 | 
					  GrDiffBuilder.prototype._appendAddedLines = function(rows, lines, lineNums,
 | 
				
			||||||
 | 
					      opt_highlights) {
 | 
				
			||||||
    for (var i = 0; i < rows.length; i++) {
 | 
					    for (var i = 0; i < rows.length; i++) {
 | 
				
			||||||
      var line = new GrDiffLine(GrDiffLine.Type.ADD);
 | 
					      var line = new GrDiffLine(GrDiffLine.Type.ADD);
 | 
				
			||||||
      line.text = rows[i];
 | 
					      line.text = rows[i];
 | 
				
			||||||
      line.afterNumber = ++lineNums.right;
 | 
					      line.afterNumber = ++lineNums.right;
 | 
				
			||||||
 | 
					      if (opt_highlights) {
 | 
				
			||||||
 | 
					        line.highlights = opt_highlights.filter(function(hl) {
 | 
				
			||||||
 | 
					          return hl.contentIndex === i;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      lines.push(line);
 | 
					      lines.push(line);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -334,6 +423,14 @@
 | 
				
			|||||||
    td.classList.add(line.type);
 | 
					    td.classList.add(line.type);
 | 
				
			||||||
    var text = line.text;
 | 
					    var text = line.text;
 | 
				
			||||||
    var html = util.escapeHTML(text);
 | 
					    var html = util.escapeHTML(text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    td.classList.add(line.highlights.length > 0 ?
 | 
				
			||||||
 | 
					        'lightHighlight' : 'darkHighlight');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (line.highlights.length > 0) {
 | 
				
			||||||
 | 
					      html = this._addIntralineHighlights(text, html, line.highlights);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (text.length > this._prefs.line_length) {
 | 
					    if (text.length > this._prefs.line_length) {
 | 
				
			||||||
      html = this._addNewlines(text, html);
 | 
					      html = this._addNewlines(text, html);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -410,6 +507,41 @@
 | 
				
			|||||||
    return html.replace(GrDiffBuilder.TAB_REGEX, htmlStr);
 | 
					    return html.replace(GrDiffBuilder.TAB_REGEX, htmlStr);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  GrDiffBuilder.prototype._addIntralineHighlights = function(content, html,
 | 
				
			||||||
 | 
					      highlights) {
 | 
				
			||||||
 | 
					    var START_TAG = '<hl class="style-scope gr-new-diff">';
 | 
				
			||||||
 | 
					    var END_TAG = '</hl>';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (var i = 0; i < highlights.length; i++) {
 | 
				
			||||||
 | 
					      var hl = highlights[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var htmlStartIndex = 0;
 | 
				
			||||||
 | 
					      // Find the index of the HTML string to insert the start tag.
 | 
				
			||||||
 | 
					      for (var j = 0; j < hl.startIndex; j++) {
 | 
				
			||||||
 | 
					        htmlStartIndex = this._advanceChar(html, htmlStartIndex);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var htmlEndIndex = 0;
 | 
				
			||||||
 | 
					      if (hl.endIndex !== undefined) {
 | 
				
			||||||
 | 
					        for (var j = 0; j < hl.endIndex; j++) {
 | 
				
			||||||
 | 
					          htmlEndIndex = this._advanceChar(html, htmlEndIndex);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // If endIndex isn't present, continue to the end of the line.
 | 
				
			||||||
 | 
					        htmlEndIndex = html.length;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // 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 (htmlStartIndex !== htmlEndIndex) {
 | 
				
			||||||
 | 
					        html = html.slice(0, htmlStartIndex) + START_TAG +
 | 
				
			||||||
 | 
					              html.slice(htmlStartIndex, htmlEndIndex) + END_TAG +
 | 
				
			||||||
 | 
					              html.slice(htmlEndIndex);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return html;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GrDiffBuilder.prototype._getTabWrapper = function(tabSize, showTabs) {
 | 
					  GrDiffBuilder.prototype._getTabWrapper = function(tabSize, showTabs) {
 | 
				
			||||||
    // Force this to be a number to prevent arbitrary injection.
 | 
					    // Force this to be a number to prevent arbitrary injection.
 | 
				
			||||||
    tabSize = +tabSize;
 | 
					    tabSize = +tabSize;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -431,5 +431,86 @@ limitations under the License.
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('intraline normalization', function() {
 | 
				
			||||||
 | 
					      // The content and highlights are in the format returned by the Gerrit
 | 
				
			||||||
 | 
					      // REST API.
 | 
				
			||||||
 | 
					      var content = [
 | 
				
			||||||
 | 
					        '      <section class="summary">',
 | 
				
			||||||
 | 
					        '        <gr-linked-text content="' +
 | 
				
			||||||
 | 
					            '[[_computeCurrentRevisionMessage(change)]]"></gr-linked-text>',
 | 
				
			||||||
 | 
					        '      </section>',
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					      var highlights = [
 | 
				
			||||||
 | 
					        [31, 34], [42, 26]
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					      var results = GrDiffBuilder.prototype._normalizeIntralineHighlights(
 | 
				
			||||||
 | 
					          content, highlights);
 | 
				
			||||||
 | 
					      assert.deepEqual(results, [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 0,
 | 
				
			||||||
 | 
					          startIndex: 31,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 1,
 | 
				
			||||||
 | 
					          startIndex: 0,
 | 
				
			||||||
 | 
					          endIndex: 33,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 1,
 | 
				
			||||||
 | 
					          startIndex: 75,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 2,
 | 
				
			||||||
 | 
					          startIndex: 0,
 | 
				
			||||||
 | 
					          endIndex: 6,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      content = [
 | 
				
			||||||
 | 
					        '        this._path = value.path;',
 | 
				
			||||||
 | 
					        '',
 | 
				
			||||||
 | 
					        '        // When navigating away from the page, there is a possibility that the',
 | 
				
			||||||
 | 
					        '        // patch number is no longer a part of the URL (say when navigating to',
 | 
				
			||||||
 | 
					        '        // the top-level change info view) and therefore undefined in `params`.',
 | 
				
			||||||
 | 
					        '        if (!this._patchRange.patchNum) {',
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					      highlights = [
 | 
				
			||||||
 | 
					        [14, 17],
 | 
				
			||||||
 | 
					        [11, 70],
 | 
				
			||||||
 | 
					        [12, 67],
 | 
				
			||||||
 | 
					        [12, 67],
 | 
				
			||||||
 | 
					        [14, 29],
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					      results = GrDiffBuilder.prototype._normalizeIntralineHighlights(content,
 | 
				
			||||||
 | 
					          highlights);
 | 
				
			||||||
 | 
					      assert.deepEqual(results, [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 0,
 | 
				
			||||||
 | 
					          startIndex: 14,
 | 
				
			||||||
 | 
					          endIndex: 31,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 2,
 | 
				
			||||||
 | 
					          startIndex: 8,
 | 
				
			||||||
 | 
					          endIndex: 78,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 3,
 | 
				
			||||||
 | 
					          startIndex: 11,
 | 
				
			||||||
 | 
					          endIndex: 78,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 4,
 | 
				
			||||||
 | 
					          startIndex: 11,
 | 
				
			||||||
 | 
					          endIndex: 78,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          contentIndex: 5,
 | 
				
			||||||
 | 
					          startIndex: 12,
 | 
				
			||||||
 | 
					          endIndex: 41,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@
 | 
				
			|||||||
  function GrDiffLine(type) {
 | 
					  function GrDiffLine(type) {
 | 
				
			||||||
    this.type = type;
 | 
					    this.type = type;
 | 
				
			||||||
    this.contextLines = [];
 | 
					    this.contextLines = [];
 | 
				
			||||||
 | 
					    this.highlights = [];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GrDiffLine.prototype.beforeNumber = 0;
 | 
					  GrDiffLine.prototype.beforeNumber = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,7 +92,7 @@ limitations under the License.
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      .content {
 | 
					      .content {
 | 
				
			||||||
        overflow: hidden;
 | 
					        overflow: hidden;
 | 
				
			||||||
        width: var(--content-width, 80ch);
 | 
					        min-width: var(--content-width, 80ch);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .content.left {
 | 
					      .content.left {
 | 
				
			||||||
        -webkit-user-select: var(--left-user-select, text);
 | 
					        -webkit-user-select: var(--left-user-select, text);
 | 
				
			||||||
@@ -106,12 +106,20 @@ limitations under the License.
 | 
				
			|||||||
        -ms-user-select: var(--right-user-select, text);
 | 
					        -ms-user-select: var(--right-user-select, text);
 | 
				
			||||||
        user-select: var(--right-user-select, text);
 | 
					        user-select: var(--right-user-select, text);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .content.add {
 | 
					      .content.add hl,
 | 
				
			||||||
 | 
					      .content.add.darkHighlight {
 | 
				
			||||||
        background-color: var(--dark-add-highlight-color);
 | 
					        background-color: var(--dark-add-highlight-color);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .content.remove {
 | 
					      .content.add.lightHighlight {
 | 
				
			||||||
 | 
					        background-color: var(--light-add-highlight-color);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .content.remove hl,
 | 
				
			||||||
 | 
					      .content.remove.darkHighlight {
 | 
				
			||||||
        background-color: var(--dark-remove-highlight-color);
 | 
					        background-color: var(--dark-remove-highlight-color);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      .content.remove.lightHighlight {
 | 
				
			||||||
 | 
					        background-color: var(--light-remove.highlight-color);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      .contextControl {
 | 
					      .contextControl {
 | 
				
			||||||
        color: #849;
 | 
					        color: #849;
 | 
				
			||||||
        background-color: #fef;
 | 
					        background-color: #fef;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user