A bit of refactoring.

This commit is contained in:
Jamie Peabody
2013-06-30 20:11:57 +01:00
parent bdd060556b
commit 938acf5f36

View File

@@ -54,18 +54,18 @@ Mgly.LCS = function(x, y) {
jQuery.extend(Mgly.LCS.prototype, {
clear: function() { this.ready = 0; },
diff: function(added, removed) {
var d = new Mgly.diff(this.x, this.y, retain_lines = true, ignore_ws = false);
var d = new Mgly.diff(this.x, this.y, {ignorews: false});
var changes = Mgly.DiffParser(d.normal_form());
var li = 0, lj = 0;
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
if (change.op != 'a') {
// find the starting index of the line
li = d.lhs_lines.slice(0, change['lhs-line-from']).join(' ').length;
li = d.getLines('lhs').slice(0, change['lhs-line-from']).join(' ').length;
// get the index of the the span of the change
lj = change['lhs-line-to'] + 1;
// get the changed text
var lchange = d.lhs_lines.slice(change['lhs-line-from'], lj).join(' ');
var lchange = d.getLines('lhs').slice(change['lhs-line-from'], lj).join(' ');
if (change.op == 'd') lchange += ' ';// include the leading space
else if (li > 0 && change.op == 'c') li += 1; // ignore leading space if not first word
// output the changed index and text
@@ -73,11 +73,11 @@ jQuery.extend(Mgly.LCS.prototype, {
}
if (change.op != 'd') {
// find the starting index of the line
li = d.rhs_lines.slice(0, change['rhs-line-from']).join(' ').length;
li = d.getLines('lhs').slice(0, change['rhs-line-from']).join(' ').length;
// get the index of the the span of the change
lj = change['rhs-line-to'] + 1;
// get the changed text
var rchange = d.rhs_lines.slice(change['rhs-line-from'], lj).join(' ');
var rchange = d.getLines('lhs').slice(change['rhs-line-from'], lj).join(' ');
if (change.op == 'a') rchange += ' ';// include the leading space
else if (li > 0 && change.op == 'c') li += 1; // ignore leading space if not first word
// output the changed index and text
@@ -86,39 +86,83 @@ jQuery.extend(Mgly.LCS.prototype, {
}
}
});
Mgly.diff = function(lhs, rhs, retain_lines, ignore_ws) {
this.diff_codes = {};
this.max_code = 0;
var lhs_lines = lhs.split('\n');
var rhs_lines = rhs.split('\n');
if (lhs.length == 0) lhs_lines = [];
if (rhs.length == 0) rhs_lines = [];
var lhs_data = new Object();
lhs_data.data = this._diff_codes(lhs_lines, ignore_ws);
lhs_data.modified = {};
lhs_data.length = Mgly.sizeOf(lhs_data.data);
var rhs_data = new Object();
rhs_data.data = this._diff_codes(rhs_lines, ignore_ws);
rhs_data.modified = {};
rhs_data.length = Mgly.sizeOf(rhs_data.data);
var max = (lhs_data.length + rhs_data.length + 1);
Mgly.CodeifyText = function(settings) {
this._max_code = 0;
this._diff_codes = {};
this.ctxs = {};
this.options = {ignorews: false};
jQuery.extend(this, settings);
this.lhs = settings.lhs.split('\n');
this.rhs = settings.rhs.split('\n');
}
jQuery.extend(Mgly.CodeifyText.prototype, {
getCodes: function(side) {
if (!this.ctxs.hasOwnProperty(side)) {
var ctx = this._diff_ctx(this[side]);
this.ctxs[side] = ctx;
ctx.codes.length = Object.keys(ctx.codes).length;
}
return this.ctxs[side].codes;
},
getLines: function(side) {
return this.ctxs[side].lines;
},
_diff_ctx: function(lines) {
var ctx = {i: 0, codes: {}, lines: lines};
this._codeify(lines, ctx);
return ctx;
},
_codeify: function(lines, ctx) {
var code = this._max_code;
for (var i = 0; i < lines.length; ++i) {
var line = lines[i];
if (this.options.ignorews) {
line = line.replace(/\s+/g, '');
}
var aCode = this._diff_codes[line];
if (aCode != undefined) {
ctx.codes[i] = aCode;
}
else {
this._max_code++;
this._diff_codes[line] = this._max_code;
ctx.codes[i] = this._max_code;
}
}
}
});
Mgly.diff = function(lhs, rhs, options) {
var opts = jQuery.extend({ignorews: false}, options);
this.codeify = new Mgly.CodeifyText({
lhs: lhs,
rhs: rhs,
options: opts
});
var lhs_ctx = {
codes: this.codeify.getCodes('lhs'),
modified: {}
};
var rhs_ctx = {
codes: this.codeify.getCodes('rhs'),
modified: {}
};
var max = (lhs_ctx.codes.length + rhs_ctx.codes.length + 1);
var vector_d = Array( 2 * max + 2 );
var vector_u = Array( 2 * max + 2 );
this._lcs(lhs_data, 0, lhs_data.length, rhs_data, 0, rhs_data.length, vector_u, vector_d);
this._optimize(lhs_data);
this._optimize(rhs_data);
this.items = this._create_diffs(lhs_data, rhs_data);
if (retain_lines) {
this.lhs_lines = lhs_lines;
this.rhs_lines = rhs_lines;
}
this._lcs(lhs_ctx, 0, lhs_ctx.codes.length, rhs_ctx, 0, rhs_ctx.codes.length, vector_u, vector_d);
this._optimize(lhs_ctx);
this._optimize(rhs_ctx);
this.items = this._create_diffs(lhs_ctx, rhs_ctx);
};
jQuery.extend(Mgly.diff.prototype, {
changes: function() { return this.items; },
getLines: function(side) {
return this.codeify.getLines(side);
},
normal_form: function() {
var nf = '';
for (var index = 0; index < this.items.length; ++index) {
@@ -150,53 +194,33 @@ jQuery.extend(Mgly.diff.prototype, {
}
return nf;
},
_diff_codes: function(lines, ignore_ws) {
var code = this.max_code;
var codes = {};
for (var i = 0; i < lines.length; ++i) {
var line = lines[i];
if (ignore_ws) {
line = line.replace(/\s+/g, '');
}
var aCode = this.diff_codes[line];
if (aCode != undefined) {
codes[i] = aCode;
}
else {
this.max_code++;
this.diff_codes[line] = this.max_code;
codes[i] = this.max_code;
}
}
return codes;
},
_lcs: function(lhs, lhs_lower, lhs_upper, rhs, rhs_lower, rhs_upper, vector_u, vector_d) {
while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs.data[lhs_lower] == rhs.data[rhs_lower]) ) {
_lcs: function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) {
while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs_ctx.codes[lhs_lower] == rhs_ctx.codes[rhs_lower]) ) {
++lhs_lower;
++rhs_lower;
}
while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs.data[lhs_upper - 1] == rhs.data[rhs_upper - 1]) ) {
while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs_ctx.codes[lhs_upper - 1] == rhs_ctx.codes[rhs_upper - 1]) ) {
--lhs_upper;
--rhs_upper;
}
if (lhs_lower == lhs_upper) {
while (rhs_lower < rhs_upper) {
rhs.modified[ rhs_lower++ ] = true;
rhs_ctx.modified[ rhs_lower++ ] = true;
}
}
else if (rhs_lower == rhs_upper) {
while (lhs_lower < lhs_upper) {
lhs.modified[ lhs_lower++ ] = true;
lhs_ctx.modified[ lhs_lower++ ] = true;
}
}
else {
var sms = this._sms(lhs, lhs_lower, lhs_upper, rhs, rhs_lower, rhs_upper, vector_u, vector_d);
this._lcs(lhs, lhs_lower, sms.x, rhs, rhs_lower, sms.y, vector_u, vector_d);
this._lcs(lhs, sms.x, lhs_upper, rhs, sms.y, rhs_upper, vector_u, vector_d);
var sms = this._sms(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d);
this._lcs(lhs_ctx, lhs_lower, sms.x, rhs_ctx, rhs_lower, sms.y, vector_u, vector_d);
this._lcs(lhs_ctx, sms.x, lhs_upper, rhs_ctx, sms.y, rhs_upper, vector_u, vector_d);
}
},
_sms: function(lhs, lhs_lower, lhs_upper, rhs, rhs_lower, rhs_upper, vector_u, vector_d) {
var max = lhs.length + rhs.length + 1;
_sms: function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) {
var max = lhs_ctx.codes.length + rhs_ctx.codes.length + 1;
var kdown = lhs_lower - rhs_lower;
var kup = lhs_upper - rhs_upper;
var delta = (lhs_upper - lhs_lower) - (rhs_upper - rhs_lower);
@@ -221,7 +245,7 @@ jQuery.extend(Mgly.diff.prototype, {
}
y = x - k;
// find the end of the furthest reaching forward D-path in diagonal k.
while ((x < lhs_upper) && (y < rhs_upper) && (lhs.data[x] == rhs.data[y])) {
while ((x < lhs_upper) && (y < rhs_upper) && (lhs_ctx.codes[x] == rhs_ctx.codes[y])) {
x++; y++;
}
vector_d[ offset_down + k ] = x;
@@ -246,7 +270,7 @@ jQuery.extend(Mgly.diff.prototype, {
x = vector_u[offset_up + k - 1]; // up
}
y = x - k;
while ((x > lhs_lower) && (y > rhs_lower) && (lhs.data[x - 1] == rhs.data[y - 1])) {
while ((x > lhs_lower) && (y > rhs_lower) && (lhs_ctx.codes[x - 1] == rhs_ctx.codes[y - 1])) {
// diagonal
x--;
y--;
@@ -264,33 +288,33 @@ jQuery.extend(Mgly.diff.prototype, {
}
throw "the algorithm should never come here.";
},
_optimize: function(data) {
_optimize: function(ctx) {
var start = 0, end = 0;
while (start < data.length) {
while ((start < data.length) && (data.modified[start] == undefined || data.modified[start] == false)) {
while (start < ctx.length) {
while ((start < ctx.length) && (ctx.modified[start] == undefined || ctx.modified[start] == false)) {
start++;
}
end = start;
while ((end < data.length) && (data.modified[end] == true)) {
while ((end < ctx.length) && (ctx.modified[end] == true)) {
end++;
}
if ((end < data.length) && (data.data[start] == data.data[end])) {
data.modified[start] = false;
data.modified[end] = true;
if ((end < ctx.length) && (ctx.ctx[start] == ctx.codes[end])) {
ctx.modified[start] = false;
ctx.modified[end] = true;
}
else {
start = end;
}
}
},
_create_diffs: function(lhs_data, rhs_data) {
_create_diffs: function(lhs_ctx, rhs_ctx) {
var items = [];
var lhs_start = 0, rhs_start = 0;
var lhs_line = 0, rhs_line = 0;
while (lhs_line < lhs_data.length || rhs_line < rhs_data.length) {
if ((lhs_line < lhs_data.length) && (!lhs_data.modified[lhs_line])
&& (rhs_line < rhs_data.length) && (!rhs_data.modified[rhs_line])) {
while (lhs_line < lhs_ctx.codes.length || rhs_line < rhs_ctx.codes.length) {
if ((lhs_line < lhs_ctx.codes.length) && (!lhs_ctx.modified[lhs_line])
&& (rhs_line < rhs_ctx.codes.length) && (!rhs_ctx.modified[rhs_line])) {
// equal lines
lhs_line++;
rhs_line++;
@@ -300,20 +324,20 @@ jQuery.extend(Mgly.diff.prototype, {
lhs_start = lhs_line;
rhs_start = rhs_line;
while (lhs_line < lhs_data.length && (rhs_line >= rhs_data.length || lhs_data.modified[lhs_line]))
while (lhs_line < lhs_ctx.codes.length && (rhs_line >= rhs_ctx.codes.length || lhs_ctx.modified[lhs_line]))
lhs_line++;
while (rhs_line < rhs_data.length && (lhs_line >= lhs_data.length || rhs_data.modified[rhs_line]))
while (rhs_line < rhs_ctx.codes.length && (lhs_line >= lhs_ctx.codes.length || rhs_ctx.modified[rhs_line]))
rhs_line++;
if ((lhs_start < lhs_line) || (rhs_start < rhs_line)) {
// store a new difference-item
var aItem = new Object();
aItem.lhs_start = lhs_start;
aItem.rhs_start = rhs_start;
aItem.lhs_deleted_count = lhs_line - lhs_start;
aItem.rhs_inserted_count = rhs_line - rhs_start;
items.push(aItem);
items.push({
lhs_start: lhs_start,
rhs_start: rhs_start,
lhs_deleted_count: lhs_line - lhs_start,
rhs_inserted_count: rhs_line - rhs_start
});
}
}
}
@@ -322,12 +346,6 @@ jQuery.extend(Mgly.diff.prototype, {
});
Mgly.mergely = function(el, options) {
CodeMirror.defineExtension('centerOnCursor', function() {
var coords = this.cursorCoords(null, 'local');
this.scrollTo(null,
(coords.y + coords.yBot) / 2 - (this.getScrollerElement().clientHeight / 2));
});
if (el) {
this.init(el, options);
}
@@ -336,6 +354,25 @@ Mgly.mergely = function(el, options) {
jQuery.extend(Mgly.mergely.prototype, {
name: 'mergely',
//http://jupiterjs.com/news/writing-the-perfect-jquery-plugin
init: function(el, options) {
this.diffView = new Mgly.CodeMirrorDiffView(el, options);
this.bind(el);
},
bind: function(el) {
this.diffView.bind(el);
}
});
Mgly.CodeMirrorDiffView = function(el, options) {
CodeMirror.defineExtension('centerOnCursor', function() {
var coords = this.cursorCoords(null, 'local');
this.scrollTo(null,
(coords.y + coords.yBot) / 2 - (this.getScrollerElement().clientHeight / 2));
});
this.init(el, options);
};
jQuery.extend(Mgly.CodeMirrorDiffView.prototype, {
init: function(el, options) {
this.settings = {
autoupdate: true,
@@ -415,53 +452,8 @@ jQuery.extend(Mgly.mergely.prototype, {
// bind if the element is destroyed
this.element.bind('destroyed', jQuery.proxy(this.teardown, this));
// save this instance in jQuery data
jQuery.data(el, this.name, this);
this._setup(el);
},
// bind events to this instance's methods
bind: function() {
var rhstx = jQuery('#' + this.id + '-rhs').get(0);
if (!rhstx) {
console.error('rhs textarea not defined - Mergely not initialized properly');
return;
}
var lhstx = jQuery('#' + this.id + '-lhs').get(0);
if (!rhstx) {
console.error('lhs textarea not defined - Mergely not initialized properly');
return;
}
var self = this;
this.editor = [];
this.editor[this.id + '-lhs'] = CodeMirror.fromTextArea(lhstx, this.lhs_cmsettings);
this.editor[this.id + '-rhs'] = CodeMirror.fromTextArea(rhstx, this.rhs_cmsettings);
this.editor[this.id + '-lhs'].on('change', function(){ if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); });
this.editor[this.id + '-lhs'].on('scroll', function(){ self._scrolling(self.id + '-lhs'); });
this.editor[this.id + '-rhs'].on('change', function(){ if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); });
this.editor[this.id + '-rhs'].on('scroll', function(){ self._scrolling(self.id + '-rhs'); });
// resize
if (this.settings.autoresize) {
var sz_timeout1 = null;
var sz = function(init) {
//self.em_height = null; //recalculate
if (self.settings.resize) self.settings.resize(init);
self.editor[self.id + '-lhs'].refresh();
self.editor[self.id + '-rhs'].refresh();
if (self.settings.autoupdate) {
self._changing(self.id + '-lhs', self.id + '-rhs');
}
}
jQuery(window).resize(
function () {
if (sz_timeout1) clearTimeout(sz_timeout1);
sz_timeout1 = setTimeout(sz, self.settings.resize_timeout);
}
);
sz(true);
}
// save this instance in jQuery data, binding this view to the node
jQuery.data(el, 'mergely', this);
},
unbind: function() {
if (this.changed_timeout != null) clearTimeout(this.changed_timeout);
@@ -585,10 +577,10 @@ jQuery.extend(Mgly.mergely.prototype, {
diff: function() {
var lhs = this.editor[this.id + '-lhs'].getValue();
var rhs = this.editor[this.id + '-rhs'].getValue();
var d = new Mgly.diff(lhs, rhs, retain_lines = true, ignore_ws = this.settings.ignorews);
var d = new Mgly.diff(lhs, rhs, this.settings);
return d.normal_form();
},
_setup: function(el) {
bind: function(el) {
jQuery(this.element).hide();//hide
this.id = jQuery(el).attr('id');
var height = this.settings.editor_height;
@@ -640,7 +632,48 @@ jQuery.extend(Mgly.mergely.prototype, {
cmstyle += this.id + ' .CodeMirror-scroll { height: 100%; overflow: auto; }';
}
jQuery('<style type="text/css">' + cmstyle + '</style>').appendTo('head');
this.bind();
//bind
var rhstx = jQuery('#' + this.id + '-rhs').get(0);
if (!rhstx) {
console.error('rhs textarea not defined - Mergely not initialized properly');
return;
}
var lhstx = jQuery('#' + this.id + '-lhs').get(0);
if (!rhstx) {
console.error('lhs textarea not defined - Mergely not initialized properly');
return;
}
var self = this;
this.editor = [];
this.editor[this.id + '-lhs'] = CodeMirror.fromTextArea(lhstx, this.lhs_cmsettings);
this.editor[this.id + '-rhs'] = CodeMirror.fromTextArea(rhstx, this.rhs_cmsettings);
this.editor[this.id + '-lhs'].on('change', function(){ if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); });
this.editor[this.id + '-lhs'].on('scroll', function(){ self._scrolling(self.id + '-lhs'); });
this.editor[this.id + '-rhs'].on('change', function(){ if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); });
this.editor[this.id + '-rhs'].on('scroll', function(){ self._scrolling(self.id + '-rhs'); });
// resize
if (this.settings.autoresize) {
var sz_timeout1 = null;
var sz = function(init) {
//self.em_height = null; //recalculate
if (self.settings.resize) self.settings.resize(init);
self.editor[self.id + '-lhs'].refresh();
self.editor[self.id + '-rhs'].refresh();
if (self.settings.autoupdate) {
self._changing(self.id + '-lhs', self.id + '-rhs');
}
}
jQuery(window).resize(
function () {
if (sz_timeout1) clearTimeout(sz_timeout1);
sz_timeout1 = setTimeout(sz, self.settings.resize_timeout);
}
);
sz(true);
}
//bind
if (this.settings.lhs) {
var setv = this.editor[this.id + '-lhs'].getDoc().setValue;
this.settings.lhs(setv.bind(this.editor[this.id + '-lhs'].getDoc()));
@@ -803,7 +836,7 @@ jQuery.extend(Mgly.mergely.prototype, {
var lhs = this.editor[editor_name1].getValue();
var rhs = this.editor[editor_name2].getValue();
var timer = new Mgly.Timer();
var d = new Mgly.diff(lhs, rhs, false, this.settings.ignorews);
var d = new Mgly.diff(lhs, rhs, this.settings);
this.trace('change', 'diff time', timer.stop());
this.changes = Mgly.DiffParser(d.normal_form());
this.trace('change', 'parse time', timer.stop());