713 lines
22 KiB
JavaScript
713 lines
22 KiB
JavaScript
// 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';
|
|
|
|
Polymer({
|
|
is: 'gr-diff',
|
|
|
|
/**
|
|
* Fired when the diff is rendered.
|
|
*
|
|
* @event render
|
|
*/
|
|
|
|
properties: {
|
|
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,
|
|
prefs: {
|
|
type: Object,
|
|
notify: true,
|
|
},
|
|
projectConfig: Object,
|
|
|
|
_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.
|
|
* Only used for initial rendering.
|
|
*/
|
|
_groupedBaseComments: {
|
|
type: Object,
|
|
value: function() { return {}; },
|
|
},
|
|
/**
|
|
* Comments and drafts (right side) grouped by line number.
|
|
* Only used for initial rendering.
|
|
*/
|
|
_groupedComments: {
|
|
type: Object,
|
|
value: function() { return {}; },
|
|
},
|
|
_diffResponse: Object,
|
|
_diff: {
|
|
type: Object,
|
|
value: function() { return {}; },
|
|
},
|
|
_loggedIn: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
_initialRenderComplete: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
_loading: {
|
|
type: Boolean,
|
|
value: true,
|
|
},
|
|
_savedPrefs: Object,
|
|
|
|
_diffPreferencesPromise: Object, // Used for testing.
|
|
},
|
|
|
|
observers: [
|
|
'_prefsChanged(prefs.*)',
|
|
],
|
|
|
|
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);
|
|
},
|
|
|
|
scrollToNextDiffChunk: function() {
|
|
this.$.rightDiff.scrollToNextDiffChunk();
|
|
},
|
|
|
|
scrollToPreviousDiffChunk: function() {
|
|
this.$.rightDiff.scrollToPreviousDiffChunk();
|
|
},
|
|
|
|
scrollToNextCommentThread: function() {
|
|
this.$.rightDiff.scrollToNextCommentThread();
|
|
},
|
|
|
|
scrollToPreviousCommentThread: function() {
|
|
this.$.rightDiff.scrollToPreviousCommentThread();
|
|
},
|
|
|
|
reload: function() {
|
|
this._loading = true;
|
|
// If a diff takes a considerable amount of time to render, the previous
|
|
// diff can end up showing up while the DOM is constructed. Clear the
|
|
// content on a reload to prevent this.
|
|
this._diff = {
|
|
leftSide: [],
|
|
rightSide: [],
|
|
};
|
|
|
|
var diffLoaded = this._getDiff().then(function(diff) {
|
|
this._diffResponse = diff;
|
|
}.bind(this));
|
|
|
|
var promises = [
|
|
this._prefsReady,
|
|
diffLoaded,
|
|
];
|
|
|
|
return app.accountReady.then(function() {
|
|
promises.push(this._getDiffComments().then(function(res) {
|
|
this._baseComments = res.baseComments;
|
|
this._comments = res.comments;
|
|
}.bind(this)));
|
|
|
|
if (!app.loggedIn) {
|
|
this._baseDrafts = [];
|
|
this._drafts = [];
|
|
} else {
|
|
promises.push(this._getDiffDrafts().then(function(res) {
|
|
this._baseDrafts = res.baseComments;
|
|
this._drafts = res.comments;
|
|
}.bind(this)));
|
|
}
|
|
|
|
return Promise.all(promises).then(function() {
|
|
this._render();
|
|
this._loading = false;
|
|
}.bind(this)).catch(function(err) {
|
|
this._loading = false;
|
|
alert('Oops. Something went wrong. Check the console and bug the ' +
|
|
'PolyGerrit team for assistance.');
|
|
throw err;
|
|
}.bind(this));
|
|
}.bind(this));
|
|
},
|
|
|
|
_getDiff: function() {
|
|
return this.$.restAPI.getDiff(
|
|
this.changeNum,
|
|
this.patchRange.basePatchNum,
|
|
this.patchRange.patchNum,
|
|
this.path);
|
|
},
|
|
|
|
_getDiffComments: function() {
|
|
return this.$.restAPI.getDiffComments(
|
|
this.changeNum,
|
|
this.patchRange.basePatchNum,
|
|
this.patchRange.patchNum,
|
|
this.path);
|
|
},
|
|
|
|
_getDiffDrafts: function() {
|
|
return this.$.restAPI.getDiffDrafts(
|
|
this.changeNum,
|
|
this.patchRange.basePatchNum,
|
|
this.patchRange.patchNum,
|
|
this.path);
|
|
},
|
|
|
|
showDiffPreferences: function() {
|
|
this.$.prefsOverlay.open();
|
|
},
|
|
|
|
_prefsChanged: function(changeRecord) {
|
|
if (this._initialRenderComplete) {
|
|
this._render();
|
|
}
|
|
this._resolvePrefsReady(changeRecord.base);
|
|
},
|
|
|
|
_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;
|
|
},
|
|
|
|
_handlePrefsTap: function(e) {
|
|
e.preventDefault();
|
|
|
|
// TODO(andybons): This is not supported in IE. Implement a polyfill.
|
|
// NOTE: Object.assign is NOT automatically a deep copy. If prefs adds
|
|
// an object as a value, it must be marked enumerable.
|
|
this._savedPrefs = Object.assign({}, this.prefs);
|
|
this.$.prefsOverlay.open();
|
|
},
|
|
|
|
_handlePrefsSave: function(e) {
|
|
e.stopPropagation();
|
|
var el = Polymer.dom(e).rootTarget;
|
|
el.disabled = true;
|
|
app.accountReady.then(function() {
|
|
if (!this._loggedIn) {
|
|
el.disabled = false;
|
|
this.$.prefsOverlay.close();
|
|
return;
|
|
}
|
|
this._saveDiffPreferences().then(function() {
|
|
this.$.prefsOverlay.close();
|
|
el.disabled = false;
|
|
}.bind(this)).catch(function(err) {
|
|
el.disabled = false;
|
|
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;
|
|
},
|
|
|
|
_handlePrefsCancel: function(e) {
|
|
e.stopPropagation();
|
|
this.prefs = this._savedPrefs;
|
|
this.$.prefsOverlay.close();
|
|
},
|
|
|
|
_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 (diffEl == this.$.leftDiff &&
|
|
this.patchRange.basePatchNum == 'PARENT') {
|
|
comment.comments[0].side = 'PARENT';
|
|
comment.patchNum = this.patchRange.patchNum;
|
|
}
|
|
|
|
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._breakUpCommonChunksWithComments(ctx,
|
|
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,
|
|
};
|
|
},
|
|
|
|
// In order to show comments out of the bounds of the selected context,
|
|
// treat them as diffs within the model so that the content (and context
|
|
// surrounding it) renders correctly.
|
|
_breakUpCommonChunksWithComments: function(ctx, content) {
|
|
var result = [];
|
|
var leftLineNum = ctx.left.lineNum;
|
|
var rightLineNum = ctx.right.lineNum;
|
|
for (var i = 0; i < content.length; i++) {
|
|
if (!content[i].ab) {
|
|
result.push(content[i]);
|
|
if (content[i].a) {
|
|
leftLineNum += content[i].a.length;
|
|
}
|
|
if (content[i].b) {
|
|
rightLineNum += content[i].b.length;
|
|
}
|
|
continue;
|
|
}
|
|
var chunk = content[i].ab;
|
|
var currentChunk = {ab: []};
|
|
for (var j = 0; j < chunk.length; j++) {
|
|
leftLineNum++;
|
|
rightLineNum++;
|
|
if (this._groupedBaseComments[leftLineNum] == null &&
|
|
this._groupedComments[rightLineNum] == null) {
|
|
currentChunk.ab.push(chunk[j]);
|
|
} else {
|
|
if (currentChunk.ab && currentChunk.ab.length > 0) {
|
|
result.push(currentChunk);
|
|
currentChunk = {ab: []};
|
|
}
|
|
// Append an annotation to indicate that this line should not be
|
|
// highlighted even though it's implied with both `a` and `b`
|
|
// defined. This is needed since there may be two lines that
|
|
// should be highlighted but are equal (blank lines, for example).
|
|
result.push({
|
|
__noHighlight: true,
|
|
a: [chunk[j]],
|
|
b: [chunk[j]],
|
|
});
|
|
}
|
|
}
|
|
if (currentChunk.ab != null && currentChunk.ab.length > 0) {
|
|
result.push(currentChunk);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
|
|
_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(
|
|
this._visibleLineLength(chunk.ab[i]) / this.prefs.line_length);
|
|
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 highlight = !chunk.__noHighlight;
|
|
var maxNumLines = this._maxLinesSpanned(leftContent, rightContent);
|
|
if (hasLeftContent) {
|
|
leftSide.push({
|
|
type: 'CODE',
|
|
content: leftContent || '\n',
|
|
numLines: maxNumLines,
|
|
lineNum: ++ctx.left.lineNum,
|
|
highlight: highlight,
|
|
intraline: highlight && 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: highlight,
|
|
intraline: highlight && 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) {
|
|
var thread = {
|
|
type: 'COMMENT_THREAD',
|
|
comments: leftComments,
|
|
};
|
|
if (this.patchRange.basePatchNum == 'PARENT') {
|
|
thread.patchNum = this.patchRange.patchNum;
|
|
}
|
|
leftSide.push(thread);
|
|
}
|
|
if (rightComments) {
|
|
rightSide.push({
|
|
type: 'COMMENT_THREAD',
|
|
comments: rightComments,
|
|
});
|
|
}
|
|
if (leftComments && !rightComments) {
|
|
rightSide.push({type: 'FILLER'});
|
|
} else if (!leftComments && rightComments) {
|
|
leftSide.push({type: 'FILLER'});
|
|
}
|
|
this._groupedBaseComments[ctx.left.lineNum] = null;
|
|
this._groupedComments[ctx.right.lineNum] = null;
|
|
},
|
|
|
|
// 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;
|
|
},
|
|
|
|
_visibleLineLength: function(contents) {
|
|
// http://jsperf.com/performance-of-match-vs-split
|
|
var numTabs = contents.split('\t').length - 1;
|
|
return contents.length - numTabs + (this.prefs.tab_size * numTabs);
|
|
},
|
|
|
|
_maxLinesSpanned: function(left, right) {
|
|
return Math.max(
|
|
Math.ceil(this._visibleLineLength(left) / this.prefs.line_length),
|
|
Math.ceil(this._visibleLineLength(right) / this.prefs.line_length));
|
|
},
|
|
});
|
|
})();
|