b3caf48ffa
This code will eventually live within a diff preference pane once options other than context are supported. Change-Id: I3c752a18d163a55cb3398c0a46544593b0a9faed
733 lines
24 KiB
HTML
733 lines
24 KiB
HTML
<!--
|
|
Copyright (C) 2015 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="gr-ajax.html">
|
|
<link rel="import" href="gr-diff-side.html">
|
|
<link rel="import" href="gr-patch-range-select.html">
|
|
<link rel="import" href="gr-request.html">
|
|
|
|
<dom-module id="gr-diff">
|
|
<template>
|
|
<style>
|
|
.header {
|
|
display: flex;
|
|
margin: 0 var(--default-horizontal-margin) .75em;
|
|
}
|
|
.contextControl {
|
|
flex: 1;
|
|
text-align: right;
|
|
}
|
|
.diffContainer {
|
|
border-bottom: 1px solid #eee;
|
|
border-top: 1px solid #eee;
|
|
display: flex;
|
|
font-family: var(--monospace-font-family);
|
|
overflow-x: auto;
|
|
white-space: pre;
|
|
}
|
|
gr-diff-side:first-of-type {
|
|
--light-highlight-color: #fee;
|
|
--dark-highlight-color: #ffd4d4;
|
|
}
|
|
gr-diff-side:last-of-type {
|
|
--light-highlight-color: #efe;
|
|
--dark-highlight-color: #d4ffd4;
|
|
border-right: 1px solid #ddd;
|
|
}
|
|
</style>
|
|
<gr-ajax id="diffXHR"
|
|
url="[[_computeDiffPath(changeNum, patchRange.patchNum, path)]]"
|
|
params="[[_computeDiffQueryParams(patchRange.basePatchNum)]]"
|
|
last-response="{{_diffResponse}}"></gr-ajax>
|
|
<gr-ajax id="baseCommentsXHR"
|
|
url="[[_computeCommentsPath(changeNum, patchRange.basePatchNum)]]"></gr-ajax>
|
|
<gr-ajax id="commentsXHR"
|
|
url="[[_computeCommentsPath(changeNum, patchRange.patchNum)]]"></gr-ajax>
|
|
<gr-ajax id="baseDraftsXHR"
|
|
url="[[_computeDraftsPath(changeNum, patchRange.basePatchNum)]]"></gr-ajax>
|
|
<gr-ajax id="draftsXHR"
|
|
url="[[_computeDraftsPath(changeNum, patchRange.patchNum)]]"></gr-ajax>
|
|
|
|
<div class="header">
|
|
<gr-patch-range-select
|
|
path="[[path]]"
|
|
change-num="[[changeNum]]"
|
|
patch-range="[[patchRange]]"
|
|
available-patches="[[availablePatches]]"></gr-patch-range-select>
|
|
<div class="contextControl" hidden$="[[!prefs.context]]" hidden>
|
|
Context:
|
|
<select id="contextSelect" on-change="_handleContextSelectChange">
|
|
<option value="3">3 lines</option>
|
|
<option value="10">10 lines</option>
|
|
<option value="25">25 lines</option>
|
|
<option value="50">50 lines</option>
|
|
<option value="75">75 lines</option>
|
|
<option value="100">100 lines</option>
|
|
<option value="-1">Whole file</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="diffContainer">
|
|
<gr-diff-side id="leftDiff"
|
|
change-num="[[changeNum]]"
|
|
patch-num="[[patchRange.basePatchNum]]"
|
|
path="[[path]]"
|
|
content="{{_diff.leftSide}}"
|
|
width="[[sideWidth]]"
|
|
can-comment="[[_loggedIn]]"
|
|
on-expand-context="_handleExpandContext"
|
|
on-thread-height-change="_handleThreadHeightChange"
|
|
on-add-draft="_handleAddDraft"
|
|
on-remove-thread="_handleRemoveThread"></gr-diff-side>
|
|
<gr-diff-side id="rightDiff"
|
|
change-num="[[changeNum]]"
|
|
patch-num="[[patchRange.patchNum]]"
|
|
path="[[path]]"
|
|
content="{{_diff.rightSide}}"
|
|
width="[[sideWidth]]"
|
|
can-comment="[[_loggedIn]]"
|
|
on-expand-context="_handleExpandContext"
|
|
on-thread-height-change="_handleThreadHeightChange"
|
|
on-add-draft="_handleAddDraft"
|
|
on-remove-thread="_handleRemoveThread"></gr-diff-side>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
Polymer({
|
|
is: 'gr-diff',
|
|
|
|
/**
|
|
* Fired when the diff is rendered.
|
|
*
|
|
* @event render
|
|
*/
|
|
|
|
properties: {
|
|
auto: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
availablePatches: Array,
|
|
changeNum: String,
|
|
/*
|
|
* A single object to encompass basePatchNum and patchNum is used
|
|
* so that both can be set at once without incremental observers
|
|
* firing after each property changes.
|
|
*/
|
|
patchRange: Object,
|
|
path: String,
|
|
sideWidth: {
|
|
type: Number,
|
|
value: 80,
|
|
},
|
|
prefs: {
|
|
type: Object,
|
|
notify: true,
|
|
},
|
|
_prefsReady: {
|
|
type: Object,
|
|
readOnly: true,
|
|
value: function() {
|
|
return new Promise(function(resolve) {
|
|
this._resolvePrefsReady = resolve;
|
|
}.bind(this));
|
|
},
|
|
},
|
|
_baseComments: Array,
|
|
_comments: Array,
|
|
_drafts: Array,
|
|
_baseDrafts: Array,
|
|
/**
|
|
* Base (left side) comments and drafts grouped by line number.
|
|
*/
|
|
_groupedBaseComments: {
|
|
type: Object,
|
|
value: function() { return {}; },
|
|
},
|
|
/**
|
|
* Comments and drafts (right side) grouped by line number.
|
|
*/
|
|
_groupedComments: {
|
|
type: Object,
|
|
value: function() { return {}; },
|
|
},
|
|
_diffResponse: Object,
|
|
_diff: {
|
|
type: Object,
|
|
value: function() { return {}; },
|
|
},
|
|
_loggedIn: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
_initialRenderComplete: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
_diffRequestsPromise: Object, // Used for testing.
|
|
_diffPreferencesPromise: Object, // Used for testing.
|
|
},
|
|
|
|
observers: [
|
|
'_prefsChanged(prefs.*)',
|
|
'_diffOptionsChanged(changeNum, patchRange, path)',
|
|
],
|
|
|
|
ready: function() {
|
|
app.accountReady.then(function() {
|
|
this._loggedIn = app.loggedIn;
|
|
}.bind(this));
|
|
},
|
|
|
|
scrollToLine: function(lineNum) {
|
|
// TODO(andybons): Should this always be the right side?
|
|
this.$.rightDiff.scrollToLine(lineNum);
|
|
},
|
|
|
|
_prefsChanged: function(changeRecord) {
|
|
var prefs = changeRecord.base;
|
|
|
|
this.$.contextSelect.value = prefs.context;
|
|
|
|
if (this._initialRenderComplete) {
|
|
this._render();
|
|
}
|
|
|
|
this._resolvePrefsReady(prefs);
|
|
},
|
|
|
|
_diffOptionsChanged: function(changeNum, patchRange, path) {
|
|
if (!this.auto) { return; }
|
|
|
|
var promises = [
|
|
this._prefsReady,
|
|
this.$.diffXHR.generateRequest().completes
|
|
];
|
|
|
|
var basePatchNum = patchRange.basePatchNum;
|
|
var patchNum = patchRange.patchNum;
|
|
|
|
app.accountReady.then(function() {
|
|
promises.push(this._getCommentsAndDrafts(basePatchNum, app.loggedIn));
|
|
this._diffRequestsPromise = Promise.all(promises).then(function() {
|
|
this._render();
|
|
}.bind(this)).catch(function(err) {
|
|
alert('Oops. Something went wrong. Check the console and bug the ' +
|
|
'PolyGerrit team for assistance.');
|
|
throw err;
|
|
});
|
|
}.bind(this));
|
|
},
|
|
|
|
_render: function() {
|
|
this._groupCommentsAndDrafts();
|
|
this._processContent();
|
|
|
|
// Allow for the initial rendering to complete before firing the event.
|
|
this.async(function() {
|
|
this.fire('render', null, {bubbles: false});
|
|
}.bind(this), 1);
|
|
|
|
this._initialRenderComplete = true;
|
|
},
|
|
|
|
_getCommentsAndDrafts: function(basePatchNum, loggedIn) {
|
|
var promises = [];
|
|
|
|
function onlyParent(c) { return c.side == 'PARENT'; }
|
|
function withoutParent(c) { return c.side != 'PARENT'; }
|
|
|
|
var promises = [];
|
|
var commentsPromise = this.$.commentsXHR.generateRequest().completes;
|
|
promises.push(commentsPromise.then(function(req) {
|
|
var comments = req.response[this.path] || [];
|
|
if (basePatchNum == 'PARENT') {
|
|
this._baseComments = comments.filter(onlyParent);
|
|
}
|
|
this._comments = comments.filter(withoutParent);
|
|
}.bind(this)));
|
|
|
|
if (basePatchNum != 'PARENT') {
|
|
commentsPromise = this.$.baseCommentsXHR.generateRequest().completes;
|
|
promises.push(commentsPromise.then(function(req) {
|
|
this._baseComments =
|
|
(req.response[this.path] || []).filter(withoutParent);
|
|
}.bind(this)));
|
|
}
|
|
|
|
if (!loggedIn) {
|
|
this._baseDrafts = [];
|
|
this._drafts = [];
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
var draftsPromise = this.$.draftsXHR.generateRequest().completes;
|
|
promises.push(draftsPromise.then(function(req) {
|
|
var drafts = req.response[this.path] || [];
|
|
if (basePatchNum == 'PARENT') {
|
|
this._baseDrafts = drafts.filter(onlyParent);
|
|
}
|
|
this._drafts = drafts.filter(withoutParent);
|
|
}.bind(this)));
|
|
|
|
if (basePatchNum != 'PARENT') {
|
|
draftsPromise = this.$.baseDraftsXHR.generateRequest().completes;
|
|
promises.push(draftsPromise.then(function(req) {
|
|
this._baseDrafts =
|
|
(req.response[this.path] || []).filter(withoutParent);
|
|
}.bind(this)));
|
|
}
|
|
|
|
return Promise.all(promises);
|
|
},
|
|
|
|
_computeDiffPath: function(changeNum, patchNum, path) {
|
|
return Changes.baseURL(changeNum, patchNum) + '/files/' +
|
|
encodeURIComponent(path) + '/diff';
|
|
},
|
|
|
|
_computeCommentsPath: function(changeNum, patchNum) {
|
|
return Changes.baseURL(changeNum, patchNum) + '/comments';
|
|
},
|
|
|
|
_computeDraftsPath: function(changeNum, patchNum) {
|
|
return Changes.baseURL(changeNum, patchNum) + '/drafts';
|
|
},
|
|
|
|
_computeDiffQueryParams: function(basePatchNum) {
|
|
var params = {
|
|
context: 'ALL',
|
|
intraline: null
|
|
};
|
|
if (basePatchNum != 'PARENT') {
|
|
params.base = basePatchNum;
|
|
}
|
|
return params;
|
|
},
|
|
|
|
_handleContextSelectChange: function(e) {
|
|
var selectEl = Polymer.dom(e).rootTarget;
|
|
this.set('prefs.context', parseInt(selectEl.value, 10));
|
|
|
|
app.accountReady.then(function() {
|
|
if (!this._loggedIn) { return; }
|
|
|
|
this._diffPreferencesPromise =
|
|
this._saveDiffPreferences().catch(function(err) {
|
|
alert('Oops. Something went wrong. Check the console and bug the ' +
|
|
'PolyGerrit team for assistance.');
|
|
throw err;
|
|
});
|
|
}.bind(this));
|
|
},
|
|
|
|
_saveDiffPreferences: function() {
|
|
var xhr = document.createElement('gr-request');
|
|
this._diffPreferencesPromise = xhr.send({
|
|
method: 'PUT',
|
|
url: '/accounts/self/preferences.diff',
|
|
body: this.prefs,
|
|
});
|
|
return this._diffPreferencesPromise;
|
|
},
|
|
|
|
_handleExpandContext: function(e) {
|
|
var ctx = e.detail.context;
|
|
var contextControlIndex = -1;
|
|
for (var i = ctx.start; i <= ctx.end; i++) {
|
|
this._diff.leftSide[i].hidden = false;
|
|
this._diff.rightSide[i].hidden = false;
|
|
if (this._diff.leftSide[i].type == 'CONTEXT_CONTROL' &&
|
|
this._diff.rightSide[i].type == 'CONTEXT_CONTROL') {
|
|
contextControlIndex = i;
|
|
}
|
|
}
|
|
this._diff.leftSide[contextControlIndex].hidden = true;
|
|
this._diff.rightSide[contextControlIndex].hidden = true;
|
|
|
|
this.$.leftDiff.hideElementsWithIndex(contextControlIndex);
|
|
this.$.rightDiff.hideElementsWithIndex(contextControlIndex);
|
|
|
|
this.$.leftDiff.renderLineIndexRange(ctx.start, ctx.end);
|
|
this.$.rightDiff.renderLineIndexRange(ctx.start, ctx.end);
|
|
},
|
|
|
|
_handleThreadHeightChange: function(e) {
|
|
var index = e.detail.index;
|
|
var diffEl = Polymer.dom(e).rootTarget;
|
|
var otherSide = diffEl == this.$.leftDiff ?
|
|
this.$.rightDiff : this.$.leftDiff;
|
|
|
|
var threadHeight = e.detail.height;
|
|
var otherSideHeight;
|
|
if (otherSide.content[index].type == 'COMMENT_THREAD') {
|
|
otherSideHeight = otherSide.getRowNaturalHeight(index);
|
|
} else {
|
|
otherSideHeight = otherSide.getRowHeight(index);
|
|
}
|
|
var maxHeight = Math.max(threadHeight, otherSideHeight);
|
|
this.$.leftDiff.setRowHeight(index, maxHeight);
|
|
this.$.rightDiff.setRowHeight(index, maxHeight);
|
|
},
|
|
|
|
_handleAddDraft: function(e) {
|
|
var insertIndex = e.detail.index + 1;
|
|
var diffEl = Polymer.dom(e).rootTarget;
|
|
var content = diffEl.content;
|
|
if (content[insertIndex] &&
|
|
content[insertIndex].type == 'COMMENT_THREAD') {
|
|
// A thread is already here. Do nothing.
|
|
return;
|
|
}
|
|
var comment = {
|
|
type: 'COMMENT_THREAD',
|
|
comments: [{
|
|
__draft: true,
|
|
__draftID: Math.random().toString(36),
|
|
line: e.detail.line,
|
|
path: this.path,
|
|
}]
|
|
};
|
|
if (content[insertIndex] &&
|
|
content[insertIndex].type == 'FILLER') {
|
|
content[insertIndex] = comment;
|
|
diffEl.rowUpdated(insertIndex);
|
|
} else {
|
|
content.splice(insertIndex, 0, comment);
|
|
diffEl.rowInserted(insertIndex);
|
|
}
|
|
|
|
var otherSide = diffEl == this.$.leftDiff ?
|
|
this.$.rightDiff : this.$.leftDiff;
|
|
if (otherSide.content[insertIndex] == null ||
|
|
otherSide.content[insertIndex].type != 'COMMENT_THREAD') {
|
|
otherSide.content.splice(insertIndex, 0, {
|
|
type: 'FILLER',
|
|
});
|
|
otherSide.rowInserted(insertIndex);
|
|
}
|
|
},
|
|
|
|
_handleRemoveThread: function(e) {
|
|
var diffEl = Polymer.dom(e).rootTarget;
|
|
var otherSide = diffEl == this.$.leftDiff ?
|
|
this.$.rightDiff : this.$.leftDiff;
|
|
var index = e.detail.index;
|
|
|
|
if (otherSide.content[index].type == 'FILLER') {
|
|
otherSide.content.splice(index, 1);
|
|
otherSide.rowRemoved(index);
|
|
diffEl.content.splice(index, 1);
|
|
diffEl.rowRemoved(index);
|
|
} else if (otherSide.content[index].type == 'COMMENT_THREAD') {
|
|
diffEl.content[index] = {type: 'FILLER'};
|
|
diffEl.rowUpdated(index);
|
|
var height = otherSide.setRowNaturalHeight(index);
|
|
diffEl.setRowHeight(index, height);
|
|
} else {
|
|
throw Error('A thread cannot be opposite anything but filler or ' +
|
|
'another thread');
|
|
}
|
|
},
|
|
|
|
_processContent: function() {
|
|
var leftSide = [];
|
|
var rightSide = [];
|
|
var initialLineNum = 0 + (this._diffResponse.content.skip || 0);
|
|
var ctx = {
|
|
hidingLines: false,
|
|
lastNumLinesHidden: 0,
|
|
left: {
|
|
lineNum: initialLineNum,
|
|
},
|
|
right: {
|
|
lineNum: initialLineNum,
|
|
}
|
|
};
|
|
var content = this._diffResponse.content;
|
|
var context = this.prefs.context;
|
|
if (context == -1) {
|
|
// Show the entire file.
|
|
context = Infinity;
|
|
}
|
|
for (var i = 0; i < content.length; i++) {
|
|
if (i == 0) {
|
|
ctx.skipRange = [0, context];
|
|
} else if (i == content.length - 1) {
|
|
ctx.skipRange = [context, 0];
|
|
} else {
|
|
ctx.skipRange = [context, context];
|
|
}
|
|
ctx.diffChunkIndex = i;
|
|
this._addDiffChunk(ctx, content[i], leftSide, rightSide);
|
|
}
|
|
|
|
this._diff = {
|
|
leftSide: leftSide,
|
|
rightSide: rightSide,
|
|
};
|
|
},
|
|
|
|
_groupCommentsAndDrafts: function() {
|
|
this._baseDrafts.forEach(function(d) { d.__draft = true; });
|
|
this._drafts.forEach(function(d) { d.__draft = true; });
|
|
var allLeft = this._baseComments.concat(this._baseDrafts);
|
|
var allRight = this._comments.concat(this._drafts);
|
|
|
|
var leftByLine = {};
|
|
var rightByLine = {};
|
|
var mapFunc = function(byLine) {
|
|
return function(c) {
|
|
// File comments/drafts are grouped with line 1 for now.
|
|
var line = c.line || 1;
|
|
if (byLine[line] == null) {
|
|
byLine[line] = [];
|
|
}
|
|
byLine[line].push(c);
|
|
}
|
|
};
|
|
allLeft.forEach(mapFunc(leftByLine));
|
|
allRight.forEach(mapFunc(rightByLine));
|
|
|
|
this._groupedBaseComments = leftByLine;
|
|
this._groupedComments = rightByLine;
|
|
},
|
|
|
|
_addContextControl: function(ctx, leftSide, rightSide) {
|
|
var numLinesHidden = ctx.lastNumLinesHidden;
|
|
var leftStart = leftSide.length - numLinesHidden;
|
|
var leftEnd = leftSide.length;
|
|
var rightStart = rightSide.length - numLinesHidden;
|
|
var rightEnd = rightSide.length;
|
|
if (leftStart != rightStart || leftEnd != rightEnd) {
|
|
throw Error(
|
|
'Left and right ranges for context control should be equal:' +
|
|
'Left: [' + leftStart + ', ' + leftEnd + '] ' +
|
|
'Right: ['+ rightStart + ', ' + rightEnd + ']');
|
|
}
|
|
var obj = {
|
|
type: 'CONTEXT_CONTROL',
|
|
numLines: numLinesHidden,
|
|
start: leftStart,
|
|
end: leftEnd,
|
|
};
|
|
// NOTE: Be careful, here. This object is meant to be immutable. If the
|
|
// object is altered within one side's array it will reflect the
|
|
// alterations in another.
|
|
leftSide.push(obj);
|
|
rightSide.push(obj);
|
|
},
|
|
|
|
_addCommonDiffChunk: function(ctx, chunk, leftSide, rightSide) {
|
|
for (var i = 0; i < chunk.ab.length; i++) {
|
|
var numLines = Math.ceil(chunk.ab[i].length / this.sideWidth);
|
|
var hidden = i >= ctx.skipRange[0] &&
|
|
i < chunk.ab.length - ctx.skipRange[1];
|
|
if (ctx.hidingLines && hidden == false) {
|
|
// No longer hiding lines. Add a context control.
|
|
this._addContextControl(ctx, leftSide, rightSide);
|
|
ctx.lastNumLinesHidden = 0;
|
|
}
|
|
ctx.hidingLines = hidden;
|
|
if (hidden) {
|
|
ctx.lastNumLinesHidden++;
|
|
}
|
|
|
|
// Blank lines within a diff content array indicate a newline.
|
|
leftSide.push({
|
|
type: 'CODE',
|
|
hidden: hidden,
|
|
content: chunk.ab[i] || '\n',
|
|
numLines: numLines,
|
|
lineNum: ++ctx.left.lineNum,
|
|
});
|
|
rightSide.push({
|
|
type: 'CODE',
|
|
hidden: hidden,
|
|
content: chunk.ab[i] || '\n',
|
|
numLines: numLines,
|
|
lineNum: ++ctx.right.lineNum,
|
|
});
|
|
|
|
this._addCommentsIfPresent(ctx, leftSide, rightSide);
|
|
}
|
|
if (ctx.lastNumLinesHidden > 0) {
|
|
this._addContextControl(ctx, leftSide, rightSide);
|
|
}
|
|
},
|
|
|
|
_addDiffChunk: function(ctx, chunk, leftSide, rightSide) {
|
|
if (chunk.ab) {
|
|
this._addCommonDiffChunk(ctx, chunk, leftSide, rightSide);
|
|
return;
|
|
}
|
|
|
|
var leftHighlights = [];
|
|
if (chunk.edit_a) {
|
|
leftHighlights =
|
|
this._normalizeIntralineHighlights(chunk.a, chunk.edit_a);
|
|
}
|
|
var rightHighlights = [];
|
|
if (chunk.edit_b) {
|
|
rightHighlights =
|
|
this._normalizeIntralineHighlights(chunk.b, chunk.edit_b);
|
|
}
|
|
|
|
var aLen = (chunk.a && chunk.a.length) || 0;
|
|
var bLen = (chunk.b && chunk.b.length) || 0;
|
|
var maxLen = Math.max(aLen, bLen);
|
|
for (var i = 0; i < maxLen; i++) {
|
|
var hasLeftContent = chunk.a && i < chunk.a.length;
|
|
var hasRightContent = chunk.b && i < chunk.b.length;
|
|
var leftContent = hasLeftContent ? chunk.a[i] : '';
|
|
var rightContent = hasRightContent ? chunk.b[i] : '';
|
|
var maxNumLines = this._maxLinesSpanned(leftContent, rightContent);
|
|
if (hasLeftContent) {
|
|
leftSide.push({
|
|
type: 'CODE',
|
|
content: leftContent || '\n',
|
|
numLines: maxNumLines,
|
|
lineNum: ++ctx.left.lineNum,
|
|
highlight: true,
|
|
intraline: leftHighlights.filter(function(hl) {
|
|
return hl.contentIndex == i;
|
|
}),
|
|
});
|
|
} else {
|
|
leftSide.push({
|
|
type: 'FILLER',
|
|
numLines: maxNumLines,
|
|
});
|
|
}
|
|
if (hasRightContent) {
|
|
rightSide.push({
|
|
type: 'CODE',
|
|
content: rightContent || '\n',
|
|
numLines: maxNumLines,
|
|
lineNum: ++ctx.right.lineNum,
|
|
highlight: true,
|
|
intraline: rightHighlights.filter(function(hl) {
|
|
return hl.contentIndex == i;
|
|
}),
|
|
});
|
|
} else {
|
|
rightSide.push({
|
|
type: 'FILLER',
|
|
numLines: maxNumLines,
|
|
});
|
|
}
|
|
this._addCommentsIfPresent(ctx, leftSide, rightSide);
|
|
}
|
|
},
|
|
|
|
_addCommentsIfPresent: function(ctx, leftSide, rightSide) {
|
|
var leftComments = this._groupedBaseComments[ctx.left.lineNum];
|
|
var rightComments = this._groupedComments[ctx.right.lineNum];
|
|
if (leftComments) {
|
|
leftSide.push({
|
|
type: 'COMMENT_THREAD',
|
|
comments: leftComments,
|
|
});
|
|
}
|
|
if (rightComments) {
|
|
rightSide.push({
|
|
type: 'COMMENT_THREAD',
|
|
comments: rightComments,
|
|
});
|
|
}
|
|
if (leftComments && !rightComments) {
|
|
rightSide.push({ type: 'FILLER' });
|
|
} else if (!leftComments && rightComments) {
|
|
leftSide.push({ type: 'FILLER' });
|
|
}
|
|
delete(this._groupedBaseComments[ctx.left.lineNum]);
|
|
delete(this._groupedComments[ctx.right.lineNum]);
|
|
},
|
|
|
|
// 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.
|
|
_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;
|
|
},
|
|
|
|
_maxLinesSpanned: function(left, right) {
|
|
return Math.max(Math.ceil(left.length / this.sideWidth),
|
|
Math.ceil(right.length / this.sideWidth));
|
|
},
|
|
|
|
});
|
|
})();
|
|
</script>
|
|
</dom-module>
|