%PDF- %PDF-
| Direktori : /data/old/home/stash/stash/atlassian-stash/static/feature/file-content/ediff/ |
| Current File : //data/old/home/stash/stash/atlassian-stash/static/feature/file-content/ediff/ediff.js |
define('feature/file-content/ediff', [
'util/html'
],
function(
htmlUtil
) {
var tokenizer = /\w+|\s+|./gim,
prefixingWhitespace = /\n[ \u00a0\t\r]+/mg, // \n followed by spaces, tabs and nbsp
whitespace = /\s/m,
ADD = 'add',
DELETE = 'delete',
CHANGE = 'change',
NodeStream = htmlUtil.NodeStream,
NodeType = htmlUtil.NodeType,
OPEN_NODE = NodeType.OPEN,
CLOSE_NODE = NodeType.CLOSE;
function tokenizeString(s) {
/*jshint boss:true */
var l = [], t;
tokenizer.lastIndex = 0; // reset the tokenizer
while (t = tokenizer.exec(s)) {
l.push({
start: t.index,
value: t[0],
end: tokenizer.lastIndex
});
}
return l;
}
function getTokensComparableValue(t) {
var comparableValue = t.comparableValue,
value;
if (typeof comparableValue !== 'string') {
value = comparableValue = t.value;
whitespace.lastIndex = 0; // reset the whitespace
if (whitespace.test(value)) {
if (t.start === 0) {
value = '\n' + value;
}
if (value.indexOf('\n') !== -1) {
comparableValue = value.replace(prefixingWhitespace, "\n");
}
}
t.comparableValue = comparableValue;
}
return comparableValue;
}
/**
* This algorithm is based on FishEye's java implementation of diff which itself is an implementation of
* diff algorithm described in An O(ND) Difference Algorithm and its Variations by Eugene W. Myers
* @param originalTokens the tokens for the original text
* @param revisedTokens the tokens for the revised text
* @return an array of hunks
*/
function generateHunks(originalTokens, revisedTokens) {
var l = [];
if (originalTokens.length !== 0 || revisedTokens.length !== 0) {
var path = buildPath(originalTokens, revisedTokens);
if (path.snake) {
path = path.prev;
}
while (path && path.prev && path.prev.j >= 0) {
if (path.snake) {
throw "bad diffpath: found snake when looking for diff";
}
var i = path.i;
var j = path.j;
path = path.prev;
var ianchor = path.i;
var janchor = path.j;
var iCount = i - ianchor;
var jCount = j - janchor;
l.unshift({
from : ianchor,
fromCount : iCount,
to : janchor,
toCount : jCount,
type : (iCount === 0 && ADD) || (jCount === 0 && DELETE) || CHANGE
});
if (path.snake) {
path = path.prev;
}
}
}
return l;
}
/**
* creates these path objects which are used to figure out what the hunks are.
* To be honest I don't really know how it works. Each path is made of snake nodes
* and diff nodes.
* BuildPath is by far the slowest component of ediff, on a test case with 2 x 5500 token segments, it takes ~4.5s.
* getTokensComparableValue is a significant contributor (~10% of the BuildPath CPU time), though mostly through
* the sheer number of times it is called (~30,000,000 times for the previous case).
* Garbage Collection is also a contributing factor, consuming ~10% as much CPU time as BuildPath.
* @param originalTokens
* @param revisedTokens
* @return a path
*/
function buildPath(originalTokens, revisedTokens) {
var N = originalTokens.length,
M = revisedTokens.length,
MAX = N + M + 1,
size = 1 + 2 * MAX,
middle = (size + 1) / 2,
diagonal = [];
diagonal.length = size;
diagonal[middle + 1] = createSnakeNode(0, -1, null);
for (var d = 0; d < MAX; d++) {
for (var k = -d; k <= d; k += 2) {
var kmiddle = middle + k,
kplus = kmiddle + 1,
kminus = kmiddle - 1,
prev,
i;
if ((k === -d) ||
(k !== d && diagonal[kminus].i < diagonal[kplus].i)) {
i = diagonal[kplus].i;
prev = diagonal[kplus];
} else {
i = diagonal[kminus].i + 1;
prev = diagonal[kminus];
}
diagonal[kminus] = null; // no longer used
var j = i - k;
var node = createDiffNode(i, j, prev);
// orig and rev are zero-based
// but the algorithm is one-based
// that's why there's no +1 when indexing the sequences
while (i < N && j < M && (getTokensComparableValue(originalTokens[i]) === getTokensComparableValue(revisedTokens[j]))) {
i++;
j++;
}
if (i > node.i) {
node = createSnakeNode(i, j, node);
}
diagonal[kmiddle] = node;
if (i >= N && j >= M) {
return diagonal[kmiddle];
}
}
diagonal[middle + d - 1] = null;
}
// According to Myers, this cannot happen
throw "could not find a diff path";
}
function createSnakeNode(i, j, prev) {
return {
i : i,
j : j,
prev : prev,
snake : true
};
}
function createDiffNode(i, j, prev) {
return {
i : i,
j : j,
prev : previousSnake(prev)
};
}
function previousSnake(node) {
while (node && node.i !== 0 && node.j !== 0 && !node.snake) {
if (!node.prev) {
return node;
}
node = node.prev;
}
return node;
}
function updateRegions(tokens, regions, start, len, type) {
for (var i = start; i < start + len; i++) {
var lastI = regions.length - 1;
var prev = regions.length && regions[lastI];
var token = tokens[i];
if (prev && prev.end === token.start) {
regions[lastI] = {
start: prev.start,
end: token.end,
type: prev.type
};
} else {
regions.push({
start: token.start,
end: token.end,
type: type
});
}
}
}
/**
* this loops through each of the hunks and uses original and revised tokens
* to determine the regions of the original text which have been changed or deleted
* and the revised text which have been changed or added
* @param originalTokens
* @param revisedTokens
* @param hunks
* @return {Object}
*/
function generateDiff(originalTokens, revisedTokens, hunks) {
var originalRegions = [],
revisedRegions = [];
for (var i = 0, l = hunks.length, hunk, hunkType; i < l; i++) {
hunk = hunks[i];
hunkType = hunk.type;
if (hunkType === CHANGE || hunkType === DELETE) {
updateRegions(originalTokens, originalRegions, hunk.from, hunk.fromCount, hunkType);
}
if (hunkType === CHANGE || hunkType === ADD) {
updateRegions(revisedTokens, revisedRegions, hunk.to, hunk.toCount, hunkType);
}
}
return {
originalRegions : originalRegions,
revisedRegions : revisedRegions
};
}
/**
*
* @param originalTokens
* @param revisedTokens
* @return An object in the following form:
* {
* 'originalRegions' : [{start:0, end:0, type:'add'}],
* 'revisedRegions' : [{start:0, end:0}]
* }
*/
function diff(originalTokens, revisedTokens) {
var hunks = generateHunks(originalTokens, revisedTokens);
return generateDiff(originalTokens, revisedTokens, hunks);
}
/**
* A node stream of the diff regions
* @param regions
* @constructor
*/
function DiffRegionNodeStream(regions) {
this._regions = regions.slice(0);
this._regionsLength = this._regions.length;
this._opened = false;
this._nextPosition = 0;
this._current = undefined;
}
DiffRegionNodeStream.prototype = new NodeStream();
DiffRegionNodeStream.prototype.hasNext = function() {
return this._opened || this._nextPosition < this._regionsLength;
};
DiffRegionNodeStream.prototype.next = function() {
if (!this.hasNext()) {
return null;
} else if (this._opened) {
this._opened = false;
return {
type : CLOSE_NODE,
textPosition : this._current.end,
value : '</span>'
};
} else {
this._opened = true;
var current = this._current = this._regions[this._nextPosition];
this._nextPosition++;
return {
type : OPEN_NODE,
textPosition : current.start,
value : '<span class="ediff-' + current.type + '">',
close : {
type : CLOSE_NODE,
value : '</span>'
}
};
}
};
DiffRegionNodeStream.prototype.getNextType = function() {
return this.hasNext() ? (this._opened ? CLOSE_NODE : OPEN_NODE) : null;
};
DiffRegionNodeStream.prototype.getNextTextPosition = function() {
return this.hasNext() ? (this._opened ? this._current.end : this._regions[this._nextPosition].start) : null;
};
return {
tokenizeString : tokenizeString,
getTokensComparableValue : getTokensComparableValue,
diff : diff,
DiffRegionNodeStream : DiffRegionNodeStream
};
});