c9-core/plugins/c9.ide.scm/diff/twoway.js

1244 wiersze
43 KiB
JavaScript

define(function(require, exports, module) {
"use strict";
var oop = require("ace/lib/oop");
var event = require("ace/lib/event");
var Range = require("ace/range").Range;
var dom = require("ace/lib/dom");
var config = require("ace/config");
var LineWidgets = require("ace/line_widgets").LineWidgets;
var css = require("text!./styles.css");
var diff_match_patch = require("./diff_match_patch").diff_match_patch;
var SVG_NS = "http://www.w3.org/2000/svg";
var Editor = require("ace/editor").Editor;
var Renderer = require("ace/virtual_renderer").VirtualRenderer;
var UndoManager = require("ace/undomanager").UndoManager;
var EditSession = require("ace/edit_session").EditSession;
require("ace/theme/textmate");
function createEditor() {
var editor = new Editor(new Renderer(), null);
editor.session.setUndoManager(new UndoManager());
return editor;
}
function DiffView(element, options) {
this.onInput = this.onInput.bind(this);
this.onConnectorScroll = this.onConnectorScroll.bind(this);
this.onMouseWheel = this.onMouseWheel.bind(this);
this.onScroll = this.onScroll.bind(this);
this.onChangeFold = this.onChangeFold.bind(this);
this.onChangeTheme = this.onChangeTheme.bind(this);
dom.importCssString(css, "diffview.css");
this.options = {};
this.container = element;
oop.mixin(this.options, {
showDiffs: true,
maxDiffs: 5000
}, options);
this.orig = this.left = createEditor();
this.edit = this.right = createEditor();
this.gutterEl = document.createElement("div");
element.appendChild(this.left.container);
element.appendChild(this.gutterEl);
element.appendChild(this.right.container);
this.left.setOption("scrollPastEnd", 0.5);
this.right.setOption("scrollPastEnd", 0.5);
this.left.setOption("highlightActiveLine", false);
this.right.setOption("highlightActiveLine", false);
this.left.setOption("highlightGutterLine", false);
this.right.setOption("highlightGutterLine", false);
this.left.setOption("animatedScroll", true);
this.right.setOption("animatedScroll", true);
this.markerLeft = new DiffHighlight(this, -1);
this.markerRight = new DiffHighlight(this, 1);
this.setSession({
orig: this.orig.session,
edit: this.edit.session,
chunks: []
});
this.connector = new Connector(this);
this.connector.createGutter();
this.onChangeTheme();
this.$initArrow();
this.$attachEventHandlers();
config.resetOptions(this);
config._signal("diffView", this);
}
(function() {
/*** theme/session ***/
this.setSession = function(session) {
if (this.session) {
this.$detachSessionEventHandlers();
}
this.session = session;
if (this.session) {
this.chunks = this.session.chunks;
this.orig.setSession(session.orig);
this.edit.setSession(session.edit);
this.$attachSessionEventHandlers();
}
};
this.getSession = function() {
return this.session;
};
this.createSession = function() {
var session = new EditSession("");
session.setUndoManager(new UndoManager());
return session;
};
this.setTheme = function(theme) {
this.left.setTheme(theme);
};
this.getTheme = function() {
return this.left.getTheme();
};
this.onChangeTheme = function() {
this.right.setTheme(this.left.getTheme());
var theme = this.right.renderer.theme;
this.gutterEl.className = "ace_diff-gutter " + theme.cssClass;
dom.setCssClass(this.gutterEl, "ace_dark", theme.isDark);
};
this.resize = function() {
this.edit.resize();
this.orig.resize();
};
/*** compute diff ***/
this.getDmp = function() {
var dmp = new diff_match_patch();
return dmp;
};
this.onInput =
this.computeDiff = function() {
var val1 = this.left.session.doc.getAllLines();
var val2 = this.right.session.doc.getAllLines();
var chunks = this.$diffLines(val1, val2);
this.session.chunks = this.chunks = chunks;
// if we"re dealing with too many chunks, fail silently
if (this.chunks.length > this.options.maxDiffs) {
return;
}
if (this.$alignDiffs)
this.align();
this.left.renderer.updateBackMarkers();
this.right.renderer.updateBackMarkers();
};
this.$diffLines = function(val1, val2) {
var dmp = this.getDmp();
var a = diff_linesToChars_(val1, val2);
var diff = dmp.diff_main(a.chars1, a.chars2, false);
var chunks = [];
var offset = {
left: 0,
right: 0
};
var lastChunk;
diff.forEach(function(chunk) {
var chunkType = chunk[0];
var length = chunk[1].length;
// oddly, occasionally the algorithm returns a diff with no changes made
if (length === 0) {
return;
}
if (chunkType === 0) {
offset.left += length;
offset.right += length;
lastChunk = null;
}
else if (chunkType === -1) {
if (lastChunk) {
lastChunk.origEnd = Math.max(offset.left + length, lastChunk.origEnd);
lastChunk.editEnd = Math.max(offset.right, lastChunk.editEnd);
} else {
chunks.push(lastChunk = {
origStart: offset.left,
origEnd: offset.left + length,
editStart: offset.right,
editEnd: offset.right,
});
}
offset.left += length;
}
else if (chunkType === 1) {
if (lastChunk) {
lastChunk.origEnd = Math.max(offset.left, lastChunk.origEnd);
lastChunk.editEnd = Math.max(offset.right + length, lastChunk.editEnd);
} else {
chunks.push(lastChunk = {
origStart: offset.left,
origEnd: offset.left,
editStart: offset.right,
editEnd: offset.right + length,
});
}
offset.right += length;
}
}, this);
chunks.forEach(function(diff) {
var inlineChanges = [];
var type = 0;
if (diff.origStart == diff.origEnd) {
type = 1;
} else if (diff.editStart == diff.editEnd) {
type = -1;
} else {
var inlineDiff = dmp.diff_main(
val1.slice(diff.origStart, diff.origEnd).join("\n"),
val2.slice(diff.editStart, diff.editEnd).join("\n"),
false
);
dmp.diff_cleanupSemantic(inlineDiff);
inlineDiff.forEach(function(change) {
var text = change[1];
var lines = text.split("\n");
var rowCh = lines.length - 1;
var colCh = lines[rowCh].length;
var changeType = change[0];
if (text.length) {
inlineChanges.push([changeType, rowCh, colCh]);
// if (changeType) {
// if (!type) {
// type = changeType;
// } else if (type != changeType) {
// type = 2;
// }
// }
}
});
}
diff.inlineChanges = inlineChanges;
diff.type = type;
});
return chunks;
};
/*** scroll locking ***/
this.align = function() {
var diffView = this;
function add(editor, w) {
editor.session.lineWidgets[w.row] = w;
editor.session.widgetManager.lineWidgets.push(w);
}
function init(editor) {
var session = editor.session;
if (!session.widgetManager) {
session.widgetManager = new LineWidgets(session);
session.widgetManager.attach(editor);
}
editor.session.lineWidgets = [];
editor.session.widgetManager.lineWidgets = [];
}
init(diffView.edit);
init(diffView.orig);
diffView.chunks.forEach(function(ch) {
var diff1 = ch.origEnd - ch.origStart;
var diff2 = ch.editEnd - ch.editStart;
if (diff1 < diff2) {
add(diffView.orig, {
rowCount: diff2 - diff1,
row: ch.origEnd - 1
});
}
else if (diff1 > diff2) {
add(diffView.edit, {
rowCount: diff1 - diff2,
row: ch.editEnd - 1
});
}
});
diffView.edit.session._emit("changeFold", { data: { start: { row: 0 }}});
diffView.orig.session._emit("changeFold", { data: { start: { row: 0 }}});
};
this.onScroll = function(e, session) {
this.syncScroll(this.left.session == session ? this.left.renderer : this.right.renderer);
};
this.syncScroll = function(renderer) {
if (this.$syncScroll == false) return;
var r1 = this.left.renderer;
var r2 = this.right.renderer;
var isOrig = renderer == r1;
if (r1.$scrollAnimation && r2.$scrollAnimation) return;
var now = Date.now();
if (this.scrollSetBy != renderer && now - this.scrollSetAt < 500) return;
var r = isOrig ? r1 : r2;
if (this.scrollSetBy != renderer) {
if (isOrig && this.scrollOrig == r.session.getScrollTop())
return;
else if (!isOrig && this.scrollEdit == r.session.getScrollTop())
return;
}
var rOther = isOrig ? r2 : r1;
if (this.$alignDiffs) {
targetPos = r.session.getScrollTop();
} else {
var layerConfig = r.layerConfig;
var chunks = this.chunks;
var halfScreen = 0.4 * r.$size.scrollerHeight;
var lc = layerConfig;
var midY = halfScreen + r.scrollTop;
var mid = r.session.screenToDocumentRow(midY / lc.lineHeight, 0);
var i = findChunkIndex(chunks, mid, isOrig);
var ch = chunks[i];
if (!ch) {
ch = {
editStart: 0,
editEnd: 0,
origStart: 0,
origEnd: 0
};
}
if (mid >= (isOrig ? ch.origEnd : ch.editEnd)) {
var next = chunks[i + 1] || {
editStart: r2.session.getLength(),
editEnd: r2.session.getLength(),
origStart: r1.session.getLength(),
origEnd: r1.session.getLength()
};
ch = {
origStart: ch.origEnd,
origEnd: next.origStart,
editStart: ch.editEnd,
editEnd: next.editStart
};
}
if (r == r1) {
var start = ch.origStart;
var end = ch.origEnd;
var otherStart = ch.editStart;
var otherEnd = ch.editEnd;
}
else {
otherStart = ch.origStart;
otherEnd = ch.origEnd;
start = ch.editStart;
end = ch.editEnd;
}
var offOtherTop = rOther.session.documentToScreenRow(otherStart, 0) * lc.lineHeight;
var offOtherBot = rOther.session.documentToScreenRow(otherEnd, 0) * lc.lineHeight;
var offTop = r.session.documentToScreenRow(start, 0) * lc.lineHeight;
var offBot = r.session.documentToScreenRow(end, 0) * lc.lineHeight;
var ratio = (midY - offTop) / (offBot - offTop || offOtherBot - offOtherTop);
var targetPos = (offOtherTop - halfScreen) + ratio * (offOtherBot - offOtherTop);
targetPos = Math.max(0, targetPos);
}
this.$syncScroll = false;
if (isOrig) {
this.scrollOrig = r.session.getScrollTop();
this.scrollEdit = targetPos;
}
else {
this.scrollOrig = targetPos;
this.scrollEdit = r.session.getScrollTop();
}
this.scrollSetBy = renderer;
rOther.session.setScrollTop(targetPos);
this.$syncScroll = true;
this.scrollSetAt = now;
};
this.onConnectorScroll = function(ev) {
var dir = ev.wheelY > 0 ? 1 : -1;
var r1 = this.left.renderer;
var r2 = this.right.renderer;
if (r1.$scrollAnimation && r2.$scrollAnimation) return;
var minAmount = ev.wheelY * 2;
var r = r1.session.getLength() > r2.session.getLength() ? r1 : r2;
var layerConfig = r.layerConfig;
var chunks = this.chunks;
var halfScreen = 0.5 * r.$size.scrollerHeight;
var lc = layerConfig;
var isOrig = r == r1;
var midY = halfScreen + r.scrollTop;
var mid = r.session.screenToDocumentRow(midY / lc.lineHeight, 0);
var i = findChunkIndex(chunks, mid, isOrig);
var ch = chunks[i];
if (dir < 0) {
if (!ch)
ch = {
editStart: 0,
editEnd: 0,
origStart: 0,
origEnd: 0
};
else if (mid < (isOrig ? ch.origEnd : ch.editEnd))
ch = chunks[i - 1] || ch;
else
ch = chunks[i];
}
else {
ch = chunks[i + 1] || {
editStart: r2.session.getLength(),
editEnd: r2.session.getLength(),
origStart: r1.session.getLength(),
origEnd: r1.session.getLength()
};
}
var scrollTop1 = r1.session.getScrollTop();
var scrollTop2 = r2.session.getScrollTop();
this.$syncScroll = false;
r1.scrollToLine(ch.origStart, true, true);
r2.scrollToLine(ch.editStart, true, true);
this.$syncScroll = true;
if (Math.abs(scrollTop1 - r1.session.getScrollTop()) <= 1
&& Math.abs(scrollTop2 - r2.session.getScrollTop()) <= 1
) {
if (r1.isScrollableBy(0, minAmount))
r1.scrollBy(0, minAmount);
}
};
this.onMouseWheel = function(ev) {
if (ev.getAccelKey())
return;
if (ev.getShiftKey() && ev.wheelY && !ev.wheelX) {
ev.wheelX = ev.wheelY;
ev.wheelY = 0;
}
var editor = ev.editor;
var isScrolable = editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
if (!isScrolable) {
var other = editor == this.left ? this.right : this.left;
if (other.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed))
other.renderer.scrollBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
return ev.stop();
}
};
this.onChangeFold = function(ev, session) {
if (ev.action == "remove") {
var other = session == this.orig.session ? this.edit.session : this.orig.session;
var fold = ev.data;
if (fold && fold.other) {
fold.other.other = null;
other.removeFold(fold.other);
}
}
};
this.$attachSessionEventHandlers = function() {
this.left.session.on("changeScrollTop", this.onScroll);
this.right.session.on("changeScrollTop", this.onScroll);
this.left.session.on("changeFold", this.onChangeFold);
this.right.session.on("changeFold", this.onChangeFold);
this.left.session.addDynamicMarker(this.markerLeft);
this.right.session.addDynamicMarker(this.markerRight);
};
this.$detachSessionEventHandlers = function() {
this.left.session.off("changeScrollTop", this.onScroll);
this.right.session.off("changeScrollTop", this.onScroll);
this.left.session.off("changeFold", this.onChangeFold);
this.right.session.off("changeFold", this.onChangeFold);
this.left.session.removeMarker(this.markerLeft.id);
this.right.session.removeMarker(this.markerRight.id);
};
this.$attachEventHandlers = function() {
var _self = this;
this.left.renderer.on("themeLoaded", this.onChangeTheme);
this.right.renderer.on("afterRender", function() {
if (!_self.left.renderer.$loop.changes)
_self.connector.decorate();
});
this.left.renderer.on("afterRender", function() {
if (!_self.right.renderer.$loop.changes)
_self.connector.decorate();
});
event.addMouseWheelListener(this.gutterEl, this.onConnectorScroll);
this.left.on("mousewheel", this.onMouseWheel);
this.right.on("mousewheel", this.onMouseWheel);
this.left.on("input", this.onInput);
this.right.on("input", this.onInput);
};
this.$initArrow = function() {
var arrow = document.createElement("div");
this.gutterEl.appendChild(arrow);
var region = 0;
var diffView = this;
arrow.addEventListener("click", function(e) {
if (region && region.chunk) {
var range = diffView.useChunk(region.chunk, region.side == 1);
var editor = region.side == 1 ? diffView.orig : diffView.edit;
editor.selection.moveToPosition(range.start);
editor.focus();
}
hide();
});
this.gutterEl.addEventListener("mousemove", function(e) {
var rect = e.currentTarget.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY; // - rect.top;
if (!region) {
arrow.style.display = "";
region = {};
}
if (x < rect.width / 2) {
if (region.side != -1)
arrow.className = "diff-arrow diff-arrow-left";
region.side = -1;
} else {
if (region.side != 1)
arrow.className = "diff-arrow diff-arrow-right";
region.side = 1;
}
var editor = region.side == 1 ? diffView.edit : diffView.orig;
var other = editor == diffView.edit ? diffView.orig : diffView.edit;
var renderer = editor.renderer;
if (other.getReadOnly())
return hide();
var p = renderer.screenToTextCoordinates(x, y);
var row = p.row;
if (row == renderer.session.getLength() - 1)
row++;
var chunks = diffView.chunks;
var i = findChunkIndex(chunks, row, region.side == -1);
if (i == -1) i = 0;
var ch = chunks[i] || chunks[chunks.length - 1];
var next = chunks[i + 1];
var side = region.side == -1 ? "orig" : "edit";
if (next && ch) {
if (ch[side + "End"] + next[side + "Start"] < 2 * row)
ch = next;
}
region.chunk = ch;
if (!ch)
return hide();
if (renderer.layerConfig.firstRow > ch[side + "End"])
return hide();
if (renderer.layerConfig.lastRow < ch[side + "Start"] - 1)
return hide();
var screenPos = renderer.$cursorLayer.getPixelPosition({
row: ch[side + "Start"],
column: 0
}, true).top;
if (screenPos < renderer.layerConfig.offset)
screenPos = renderer.layerConfig.offset;
arrow.style.top = screenPos - renderer.layerConfig.offset + "px";
if (region.side == -1) {
arrow.style.left = "2px";
arrow.style.right = "";
} else {
arrow.style.left = "";
arrow.style.right = "2px";
}
}.bind(this));
this.gutterEl.addEventListener("mouseout", function(e) {
hide();
});
event.addMouseWheelListener(this.gutterEl, hide);
function hide() {
if (region) {
region = null;
arrow.style.display = "none";
}
}
};
/*** other ***/
this.destroy = function() {
this.left.destroy();
this.right.destroy();
};
this.foldUnchanged = function() {
this.edit.session.unfold();
this.orig.session.unfold();
var chunks = this.chunks;
var sep = "---";
var prev = { editEnd: 0, origEnd: 0 };
for (var i = 0; i < chunks.length + 1; i++) {
var ch = chunks[i] || {
editStart: this.edit.session.getLength(),
origStart: this.orig.session.getLength()
};
var l = ch.editStart - prev.editEnd - 5;
if (l > 2) {
var s = prev.origEnd + 2;
var f1 = this.orig.session.addFold(sep, new Range(s, 0, s + l, Number.MAX_VALUE));
s = prev.editEnd + 2;
var f2 = this.edit.session.addFold(sep, new Range(s, 0, s + l, Number.MAX_VALUE));
f1.other = f2;
f2.other = f1;
}
prev = ch;
}
};
this.gotoNext = function(dir) {
var orig = false;
var ace = orig ? this.orig : this.edit;
var row = ace.selection.lead.row;
var i = findChunkIndex(this.chunks, row, orig);
var chunk = this.chunks[i + dir] || this.chunks[i];
var scrollTop = ace.session.getScrollTop();
if (chunk) {
var line = Math.max(chunk.editStart, chunk.editEnd - 1);
ace.selection.setRange(new Range(line, 0, line, 0));
}
ace.renderer.scrollSelectionIntoView(ace.selection.lead, ace.selection.anchor, 0.5);
ace.renderer.animateScrolling(scrollTop);
};
this.transformPosition = function(pos, orig) {
var chunkIndex = findChunkIndex(this.chunks, pos.row, orig);
var chunk = this.chunks[chunkIndex];
var result = {
row: pos.row,
column: pos.column
};
if (orig) {
if (chunk.origEnd <= pos.row) {
result.row = pos.row - chunk.origEnd + chunk.editEnd;
}
else {
console.log("======================================");
var d = pos.row - chunk.origStart;
var c = pos.column;
var r1 = 0, c1 = 0, r2 = 0, c2 = 0;
var inlineChanges = chunk.inlineChanges;
for (var i = 0; i < inlineChanges.length; i++) {
var diff = inlineChanges[i];
if (diff[1]) {
if (diff[0] == 0) {
r1 += diff[1];
r2 += diff[1];
if (r1 == d)
c2 = c1 = diff[2];
} else if (diff[0] == 1) {
r2 += diff[1];
if (r1 == d)
c2 = diff[2];
} else if (diff[0] == -1) {
r1 += diff[1];
if (r1 == d)
c1 = diff[2];
}
}
else if (r1 == d) {
if (diff[0] == 0) {
c1 += diff[2];
c2 += diff[2];
} else if (diff[0] == 1) {
c2 += diff[2];
} else if (diff[0] == -1) {
c1 += diff[2];
}
}
console.log(diff + "", r1, c1, r2, c2, d, c);
if (r1 > d || r1 == d && c1 >= c) {
break;
}
}
if (r1 > d) {
r2 -= r1 - d;
}
if (c1 != c) {
c2 -= c1 - c;
}
result.row = r2 + chunk.editStart;
result.column = c2;
}
}
return result;
};
this.useChunk = function(chunk, toOrig) {
var origRange = new Range(chunk.origStart, 0, chunk.origEnd, 0);
var editRange = new Range(chunk.editStart, 0, chunk.editEnd, 0);
var srcEditor = toOrig ? this.edit : this.orig;
var destEditor = toOrig ? this.orig : this.edit;
var destRange = toOrig ? origRange : editRange;
var srcRange = toOrig ? editRange : origRange;
var value = srcEditor.session.getTextRange(srcRange);
// missing eol at the end of document
if (srcRange.isEmpty() && !destRange.isEmpty()) {
if (destRange.end.row == destEditor.session.getLength()) {
destRange.start.row--;
destRange.start.column = Number.MAX_VALUE;
}
} else if (destRange.isEmpty() && !srcRange.isEmpty()) {
if (srcRange.end.row == srcEditor.session.getLength()) {
value = "\n" + value;
}
}
destRange.end = destEditor.session.replace(destRange, value);
return destRange;
};
this.transformRange = function(range, orig) {
return Range.fromPoints(
this.transformPosition(range.start, orig),
this.transformPosition(range.end, orig)
);
};
this.findChunkIndex = function(row, orig) {
return findChunkIndex(this.chunks, row, orig);
};
/*** patch ***/
this.createPatch = function(options) {
var chunks = this.chunks;
var editLines = this.edit.session.doc.getAllLines();
var origLines = this.orig.session.doc.getAllLines();
var path1 = options.path1 || options.path || "_";
var path2 = options.path2 || path1;
var patch = [
"diff --git a/" + path1 + " b/" + path2,
"--- a/" + path1,
"+++ b/" + path2,
].join("\n");
if (!chunks.length) {
chunks = [{
origStart: 0,
origEnd: 0,
editStart: 0,
editEnd: 0
}];
}
function header(s1, c1, s2, c2) {
return "@@ -" + (c1 ? s1 + 1 : s1) + "," + c1
+ " +" + (c2 ? s2 + 1 : s2) + "," + c2 + " @@";
}
var context = options.context || 0;
// changed newline at the end of file
var editEOF = !editLines[editLines.length - 1];
var origEOF = !origLines[origLines.length - 1];
if (editEOF)
editLines.pop();
if (origEOF)
origLines.pop();
if (editEOF != origEOF) {
chunks = chunks.slice();
var last = chunks.pop();
chunks.push(last = {
origStart: Math.min(last.origStart, origLines.length - 1),
origEnd: Math.min(last.origEnd, origLines.length),
editStart: Math.min(last.editStart, editLines.length - 1),
editEnd: Math.min(last.editEnd, editLines.length)
});
}
var hunk = "";
var start1 = 0;
var start2 = 0;
var end1 = 0;
var end2 = 0;
var length1 = 0;
var length2 = 0;
var mergeWithNext = false;
for (var i = 0; i < chunks.length; i++) {
var ch = chunks[i];
var s1 = ch.origStart;
var e1 = ch.origEnd;
var s2 = ch.editStart;
var e2 = ch.editEnd;
var next = chunks[i + 1];
start1 = Math.max(s1 - context, end1);
start2 = Math.max(s2 - context, end2);
end1 = Math.min(e1 + context, origLines.length);
end2 = Math.min(e2 + context, editLines.length);
mergeWithNext = false;
if (next) {
if (end1 >= next.origStart - context) {
end1 = next.origStart;
end2 = next.editStart;
mergeWithNext = true;
}
}
for (var j = start1; j < s1; j++)
hunk += "\n " + origLines[j];
for (var j = s1; j < e1; j++)
hunk += "\n-" + origLines[j];
if (ch == last && editEOF)
hunk += "\n\\ No newline at end of file";
for (var j = s2; j < e2; j++)
hunk += "\n+" + editLines[j];
if (ch == last && origEOF)
hunk += "\n\\ No newline at end of file";
for (var j = e1; j < end1; j++)
hunk += "\n " + origLines[j];
length1 += end1 - start1;
length2 += end2 - start2;
if (mergeWithNext)
continue;
patch += "\n" + header(end1 - length1, length1, end2 - length2, length2) + hunk;
length2 = length1 = 0;
hunk = "";
}
if (!editEOF && !origEOF && end1 == origLines.length) {
patch += "\n\\ No newline at end of file";
}
return patch;
};
this.setValueFromFullPatch = function(fullUniDiff) {
var lines = fullUniDiff.split("\n");
var missingEOF = "";
var oldLines = [];
var newLines = [];
var i = 0;
while (i < lines.length && !(/^@@/.test(lines[i])))
i++;
while (++i < lines.length) {
var tag = lines[i][0];
var line = lines[i].substr(1);
if (tag === "+") {
newLines.push(line);
}
else if (tag === "-") {
oldLines.push(line);
}
else if (tag === " ") {
newLines.push(line);
oldLines.push(line);
}
else if (tag === "\\") {
missingEOF = lines[i - 1][0];
}
}
if (missingEOF === "+") {
oldLines.push("");
}
else if (missingEOF === "-") {
newLines.push("");
}
else if (missingEOF === "") {
newLines.push("");
oldLines.push("");
}
this.orig.session.setValue(oldLines.join("\n"));
this.edit.session.setValue(newLines.join("\n"));
};
this.applyPatch = function(oldStr, uniDiff) {
var lines = uniDiff.split("\n");
var hunks = [];
var i = 0;
var EOFChanged = 0;
// Skip to the first change hunk
while (i < lines.length && !(/^@@/.test(lines[i]))) {
i++;
}
// Parse the unified diff
for (; i < lines.length; i++) {
var tag = lines[i][0];
var line = lines[i].substr(1);
if (tag === "@") {
var chunkHeader = /@@ -(\d+)(?:,(\d*))? \+(\d+)(?:,(\d*)) @@/.exec(line);
hunks.unshift({
start: +chunkHeader[1],
oldlength: +chunkHeader[2] || 1,
removed: [],
added: []
});
}
else if (tag === "+") {
hunks[0].added.push(line);
}
else if (tag === "-") {
hunks[0].removed.push(line);
}
else if (tag === " ") {
hunks[0].added.push(line);
hunks[0].removed.push(line);
}
else if (tag === "\\") {
if (lines[i - 1][0] === "+")
EOFChanged = 1;
else if (lines[i - 1][0] === "-")
EOFChanged = -1;
}
}
// Apply the diff to the input
lines = oldStr.split("\n");
for (i = hunks.length - 1; i >= 0; i--) {
var hunk = hunks[i];
// Sanity check the input string. Bail if we don't match.
for (var j = 0; j < hunk.oldlength; j++) {
if (lines[hunk.start - 1 + j] !== hunk.removed[j]) {
return false;
}
}
lines.splice.apply(lines, [hunk.start - 1, hunk.oldlength].concat(hunk.added));
}
// Handle EOFNL insertion/removal
if (EOFChanged == -1) {
while (!lines[lines.length - 1]) {
lines.pop();
}
}
else if (EOFChanged == 1) {
lines.push("");
}
return lines.join("\n");
};
/*** options ***/
config.defineOptions(this, "editor", {
alignDiffs: {
set: function(val) {
if (val)
this.align();
},
initialValue: false
},
});
}).call(DiffView.prototype);
var diff_linesToChars_ = function(text1, text2) {
var lineHash = Object.create(null);
var lineCount = 1;
function diff_linesToCharsMunge_(lines) {
var chars = "";
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (typeof lineHash[line] === "number") {
chars += String.fromCharCode(lineHash[line]);
}
else {
chars += String.fromCharCode(lineCount);
lineHash[line] = lineCount++;
}
}
return chars;
}
var chars1 = diff_linesToCharsMunge_(text1);
var chars2 = diff_linesToCharsMunge_(text2);
return {
chars1: chars1,
chars2: chars2
};
};
function findChunkIndex(chunks, row, orig) {
if (orig) {
for (var i = 0; i < chunks.length; i++) {
var ch = chunks[i];
if (ch.origEnd < row) continue;
if (ch.origStart > row) break;
}
}
else {
for (var i = 0; i < chunks.length; i++) {
var ch = chunks[i];
if (ch.editEnd < row) continue;
if (ch.editStart > row) break;
}
}
return i - 1;
}
var Connector = function(diffView) {
this.diffView = diffView;
};
(function() {
this.addConnector = function(diffView, origStart, origEnd, editStart, editEnd, type) {
origStart = Math.ceil(origStart);
origEnd = Math.ceil(origEnd);
editStart = Math.ceil(editStart);
editEnd = Math.ceil(editEnd);
// p1 p2
//
// p3 p4
var p1_x = -1;
var p1_y = origStart + 0.5;
var p2_x = diffView.gutterWidth + 1;
var p2_y = editStart + 0.5;
var p3_x = -1;
var p3_y = (origEnd === origStart) ? origEnd + 0.5 : origEnd + 1.5;
var p4_x = diffView.gutterWidth + 1;
var p4_y = (editEnd === editStart) ? editEnd + 0.5 : editEnd + 1.5;
var curve1 = this.getCurve(p1_x, p1_y, p2_x, p2_y);
var curve2 = this.getCurve(p4_x, p4_y, p3_x, p3_y);
var verticalLine1 = "L" + p2_x + "," + p2_y + " " + p4_x + "," + p4_y;
var verticalLine2 = "L" + p3_x + "," + p3_y + " " + p1_x + "," + p1_y;
var d = curve1 + " " + verticalLine1 + " " + curve2 + " " + verticalLine2;
var el = document.createElementNS(SVG_NS, "path");
el.setAttribute("d", d);
el.setAttribute("class", "ace_diff-connector" + (
type == 1 ? " insert" :
type == -1 ? " delete" : ""
));
diffView.gutterSVG.appendChild(el);
};
// generates a Bezier curve in SVG format
this.getCurve = function(startX, startY, endX, endY) {
var w = endX - startX;
var halfWidth = startX + w * 0.5;
// position it at the initial x,y coords
var curve = "M " + startX + "," + startY +
// now create the curve. This is of the form "C M,N O,P Q,R" where C is a directive for SVG ("curveto"),
// M,N are the first curve control point, O,P the second control point and Q,R are the final coords
" C " + halfWidth + "," + startY + " " + halfWidth + "," + endY + " " + endX + "," + endY;
return curve;
};
this.createGutter = function() {
var diffView = this.diffView;
diffView.gutterWidth = diffView.gutterEl.clientWidth;
var leftHeight = diffView.left.renderer.$size.height;
var rightHeight = diffView.right.renderer.$size.height;
var height = Math.max(leftHeight, rightHeight);
diffView.gutterSVG = document.createElementNS(SVG_NS, "svg");
diffView.gutterSVG.setAttribute("width", diffView.gutterWidth);
diffView.gutterSVG.setAttribute("height", height);
diffView.gutterEl.appendChild(diffView.gutterSVG);
};
this.clearGutter = function(diffView) {
var gutterEl = diffView.gutterEl;
gutterEl.removeChild(diffView.gutterSVG);
this.createGutter();
};
this.decorate = function() {
var diffView = this.diffView;
if (diffView.$alignDiffs) return;
this.clearGutter(diffView);
var orig = diffView.left;
var edit = diffView.right;
var chunks = diffView.chunks;
var c1 = orig.renderer.layerConfig;
var c2 = edit.renderer.layerConfig;
// var existing =
for (var i = 0; i < chunks.length; i++) {
var ch = chunks[i];
if (ch.origEnd < c1.firstRow && ch.editEnd < c2.firstRow)
continue;
if (ch.origStart > c1.lastRow && ch.editStart > c2.lastRow)
break;
var origStart = orig.renderer.$cursorLayer.getPixelPosition({
row: ch.origStart, column: 0
}, true).top - c1.offset;
var origEnd = orig.renderer.$cursorLayer.getPixelPosition({
row: ch.origEnd, column: 0
}, true).top - c1.offset;
var editStart = edit.renderer.$cursorLayer.getPixelPosition({
row: ch.editStart, column: 0
}, true).top - c2.offset;
var editEnd = edit.renderer.$cursorLayer.getPixelPosition({
row: ch.editEnd, column: 0
}, true).top - c2.offset;
if (i == chunks.length - 1) {
if (ch.origEnd >= orig.session.getLength())
origEnd += c1.lineHeight;
if (ch.origStart >= orig.session.getLength())
origStart += c1.lineHeight;
if (ch.editEnd >= edit.session.getLength())
editEnd += c1.lineHeight;
if (ch.editStart >= edit.session.getLength())
editStart += c1.lineHeight;
}
this.addConnector(diffView, origStart, origEnd, editStart, editEnd, ch.type);
}
};
}).call(Connector.prototype);
var DiffHighlight = function(diffView, type) {
this.diffView = diffView;
this.type = type;
};
(function() {
this.MAX_RANGES = 500;
this.update = function(html, markerLayer, session, config) {
var start = config.firstRow;
var end = config.lastRow;
var diffView = this.diffView;
var chunks = diffView.chunks;
var isOrig = this.type == -1;
var type = this.type;
var index = findChunkIndex(chunks, start, isOrig);
if (index == -1 && chunks.length && (isOrig ? chunks[0].origStart : chunks[0].editStart) > start)
index = 0;
var chunk = chunks[index];
while (chunk) {
if (isOrig) {
if (chunk.origStart > end && chunk.origStart != chunk.origEnd)
return;
var range = new Range(chunk.origStart, 0, chunk.origEnd - 1, 1);
var l1 = chunk.origEnd - chunk.origStart;
var l2 = chunk.editEnd - chunk.editStart;
}
else {
if (chunk.editStart > end && chunk.editStart != chunk.editEnd)
return;
range = new Range(chunk.editStart, 0, chunk.editEnd - 1, 1);
l1 = chunk.origEnd - chunk.origStart;
l2 = chunk.editEnd - chunk.editStart;
}
var className = "";
if (!l1 && isOrig || !l2 && !isOrig) {
className = range.start.row == session.getLength() ? "insertEnd" : "insertStart";
}
className += chunk.type == -1 ? " delete" : chunk.type == 1 ? " insert" : "";
markerLayer.drawFullLineMarker(html, range.toScreenRange(session),
"ace_diff " + className, config);
var inlineChanges = chunk.inlineChanges;
var row = range.start.row;
var column = 0;
for (var j = 0; j < inlineChanges.length; j++) {
var diff = inlineChanges[j];
if (diff[0] == 0) {
if (diff[1]) {
row += diff[1];
column = diff[2];
}
else {
column += diff[2];
}
}
else {
range.start.row = row;
range.start.column = column;
if (row > end)
break;
if (diff[0] == (isOrig ? -1 : 1)) {
type = isOrig ? "delete" : "insert";
if (diff[1]) {
row += diff[1];
column = diff[2];
}
else {
column += diff[2];
}
}
else {
type = isOrig ? "insert" : "delete";
}
if (row < start)
continue;
range.end.row = row;
range.end.column = column;
if (range.isEmpty())
type += " empty";
var screenRange = range.clipRows(start, end).toScreenRange(session);
if (screenRange.isMultiLine()) {
markerLayer.drawTextMarker(html, screenRange, "ace_diff inline " + type, config);
}
else {
markerLayer.drawSingleLineMarker(html, screenRange, "ace_diff inline " + type, config);
}
}
}
chunk = chunks[++index];
}
};
}).call(DiffHighlight.prototype);
module.exports.DiffView = DiffView;
});