Changeset - aa3b55946089
[Not reviewed]
"Bradley M. Kuhn" - 12 years ago 2014-05-27 02:21:00
bkuhn@ebb.org
Migrate to Mergely 3.3.4.

RhodeCode 2.2.5 distributed Mergely 3.3.4 with some of the changes that
Mergely 3.3.3 in RhodeCode 1.7.2 also had. That do however not seem to be
changes we want for Kallithea this way and we take the 3.3.4 files as they are.

I've also included the Mergely license file, as downloaded from:
http://www.mergely.com/license.php

That LICENSE file is kept in HTML just as it was downloaded from their
website. While it's a bit annoying to keep the license file in HTML, this is
the way it came from upstream so we'll leave it that way.

Since the Javascript code is used with other GPLv3 Javascript, we are using the
GPL option of Mergely's tri-license.

Finally, note that previously, this was incorrectly called "mergerly", so the
opportunity is taken here to correct the name. That required changes to
diff_2way.html.

As commands::

$ wget -N --output-document LICENSE-MERGELY.html http://www.mergely.com/license.php
$ hg add LICENSE-MERGELY.html
$ hg mv rhodecode/public/css/mergerly.css rhodecode/public/css/mergely.css
$ hg mv rhodecode/public/js/mergerly.js rhodecode/public/js/mergely.js
$ sed -i 's,mergerly\.,mergely,g' rhodecode/templates/files/diff_2way.html
$ ( cd /tmp; \
wget -N http://www.mergely.com/releases/mergely-3.3.4.zip; \
unzip mergely-3.3.4.zip )
$ sha256sum /tmp/mergely-3.3.4.zip
87415d30494bbe829c248881aa7cdc0303f7e70b458a5f687615564d4498cc82 mergely-3.3.4.zip
$ cp /tmp/mergely-3.3.4/lib/mergely.js rhodecode/public/js/mergely.js
$ cp /tmp/mergely-3.3.4/lib/mergely.css rhodecode/public/css/mergely.css
$ sed -i -e '/^ \* Version/a\ *\n * NOTE by bkuhn@sfconservancy.org for Kallithea:\n * Mergely license appears at http://www.mergely.com/license.php and in LICENSE-MERGELY.html' rhodecode/public/js/mergely.js rhodecode/public/css/mergely.css
5 files changed with 416 insertions and 235 deletions:
0 comments (0 inline, 0 general)
LICENSE-MERGELY.html
Show inline comments
 
new file 100644
 
<!DOCTYPE html>
 
<html lang="en">
 
<!--[if IE]>
 
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
 
<![endif]-->
 
<head>
 
	<meta charset="utf-8" /><title>Mergely License</title>
 
	<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
 
	<meta name="description" content="Mergely license requirements for open source software and commercial software" />
 
	<meta name="keywords" content="diff,merge,compare,compare documents,js diff,javascript diff,comparison,online diff,difference,file,text,unix,patch,algorithm,saas,longest common subsequence,diff online" />
 
	<meta name="author" content="Jamie Peabody" />
 
	<meta name="author" content="Jamie Peabody" />
 
	<link rel="shortcut icon" href="http://www.mergely.com/favicon.ico" />
 
    <link href='http://fonts.googleapis.com/css?family=Noto+Sans:400,700' rel='stylesheet' type='text/css' />
 
    <link href='fonts/berlin-sans-fb-demi.css' rel='stylesheet' type='text/css' />
 
    <link href='style/mergely.css' rel='stylesheet' type='text/css' />
 
    <link href='/Mergely/lib/mergely.css' rel='stylesheet' type='text/css' />
 
	<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
 
	<script type="text/javascript">
 
		var _gaq = _gaq || [];
 
		_gaq.push(['_setAccount', 'UA-85576-5']);
 
		_gaq.push(['_trackPageview']);
 
		(function() {
 
			var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
 
			ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
 
			var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
 
		})();
 
	</script>
 
</head>
 
<body>
 
    <div id="page">
 
        <div id="content">
 
            <div id="header">
 
                <h1><span>Mergely License - Closed Distribution License</span></h1>
 
                <div id="options">
 
                    <a href="/editor" class="button">Online Diff</a>
 
                    <a href="/download" class="button">Download</a>
 
                </div>
 
                <nav>
 
                    <ul>
 
                        <li><a href="/">Home</a></li>                        <li><a href="/doc">Documentation</a></li>                        <li><a href="/about">About Mergely</a></li>                        <li><a href="/license">License</a></li>                        <li><a href="#footer">Contact</a></li>                    </ul>
 
                </nav>
 
            </div>
 

	
 
            <div id="main">
 
				<h1>Mergely License</h1>
 
				<p>
 
					All Mergely code is Copyright 2014 by Jamie Peabody.
 
					Mergely is distributed under the 
 
					<a href="http://www.gnu.org/licenses/gpl.html">GPL</a>, 
 
					<a href="http://www.gnu.org/licenses/lgpl.html">LGPL</a>
 
					and 
 
					<a href="http://www.mozilla.org/MPL/MPL-1.1.html">MPL</a> open source licenses. 
 
					This triple <b>copyleft</b> licensing model avoids incompatibility with other open 
 
					source licenses. These open source licenses are specially indicated for:
 
					<ul>
 
						<li>Integrating Mergely into Open Source software;</li>
 
						<li>Personal and educational use of Mergely;</li>
 
						<li>
 
							Integrating Mergely in commercial software, taking care of satisfying 
 
							the Open Source licenses terms, while not able or interested on supporting 
 
							Mergely and its development.
 
						</li>
 
					</ul>
 
				</p>
 
				<h2>Mergely Commercial License - Closed Distribution License - CDL</h2>
 
				<p>
 
					You may contact <a href="mailto:jamie.peabody@gmail.com">Jamie Peabody</a> to enquire about
 
					obtaining a CDL license.
 
				</p>
 
				<p>
 
					This license offers a very flexible way to integrate Mergely in your commercial 
 
					application. These are the main advantages it offers over an Open Source license:
 
				</p>
 
				<p>
 
					Modifications and enhancements do not need to be released under an Open 
 
					Source license; There is no need to distribute any Open Source license terms 
 
					along with your product and no reference to it have to be done; You do not have
 
					to mention any reference to Mergely in your product; Mergely source code does not
 
					have to be distributed with your product; You can remove any file from Mergely 
 
					when integrating it with your product.
 
				</p>
 
				<p>
 
					The CDL is a lifetime license valid for all previous releases of Mergely published 
 
                    prior to the year of purchase, and any releases in the following year. Please select 
 
                    the license option that best fit your needs above. It includes 1 year of 
 
                    <b>personal e-mail support</b>.
 
				</p>
 
				<h2>Third party codes</h2>
 
				<p>
 
					Mergely utilizes <b>CodeMirror</b>, a third-party library released under an 
 
					<a href="http://en.wikipedia.org/wiki/MIT_License">MIT</a>
 
					license.  Also used is <b>jQuery</b> and is released under the 
 
					<a href="http://en.wikipedia.org/wiki/MIT_License">MIT</a> or 
 
					<a href="http://www.gnu.org/licenses/gpl.html">GPL</a> Version 2 license.
 
				</p>
 
            </div>
 
            
 
            <div id="footer">
 
                <a href="/download" class="download">Download</a>
 
                <ul>
 
                    <li id="google-plus"><a target="_blank" href="http://groups.google.com/group/mergely">http://groups.google.com/group/mergely</a></li>
 
                    <li id="github"><a target="_blank" href="https://github.com/wickedest/Mergely">https://github.com/wickedest/Mergely</a></li>
 
                    <li id="email"><a target="_blank" href="mailto:jamie.peabody@gmail.com">jamie.peabody@gmail.com</a></li>
 
                </ul>
 
            </div>
 
        </div>
 
        <div id="copyright">By <b>Jamie Peabody</b></div>
 
    </div>
 
</body>
 
</html>
LICENSE.md
Show inline comments
 
@@ -46,13 +46,25 @@ jQuery
 

	
 
Kallithea incorporates the Javascript system called
 
[jQuery](http://jquery.org/),
 
[herein](rhodecode/public/js/jquery-1.10.2.min.js), and the Corresponding
 
Source can be found in https://github.com/jquery/jquery at tag 1.10.2
 
(mirrored at https://kallithea-scm.org/repos/mirror/jquery/files/1.10.2/ ).
 

	
 
It is Copyright 2013 jQuery Foundation and other contributors http://jquery.com/ and is under an
 
[MIT-permissive license](MIT-Permissive-License.txt).
 

	
 

	
 

	
 
Mergely
 
-------
 

	
 
Kallithea incorporates some code from the Javascript system called
 
[Mergely](http://http://www.mergely.com/).
 
[Mergely's license](http://www.mergely.com/license.php), a
 
[copy of which is included in this repository](LICENSE-MERGELY.html),
 
is (GPL|LGPL|MPL).  Kallithea as GPLv3'd project chooses the GPL arm of that
 
tri-license.
 

	
 

	
 

	
 
EOF
rhodecode/public/css/mergely.css
Show inline comments
 
file renamed from rhodecode/public/css/mergerly.css to rhodecode/public/css/mergely.css
 
/**
 
 * Copyright (c) 2013 by Jamie Peabody, http://www.mergely.com
 
 * All rights reserved.
 
 * Version: 3.3.4 2013-11-02
 
 *
 
 * NOTE by bkuhn@sfconservancy.org for Kallithea:
 
 * Mergely license appears at http://www.mergely.com/license.php and in LICENSE-MERGELY.html
 
 */
 

	
 
/* required */
 
.mergely-column textarea { width: 80px; height: 200px; }
 
.mergely-column { float: left; }
 
.mergely-margin { float: left; }
 
.mergely-canvas { float: left; width: 28px; }
 

	
 
/* resizeable */
 
.mergely-resizer { width: 100%; height: 100%; }
 

	
 
/* style configuration */
 
.mergely-column { border: 1px solid #ccc; }
 
.mergely-active { border: 1px solid #a3d1ff; }
 

	
 
.mergely.a.rhs.start { border-top: 1px solid #ddffdd; }
 
.mergely.a.rhs.start { border-top: 1px solid #a3d1ff; }
 
.mergely.a.lhs.start.end,
 
.mergely.a.rhs.end { border-bottom: 1px solid #ddffdd; }
 
.mergely.a.rhs { background-color: #ddffdd; }
 
.mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #ddffdd; }
 
.mergely.a.rhs.end { border-bottom: 1px solid #a3d1ff; }
 
.mergely.a.rhs { background-color: #ddeeff; }
 
.mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #a3d1ff; }
 

	
 
.mergely.d.lhs { background-color: #edc0c0; }
 
.mergely.d.lhs.end,
 
.mergely.d.rhs.start.end { border-bottom: 1px solid #ffdddd; }
 
.mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #ffdddd; }
 
.mergely.d.lhs.start { border-top: 1px solid #ffdddd; }
 
.mergely.d.rhs.start.end { border-bottom: 1px solid #ff7f7f; }
 
.mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #ff7f7f; }
 
.mergely.d.lhs.start { border-top: 1px solid #ff7f7f; }
 

	
 
.mergely.c.lhs,
 
.mergely.c.rhs { background-color: #fafafa; }
 
.mergely.c.lhs.start,
 
.mergely.c.rhs.start { border-top: 1px solid #a3a3a3; }
 
.mergely.c.lhs.end,
 
.mergely.c.rhs.end { border-bottom: 1px solid #a3a3a3; }
 

	
 
.mergely.ch.a.rhs { background-color: #ddffdd; }
 
.mergely.ch.d.lhs { background-color: #ffdddd; }
 

	
 

	
 
.mergely-margin #compare-lhs-margin,
 
.mergely-margin #compare-rhs-margin {
 
    cursor: pointer
 
}
 
.mergely.ch.a.rhs { background-color: #ddeeff; }
 
.mergely.ch.d.lhs { background-color: #edc0c0; text-decoration: line-through; color: #888; }
rhodecode/public/js/mergely.js
Show inline comments
 
file renamed from rhodecode/public/js/mergerly.js to rhodecode/public/js/mergely.js
 
/**
 
 * Copyright (c) 2013 by Jamie Peabody, http://www.mergely.com
 
 * All rights reserved.
 
 * Version: 3.3.4 2013-11-02
 
 *
 
 * NOTE by bkuhn@sfconservancy.org for Kallithea:
 
 * Mergely license appears at http://www.mergely.com/license.php and in LICENSE-MERGELY.html
 
 */
 
Mgly = {};
 

	
 
Mgly.Timer = function(){
 
	var self = this;
 
	self.start = function() { self.t0 = new Date().getTime(); }
 
	self.stop = function() {
 
		var t1 = new Date().getTime();
 
		var d = t1 - self.t0;
 
		var d = t1 - self.t0; 
 
		self.t0 = t1;
 
		return d;
 
	}
 
	self.start();
 
}
 

	
 
Mgly.ChangeExpression = new RegExp(/(\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/);
 

	
 
Mgly.DiffParser = function(diff) {
 
	var changes = [];
 
	var change_id = 0;
 
	// parse diff
 
@@ -45,330 +53,367 @@ Mgly.sizeOf = function(obj) {
 
		if (obj.hasOwnProperty(key)) size++;
 
	}
 
	return size;
 
}
 

	
 
Mgly.LCS = function(x, y) {
 
	this.x = x.replace(/[ ]{1}/g, '\n');
 
	this.y = y.replace(/[ ]{1}/g, '\n');
 
}
 
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
 
				removed(li, li + lchange.length);
 
			}
 
			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
 
				added(li, li + rchange.length);
 
			}
 
		}
 
	}
 
});
 
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 = [];
 

	
 
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');
 
}
 

	
 
	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);
 
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;
 
			}
 
		}
 
	}
 
});
 

	
 
	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.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_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);
 
};
 

	
 
	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;
 
	}
 
};
 
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) {
 
			var item = this.items[index];
 
			var lhs_str = '';
 
			var rhs_str = '';
 
			var change = 'c';
 
			if (item.lhs_deleted_count == 0 && item.rhs_inserted_count > 0) change = 'a';
 
			else if (item.lhs_deleted_count > 0 && item.rhs_inserted_count == 0) change = 'd';
 

	
 
			
 
			if (item.lhs_deleted_count == 1) lhs_str = item.lhs_start + 1;
 
			else if (item.lhs_deleted_count == 0) lhs_str = item.lhs_start;
 
			else lhs_str = (item.lhs_start + 1) + ',' + (item.lhs_start + item.lhs_deleted_count);
 

	
 
			
 
			if (item.rhs_inserted_count == 1) rhs_str = item.rhs_start + 1;
 
			else if (item.rhs_inserted_count == 0) rhs_str = item.rhs_start;
 
			else rhs_str = (item.rhs_start + 1) + ',' + (item.rhs_start + item.rhs_inserted_count);
 
			nf += lhs_str + change + rhs_str + '\n';
 
			if (this.rhs_lines && this.lhs_lines) {
 
				// if rhs/lhs lines have been retained, output contextual diff
 
				for (var i = item.lhs_start; i < item.lhs_start + item.lhs_deleted_count; ++i) {
 
					nf += '< ' + this.lhs_lines[i] + '\n';
 
				}
 
				if (item.rhs_inserted_count && item.lhs_deleted_count) nf += '---\n';
 
				for (var i = item.rhs_start; i < item.rhs_start + item.rhs_inserted_count; ++i) {
 
					nf += '> ' + this.rhs_lines[i] + '\n';
 
				}
 
			}
 
		}
 
		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);
 
		var odd = (delta & 1) != 0;
 
		var offset_down = max - kdown;
 
		var offset_up = max - kup;
 
		var maxd = ((lhs_upper - lhs_lower + rhs_upper - rhs_lower) / 2) + 1;
 
		vector_d[ offset_down + kdown + 1 ] = lhs_lower;
 
		vector_u[ offset_up + kup - 1 ] = lhs_upper;
 
		var ret = {x:0,y:0};
 
		for (var d = 0; d <= maxd; ++d) {
 
			for (var k = kdown - d; k <= kdown + d; k += 2) {
 
				var x, y;
 
				if (k == kdown - d) {
 
					x = vector_d[ offset_down + k + 1 ];//down
 
				}
 
				else {
 
					x = vector_d[ offset_down + k - 1 ] + 1;//right
 
					if ((k < (kdown + d)) && (vector_d[ offset_down + k + 1 ] >= x)) {
 
						x = vector_d[ offset_down + k + 1 ];//down
 
					}
 
				}
 
				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;
 
				// overlap ?
 
				if (odd && (kup - d < k) && (k < kup + d)) {
 
					if (vector_u[offset_up + k] <= vector_d[offset_down + k]) {
 
						ret.x = vector_d[offset_down + k];
 
						ret.y = vector_d[offset_down + k] - k;
 
						return (ret);
 
					}
 
				}
 
			}
 
			// Extend the reverse path.
 
			for (var k = kup - d; k <= kup + d; k += 2) {
 
				// find the only or better starting point
 
				var x, y;
 
				if (k == kup + d) {
 
					x = vector_u[offset_up + k - 1]; // up
 
				} else {
 
					x = vector_u[offset_up + k + 1] - 1; // left
 
					if ((k > kup - d) && (vector_u[offset_up + k - 1] < x))
 
						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--;
 
				}
 
				vector_u[offset_up + k] = x;
 
				// overlap ?
 
				if (!odd && (kdown - d <= k) && (k <= kdown + d)) {
 
					if (vector_u[offset_up + k] <= vector_d[offset_down + k]) {
 
						ret.x = vector_d[offset_down + k];
 
						ret.y = vector_d[offset_down + k] - k;
 
						return (ret);
 
					}
 
				}
 
			}
 
		}
 
		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++;
 
			}
 
			else {
 
				// maybe deleted and/or inserted lines
 
				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
 
					});
 
				}
 
			}
 
		}
 
		return items;
 
	}
 
});
 

	
 
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);
 
	}
 
};
 

	
 
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,
 
			autoresize: true,
 
			rhs_margin: 'right',
 
			lcs: true,
 
			sidebar: true,
 
			viewport: false,
 
			ignorews: false,
 
			fadein: 'fast',
 
			editor_width: '400px',
 
			editor_height: '400px',
 
			resize_timeout: 500,
 
			change_timeout: 150,
 
			fgcolor: {a:'#4ba3fa',c:'#a3a3a3',d:'#ff7f7f'},
 
			bgcolor: '#eee',
 
			vpcolor: 'rgba(0, 0, 200, 0.2)',
 
			vpcolor: 'rgba(0, 0, 200, 0.5)',
 
			lhs: function(setValue) { },
 
			rhs: function(setValue) { },
 
			loaded: function() { },
 
			//_auto_height: function(h) { return h - 20; },
 
			_auto_width: function(w) { return w; },
 
			resize: function(init) {
 
				var scrollbar = init ? -15 : 0;
 
				var scrollbar = init ? 16 : 0;
 
				var w = jQuery(el).parent().width() + scrollbar;
 
				if (this.width == 'auto') {
 
					w = this._auto_width(w);
 
				}
 
				else {
 
					w = this.width;
 
					this.editor_width = w;
 
				}
 
				if (this.height == 'auto') {
 
					//h = this._auto_height(h);
 
					h = jQuery(el).parent().height();
 
				}
 
@@ -394,83 +439,38 @@ jQuery.extend(Mgly.mergely.prototype, {
 
			_debug: '', //scroll,draw,calc,diff,markup,change
 
			resized: function() { }
 
		};
 
		var cmsettings = {
 
			mode: 'text/plain',
 
			readOnly: false,
 
			lineWrapping: false,
 
			lineNumbers: true,
 
			gutters: ['merge', 'CodeMirror-linenumbers']
 
		}
 
		this.lhs_cmsettings = {};
 
		this.rhs_cmsettings = {};
 

	
 
		
 
		// save this element for faster queries
 
		this.element = jQuery(el);
 

	
 
		
 
		// save options if there are any
 
		if (options && options.cmsettings) jQuery.extend(this.lhs_cmsettings, cmsettings, options.cmsettings, options.lhs_cmsettings);
 
		if (options && options.cmsettings) jQuery.extend(this.rhs_cmsettings, cmsettings, options.cmsettings, options.rhs_cmsettings);
 
		if (options) jQuery.extend(this.settings, options);
 

	
 
		
 
		// 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);
 
		this.editor[this.id + '-lhs'].toTextArea();
 
		this.editor[this.id + '-rhs'].toTextArea();
 
	},
 
	destroy: function() {
 
		this.element.unbind('destroyed', this.teardown);
 
		this.teardown();
 
	},
 
	teardown: function() {
 
		this.unbind();
 
@@ -493,40 +493,40 @@ jQuery.extend(Mgly.mergely.prototype, {
 
		if (side == 'lhs') {
 
			le.setCursor(num);
 
			le.centerOnCursor();
 
		}
 
		else {
 
			re.setCursor(num);
 
			re.centerOnCursor();
 
		}
 
	},
 
	options: function(opts) {
 
		if (opts) {
 
			jQuery.extend(this.settings, opts);
 
			if (opts.autoresize) this.resize();
 
			if (opts.autoupdate) this.update();
 
			if (opts.hasOwnProperty('rhs_margin')) {
 
			if (this.settings.autoresize) this.resize();
 
			if (this.settings.autoupdate) this.update();
 
			if (this.settings.hasOwnProperty('rhs_margin')) {
 
				// dynamically swap the margin
 
				if (opts.rhs_margin == 'left') {
 
				if (this.settings.rhs_margin == 'left') {
 
					this.element.find('.mergely-margin:last-child').insertAfter(
 
						this.element.find('.mergely-canvas'));
 
				}
 
				else {
 
					var target = this.element.find('.mergely-margin').last();
 
					target.appendTo(target.parent());
 
				}
 
			}
 
			if (opts.hasOwnProperty('sidebar')) {
 
			if (this.settings.hasOwnProperty('sidebar')) {
 
				// dynamically enable sidebars
 
				if (opts.sidebar) {
 
				if (this.settings.sidebar) {
 
					jQuery(this.element).find('.mergely-margin').css({display: 'block'});
 
				}
 
				else {
 
					jQuery(this.element).find('.mergely-margin').css({display: 'none'});
 
				}
 
			}
 
		}
 
		else {
 
			return this.settings;
 
		}
 
	},
 
	swap: function() {
 
@@ -561,43 +561,44 @@ jQuery.extend(Mgly.mergely.prototype, {
 
	search: function(side, query, direction) {
 
		var le = this.editor[this.id + '-lhs'];
 
		var re = this.editor[this.id + '-rhs'];
 
		var editor;
 
		if (side == 'lhs') editor = le;
 
		else editor = re;
 
		direction = (direction == 'prev') ? 'findPrevious' : 'findNext';
 
		if ((editor.getSelection().length == 0) || (this.prev_query[side] != query)) {
 
			this.cursor[this.id] = editor.getSearchCursor(query, { line: 0, ch: 0 }, false);
 
			this.prev_query[side] = query;
 
		}
 
		var cursor = this.cursor[this.id];
 

	
 
		
 
		if (cursor[direction]()) {
 
			editor.setSelection(cursor.from(), cursor.to());
 
		}
 
		else {
 
			cursor = editor.getSearchCursor(query, { line: 0, ch: 0 }, false);
 
		}
 
	},
 
	resize: function() {
 
		this.settings.resize();
 
		this._changing(this.id + '-lhs', this.id + '-rhs');
 
		this._set_top_offset(this.id + '-lhs');
 
	},
 
	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;
 
		var width = this.settings.editor_width;
 
		this.changed_timeout = null;
 
		this.chfns = {};
 
		this.chfns[this.id + '-lhs'] = [];
 
		this.chfns[this.id + '-rhs'] = [];
 
		this.prev_query = [];
 
		this.cursor = [];
 
		this._skipscroll = {};
 
		this.change_exp = new RegExp(/(\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/);
 
@@ -607,133 +608,174 @@ jQuery.extend(Mgly.mergely.prototype, {
 
			//jquery ui
 
			merge_lhs_button = '<button title="Merge left"></button>';
 
			merge_rhs_button = '<button title="Merge right"></button>';
 
		}
 
		else {
 
			// homebrew
 
			var style = 'opacity:0.4;width:10px;height:15px;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;';
 
			merge_lhs_button = '<div style="' + style + '" title="Merge left">&lt;</div>';
 
			merge_rhs_button = '<div style="' + style + '" title="Merge right">&gt;</div>';
 
		}
 
		this.merge_rhs_button = jQuery(merge_rhs_button);
 
		this.merge_lhs_button = jQuery(merge_lhs_button);
 

	
 
		
 
		// create the textarea and canvas elements
 
		jQuery(this.element).append(jQuery('<div class="mergely-margin" style="height: ' + height + '"><canvas id="' + this.id + '-lhs-margin" width="8px" height="' + height + '"></canvas></div>'));
 
		jQuery(this.element).append(jQuery('<div style="position:relative;width:' + width + '; height:' + height + '" id="' + this.id + '-editor-lhs" class="mergely-column"><textarea style="" id="' + this.id + '-lhs"></textarea></div>'));
 
		jQuery(this.element).append(jQuery('<div class="mergely-canvas" style="height: ' + height + '"><canvas id="' + this.id + '-lhs-' + this.id + '-rhs-canvas" style="width:28px" width="28px" height="' + height + '"></canvas></div>'));
 
		var rmargin = jQuery('<div class="mergely-margin" style="height: ' + height + '"><canvas id="' + this.id + '-rhs-margin" width="8px" height="' + height + '"></canvas></div>');
 
		if (!this.settings.sidebar) {
 
			jQuery(this.element).find('.mergely-margin').css({display: 'none'});
 
		}
 
		if (this.settings.rhs_margin == 'left') {
 
			jQuery(this.element).append(rmargin);
 
		}
 
		jQuery(this.element).append(jQuery('<div style="width:' + width + '; height:' + height + '" id="' + this.id + '-editor-rhs" class="mergely-column"><textarea style="" id="' + this.id + '-rhs"></textarea></div>'));
 
		if (this.settings.rhs_margin != 'left') {
 
			jQuery(this.element).append(rmargin);
 
		}
 
		//codemirror
 
		var cmstyle = '#' + this.id + ' .CodeMirror-gutter-text { padding: 5px 0 0 0; }' +
 
			'#' + this.id + ' .CodeMirror-lines pre, ' + '#' + this.id + ' .CodeMirror-gutter-text pre { line-height: 18px; }' +
 
			'.CodeMirror-linewidget { overflow: hidden; };';
 
		if (this.settings.autoresize) {
 
			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()));
 
		}
 
		if (this.settings.rhs) {
 
			var setv = this.editor[this.id + '-rhs'].getDoc().setValue;
 
			this.settings.rhs(setv.bind(this.editor[this.id + '-rhs'].getDoc()));
 
		}
 
	},
 

	
 
	
 
	_scrolling: function(editor_name) {
 
		if (this._skipscroll[editor_name] === true) {
 
			// scrolling one side causes the other to event - ignore it
 
			this._skipscroll[editor_name] = false;
 
			return;
 
		}
 
		var scroller = jQuery(this.editor[editor_name].getScrollerElement());
 
		if (this.midway == undefined) {
 
			this.midway = (scroller.height() / 2.0 + scroller.offset().top).toFixed(2);
 
		}
 
		// balance-line
 
		var midline = this.editor[editor_name].coordsChar({left:0, top:this.midway});
 
		var top_to = scroller.scrollTop();
 
		var left_to = scroller.scrollLeft();
 

	
 
		
 
		this.trace('scroll', 'side', editor_name);
 
		this.trace('scroll', 'midway', this.midway);
 
		this.trace('scroll', 'midline', midline);
 
		this.trace('scroll', 'top_to', top_to);
 
		this.trace('scroll', 'left_to', left_to);
 

	
 
		
 
		var editor_name1 = this.id + '-lhs';
 
		var editor_name2 = this.id + '-rhs';
 

	
 
		
 
		for (var name in this.editor) {
 
			if (!this.editor.hasOwnProperty(name)) continue;
 
			if (editor_name == name) continue; //same editor
 
			var this_side = editor_name.replace(this.id + '-', '');
 
			var other_side = name.replace(this.id + '-', '');
 
			var top_adjust = 0;
 

	
 
			
 
			// find the last change that is less than or within the midway point
 
			// do not move the rhs until the lhs end point is >= the rhs end point.
 
			var last_change = null;
 
			var force_scroll = false;
 
			for (var i = 0; i < this.changes.length; ++i) {
 
				var change = this.changes[i];
 
				if ((midline.line >= change[this_side+'-line-from'])) {
 
					last_change = change;
 
					if (midline.line >= last_change[this_side+'-line-to']) {
 
						if (!change.hasOwnProperty(this_side+'-y-start') ||
 
							!change.hasOwnProperty(this_side+'-y-end') ||
 
							!change.hasOwnProperty(other_side+'-y-start') ||
 
							!change.hasOwnProperty(other_side+'-y-end')){
 
							// change outside of viewport
 
							force_scroll = true;
 
						}
 
						else {
 
							top_adjust +=
 
								(change[this_side+'-y-end'] - change[this_side+'-y-start']) -
 
							top_adjust += 
 
								(change[this_side+'-y-end'] - change[this_side+'-y-start']) - 
 
								(change[other_side+'-y-end'] - change[other_side+'-y-start']);
 
						}
 
					}
 
				}
 
			}
 

	
 
			
 
			var vp = this.editor[name].getViewport();
 
			var scroll = true;
 
			if (last_change) {
 
				this.trace('scroll', 'last change before midline', last_change);
 
				if (midline.line >= vp.from && midline <= vp.to) {
 
					scroll = false;
 
				}
 
			}
 
			this.trace('scroll', 'scroll', scroll);
 
			if (scroll || force_scroll) {
 
				// scroll the other side
 
				this.trace('scroll', 'scrolling other side', top_to - top_adjust);
 
				var scroller = jQuery(this.editor[name].getScrollerElement());
 
				this._skipscroll[name] = true;//disable next event
 
				scroller.scrollTop(top_to - top_adjust).scrollLeft(left_to);
 
			}
 
			else this.trace('scroll', 'not scrolling other side');
 

	
 
			
 
			if (this.settings.autoupdate) {
 
				var timer = new Mgly.Timer();
 
				this._calculate_offsets(editor_name1, editor_name2, this.changes);
 
				this.trace('change', 'offsets time', timer.stop());
 
				this._markup_changes(editor_name1, editor_name2, this.changes);
 
				this.trace('change', 'markup time', timer.stop());
 
				this._draw_diff(editor_name1, editor_name2, this.changes);
 
				this.trace('change', 'draw time', timer.stop());
 
			}
 
			this.trace('scroll', 'scrolled');
 
		}
 
	},
 
@@ -768,51 +810,51 @@ jQuery.extend(Mgly.mergely.prototype, {
 
					var change = fns[i];
 
					//if (change.doc.id != edid) continue;
 
					if (change.lines.length) {
 
						self.trace('change', 'clear text', change.lines[0].text);
 
					}
 
					change.clear();
 
				}
 
				editor.clearGutter('merge');
 
				self.trace('change', 'clear time', timer.stop());
 
			});
 
		}
 
		self.chfns[name] = [];
 

	
 
		
 
		var ex = this._draw_info(this.id + '-lhs', this.id + '-rhs');
 
		var ctx_lhs = ex.clhs.get(0).getContext('2d');
 
		var ctx_rhs = ex.crhs.get(0).getContext('2d');
 
		var ctx = ex.dcanvas.getContext('2d');
 

	
 
		
 
		ctx_lhs.beginPath();
 
		ctx_lhs.fillStyle = this.settings.bgcolor;
 
		ctx_lhs.strokeStyle = '#888';
 
		ctx_lhs.fillRect(0, 0, 6.5, ex.visible_page_height);
 
		ctx_lhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
 

	
 
		ctx_rhs.beginPath();
 
		ctx_rhs.fillStyle = this.settings.bgcolor;
 
		ctx_rhs.strokeStyle = '#888';
 
		ctx_rhs.fillRect(0, 0, 6.5, ex.visible_page_height);
 
		ctx_rhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
 

	
 
		
 
		ctx.beginPath();
 
		ctx.fillStyle = '#fff';
 
		ctx.fillRect(0, 0, this.draw_mid_width, ex.visible_page_height);
 
	},
 
	_diff: function(editor_name1, editor_name2) {
 
		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());
 
		this._calculate_offsets(editor_name1, editor_name2, this.changes);
 
		this.trace('change', 'offsets time', timer.stop());
 
		this._markup_changes(editor_name1, editor_name2, this.changes);
 
		this.trace('change', 'markup time', timer.stop());
 
		this._draw_diff(editor_name1, editor_name2, this.changes);
 
		this.trace('change', 'draw time', timer.stop());
 
	},
 
	_parse_diff: function (editor_name1, editor_name2, diff) {
 
		this.trace('diff', 'diff results:\n', diff);
 
@@ -852,110 +894,124 @@ jQuery.extend(Mgly.mergely.prototype, {
 
	},
 
	_is_change_in_view: function(vp, change) {
 
		if (!this.settings.viewport) return true;
 
		if ((change['lhs-line-from'] < vp.from && change['lhs-line-to'] < vp.to) ||
 
			(change['lhs-line-from'] > vp.from && change['lhs-line-to'] > vp.to) ||
 
			(change['rhs-line-from'] < vp.from && change['rhs-line-to'] < vp.to) ||
 
			(change['rhs-line-from'] > vp.from && change['rhs-line-to'] > vp.to)) {
 
			// if the change is outside the viewport, skip
 
			return false;
 
		}
 
		return true;
 
	},
 
	_set_top_offset: function (editor_name1) {
 
		// save the current scroll position of the editor
 
		var saveY = this.editor[editor_name1].getScrollInfo().top;
 
		// temporarily scroll to top
 
		this.editor[editor_name1].scrollTo(null, 0);
 
		
 
		// this is the distance from the top of the screen to the top of the 
 
		// content of the first codemirror editor
 
		var topnode = jQuery('#' + this.id + ' .CodeMirror-measure').first();
 
		var top_offset = topnode.offset().top - 4;
 
		if(!top_offset) return false;
 
		
 
		// restore editor's scroll position
 
		this.editor[editor_name1].scrollTo(null, saveY);
 
		
 
		this.draw_top_offset = 0.5 - top_offset;
 
		return true;
 
	},
 
	_calculate_offsets: function (editor_name1, editor_name2, changes) {
 
		if (this.em_height == null) {
 
			// this is the distance from the top of the screen
 
			var topnode = jQuery('#' + this.id + ' .CodeMirror-measure').first();
 
			var top_offset = topnode.offset().top - 4;
 
			if (!top_offset) return;//try again
 
			this.draw_top_offset = 0.5 - top_offset;
 
			if(!this._set_top_offset(editor_name1)) return; //try again
 
			this.em_height = this.editor[editor_name1].defaultTextHeight();
 
			if (!this.em_height) {
 
				console.warn('Failed to calculate offsets, using 18 by default');
 
				this.em_height = 18;
 
			}
 
			this.draw_lhs_min = 0.5;
 
			var c = jQuery('#' + editor_name1 + '-' + editor_name2 + '-canvas');
 
			if (!c.length) {
 
				console.error('failed to find canvas', '#' + editor_name1 + '-' + editor_name2 + '-canvas');
 
			}
 
			if (!c.width()) {
 
				console.error('canvas width is 0');
 
				return;
 
			}
 
			this.draw_mid_width = jQuery('#' + editor_name1 + '-' + editor_name2 + '-canvas').width();
 
			this.draw_rhs_max = this.draw_mid_width - 0.5; //24.5;
 
			this.draw_lhs_width = 5;
 
			this.draw_rhs_width = 5;
 
			this.trace('calc', 'change offsets calculated', {top_offset: top_offset, lhs_min: this.draw_lhs_min, rhs_max: this.draw_rhs_max, lhs_width: this.draw_lhs_width, rhs_width: this.draw_rhs_width});
 
			this.trace('calc', 'change offsets calculated', {top_offset: this.draw_top_offset, lhs_min: this.draw_lhs_min, rhs_max: this.draw_rhs_max, lhs_width: this.draw_lhs_width, rhs_width: this.draw_rhs_width});
 
		}
 
		var lhschc = this.editor[editor_name1].charCoords({line: 0});
 
		var rhschc = this.editor[editor_name2].charCoords({line: 0});
 
		var vp = this._get_viewport(editor_name1, editor_name2);
 

	
 
		
 
		for (var i = 0; i < changes.length; ++i) {
 
			var change = changes[i];
 

	
 
			
 
			if (!this.settings.sidebar && !this._is_change_in_view(vp, change)) {
 
				// if the change is outside the viewport, skip
 
				delete change['lhs-y-start'];
 
				delete change['lhs-y-end'];
 
				delete change['rhs-y-start'];
 
				delete change['rhs-y-end'];
 
				continue;
 
			}
 
			var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
 
			var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
 
			var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
 
			var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
 

	
 
			
 
			var ls, le, rs, re;
 
			if (this.editor[editor_name1].getOption('lineWrapping') || this.editor[editor_name1].getOption('lineWrapping')) {
 
				// If using line-wrapping, we must get the height of the line
 
				var tls = this.editor[editor_name1].cursorCoords({line: llf, ch: 0}, 'page');
 
				var lhssh = this.editor[editor_name1].getLineHandle(llf);
 
				ls = { top: tls.top, bottom: tls.top + lhssh.height };
 

	
 
				var tle = this.editor[editor_name1].cursorCoords({line: llt, ch: 0}, 'page');
 
				var lhseh = this.editor[editor_name1].getLineHandle(llt);
 
				le = { top: tle.top, bottom: tle.top + lhseh.height };
 

	
 
				
 
				var tls = this.editor[editor_name2].cursorCoords({line: rlf, ch: 0}, 'page');
 
				var rhssh = this.editor[editor_name2].getLineHandle(rlf);
 
				rs = { top: tls.top, bottom: tls.top + rhssh.height };
 

	
 
				var tle = this.editor[editor_name2].cursorCoords({line: rlt, ch: 0}, 'page');
 
				var rhseh = this.editor[editor_name2].getLineHandle(rlt);
 
				re = { top: tle.top, bottom: tle.top + rhseh.height };
 
			}
 
			else {
 
				// If not using line-wrapping, we can calculate the line position
 
				ls = {
 
					top: lhschc.top + llf * this.em_height,
 
				ls = { 
 
					top: lhschc.top + llf * this.em_height, 
 
					bottom: lhschc.bottom + llf * this.em_height + 2
 
				};
 
				le = {
 
					top: lhschc.top + llt * this.em_height,
 
					top: lhschc.top + llt * this.em_height, 
 
					bottom: lhschc.bottom + llt * this.em_height + 2
 
				};
 
				rs = {
 
					top: rhschc.top + rlf * this.em_height,
 
					top: rhschc.top + rlf * this.em_height, 
 
					bottom: rhschc.bottom + rlf * this.em_height + 2
 
				};
 
				re = {
 
					top: rhschc.top + rlt * this.em_height,
 
					top: rhschc.top + rlt * this.em_height, 
 
					bottom: rhschc.bottom + rlt * this.em_height + 2
 
				};
 
			}
 

	
 
			
 
			if (change['op'] == 'a') {
 
				// adds (right), normally start from the end of the lhs,
 
				// except for the case when the start of the rhs is 0
 
				if (rlf > 0) {
 
					ls.top = ls.bottom;
 
					ls.bottom += this.em_height;
 
					le = ls;
 
				}
 
			}
 
			else if (change['op'] == 'd') {
 
				// deletes (left) normally finish from the end of the rhs,
 
				// except for the case when the start of the lhs is 0
 
@@ -976,88 +1032,88 @@ jQuery.extend(Mgly.mergely.prototype, {
 
			if (change['op'] == 'c' || change['op'] == 'a') {
 
				change['rhs-y-end'] = this.draw_top_offset + re.bottom;
 
			}
 
			else {
 
				change['rhs-y-end'] = this.draw_top_offset + re.top;
 
			}
 
			this.trace('calc', 'change calculated', i, change);
 
		}
 
		return changes;
 
	},
 
	_markup_changes: function (editor_name1, editor_name2, changes) {
 
		jQuery('.merge-button').remove(); // clear
 

	
 
		
 
		var self = this;
 
		var led = this.editor[editor_name1];
 
		var red = this.editor[editor_name2];
 

	
 
		var timer = new Mgly.Timer();
 
		led.operation(function() {
 
			for (var i = 0; i < changes.length; ++i) {
 
				var change = changes[i];
 
				var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
 
				var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
 
				var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
 
				var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
 

	
 
				
 
				var clazz = ['mergely', 'lhs', change['op'], 'cid-' + i];
 
				led.addLineClass(llf, 'background', 'start');
 
				led.addLineClass(llt, 'background', 'end');
 

	
 
				
 
				if (llf == 0 && llt == 0 && rlf == 0) {
 
					led.addLineClass(llf, 'background', clazz.join(' '));
 
					led.addLineClass(llf, 'background', 'first');
 
				}
 
				else {
 
					// apply change for each line in-between the changed lines
 
					for (var j = llf; j <= llt; ++j) {
 
						led.addLineClass(j, 'background', clazz.join(' '));
 
						led.addLineClass(j, 'background', clazz.join(' '));
 
					}
 
				}
 

	
 
				
 
				if (!red.getOption('readOnly')) {
 
					// add widgets to lhs, if rhs is not read only
 
					var rhs_button = self.merge_rhs_button.clone();
 
					if (rhs_button.button) {
 
						//jquery-ui support
 
						rhs_button.button({icons: {primary: 'ui-icon-triangle-1-e'}, text: false});
 
					}
 
					rhs_button.addClass('merge-button');
 
					rhs_button.attr('id', 'merge-rhs-' + i);
 
					led.setGutterMarker(llf, 'merge', rhs_button.get(0));
 
				}
 
			}
 
		});
 

	
 
		var vp = this._get_viewport(editor_name1, editor_name2);
 

	
 
		
 
		this.trace('change', 'markup lhs-editor time', timer.stop());
 
		red.operation(function() {
 
			for (var i = 0; i < changes.length; ++i) {
 
				var change = changes[i];
 
				var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
 
				var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
 
				var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
 
				var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
 

	
 
				
 
				if (!self._is_change_in_view(vp, change)) {
 
					// if the change is outside the viewport, skip
 
					continue;
 
				}
 

	
 
				
 
				var clazz = ['mergely', 'rhs', change['op'], 'cid-' + i];
 
				red.addLineClass(rlf, 'background', 'start');
 
				red.addLineClass(rlt, 'background', 'end');
 

	
 
				
 
				if (rlf == 0 && rlt == 0 && llf == 0) {
 
					red.addLineClass(rlf, 'background', clazz.join(' '));
 
					red.addLineClass(rlf, 'background', 'first');
 
				}
 
				else {
 
					// apply change for each line in-between the changed lines
 
					for (var j = rlf; j <= rlt; ++j) {
 
						red.addLineClass(j, 'background', clazz.join(' '));
 
						red.addLineClass(j, 'background', clazz.join(' '));
 
					}
 
				}
 

	
 
@@ -1066,104 +1122,104 @@ jQuery.extend(Mgly.mergely.prototype, {
 
					var lhs_button = self.merge_lhs_button.clone();
 
					if (lhs_button.button) {
 
						//jquery-ui support
 
						lhs_button.button({icons: {primary: 'ui-icon-triangle-1-w'}, text: false});
 
					}
 
					lhs_button.addClass('merge-button');
 
					lhs_button.attr('id', 'merge-lhs-' + i);
 
					red.setGutterMarker(rlf, 'merge', lhs_button.get(0));
 
				}
 
			}
 
		});
 
		this.trace('change', 'markup rhs-editor time', timer.stop());
 

	
 
		
 
		// mark text deleted, LCS changes
 
		var marktext = [];
 
		for (var i = 0; this.settings.lcs && i < changes.length; ++i) {
 
			var change = changes[i];
 
			var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0;
 
			var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0;
 
			var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0;
 
			var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0;
 

	
 
			
 
			if (!this._is_change_in_view(vp, change)) {
 
				// if the change is outside the viewport, skip
 
				continue;
 
			}
 
			if (change['op'] == 'd') {
 
				// apply delete to cross-out (left-hand side only)
 
				var from = llf;
 
				var to = llt;
 
				var to_ln = led.lineInfo(to);
 
				if (to_ln) {
 
					marktext.push([led, {line:from, ch:0}, {line:to, ch:to_ln.text.length}, {className: 'mergely ch d lhs'}]);
 
				}
 
			}
 
			else if (change['op'] == 'c') {
 
				// apply LCS changes to each line
 
				for (var j = llf, k = rlf, p = 0;
 
				for (var j = llf, k = rlf, p = 0; 
 
					 ((j >= 0) && (j <= llt)) || ((k >= 0) && (k <= rlt));
 
					 ++j, ++k) {
 
					if (k + p > rlt) {
 
						// lhs continues past rhs, mark lhs as deleted
 
						var lhs_line = led.getLine( j );
 
						marktext.push([led, {line:j, ch:0}, {line:j, ch:lhs_line.length}, {className: 'mergely ch d lhs'}]);
 
						continue;
 
					}
 
					if (j + p > llt) {
 
						// rhs continues past lhs, mark rhs as added
 
						var rhs_line = red.getLine( k );
 
						marktext.push([red, {line:k, ch:0}, {line:k, ch:rhs_line.length}, {className: 'mergely ch a rhs'}]);
 
						continue;
 
					}
 
					var lhs_line = led.getLine( j );
 
					var rhs_line = red.getLine( k );
 
					var lhs_start = { line: -1, ch: -1 };
 
					var lhs_stop = { line: -1, ch: -1 };
 
					var rhs_start = { line: -1, ch: -1 };
 
					var rhs_stop = { line: -1, ch: -1 };
 

	
 
					
 
					var lcs = new Mgly.LCS(lhs_line, rhs_line);
 
					lcs.diff(
 
						function (from, to) {//added
 
							marktext.push([red, {line:k, ch:from}, {line:k, ch:to}, {className: 'mergely ch a rhs'}]);
 
						},
 
						removed = function (from, to) {//removed
 
							marktext.push([led, {line:j, ch:from}, {line:j, ch:to}, {className: 'mergely ch d lhs'}]);
 
						}
 
					);
 
				}
 
			}
 
		}
 
		this.trace('change', 'LCS marktext time', timer.stop());
 

	
 
		
 
		// mark changes outside closure
 
		led.operation(function() {
 
			// apply lhs markup
 
			for (var i = 0; i < marktext.length; ++i) {
 
				var m = marktext[i];
 
				if (m[0].doc.id != led.getDoc().id) continue;
 
				self.chfns[self.id + '-lhs'].push(m[0].markText(m[1], m[2], m[3]));
 
			}
 
		});
 
		red.operation(function() {
 
			// apply lhs markup
 
			for (var i = 0; i < marktext.length; ++i) {
 
				var m = marktext[i];
 
				if (m[0].doc.id != red.getDoc().id) continue;
 
				self.chfns[self.id + '-rhs'].push(m[0].markText(m[1], m[2], m[3]));
 
			}
 
		});
 
		this.trace('change', 'LCS markup time', timer.stop());
 

	
 
		
 
		// merge buttons
 
		var ed = {lhs:led, rhs:red};
 
		jQuery('.merge-button').on('click', function(ev){
 
			// side of mouseenter
 
			var side = 'rhs';
 
			var oside = 'lhs';
 
			var parent = jQuery(this).parents('#' + self.id + '-editor-lhs');
 
			if (parent.length) {
 
				side = 'lhs';
 
				oside = 'rhs';
 
			}
 
			var pos = ed[side].coordsChar({left:ev.pageX, top:ev.pageY});
 
@@ -1171,29 +1227,29 @@ jQuery.extend(Mgly.mergely.prototype, {
 
			// get the change id
 
			var cid = null;
 
			var info = ed[side].lineInfo(pos.line);
 
			jQuery.each(info.bgClass.split(' '), function(i, clazz) {
 
				if (clazz.indexOf('cid-') == 0) {
 
					cid = parseInt(clazz.split('-')[1], 10);
 
					return false;
 
				}
 
			});
 
			var change = self.changes[cid];
 

	
 
			var line = {lhs: ed['lhs'].lineInfo(llt), rhs: ed['rhs'].lineInfo(rlt)};
 

	
 
	
 
			var text = ed[side].getRange(
 
				CodeMirror.Pos(change[side + '-line-from'], 0),
 
				CodeMirror.Pos(change[side + '-line-to'] + 1, 0));
 

	
 
			
 
			if (change['op'] == 'c') {
 
				ed[oside].replaceRange(text,
 
					CodeMirror.Pos(change[oside + '-line-from'], 0),
 
					CodeMirror.Pos(change[oside + '-line-to'] + 1, 0));
 
			}
 
			else if (side == 'rhs') {
 
				if (change['op'] == 'a') {
 
					ed[oside].replaceRange(text,
 
						CodeMirror.Pos(change[oside + '-line-from'] + 1, 0),
 
						CodeMirror.Pos(change[oside + '-line-to'] + 1, 0));
 
				}
 
				else {// 'd'
 
@@ -1251,32 +1307,32 @@ jQuery.extend(Mgly.mergely.prototype, {
 
		var ex = this._draw_info(editor_name1, editor_name2);
 
		var mcanvas_lhs = ex.clhs.get(0);
 
		var mcanvas_rhs = ex.crhs.get(0);
 
		var ctx = ex.dcanvas.getContext('2d');
 
		var ctx_lhs = mcanvas_lhs.getContext('2d');
 
		var ctx_rhs = mcanvas_rhs.getContext('2d');
 

	
 
		this.trace('draw', 'visible_page_height', ex.visible_page_height);
 
		this.trace('draw', 'gutter_height', ex.gutter_height);
 
		this.trace('draw', 'visible_page_ratio', ex.visible_page_ratio);
 
		this.trace('draw', 'lhs-scroller-top', ex.lhs_scroller.scrollTop());
 
		this.trace('draw', 'rhs-scroller-top', ex.rhs_scroller.scrollTop());
 

	
 
		
 
		jQuery.each(jQuery.find('#' + this.id + ' canvas'), function () {
 
			jQuery(this).get(0).height = ex.visible_page_height;
 
		});
 

	
 
		
 
		ex.clhs.unbind('click');
 
		ex.crhs.unbind('click');
 

	
 
		
 
		ctx_lhs.beginPath();
 
		ctx_lhs.fillStyle = this.settings.bgcolor;
 
		ctx_lhs.strokeStyle = '#888';
 
		ctx_lhs.fillRect(0, 0, 6.5, ex.visible_page_height);
 
		ctx_lhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
 

	
 
		ctx_rhs.beginPath();
 
		ctx_rhs.fillStyle = this.settings.bgcolor;
 
		ctx_rhs.strokeStyle = '#888';
 
		ctx_rhs.fillRect(0, 0, 6.5, ex.visible_page_height);
 
		ctx_rhs.strokeRect(0, 0, 6.5, ex.visible_page_height);
 

	
 
@@ -1286,162 +1342,162 @@ jQuery.extend(Mgly.mergely.prototype, {
 

	
 
			this.trace('draw', change);
 
			// margin indicators
 
			var lhs_y_start = ((change['lhs-y-start'] + ex.lhs_scroller.scrollTop()) * ex.visible_page_ratio);
 
			var lhs_y_end = ((change['lhs-y-end'] + ex.lhs_scroller.scrollTop()) * ex.visible_page_ratio) + 1;
 
			var rhs_y_start = ((change['rhs-y-start'] + ex.rhs_scroller.scrollTop()) * ex.visible_page_ratio);
 
			var rhs_y_end = ((change['rhs-y-end'] + ex.rhs_scroller.scrollTop()) * ex.visible_page_ratio) + 1;
 
			this.trace('draw', 'marker calculated', lhs_y_start, lhs_y_end, rhs_y_start, rhs_y_end);
 

	
 
			ctx_lhs.beginPath();
 
			ctx_lhs.fillStyle = this.settings.fgcolor[change['op']];
 
			ctx_lhs.strokeStyle = '#000';
 
			ctx_lhs.lineWidth = 1.0;
 
			ctx_lhs.lineWidth = 0.5;
 
			ctx_lhs.fillRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5));
 
			ctx_lhs.strokeRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5));
 

	
 
			ctx_rhs.beginPath();
 
			ctx_rhs.fillStyle = this.settings.fgcolor[change['op']];
 
			ctx_rhs.strokeStyle = '#000';
 
			ctx_rhs.lineWidth = 1.0;
 
			ctx_rhs.lineWidth = 0.5;
 
			ctx_rhs.fillRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5));
 
			ctx_rhs.strokeRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5));
 

	
 
			
 
			if (!this._is_change_in_view(vp, change)) {
 
				continue;
 
			}
 

	
 
			
 
			lhs_y_start = change['lhs-y-start'];
 
			lhs_y_end = change['lhs-y-end'];
 
			rhs_y_start = change['rhs-y-start'];
 
			rhs_y_end = change['rhs-y-end'];
 

	
 
			
 
			var radius = 3;
 

	
 
			
 
			// draw left box
 
			ctx.beginPath();
 
			ctx.strokeStyle = this.settings.fgcolor[change['op']];
 
			ctx.lineWidth = 1;
 

	
 
			
 
			var rectWidth = this.draw_lhs_width;
 
			var rectHeight = lhs_y_end - lhs_y_start - 1;
 
			var rectX = this.draw_lhs_min;
 
			var rectY = lhs_y_start;
 
			// top and top top-right corner
 

	
 
			
 
			// draw left box
 
			ctx.moveTo(rectX, rectY);
 
			if (navigator.appName == 'Microsoft Internet Explorer') {
 
				// IE arcs look awful
 
				ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_start);
 
				ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_end + 1);
 
				ctx.lineTo(this.draw_lhs_min, lhs_y_end + 1);
 
			}
 
			else {
 
				if (rectHeight <= 0) {
 
					ctx.lineTo(rectX + rectWidth, rectY);
 
				}
 
				else {
 
					ctx.arcTo(rectX + rectWidth, rectY, rectX + rectWidth, rectY + radius, radius);
 
					ctx.arcTo(rectX + rectWidth, rectY + rectHeight, rectX + rectWidth - radius, rectY + rectHeight, radius);
 
				}
 
				// bottom line
 
				ctx.lineTo(rectX, rectY + rectHeight);
 
			}
 
			ctx.stroke();
 

	
 
			
 
			rectWidth = this.draw_rhs_width;
 
			rectHeight = rhs_y_end - rhs_y_start - 1;
 
			rectX = this.draw_rhs_max;
 
			rectY = rhs_y_start;
 

	
 
			// draw right box
 
			ctx.moveTo(rectX, rectY);
 
			if (navigator.appName == 'Microsoft Internet Explorer') {
 
				ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_start);
 
				ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_end + 1);
 
				ctx.lineTo(this.draw_rhs_max, rhs_y_end + 1);
 
			}
 
			else {
 
				if (rectHeight <= 0) {
 
					ctx.lineTo(rectX - rectWidth, rectY);
 
				}
 
				else {
 
					ctx.arcTo(rectX - rectWidth, rectY, rectX - rectWidth, rectY + radius, radius);
 
					ctx.arcTo(rectX - rectWidth, rectY + rectHeight, rectX - radius, rectY + rectHeight, radius);
 
				}
 
				ctx.lineTo(rectX, rectY + rectHeight);
 
			}
 
			ctx.stroke();
 

	
 
			
 
			// connect boxes
 
			var cx = this.draw_lhs_min + this.draw_lhs_width;
 
			var cy = lhs_y_start + (lhs_y_end + 1 - lhs_y_start) / 2.0;
 
			var dx = this.draw_rhs_max - this.draw_rhs_width;
 
			var dy = rhs_y_start + (rhs_y_end + 1 - rhs_y_start) / 2.0;
 
			ctx.moveTo(cx, cy);
 
			if (cy == dy) {
 
				ctx.lineTo(dx, dy);
 
			}
 
			else {
 
				// fancy!
 
				ctx.bezierCurveTo(
 
					cx + 12, cy - 3, // control-1 X,Y
 
					dx - 12, dy - 3, // control-2 X,Y
 
					dx, dy);
 
			}
 
			ctx.stroke();
 
		}
 

	
 
		// visible window feedback
 
		ctx_lhs.fillStyle = this.settings.vpcolor;
 
		ctx_rhs.fillStyle = this.settings.vpcolor;
 

	
 
		
 
		var lto = ex.clhs.height() * ex.visible_page_ratio;
 
		var lfrom = (ex.lhs_scroller.scrollTop() / ex.gutter_height) * ex.clhs.height();
 
		var rto = ex.crhs.height() * ex.visible_page_ratio;
 
		var rfrom = (ex.rhs_scroller.scrollTop() / ex.gutter_height) * ex.crhs.height();
 
		this.trace('draw', 'cls.height', ex.clhs.height());
 
		this.trace('draw', 'lhs_scroller.scrollTop()', ex.lhs_scroller.scrollTop());
 
		this.trace('draw', 'gutter_height', ex.gutter_height);
 
		this.trace('draw', 'visible_page_ratio', ex.visible_page_ratio);
 
		this.trace('draw', 'lhs from', lfrom, 'lhs to', lto);
 
		this.trace('draw', 'rhs from', rfrom, 'rhs to', rto);
 

	
 
		
 
		ctx_lhs.fillRect(1.5, lfrom, 4.5, lto);
 
		ctx_rhs.fillRect(1.5, rfrom, 4.5, rto);
 

	
 
		
 
		ex.clhs.click(function (ev) {
 
			var y = ev.pageY - ex.lhs_xyoffset.top - (lto / 2);
 
			var sto = Math.max(0, (y / mcanvas_lhs.height) * ex.lhs_scroller.get(0).scrollHeight);
 
			ex.lhs_scroller.scrollTop(sto);
 
		});
 
		ex.crhs.click(function (ev) {
 
			var y = ev.pageY - ex.rhs_xyoffset.top - (rto / 2);
 
			var sto = Math.max(0, (y / mcanvas_rhs.height) * ex.rhs_scroller.get(0).scrollHeight);
 
			var sto = Math.max(0, (y / mcanvas_rhs.height) * ex.rhs_scroller.get(0).scrollHeight);			
 
			ex.rhs_scroller.scrollTop(sto);
 
		});
 
	},
 
	trace: function(name) {
 
		if(this.settings._debug.indexOf(name) >= 0) {
 
			arguments[0] = name+':';
 
			console.log([].slice.apply(arguments));
 
		}
 
		} 
 
	}
 
});
 

	
 
jQuery.pluginMaker = function(plugin) {
 
	// add the plugin function as a jQuery plugin
 
	jQuery.fn[plugin.prototype.name] = function(options) {
 
		// get the arguments
 
		// get the arguments 
 
		var args = jQuery.makeArray(arguments),
 
		after = args.slice(1);
 
		var rc = undefined;
 
		this.each(function() {
 
			// see if we have an instance
 
			var instance = jQuery.data(this, plugin.prototype.name);
 
			if (instance) {
 
				// call a method on the instance
 
				if (typeof options == "string") {
 
					rc = instance[options].apply(instance, after);
 
				} else if (instance.update) {
 
					// call update on the instance
rhodecode/templates/files/diff_2way.html
Show inline comments
 
## -*- coding: utf-8 -*-
 

	
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="js_extra()">
 
<script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script>
 
<script type="text/javascript" src="${h.url('/js/mergerly.js')}"></script>
 
<script type="text/javascript" src="${h.url('/js/mergely.js')}"></script>
 
</%def>
 
<%def name="css_extra()">
 
<link rel="stylesheet" type="text/css" href="${h.url('/css/codemirror.css')}"/>
 
<link rel="stylesheet" type="text/css" href="${h.url('/css/mergerly.css')}"/>
 
<link rel="stylesheet" type="text/css" href="${h.url('/css/mergely.css')}"/>
 
</%def>
 

	
 
<%def name="title()">
 
    ${_('%s File side-by-side diff') % c.repo_name}
 
    %if c.rhodecode_name:
 
        &middot; ${c.rhodecode_name}
 
    %endif
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${_('File diff')} r${c.changeset_1.revision}:${h.short_id(c.changeset_1.raw_id)} &rarr; r${c.changeset_2.revision}:${h.short_id(c.changeset_2.raw_id)}
 
</%def>
0 comments (0 inline, 0 general)