diff --git a/doc/index.html b/doc/index.html index 540b90b..76d9152 100644 --- a/doc/index.html +++ b/doc/index.html @@ -12,11 +12,13 @@ @@ -25,19 +27,17 @@
+

Mergely Reference Manual

Overview

- The core of mergely is a javascript-based diff and customizable markup engine. - Mergely provides a rich API that enables integration into your own application. It - can be used as a diff tool (read-only) or as both a diff and merge + The core of mergely is a javascript-based Longest Common Subsequence diff algorithm (LCS) + and customizable markup engine. Mergely provides a rich API that enables integration into + your own application. It can be used as a diff tool (read-only) or as both a diff and merge tool for plain text, CSS, HTML, XML, javascript, PHP, C, C++, etc.

-

- -

- +

Basic Usage

Mergely requires jQuery and CodeMirror. @@ -87,6 +87,8 @@ $(document).ready(function () {

autoresize
Enables/disables the auto-resizing of the editor. Defaults to true.
+
autoupdate
+
Enables/disables the auto-updating of the editor when changes are made. Defaults to true
cmsettings
CodeMirror settings (see CodeMirror). Defaults to {mode: 'application/xml', readOnly: false, lineWrapping: false, lineNumbers: true}.
editor_width
@@ -98,7 +100,11 @@ $(document).ready(function () {
change_timeout
The timeout, after a text change, before Mergely calcualtes a diff. Only used when readonly enabled. Defaults to 500.
fgcolor
-
The foreground color that mergely marks changes with on the canvas. Defaults to '#4ba3fa'
+
+ The foreground color that mergely marks changes with on the canvas. Defaults to {a:'#4ba3fa',c:'#cccccc',d:'#ff7f7f'}. + The 'a' option is the color for additions, the 'c' option is the color for + changes, and the 'd' option is the color for deletions. +
bgcolor
The background color that mergely fills the margin canvas with. Defaults to '#eeeeee'
fadein
@@ -156,34 +162,45 @@ $(document).ready(function () {
The editors.
.mergely-active
The active editor.
-
mergely-c-start
-
Styles the starting line of a change.
-
mergely-c-end
-
Styles the ending line of a change.
-
mergely-a-start
-
Styles the starting line of an addition.
-
mergely-a-mid
-
Styles the middle text region of an addition.
-
mergely-a-end
-
Styles the ending line of an addition.
-
mergely-d-start
-
Styles the starting line of a deletion.
-
mergely-d-mid
-
Styles the middle text region of a deletion.
-
mergely-d-end
-
Styles the ending line of a deletion.
-
mergely-c-rem
-
Styles the middle text region of a deletion.
-
mergely-c-add
-
Styles the middle text region of an addition.
-
mergely-a-start-lhs
-
Styles the start of an addition on the left-hand side.
-
mergely-a-end-lhs
-
Styles the end of an addition on the left-hand side.
-
mergely-d-start-rhs
-
Styles the start of an deletion on the right-hand side.
-
mergely-d-end-rhs
-
Styles the start of an deletion on the right-hand side.
+
.mergely-canvas
+
The mergely canvas elements
+ +
mergely.a.rhs
+
Styles an addition to the right-hand side, regardless of starting or ending lines
+
mergely.a.rhs.start
+
Styles the starting line of an addition to the right-hand side
+
mergely.a.rhs.end
+
Styles the ending line of an addition to the right-hand side
+
mergely.a.rhs.start.end
+
Styles the start and ending line of an addition to the right-hand side when the start and end are the same line
+ +
mergely.d.lhs
+
Styles a deletion from the left-hand side, regardless of starting or ending lines
+
mergely.d.lhs.start
+
Styles the starting line of a deletion from the left-hand side
+
mergely.d.lhs.end
+
Styles the ending line of a deletion from the left-hand side
+
mergely.d.lhs.start.end
+
Styles the start and ending line of a deletion from the left-hand side when the start and end are the same line
+ +
mergely.c.lhs
+
Styles a change to the left-hand side, regardless of starting or ending lines
+
mergely.c.lhs.start
+
Styles the starting line of a change to the left-hand side
+
mergely.c.lhs.end
+
Styles the starting line of a change to the left-hand side
+ +
mergely.c.rhs
+
Styles a change to the right-hand side, regardless of starting or ending lines
+
mergely.c.rhs.start
+
Styles the starting line of a change to the right-hand side
+
mergely.c.rhs.end
+
Styles the starting line of a change to the right-hand side
+ +
mergely.ch.a.rhs
+
Styles the text of a change to the right-hand side
+
mergely.ch.a.lhs
+
Styles the text of a change to the right-hand side
diff --git a/editor/editor.js b/editor/editor.js new file mode 100755 index 0000000..be427ff --- /dev/null +++ b/editor/editor.js @@ -0,0 +1,322 @@ +String.prototype.random = function(length) { + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + var randomstring = '' + for (var i=0; i 1) { + if (side == 'lhs') sides.push('rhs'); + else sides.push('lhs'); + } + // html5 file upload to browser + function load_file(side, file) { + var reader = new FileReader(); + reader.onprogress = function (evt) { } + reader.onload = function (evt) { + $('#compare').mergely(side, evt.target.result); + document.body.style.cursor = 'default'; + } + reader.onerror = function (evt) { + alert( evt.target.error.name ); + } + try { + reader.readAsText(file, "UTF-8"); + } + catch (e) { + alert(e); + document.body.style.cursor = 'default'; + } + } + if (files.length >= 1) load_file(sides[0], files[0]); + if (files.length >= 2) load_file(sides[1], files[1]); + } + }); + + $('#compare').mergely({ + readonly: key == '4qsmsDyb', + linewrapping: false, + height: function(h) { + return h - 100; + }, + loaded: function() { + $('.toolbar, .title').fadeIn('fast'); + $('button').css({'visibility':'visible'}); + }, + resized: function() { + var lhsx = $('#compare-editor-lhs .CodeMirror-gutter').offset().left + $('#compare-editor-lhs .CodeMirror-gutter').width() + 1; + var rhsx = $('#compare-editor-rhs .CodeMirror-gutter').offset().left + $('#compare-editor-rhs .CodeMirror-gutter').width() + 1 - $('#lhs-toolbar').width(); + $('#lhs-toolbar, #title-lhs').css({'position':'relative', 'left':lhsx}); + $('#rhs-toolbar, #title-rhs').css({'position':'relative', 'left':rhsx}); + $('#title-rhs').css({'left':rhsx}); + }, + cmsettings: { + mode: 'text/plain' + }, + _debug: '' + }); + if (key.length == 8) { + $.when( + $.ajax({ + type: 'GET', async: true, dataType: 'text', + data: { 'key':key, 'name': 'lhs' }, + url: '/ajax/handle_get.php', + success: function (response) { + $('#compare').mergely('lhs', response); + }, + error: function(xhr, ajaxOptions, thrownError){ + } + }), + $.ajax({ + type: 'GET', async: true, dataType: 'text', + data: { 'key':key, 'name': 'rhs' }, + url: '/ajax/handle_get.php', + success: function (response) { + $('#compare').mergely('rhs', response); + }, + error: function(xhr, ajaxOptions, thrownError){ + } + }) + ).done(function() { + var anchor = window.location.hash.substring(1); + if (anchor) { + // if an anchor has been provided, then parse the anchor in the + // form of: 'lhs' or 'rhs', followed by a line, e.g: lhs100. + var m = anchor.match(/([lr]hs)([0-9]+)/); + if (m.length == 3) { + console.log(m); + $('#compare').mergely('scrollTo', m[1], parseInt(m[2],10)); + } + } + }); + } + else { + if (newbie) { + $('#compare').mergely('lhs', 'the quick brown fox jumped over\nthe hairy cat\n'); + $('#compare').mergely('rhs', 'the quick brown fox jumped over\nthe lazy dog\n'); + } + } + $('#lhs-search, #rhs-search').click(function(){ + var side = $(this).attr('id').split('-')[0]; + var text = $('#' + side + '-search-text').val(); + $('#compare').mergely('search', side, text); + return false; + }); + $('#lhs-search-text, #rhs-search-text').keydown(function (ev) { + var id = $(this).attr('id').split('-')[0]; + if (ev.which === $.ui.keyCode.ENTER) { $('#' + id + '-search').click(); return false; } + return true; + }); + $('#lhs-clear, #rhs-clear').click(function(){ + var side = $(this).attr('id').split('-')[0]; + $('#compare').mergely('clear', side); + return false; + }); + $('#lhs-swap, #rhs-swap').click(function(){ + $('#compare').mergely('swap'); + return false; + }); + $('#lhs-merge, #rhs-merge').click(function(){ + var side = $(this).attr('id').split('-')[0]; + // clicking rhs-merge means 'merge left' + if (side == 'rhs') side = 'lhs'; + else side = 'rhs'; + $('#compare').mergely('merge', side); + return false; + }); + $('#save, #fork').click(function(){ + var fork = $(this).attr('id') == 'fork'; + if (key == '') key = ''.random(8); + var count = 0; + function post_save(side, text) { + $.ajax({ + type: 'POST', async: true, dataType: 'text', + url: '/ajax/handle_file.php', + data: { 'key': key, 'name': side, 'content': text }, + success: function (nkey) { + ++count; + if (count == 2) { + var url = '/ajax/handle_save.php?key=' + key; + if (fork) url += '&nkey=' + ''.random(8); + $.ajax({ + type: 'GET', async: false, dataType: 'text', + url: url, + success: function (nkey) { + // redirect + if (nkey.length) window.location.href = '/' + $.trim(nkey) + '/'; + }, + error: function(xhr, ajaxOptions, thrownError){ + } + }); + } + }, + error: function(xhr, ajaxOptions, thrownError){ + alert(thrownError); + } + }); + } + function call_save() { + var lhs = $('#compare').mergely('get', 'lhs'); + var rhs = $('#compare').mergely('get', 'rhs'); + post_save('lhs', lhs); + post_save('rhs', rhs); + } + + if ($(this).attr('id') == 'save') { + $( '#dialog-confirm' ).dialog({ + resizable: false, height:210, modal: true, + buttons: { + "Save for Sharing": function() { + $( this ).dialog( "close" ); + call_save(); + }, + Cancel: function() { + $( this ).dialog( "close" ); + } + } + }); + } + else { + call_save(); + } + return false; + }); + + $('#lhs-download, #rhs-download').click(function(){ + var side = $(this).attr('id').split('-')[0]; + var content = $('#compare').mergely('get', side); + + var MIME_TYPE = 'text/plain'; + console.log('downloading...'); + var windowURL = window.webkitURL || window.URL; + var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; + var bb = new BlobBuilder(); + bb.append(content); + var a = document.createElement('a'); + a.download = side + '.txt'; + a.target = '_blank'; + a.href = windowURL.createObjectURL(bb.getBlob(MIME_TYPE)); + a.textContent = 'Download ready'; + a.dataset.downloadurl = [MIME_TYPE, a.download, a.href].join(':'); + a.draggable = true; + a = $(a); + window.open(a.attr('href'), '_blank'); + windowURL.revokeObjectURL() + return false; + }); + $('#lhs-download-diff, #rhs-download-diff').click(function(){ + var text = $('#compare').mergely('diff'); + if (key == '') key = ''.random(8); + $.post('/ajax/handle_download.php', { 'key': key, 'content': text }, function(response) { + window.location = response; + }); + return false; + }); + + var dlg = $('#dialog-settings').css('visibility','visible').hide(); + var f = $.farbtastic('#picker'); + var sd = $(''); + var sa = $(''); + var sc = $(''); + $('body').append(sd); + $('body').append(sa); + $('body').append(sc); + var conf = { + 'c-border': {id: '#c-border', defaultColor: '#cccccc', getColor: function() { return sc.css('border-top-color'); } }, + 'a-border': {id: '#a-border', defaultColor: '#4ba3fa', getColor: function() { return sa.css('border-top-color'); }, setColor: function(color) { $('#'+this.id).val(color) }}, + 'd-border': {id: '#d-border', defaultColor: '#ff7f7f', getColor: function() { return sd.css('border-top-color'); }, setColor: function(color) { $('#'+this.id).val(color) }}, + 'a-bg': {id: '#a-bg', defaultColor: '#ddeeff', getColor: function() { return sa.css('background-color'); }, setColor: function(color) { $('#'+this.id).val(color) }}, + 'd-bg': {id: '#d-bg', defaultColor: '#f9e9e9', getColor: function() { return sd.css('background-color'); }, setColor: function(color) { $('#'+this.id).val(color) }}, + }; + + $.each(conf, function(key, item){ $(item.id).val(item.getColor()); }); + + $('#settings').click(function(){ + dlg.dialog({ + height: 330, width: 450, modal: true, + buttons: { + Apply: function() { + var cborder = $('#c-border').val(); + var aborder = $('#a-border').val(); + var dborder = $('#d-border').val(); + var abg = $('#a-bg').val(); + var dbg = $('#d-bg').val(); + var cbg = $('#c-bg').val(); + var text = + '.mergely.a.rhs.start { border-top: 1px solid ' + aborder + ' }\n\ + .mergely.a.lhs.start.end,\n\ + .mergely.a.rhs.end { border-bottom: 1px solid ' + aborder + ' }\n\ + .mergely.a.rhs { background-color: ' + abg + ' }\n\ + .mergely.d.lhs { background-color: ' + dbg + ' }\n\ + .mergely.d.lhs.end,\n\ + .mergely.d.rhs.start.end { border-bottom: 1px solid ' + dborder + '; }\n\ + .mergely.d.lhs.start { border-top: 1px solid ' + dborder + '; }\n\ + .mergely.c.lhs,\n\ + .mergely.c.rhs { background-color: ' + cbg + '; }\n\ + .mergely.c.lhs.start,\n\ + .mergely.c.rhs.start { border-top: 1px solid ' + cborder + '; }\n\ + .mergely.c.lhs.end,\n\ + .mergely.c.rhs.end { border-bottom: 1px solid ' + cborder + '; }\n\ + .mergely.ch.a.rhs { background-color: ' + abg + '; }\n\ + .mergely.ch.d.lhs { background-color: ' + dbg + '; text-decoration: line-through; color: #888; }\n' + $('').appendTo('head'); + + $('#compare').mergely('options', {fgcolor:{a:aborder,c:cborder,d:dborder}}); + $('#compare').mergely('update'); + }, + Reset: function() { + $.each(conf, function(key){ + var id = '#'+key; + f.linkTo($(id).get(0)); + f.setColor(conf[key].defaultColor); + }); + }, + Close: function() { + $(this).dialog('close'); + } + } + }); + }); + $('.colorwell').each(function(){ f.linkTo(this); }).focus(function(){ + var tthis = $(this); + f.linkTo(this); + var item = conf[tthis.attr('id')]; + f.setColor(item.getColor()); + }); + +}); diff --git a/editor/gatag.js b/editor/gatag.js new file mode 100755 index 0000000..d20ea45 --- /dev/null +++ b/editor/gatag.js @@ -0,0 +1,61 @@ +// This javascript tags file downloads and external links in Google Analytics. +// You need to be using the Google Analytics New Tracking Code (ga.js) +// for this script to work. +// To use, place this file on all pages just above the Google Analytics tracking code. +// All outbound links and links to non-html files should now be automatically tracked. +// +// This script has been provided by Goodwebpractices.com +// Thanks to ShoreTel, MerryMan and Colm McBarron +// +// www.goodwebpractices.com +// VKI has made changes as indicated below. + +if (document.getElementsByTagName) { + // Initialize external link handlers + var hrefs = document.getElementsByTagName("a"); + for (var l = 0; l < hrefs.length; l++) { + // try {} catch{} block added by erikvold VKI + try{ + //protocol, host, hostname, port, pathname, search, hash + if (hrefs[l].protocol == "mailto:") { + startListening(hrefs[l],"click",trackMailto); + } else if (hrefs[l].hostname == location.host) { + var path = hrefs[l].pathname + hrefs[l].search; + var isDoc = path.match(/\.(?:doc|eps|jpg|png|svg|xls|ppt|pdf|xls|zip|txt|vsd|vxd|js|css|rar|exe|wma|mov|avi|wmv|mp3)($|\&|\?)/); + if (isDoc) { + startListening(hrefs[l],"click",trackExternalLinks); + } + } else if (!hrefs[l].href.match(/^javascript:/)) { + startListening(hrefs[l],"click",trackExternalLinks); + } + } + catch(e){ + continue; + } + } +} + +function startListening (obj,evnt,func) { + if (obj.addEventListener) { + obj.addEventListener(evnt,func,false); + } else if (obj.attachEvent) { + obj.attachEvent("on" + evnt,func); + } +} + +function trackMailto (evnt) { + var href = (evnt.srcElement) ? evnt.srcElement.href : this.href; + var mailto = "/mailto/" + href.substring(7); + if (typeof(pageTracker) == "object") pageTracker._trackPageview(mailto); +} + +function trackExternalLinks (evnt) { + var e = (evnt.srcElement) ? evnt.srcElement : this; + while (e.tagName != "A") { + e = e.parentNode; + } + var lnk = (e.pathname.charAt(0) == "/") ? e.pathname : "/" + e.pathname; + if (e.search && e.pathname.indexOf(e.search) == -1) lnk += e.search; + if (e.hostname != location.host) lnk = "/external/" + e.hostname + lnk; + if (typeof(pageTracker) == "object") pageTracker._trackPageview(lnk); +} diff --git a/editor/jquery.edit.js b/editor/jquery.edit.js new file mode 100755 index 0000000..fdd8167 --- /dev/null +++ b/editor/jquery.edit.js @@ -0,0 +1,42 @@ +/* +* jQuery Edit Plugin +* version: 1.0 (2011/06/30) +* @requires jQuery v1.1 or later +* +* Examples at: +* License: +*/ +(function ($) { + + $.fn.editable = function (method) { + return this.each(function (index) { + var tthis = $(this); + var settings = { + cancel: function (target) { }, + accept: function (target) { } + }; + $(this).addClass('editable-default'); + $(this).mouseenter(function(){ + $(this).addClass('editable-hover').removeClass('editable-default').attr('contenteditable', 'true'); + }).mouseleave(function(){ + $(this).addClass('editable-default').removeClass('editable-hover').attr('contenteditable', 'false'); + }); + + var methods = { + init: function (options) { + if (options) $.extend(settings, options); + } + }; + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(method, 1)); + } + else if (typeof (method) == 'object' || !method) { + return methods.init(method); + } + else { + $.error('Method ' + method + ' does not exist on jQuery.edit'); + } + }); + } + +})(jQuery); diff --git a/editor/jquery.upload.js b/editor/jquery.upload.js new file mode 100755 index 0000000..ac6ad59 --- /dev/null +++ b/editor/jquery.upload.js @@ -0,0 +1,54 @@ +/* +* jQuery File Upload Plugin +* version: 1.1 (2011/06/30) +* @requires jQuery v1.1 or later +* +* Examples at: +* License: +*/ +(function ($) { + + $.fn.upload = function (method) { + return this.each(function (index) { + var tthis = $(this); + var settings = { + beforeupload: function (target, evt) { }, + upload: function (target, evt) { } + }; + var methods = { + init: function (options) { + if (options) $.extend(settings, options); + var container = $('
'); + var input = $(''); + if ($.browser.mozilla) { + input.css({'left':'-190px','height':'16px'}); + } + var button = tthis.clone(); + button.css({position:'relative', float:'left', left:'0px'}); + container.append(button); + container.append(input); + container.mouseover(function(){button.addClass('ui-state-hover').css({'cursor':'pointer'});}); + container.mouseleave(function(){button.removeClass('ui-state-hover').css({'cursor':'default'});}); + tthis.after(container); + container.css({overflow:'hidden'}); + tthis.remove(); + input.change(function (evt) { + var fname = input.val().replace(/^.*\\/, ''); + if (fname.length) { + settings.upload(tthis, evt); + } + }); + } + }; + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } + else if (typeof (method) == 'object' || !method) { + return methods.init(method); + } + else { + $.error('Method ' + method + ' does not exist on jQuery.upload'); + } + }); + } +})(jQuery); diff --git a/examples/example1.html b/examples/example1.html index fe99701..e257c22 100644 --- a/examples/example1.html +++ b/examples/example1.html @@ -1,10 +1,11 @@ - + Mergely - Simple Example + @@ -16,7 +17,7 @@ This example demonstrates the minimum amount of code required to use Mergely. - + diff --git a/examples/example2.html b/examples/example2.html index f8a562e..597929f 100644 --- a/examples/example2.html +++ b/examples/example2.html @@ -1,10 +1,11 @@ - + Mergely - Simple Example + @@ -16,7 +17,7 @@ This example demonstrates how to set left and right editors using ajax. - + diff --git a/examples/example3.html b/examples/example3.html index bf9c5de..dc4a5c3 100644 --- a/examples/example3.html +++ b/examples/example3.html @@ -1,10 +1,11 @@ - + Mergely - Simple Example + diff --git a/lib/codemirror.css b/lib/codemirror.css index 89d08ff..41b8d09 100644 --- a/lib/codemirror.css +++ b/lib/codemirror.css @@ -1,6 +1,11 @@ .CodeMirror { line-height: 1em; font-family: monospace; + + /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */ + position: relative; + /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */ + overflow: hidden; } .CodeMirror-scroll { @@ -9,6 +14,36 @@ /* This is needed to prevent an IE[67] bug where the scrolled content is visible outside of the scrolling box. */ position: relative; + outline: none; +} + +/* Vertical scrollbar */ +.CodeMirror-scrollbar { + position: absolute; + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; + z-index: 5; +} +.CodeMirror-scrollbar-inner { + /* This needs to have a nonzero width in order for the scrollbar to appear + in Firefox and IE9. */ + width: 1px; +} +.CodeMirror-scrollbar.cm-sb-overlap { + /* Ensure that the scrollbar appears in Lion, and that it overlaps the content + rather than sitting to the right of it. */ + position: absolute; + z-index: 1; + float: none; + right: 0; + min-width: 12px; +} +.CodeMirror-scrollbar.cm-sb-nonoverlap { + min-width: 12px; +} +.CodeMirror-scrollbar.cm-sb-ie7 { + min-width: 18px; } .CodeMirror-gutter { @@ -24,9 +59,12 @@ text-align: right; padding: .4em .2em .4em .4em; white-space: pre !important; + cursor: default; } .CodeMirror-lines { padding: .4em; + white-space: pre; + cursor: text; } .CodeMirror pre { @@ -40,11 +78,15 @@ padding: 0; margin: 0; white-space: pre; word-wrap: normal; + line-height: inherit; + color: inherit; + overflow: visible; } .CodeMirror-wrap pre { word-wrap: break-word; white-space: pre-wrap; + word-break: normal; } .CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; @@ -59,7 +101,21 @@ position: absolute; visibility: hidden; border-left: 1px solid black; + border-right: none; + width: 0; } +.cm-keymap-fat-cursor pre.CodeMirror-cursor { + width: auto; + border: 0; + background: transparent; + background: rgba(0, 200, 0, .4); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); +} +/* Kludge to turn off filter in ie9+, which also accepts rgba */ +.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} .CodeMirror-focused pre.CodeMirror-cursor { visibility: visible; } @@ -90,10 +146,10 @@ div.CodeMirror-selected { background: #d9d9d9; } .cm-s-default span.cm-error {color: #f00;} .cm-s-default span.cm-qualifier {color: #555;} .cm-s-default span.cm-builtin {color: #30a;} -.cm-s-default span.cm-bracket {color: #cc7;} +.cm-s-default span.cm-bracket {color: #997;} .cm-s-default span.cm-tag {color: #170;} .cm-s-default span.cm-attribute {color: #00c;} -.cm-s-default span.cm-header {color: #a0a;} +.cm-s-default span.cm-header {color: blue;} .cm-s-default span.cm-quote {color: #090;} .cm-s-default span.cm-hr {color: #999;} .cm-s-default span.cm-link {color: #00c;} @@ -103,5 +159,16 @@ span.cm-em {font-style: italic;} span.cm-emstrong {font-style: italic; font-weight: bold;} span.cm-link {text-decoration: underline;} +span.cm-invalidchar {color: #f00;} + div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} + +@media print { + + /* Hide the cursor when printing */ + .CodeMirror pre.CodeMirror-cursor { + visibility: hidden; + } + +} diff --git a/lib/codemirror.js b/lib/codemirror.js index 3635eef..4b4d529 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1,12 +1,11 @@ -// CodeMirror version 2.21 -// // All functions that need access to the editor's state live inside // the CodeMirror function. Below that, at the bottom of the file, // some utilities are defined. // CodeMirror is the only global var we claim -var CodeMirror = (function() { - // This is the function that produces an editor instance. It's +window.CodeMirror = (function() { + "use strict"; + // This is the function that produces an editor instance. Its // closure is used to store the editor state. function CodeMirror(place, givenOptions) { // Determine effective options based on given values and defaults. @@ -15,57 +14,59 @@ var CodeMirror = (function() { if (defaults.hasOwnProperty(opt)) options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; - var targetDocument = options["document"]; + var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em"); + input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); + // Wraps and hides input textarea + var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The empty scrollbar content, used solely for managing the scrollbar thumb. + var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner"); + // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself. + var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar"); + // DIVs containing the selection and the actual code + var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1"); + // Blinky cursor, and element used to ensure cursor fits at the end of a line + var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden"); + // Used to measure text size + var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;"); + var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0"); + var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter"); + // Moved around its parent to cover visible view + var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the text, causes scrolling + var sizer = elt("div", [mover], null, "position: relative"); + // Provides scrolling + var scroller = elt("div", [sizer], "CodeMirror-scroll"); + scroller.setAttribute("tabIndex", "-1"); // The element in which the editor lives. - var wrapper = targetDocument.createElement("div"); - wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""); - // This mess creates the base DOM structure for the editor. - wrapper.innerHTML = - '
' + // Wraps and hides input textarea - '
' + - '
' + - '
' + // Set to the height of the text, causes scrolling - '
' + // Moved around its parent to cover visible view - '
' + - // Provides positioning relative to (visible) text origin - '
' + - '
' + - '
 
' + // Absolutely positioned blinky cursor - '
' + // DIVs containing the selection and the actual code - '
'; + var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "")); if (place.appendChild) place.appendChild(wrapper); else place(wrapper); - // I've never seen more elegant code in my life. - var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, - scroller = wrapper.lastChild, code = scroller.firstChild, - mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, - lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, - cursor = measure.nextSibling, selectionDiv = cursor.nextSibling, - lineDiv = selectionDiv.nextSibling; - themeChanged(); + + themeChanged(); keyMapChanged(); // Needed to hide big blue blinking cursor on Mobile Safari if (ios) input.style.width = "0px"; - if (!webkit) lineSpace.draggable = true; + if (!webkit) scroller.draggable = true; lineSpace.style.outline = "none"; if (options.tabindex != null) input.tabIndex = options.tabindex; + if (options.autofocus) focusInput(); if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + // Needed to handle Tab key in KHTML + if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; - // Check for problem with IE innerHTML not working when we have a - // P (or similar) parent node. - try { stringWidth("x"); } - catch (e) { - if (e.message.match(/runtime/i)) - e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)"); - throw e; - } + // Check for OS X >= 10.7. This has transparent scrollbars, so the + // overlaying of one scrollbar with another won't work. This is a + // temporary hack to simply turn off the overlay scrollbar. See + // issue #727. + if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; } + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + else if (ie_lt8) scrollbar.style.minWidth = "18px"; // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. var poll = new Delayed(), highlight = new Delayed(), blinker; // mode holds a mode API object. doc is the tree of Line objects, - // work an array of lines that should be parsed, and history the - // undo history (instance of History constructor). - var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused; + // frontier is the point up to which the content has been parsed, + // and history the undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), frontier = 0, focused; loadMode(); // The selection. These are always maintained to point at valid // positions. Inverted is used to remember that the user is @@ -73,20 +74,22 @@ var CodeMirror = (function() { var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; // Selection-related flags. shiftSelecting obviously tracks // whether the user is holding shift. - var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText, - overwrite = false, suppressEdits = false; + var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText, + overwrite = false, suppressEdits = false, pasteIncoming = false; // Variables used by startOperation/endOperation to track what // happened during the operation. - var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, + var updateInput, userSelChange, changes, textChanged, selectionChanged, gutterDirty, callbacks; // Current visible range (may be bigger than the view window). var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; - // bracketHighlighted is used to remember that a backet has been + // bracketHighlighted is used to remember that a bracket has been // marked. var bracketHighlighted; // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. - var maxLine = "", maxWidth, tabText = computeTabText(); + var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true; + var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll + var goalColumn = null; // Initialize the content. operation(function(){setValue(options.value || ""); updateInput = false;})(); @@ -95,19 +98,18 @@ var CodeMirror = (function() { // Register our event handlers. connect(scroller, "mousedown", operation(onMouseDown)); connect(scroller, "dblclick", operation(onDoubleClick)); - connect(lineSpace, "dragstart", onDragStart); connect(lineSpace, "selectstart", e_preventDefault); // Gecko browsers fire contextmenu *after* opening the menu, at // which point we can't mess with it anymore. Context menu is // handled in onMouseDown for Gecko. if (!gecko) connect(scroller, "contextmenu", onContextMenu); - connect(scroller, "scroll", function() { - lastScrollPos = scroller.scrollTop; - updateDisplay([]); - if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; - if (options.onScroll) options.onScroll(instance); - }); - connect(window, "resize", function() {updateDisplay(true);}); + connect(scroller, "scroll", onScrollMain); + connect(scrollbar, "scroll", onScrollBar); + connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);}); + var resizeHandler = connect(window, "resize", function() { + if (wrapper.parentNode) updateDisplay(true); + else resizeHandler(); + }, true); connect(input, "keyup", operation(onKeyUp)); connect(input, "input", fastPoll); connect(input, "keydown", operation(onKeyDown)); @@ -115,19 +117,32 @@ var CodeMirror = (function() { connect(input, "focus", onFocus); connect(input, "blur", onBlur); - connect(scroller, "dragenter", e_stop); - connect(scroller, "dragover", e_stop); - connect(scroller, "drop", operation(onDrop)); + function drag_(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; + e_stop(e); + } + if (options.dragDrop) { + connect(scroller, "dragstart", onDragStart); + connect(scroller, "dragenter", drag_); + connect(scroller, "dragover", drag_); + connect(scroller, "drop", operation(onDrop)); + } connect(scroller, "paste", function(){focusInput(); fastPoll();}); - connect(input, "paste", fastPoll); + connect(input, "paste", function(){pasteIncoming = true; fastPoll();}); connect(input, "cut", operation(function(){ if (!options.readOnly) replaceSelection(""); })); + // Needed to handle Tab key in KHTML + if (khtml) connect(sizer, "mouseup", function() { + if (document.activeElement == input) input.blur(); + focusInput(); + }); + // IE throws unspecified error in certain cases, when // trying to access activeElement before onload - var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { } - if (hasFocus) setTimeout(onFocus, 20); + var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { } + if (hasFocus || options.autofocus) setTimeout(onFocus, 20); else onBlur(); function isLine(l) {return l >= 0 && l < doc.size;} @@ -141,7 +156,7 @@ var CodeMirror = (function() { setValue: operation(setValue), getSelection: getSelection, replaceSelection: operation(replaceSelection), - focus: function(){focusInput(); onFocus(); fastPoll();}, + focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();}, setOption: function(option, value) { var oldVal = options[option]; options[option] = value; @@ -150,11 +165,17 @@ var CodeMirror = (function() { else if (option == "readOnly" && !value) {resetInput(true);} else if (option == "theme") themeChanged(); else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); - else if (option == "tabSize") operation(tabsChanged)(); - if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") + else if (option == "tabSize") updateDisplay(true); + else if (option == "keyMap") keyMapChanged(); + else if (option == "tabindex") input.tabIndex = value; + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || + option == "theme" || option == "lineNumberFormatter") { + gutterChanged(); updateDisplay(true); + } }, getOption: function(option) {return options[option];}, + getMode: function() {return mode;}, undo: operation(undo), redo: operation(redo), indentLine: operation(function(n, dir) { @@ -167,26 +188,52 @@ var CodeMirror = (function() { indentSelection: operation(indentSelected), historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, clearHistory: function() {history = new History();}, + setHistory: function(histData) { + history = new History(); + history.done = histData.done; + history.undone = histData.undone; + }, + getHistory: function() { + function cp(arr) { + for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { + nw.push(nwelt = []); + for (var j = 0, elt = arr[i]; j < elt.length; ++j) { + var old = [], cur = elt[j]; + nwelt.push({start: cur.start, added: cur.added, old: old}); + for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k])); + } + } + return nw; + } + return {done: cp(history.done), undone: cp(history.undone)}; + }, matchBrackets: operation(function(){matchBrackets(true);}), getTokenAt: operation(function(pos) { pos = clipPos(pos); - return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch); + return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch); }), getStateAfter: function(line) { line = clipLine(line == null ? doc.size - 1: line); return getStateBefore(line + 1); }, - cursorCoords: function(start){ + cursorCoords: function(start, mode) { if (start == null) start = sel.inverted; - return pageCoords(start ? sel.from : sel.to); + return this.charCoords(start ? sel.from : sel.to, mode); + }, + charCoords: function(pos, mode) { + pos = clipPos(pos); + if (mode == "local") return localCoords(pos, false); + if (mode == "div") return localCoords(pos, true); + return pageCoords(pos); }, - charCoords: function(pos){return pageCoords(clipPos(pos));}, coordsChar: function(coords) { var off = eltOffset(lineSpace); return coordsChar(coords.x - off.left, coords.y - off.top); }, + defaultTextHeight: function() { return textHeight(); }, markText: operation(markText), setBookmark: setBookmark, + findMarksAt: findMarksAt, setMarker: operation(addGutterMarker), clearMarker: operation(removeGutterMarker), setLineClass: operation(setLineClass), @@ -201,15 +248,16 @@ var CodeMirror = (function() { return line; }, lineInfo: lineInfo, + getViewport: function() { return {from: showingFrom, to: showingTo};}, addWidget: function(pos, node, scroll, vert, horiz) { pos = localCoords(clipPos(pos)); var top = pos.yBot, left = pos.x; node.style.position = "absolute"; - code.appendChild(node); + sizer.appendChild(node); if (vert == "over") top = pos.y; else if (vert == "near") { var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), - hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); + hspace = Math.max(sizer.clientWidth, lineSpace.clientWidth) - paddingLeft(); if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) top = pos.y - node.offsetHeight; if (left + node.offsetWidth > hspace) @@ -218,11 +266,11 @@ var CodeMirror = (function() { node.style.top = (top + paddingTop()) + "px"; node.style.left = node.style.right = ""; if (horiz == "right") { - left = code.clientWidth - node.offsetWidth; + left = sizer.clientWidth - node.offsetWidth; node.style.right = "0px"; } else { if (horiz == "left") left = 0; - else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2; + else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2; node.style.left = (left + paddingLeft()) + "px"; } if (scroll) @@ -252,14 +300,23 @@ var CodeMirror = (function() { if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); }), replaceRange: operation(replaceRange), - getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, + getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);}, + triggerOnKeyDown: operation(onKeyDown), execCommand: function(cmd) {return commands[cmd](instance);}, // Stuff used by commands, probably not much use to outside code. moveH: operation(moveH), deleteH: operation(deleteH), moveV: operation(moveV), - toggleOverwrite: function() {overwrite = !overwrite;}, + toggleOverwrite: function() { + if(overwrite){ + overwrite = false; + cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); + } else { + overwrite = true; + cursor.className += " CodeMirror-overwrite"; + } + }, posFromIndex: function(off) { var lineNo = 0, ch; @@ -280,16 +337,35 @@ var CodeMirror = (function() { return index; }, scrollTo: function(x, y) { - if (x != null) scroller.scrollTop = x; - if (y != null) scroller.scrollLeft = y; + if (x != null) scroller.scrollLeft = x; + if (y != null) scrollbar.scrollTop = scroller.scrollTop = y; updateDisplay([]); }, + getScrollInfo: function() { + return {x: scroller.scrollLeft, y: scrollbar.scrollTop, + height: scrollbar.scrollHeight, width: scroller.scrollWidth}; + }, + scrollIntoView: function(pos) { + var coords = localCoords(pos ? clipPos(pos) : sel.inverted ? sel.from : sel.to); + scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); + }, + + setSize: function(width, height) { + function interpret(val) { + val = String(val); + return /^\d+$/.test(val) ? val + "px" : val; + } + if (width != null) wrapper.style.width = interpret(width); + if (height != null) scroller.style.height = interpret(height); + instance.refresh(); + }, operation: function(f){return operation(f)();}, + compoundChange: function(f){return compoundChange(f);}, refresh: function(){ - updateDisplay(true); - if (scroller.scrollHeight > lastScrollPos) - scroller.scrollTop = lastScrollPos; + updateDisplay(true, null, lastScrollTop); + if (scrollbar.scrollHeight > lastScrollTop) + scrollbar.scrollTop = lastScrollTop; }, getInputField: function(){return input;}, getWrapperElement: function(){return wrapper;}, @@ -304,23 +380,48 @@ var CodeMirror = (function() { for (var n = line; n; n = n.parent) n.height += diff; } + function lineContent(line, wrapAt) { + if (!line.styles) + line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize); + return line.getContent(options.tabSize, wrapAt, options.lineWrapping); + } + function setValue(code) { var top = {line: 0, ch: 0}; updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, splitLines(code), top, top); updateInput = true; } - function getValue(code) { + function getValue(lineSep) { var text = []; doc.iter(0, doc.size, function(line) { text.push(line.text); }); - return text.join("\n"); + return text.join(lineSep || "\n"); + } + + function onScrollBar(e) { + if (Math.abs(scrollbar.scrollTop - lastScrollTop) > 1) { + lastScrollTop = scroller.scrollTop = scrollbar.scrollTop; + updateDisplay([]); + } + } + + function onScrollMain(e) { + if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px") + gutter.style.left = scroller.scrollLeft + "px"; + if (Math.abs(scroller.scrollTop - lastScrollTop) > 1) { + lastScrollTop = scroller.scrollTop; + if (scrollbar.scrollTop != lastScrollTop) + scrollbar.scrollTop = lastScrollTop; + updateDisplay([]); + } + if (options.onScroll) options.onScroll(instance); } function onMouseDown(e) { setShift(e_prop(e, "shiftKey")); // Check whether this is a click in a widget for (var n = e_target(e); n != wrapper; n = n.parentNode) - if (n.parentNode == code && n != mover) return; + if (n.parentNode == sizer && n != mover) return; // See if this is a click in the gutter for (var n = e_target(e); n != wrapper; n = n.parentNode) @@ -334,10 +435,12 @@ var CodeMirror = (function() { switch (e_button(e)) { case 3: - if (gecko && !mac) onContextMenu(e); + if (gecko) onContextMenu(e); return; case 2: if (start) setCursor(start.line, start.ch, true); + setTimeout(focusInput, 20); + e_preventDefault(e); return; } // For button 1, if it was clicked inside the editor @@ -347,44 +450,66 @@ var CodeMirror = (function() { if (!focused) onFocus(); - var now = +new Date; + var now = +new Date, type = "single"; if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { + type = "triple"; e_preventDefault(e); setTimeout(focusInput, 20); - return selectLine(start.line); + selectLine(start.line); } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + type = "double"; lastDoubleClick = {time: now, pos: start}; e_preventDefault(e); - return selectWordAt(start); + var word = findWordAt(start); + setSelectionUser(word.from, word.to); } else { lastClick = {time: now, pos: start}; } + function dragEnd(e2) { + if (webkit) scroller.draggable = false; + draggingText = false; + up(); drop(); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + setCursor(start.line, start.ch, true); + focusInput(); + } + } var last = start, going; - if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && - !posLess(start, sel.from) && !posLess(sel.to, start)) { + if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && + !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { // Let the drag handler handle this. - if (webkit) lineSpace.draggable = true; - var up = connect(targetDocument, "mouseup", operation(function(e2) { - if (webkit) lineSpace.draggable = false; - draggingText = false; - up(); - if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { - e_preventDefault(e2); - setCursor(start.line, start.ch, true); - focusInput(); - } - }), true); + if (webkit) scroller.draggable = true; + var up = connect(document, "mouseup", operation(dragEnd), true); + var drop = connect(scroller, "drop", operation(dragEnd), true); draggingText = true; + // IE's approach to draggable + if (scroller.dragDrop) scroller.dragDrop(); return; } e_preventDefault(e); - setCursor(start.line, start.ch, true); + if (type == "single") setCursor(start.line, start.ch, true); + + var startstart = sel.from, startend = sel.to; + + function doSelect(cur) { + if (type == "single") { + setSelectionUser(start, cur); + } else if (type == "double") { + var word = findWordAt(cur); + if (posLess(cur, startstart)) setSelectionUser(word.from, startend); + else setSelectionUser(startstart, word.to); + } else if (type == "triple") { + if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0})); + else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0})); + } + } function extend(e) { var cur = posFromMouse(e, true); if (cur && !posEq(cur, last)) { if (!focused) onFocus(); last = cur; - setSelectionUser(start, cur); + doSelect(cur); updateInput = false; var visible = visibleLines(); if (cur.line >= visible.to || cur.line < visible.from) @@ -392,120 +517,156 @@ var CodeMirror = (function() { } } - var move = connect(targetDocument, "mousemove", operation(function(e) { - clearTimeout(going); - e_preventDefault(e); - extend(e); - }), true); - var up = connect(targetDocument, "mouseup", operation(function(e) { + function done(e) { clearTimeout(going); var cur = posFromMouse(e); - if (cur) setSelectionUser(start, cur); + if (cur) doSelect(cur); e_preventDefault(e); focusInput(); updateInput = true; move(); up(); + } + var move = connect(document, "mousemove", operation(function(e) { + clearTimeout(going); + e_preventDefault(e); + if (!ie && !e_button(e)) done(e); + else extend(e); }), true); + var up = connect(document, "mouseup", operation(done), true); } function onDoubleClick(e) { for (var n = e_target(e); n != wrapper; n = n.parentNode) if (n.parentNode == gutterText) return e_preventDefault(e); - var start = posFromMouse(e); - if (!start) return; - lastDoubleClick = {time: +new Date, pos: start}; e_preventDefault(e); - selectWordAt(start); } function onDrop(e) { - e.preventDefault(); + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; + e_preventDefault(e); var pos = posFromMouse(e, true), files = e.dataTransfer.files; if (!pos || options.readOnly) return; if (files && files.length && window.FileReader && window.File) { - function loadFile(file, i) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { var reader = new FileReader; reader.onload = function() { text[i] = reader.result; if (++read == n) { - pos = clipPos(pos); - operation(function() { + pos = clipPos(pos); + operation(function() { var end = replaceRange(text.join(""), pos, pos); setSelectionUser(pos, end); })(); - } + } }; reader.readAsText(file); - } - var n = files.length, text = Array(n), read = 0; + }; for (var i = 0; i < n; ++i) loadFile(files[i], i); - } - else { + } else { + // Don't do a replace if the drop happened inside of the selected text. + if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return; try { var text = e.dataTransfer.getData("Text"); if (text) { - var curFrom = sel.from, curTo = sel.to; - setSelectionUser(pos, pos); - if (draggingText) replaceRange("", curFrom, curTo); - replaceSelection(text); - focusInput(); - } + compoundChange(function() { + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); + if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); + focusInput(); + }); + } } catch(e){} } } function onDragStart(e) { var txt = getSelection(); - // This will reset escapeElement - htmlEscape(txt); - e.dataTransfer.setDragImage(escapeElement, 0, 0); e.dataTransfer.setData("Text", txt); + + // Use dummy image instead of default browsers image. + if (e.dataTransfer.setDragImage) + e.dataTransfer.setDragImage(elt('img'), 0, 0); } - function handleKeyBinding(e) { - var name = keyNames[e_prop(e, "keyCode")], next = keyMap[options.keyMap].auto, bound, dropShift; - function handleNext() { - return next.call ? next.call(null, instance) : next; - } - if (name == null || e.altGraphKey) { - if (next) options.keyMap = handleNext(); - return null; - } - if (e_prop(e, "altKey")) name = "Alt-" + name; - if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; - if (e_prop(e, "metaKey")) name = "Cmd-" + name; - if (e_prop(e, "shiftKey") && - (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) { - dropShift = true; - } else { - bound = lookupKey(name, options.extraKeys, options.keyMap); - } + + function doHandleBinding(bound, dropShift) { if (typeof bound == "string") { - if (commands.propertyIsEnumerable(bound)) bound = commands[bound]; - else bound = null; + bound = commands[bound]; + if (!bound) return false; } - if (next && (bound || !isModifierKey(e))) options.keyMap = handleNext(); - if (!bound) return false; var prevShift = shiftSelecting; try { if (options.readOnly) suppressEdits = true; if (dropShift) shiftSelecting = null; bound(instance); + } catch(e) { + if (e != Pass) throw e; + return false; } finally { shiftSelecting = prevShift; suppressEdits = false; } - e_preventDefault(e); return true; } + var maybeTransition; + function handleKeyBinding(e) { + // Handle auto keymap transitions + var startMap = getKeyMap(options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { + if (getKeyMap(options.keyMap) == startMap) { + options.keyMap = (next.call ? next.call(null, instance) : next); + } + }, 50); + + var name = keyNames[e_prop(e, "keyCode")], handled = false; + var flipCtrlCmd = opera && mac; + if (name == null || e.altGraphKey) return false; + if (e_prop(e, "altKey")) name = "Alt-" + name; + if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name; + + var stopped = false; + function stop() { stopped = true; } + + if (e_prop(e, "shiftKey")) { + handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, + function(b) {return doHandleBinding(b, true);}, stop) + || lookupKey(name, options.extraKeys, options.keyMap, function(b) { + if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); + }, stop); + } else { + handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop); + } + if (stopped) handled = false; + if (handled) { + e_preventDefault(e); + restartBlink(); + if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } + } + return handled; + } + function handleCharBinding(e, ch) { + var handled = lookupKey("'" + ch + "'", options.extraKeys, + options.keyMap, function(b) { return doHandleBinding(b, true); }); + if (handled) { + e_preventDefault(e); + restartBlink(); + } + return handled; + } + var lastStoppedKey = null; function onKeyDown(e) { if (!focused) onFocus(); if (ie && e.keyCode == 27) { e.returnValue = false; } + if (pollingFast) { if (readInput()) pollingFast = false; } if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; var code = e_prop(e, "keyCode"); // IE does strange things with escape. setShift(code == 16 || e_prop(e, "shiftKey")); // First give onKeyEvent option a chance to handle this. var handled = handleKeyBinding(e); - if (window.opera) { + if (opera) { lastStoppedKey = handled ? code : null; // Opera has no cut event... we try to at least catch the key combo if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) @@ -513,15 +674,17 @@ var CodeMirror = (function() { } } function onKeyPress(e) { - var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); - if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (pollingFast) readInput(); if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; - if (window.opera && !e.which && handleKeyBinding(e)) return; + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); + if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { - var ch = String.fromCharCode(charCode == null ? keyCode : charCode); if (mode.electricChars.indexOf(ch) > -1) setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); } + if (handleCharBinding(e, ch)) return; fastPoll(); } function onKeyUp(e) { @@ -534,9 +697,8 @@ var CodeMirror = (function() { if (!focused) { if (options.onFocus) options.onFocus(instance); focused = true; - if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) - wrapper.className += " CodeMirror-focused"; - if (!leaveInputAlone) resetInput(true); + if (scroller.className.search(/\bCodeMirror-focused\b/) == -1) + scroller.className += " CodeMirror-focused"; } slowPoll(); restartBlink(); @@ -549,7 +711,7 @@ var CodeMirror = (function() { operation(function(){ if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } })(); - wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); + scroller.className = scroller.className.replace(" CodeMirror-focused", ""); } clearInterval(blinker); setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); @@ -559,136 +721,165 @@ var CodeMirror = (function() { // Afterwards, set the selection to selFrom, selTo. function updateLines(from, to, newText, selFrom, selTo) { if (suppressEdits) return; + var old = []; + doc.iter(from.line, to.line + 1, function(line) { + old.push(newHL(line.text, line.markedSpans)); + }); if (history) { - var old = []; - doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); history.addChange(from.line, newText.length, old); while (history.done.length > options.undoDepth) history.done.shift(); } - updateLinesNoUndo(from, to, newText, selFrom, selTo); + var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); + updateLinesNoUndo(from, to, lines, selFrom, selTo); } - function unredoHelper(from, to, dir) { - var set = from.pop(), len = set ? set.length : 0, out = []; - for (var i = dir > 0 ? 0 : len - 1, e = dir > 0 ? len : -1; i != e; i += dir) { + function unredoHelper(from, to) { + if (!from.length) return; + var set = from.pop(), out = []; + for (var i = set.length - 1; i >= 0; i -= 1) { var change = set[i]; var replaced = [], end = change.start + change.added; - doc.iter(change.start, end, function(line) { replaced.push(line.text); }); + doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); out.push({start: change.start, added: change.old.length, old: replaced}); - var pos = clipPos({line: change.start + change.old.length - 1, - ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); - updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); + var pos = {line: change.start + change.old.length - 1, + ch: editEnd(hlText(lst(replaced)), hlText(lst(change.old)))}; + updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, + change.old, pos, pos); } updateInput = true; to.push(out); } - function undo() {unredoHelper(history.done, history.undone, -1);} - function redo() {unredoHelper(history.undone, history.done, 1);} + function undo() {unredoHelper(history.done, history.undone);} + function redo() {unredoHelper(history.undone, history.done);} - function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + function updateLinesNoUndo(from, to, lines, selFrom, selTo) { if (suppressEdits) return; - var recomputeMaxLength = false, maxLineLength = maxLine.length; + var recomputeMaxLength = false, maxLineLength = maxLine.text.length; if (!options.lineWrapping) - doc.iter(from.line, to.line, function(line) { - if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} + doc.iter(from.line, to.line + 1, function(line) { + if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} }); - if (from.line != to.line || newText.length > 1) gutterDirty = true; + if (from.line != to.line || lines.length > 1) gutterDirty = true; var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); - // First adjust the line structure, taking some care to leave highlighting intact. - if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") { + var lastHL = lst(lines); + + // First adjust the line structure + if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. var added = [], prevLine = null; - if (from.line) { - prevLine = getLine(from.line - 1); - prevLine.fixMarkEnds(lastLine); - } else lastLine.fixMarkStarts(); - for (var i = 0, e = newText.length - 1; i < e; ++i) - added.push(Line.inheritMarks(newText[i], prevLine)); + for (var i = 0, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); + lastLine.update(lastLine.text, hlSpans(lastHL)); if (nlines) doc.remove(from.line, nlines, callbacks); if (added.length) doc.insert(from.line, added); } else if (firstLine == lastLine) { - if (newText.length == 1) - firstLine.replace(from.ch, to.ch, newText[0]); - else { - lastLine = firstLine.split(to.ch, newText[newText.length-1]); - firstLine.replace(from.ch, null, newText[0]); - firstLine.fixMarkEnds(lastLine); - var added = []; - for (var i = 1, e = newText.length - 1; i < e; ++i) - added.push(Line.inheritMarks(newText[i], firstLine)); - added.push(lastLine); + if (lines.length == 1) { + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0])); + } else { + for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); + added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL))); + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); doc.insert(from.line + 1, added); } - } else if (newText.length == 1) { - firstLine.replace(from.ch, null, newText[0]); - lastLine.replace(null, to.ch, ""); - firstLine.append(lastLine); + } else if (lines.length == 1) { + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0])); doc.remove(from.line + 1, nlines, callbacks); } else { var added = []; - firstLine.replace(from.ch, null, newText[0]); - lastLine.replace(null, to.ch, newText[newText.length-1]); - firstLine.fixMarkEnds(lastLine); - for (var i = 1, e = newText.length - 1; i < e; ++i) - added.push(Line.inheritMarks(newText[i], firstLine)); + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); + lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); + for (var i = 1, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); doc.insert(from.line + 1, added); } if (options.lineWrapping) { - var perLine = scroller.clientWidth / charWidth() - 3; - doc.iter(from.line, from.line + newText.length, function(line) { + var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3); + doc.iter(from.line, from.line + lines.length, function(line) { if (line.hidden) return; var guess = Math.ceil(line.text.length / perLine) || 1; if (guess != line.height) updateLineHeight(line, guess); }); } else { - doc.iter(from.line, i + newText.length, function(line) { + doc.iter(from.line, from.line + lines.length, function(line) { var l = line.text; - if (l.length > maxLineLength) { - maxLine = l; maxLineLength = l.length; maxWidth = null; + if (!line.hidden && l.length > maxLineLength) { + maxLine = line; maxLineLength = l.length; maxLineChanged = true; recomputeMaxLength = false; } }); - if (recomputeMaxLength) { - maxLineLength = 0; maxLine = ""; maxWidth = null; - doc.iter(0, doc.size, function(line) { - var l = line.text; - if (l.length > maxLineLength) { - maxLineLength = l.length; maxLine = l; - } - }); - } + if (recomputeMaxLength) updateMaxLine = true; } - // Add these lines to the work array, so that they will be - // highlighted. Adjust work lines if lines were added/removed. - var newWork = [], lendiff = newText.length - nlines - 1; - for (var i = 0, l = work.length; i < l; ++i) { - var task = work[i]; - if (task < from.line) newWork.push(task); - else if (task > to.line) newWork.push(task + lendiff); - } - var hlEnd = from.line + Math.min(newText.length, 500); - highlightLines(from.line, hlEnd); - newWork.push(hlEnd); - work = newWork; - startWorker(100); + // Adjust frontier, schedule worker + frontier = Math.min(frontier, from.line); + startWorker(400); + + var lendiff = lines.length - nlines - 1; // Remember that these lines changed, for updating the display changes.push({from: from.line, to: to.line + 1, diff: lendiff}); - var changeObj = {from: from, to: to, text: newText}; - if (textChanged) { - for (var cur = textChanged; cur.next; cur = cur.next) {} - cur.next = changeObj; - } else textChanged = changeObj; + if (options.onChange) { + // Normalize lines to contain only strings, since that's what + // the change event handler expects + for (var i = 0; i < lines.length; ++i) + if (typeof lines[i] != "string") lines[i] = lines[i].text; + var changeObj = {from: from, to: to, text: lines}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else textChanged = changeObj; + } // Update the selection function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} - setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); + setSelection(clipPos(selFrom), clipPos(selTo), + updateLine(sel.from.line), updateLine(sel.to.line)); + } - // Make sure the scroll-size div has the correct height. - if (scroller.clientHeight) - code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px"; + function needsScrollbar() { + var realHeight = doc.height * textHeight() + 2 * paddingTop(); + return realHeight * .99 > scroller.offsetHeight ? realHeight : false; + } + + function updateVerticalScroll(scrollTop) { + var scrollHeight = needsScrollbar(); + scrollbar.style.display = scrollHeight ? "block" : "none"; + if (scrollHeight) { + scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px"; + scrollbar.style.height = scroller.clientHeight + "px"; + if (scrollTop != null) { + scrollbar.scrollTop = scroller.scrollTop = scrollTop; + // 'Nudge' the scrollbar to work around a Webkit bug where, + // in some situations, we'd end up with a scrollbar that + // reported its scrollTop (and looked) as expected, but + // *behaved* as if it was still in a previous state (i.e. + // couldn't scroll up, even though it appeared to be at the + // bottom). + if (webkit) setTimeout(function() { + if (scrollbar.scrollTop != scrollTop) return; + scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1); + scrollbar.scrollTop = scrollTop; + }, 0); + } + } else { + sizer.style.minHeight = ""; + } + // Position the mover div to align with the current virtual scroll position + mover.style.top = displayOffset * textHeight() + "px"; + } + + function computeMaxLength() { + maxLine = getLine(0); maxLineChanged = true; + var maxLineLength = maxLine.text.length; + doc.iter(1, doc.size, function(line) { + var l = line.text; + if (!line.hidden && l.length > maxLineLength) { + maxLineLength = l.length; maxLine = line; + } + }); + updateMaxLine = false; } function replaceRange(code, from, to) { @@ -701,7 +892,7 @@ var CodeMirror = (function() { var line = pos.line + code.length - (to.line - from.line) - 1; var ch = pos.ch; if (pos.line == to.line) - ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0)); + ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0)); return {line: line, ch: ch}; } var end; @@ -719,42 +910,37 @@ var CodeMirror = (function() { }); } function replaceRange1(code, from, to, computeSel) { - var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length; + var endch = code.length == 1 ? code[0].length + from.ch : lst(code).length; var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); updateLines(from, to, code, newSel.from, newSel.to); } - function getRange(from, to) { + function getRange(from, to, lineSep) { var l1 = from.line, l2 = to.line; if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); var code = [getLine(l1).text.slice(from.ch)]; doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); code.push(getLine(l2).text.slice(0, to.ch)); - return code.join("\n"); + return code.join(lineSep || "\n"); } - function getSelection() { - return getRange(sel.from, sel.to); + function getSelection(lineSep) { + return getRange(sel.from, sel.to, lineSep); } - var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll function slowPoll() { if (pollingFast) return; poll.set(options.pollInterval, function() { - startOperation(); readInput(); if (focused) slowPoll(); - endOperation(); }); } function fastPoll() { var missed = false; pollingFast = true; function p() { - startOperation(); var changed = readInput(); if (!changed && !missed) {missed = true; poll.set(60, p);} else {pollingFast = false; slowPoll();} - endOperation(); } poll.set(20, p); } @@ -766,25 +952,29 @@ var CodeMirror = (function() { // supported or compatible enough yet to rely on.) var prevInput = ""; function readInput() { - if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false; + if (!focused || hasSelection(input) || options.readOnly) return false; var text = input.value; if (text == prevInput) return false; + if (!nestedOperation) startOperation(); shiftSelecting = null; var same = 0, l = Math.min(prevInput.length, text.length); while (same < l && prevInput[same] == text[same]) ++same; if (same < prevInput.length) sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; - else if (overwrite && posEq(sel.from, sel.to)) + else if (overwrite && posEq(sel.from, sel.to) && !pasteIncoming) sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; replaceSelection(text.slice(same), "end"); - prevInput = text; + if (text.length > 1000) { input.value = prevInput = ""; } + else prevInput = text; + if (!nestedOperation) endOperation(); + pasteIncoming = false; return true; } function resetInput(user) { if (!posEq(sel.from, sel.to)) { prevInput = ""; input.value = getSelection(); - input.select(); + if (focused) selectInput(input); } else if (user) prevInput = input.value = ""; } @@ -792,61 +982,79 @@ var CodeMirror = (function() { if (options.readOnly != "nocursor") input.focus(); } - function scrollEditorIntoView() { - if (!cursor.getBoundingClientRect) return; - var rect = cursor.getBoundingClientRect(); - // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden - if (ie && rect.top == rect.bottom) return; - var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); - if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); - } function scrollCursorIntoView() { + var coords = calculateCursorCoords(); + scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); + if (!focused) return; + var box = sizer.getBoundingClientRect(), doScroll = null; + if (coords.y + box.top < 0) doScroll = true; + else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null) { + var hidden = cursor.style.display == "none"; + if (hidden) { + cursor.style.display = ""; + cursor.style.left = coords.x + "px"; + cursor.style.top = (coords.y - displayOffset) + "px"; + } + cursor.scrollIntoView(doScroll); + if (hidden) cursor.style.display = "none"; + } + } + function calculateCursorCoords() { var cursor = localCoords(sel.inverted ? sel.from : sel.to); var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; - return scrollIntoView(x, cursor.y, x, cursor.yBot); + return {x: x, y: cursor.y, yBot: cursor.yBot}; } function scrollIntoView(x1, y1, x2, y2) { - var pl = paddingLeft(), pt = paddingTop(), lh = textHeight(); + var scrollPos = calculateScrollPos(x1, y1, x2, y2); + if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;} + if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;} + } + function calculateScrollPos(x1, y1, x2, y2) { + var pl = paddingLeft(), pt = paddingTop(); y1 += pt; y2 += pt; x1 += pl; x2 += pl; - var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; - if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;} - else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;} + var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {}; + var docBottom = needsScrollbar() || Infinity; + var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; + if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); + else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; var gutterw = options.fixedGutter ? gutter.clientWidth : 0; - if (x1 < screenleft + gutterw) { - if (x1 < 50) x1 = 0; - scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); - scrolled = true; + var atLeft = x1 < gutterw + pl + 10; + if (x1 < screenleft + gutterw || atLeft) { + if (atLeft) x1 = 0; + result.scrollLeft = Math.max(0, x1 - 10 - gutterw); + } else if (x2 > screenw + screenleft - 3) { + result.scrollLeft = x2 + 10 - screenw; } - else if (x2 > screenw + screenleft - 3) { - scroller.scrollLeft = x2 + 10 - screenw; - scrolled = true; - if (x2 > code.clientWidth) result = false; - } - if (scrolled && options.onScroll) options.onScroll(instance); return result; } - function visibleLines() { - var lh = textHeight(), top = scroller.scrollTop - paddingTop(); - var from_height = Math.max(0, Math.floor(top / lh)); - var to_height = Math.ceil((top + scroller.clientHeight) / lh); - return {from: lineAtHeight(doc, from_height), - to: lineAtHeight(doc, to_height)}; + function visibleLines(scrollTop) { + var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop(); + var fromHeight = Math.max(0, Math.floor(top / lh)); + var toHeight = Math.ceil((top + scroller.clientHeight) / lh); + return {from: lineAtHeight(doc, fromHeight), + to: lineAtHeight(doc, toHeight)}; } // Uses a set of changes plus the current scroll position to // determine which DOM updates have to be made, and makes the // updates. - function updateDisplay(changes, suppressCallback) { + function updateDisplay(changes, suppressCallback, scrollTop) { if (!scroller.clientWidth) { showingFrom = showingTo = displayOffset = 0; return; } // Compute the new visible window - var visible = visibleLines(); + // If scrollTop is specified, use that to determine which lines + // to render instead of the current scrollbar position. + var visible = visibleLines(scrollTop); // Bail out if the visible area is already rendered and nothing changed. - if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return; + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) { + updateVerticalScroll(scrollTop); + return; + } var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); if (showingFrom < from && from - showingFrom < 20) from = showingFrom; if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); @@ -864,7 +1072,10 @@ var CodeMirror = (function() { if (range.from >= range.to) intact.splice(i--, 1); else intactLines += range.to - range.from; } - if (intactLines == to - from) return; + if (intactLines == to - from && from == showingFrom && to == showingTo) { + updateVerticalScroll(scrollTop); + return; + } intact.sort(function(a, b) {return a.domStart - b.domStart;}); var th = textHeight(), gutterDisplay = gutter.style.display; @@ -872,17 +1083,17 @@ var CodeMirror = (function() { patchDisplay(from, to, intact); lineDiv.style.display = gutter.style.display = ""; - // Position the mover div to align with the lines it's supposed - // to be showing (which will cover the visible display) var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; // This is just a bogus formula that detects when the editor is // resized or the font size changes. if (different) lastSizeC = scroller.clientHeight + th; + if (from != showingFrom || to != showingTo && options.onViewportChange) + setTimeout(function(){ + if (options.onViewportChange) options.onViewportChange(instance, from, to); + }); showingFrom = from; showingTo = to; displayOffset = heightAtLine(doc, from); - mover.style.top = (displayOffset * th) + "px"; - if (scroller.clientHeight) - code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + startWorker(100); // Since this is all rather error prone, it is honoured with the // only assertion in the whole file. @@ -890,10 +1101,13 @@ var CodeMirror = (function() { throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + " nodes=" + lineDiv.childNodes.length); - if (options.lineWrapping) { - maxWidth = scroller.clientWidth; + function checkHeights() { var curNode = lineDiv.firstChild, heightChanged = false; doc.iter(showingFrom, showingTo, function(line) { + // Work around bizarro IE7 bug where, sometimes, our curNode + // is magically replaced with a new node in the DOM, leaving + // us with a reference to an orphan (nextSibling-less) node. + if (!curNode) return; if (!line.hidden) { var height = Math.round(curNode.offsetHeight / th) || 1; if (line.height != height) { @@ -903,21 +1117,17 @@ var CodeMirror = (function() { } curNode = curNode.nextSibling; }); - if (heightChanged) - code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; - } else { - if (maxWidth == null) maxWidth = stringWidth(maxLine); - if (maxWidth > scroller.clientWidth) { - lineSpace.style.width = maxWidth + "px"; - // Needed to prevent odd wrapping/hiding of widgets placed in here. - code.style.width = ""; - code.style.width = scroller.scrollWidth + "px"; - } else { - lineSpace.style.width = code.style.width = ""; - } + return heightChanged; } + + if (options.lineWrapping) checkHeights(); + gutter.style.display = gutterDisplay; - if (different || gutterDirty) updateGutter(); + if (different || gutterDirty) { + // If the gutter grew in size, re-check heights. If those changed, re-draw gutter. + updateGutter() && options.lineWrapping && checkHeights() && updateGutter(); + } + updateVerticalScroll(scrollTop); updateSelection(); if (!suppressCallback && options.onUpdate) options.onUpdate(instance); return true; @@ -947,14 +1157,14 @@ var CodeMirror = (function() { } function patchDisplay(from, to, intact) { + function killNode(node) { + var tmp = node.nextSibling; + node.parentNode.removeChild(node); + return tmp; + } // The first pass removes the DOM nodes that aren't intact. - if (!intact.length) lineDiv.innerHTML = ""; + if (!intact.length) removeChildren(lineDiv); else { - function killNode(node) { - var tmp = node.nextSibling; - node.parentNode.removeChild(node); - return tmp; - } var domPos = 0, curNode = lineDiv.firstChild, n; for (var i = 0; i < intact.length; ++i) { var cur = intact[i]; @@ -965,20 +1175,20 @@ var CodeMirror = (function() { } // This pass fills in the lines that actually changed. var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; - var scratch = targetDocument.createElement("div"), newElt; doc.iter(from, to, function(line) { if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); if (!nextIntact || nextIntact.from > j) { - if (line.hidden) var html = scratch.innerHTML = "
";
+          if (line.hidden) var lineElement = elt("pre");
           else {
-            var html = '
' + line.getHTML(tabText) + '
'; + var lineElement = lineContent(line); + if (line.className) lineElement.className = line.className; // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.className) - html = '
 
' + html + "
"; + if (line.bgClassName) { + var pre = elt("pre", "\u00a0", line.bgClassName, "position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2"); + lineElement = elt("div", [pre, lineElement], null, "position: relative"); + } } - scratch.innerHTML = html; - lineDiv.insertBefore(scratch.firstChild, curNode); + lineDiv.insertBefore(lineElement, curNode); } else { curNode = curNode.nextSibling; } @@ -990,31 +1200,41 @@ var CodeMirror = (function() { if (!options.gutter && !options.lineNumbers) return; var hText = mover.offsetHeight, hEditor = scroller.clientHeight; gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; - var html = [], i = showingFrom; + var fragment = document.createDocumentFragment(), i = showingFrom, normalNode; doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { if (line.hidden) { - html.push("
");
+          fragment.appendChild(elt("pre"));
         } else {
           var marker = line.gutterMarker;
-          var text = options.lineNumbers ? i + options.firstLineNumber : null;
+          var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null;
           if (marker && marker.text)
             text = marker.text.replace("%N%", text != null ? text : "");
           else if (text == null)
             text = "\u00a0";
-          html.push((marker && marker.style ? '
' : "
"), text);
-          for (var j = 1; j < line.height; ++j) html.push("
 "); - html.push("
"); + var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style)); + markerElement.innerHTML = text; + for (var j = 1; j < line.height; ++j) { + markerElement.appendChild(elt("br")); + markerElement.appendChild(document.createTextNode("\u00a0")); + } + if (!marker) normalNode = i; } ++i; }); gutter.style.display = "none"; - gutterText.innerHTML = html.join(""); - var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; - while (val.length + pad.length < minwidth) pad += "\u00a0"; - if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); + removeChildrenAndAdd(gutterText, fragment); + // Make sure scrolling doesn't cause number gutter size to pop + if (normalNode != null && options.lineNumbers) { + var node = gutterText.childNodes[normalNode - showingFrom]; + var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); + } gutter.style.display = ""; + var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; lineSpace.style.marginLeft = gutter.offsetWidth + "px"; gutterDirty = false; + return resized; } function updateSelection() { var collapsed = posEq(sel.from, sel.to); @@ -1030,22 +1250,26 @@ var CodeMirror = (function() { cursor.style.display = ""; selectionDiv.style.display = "none"; } else { - var sameLine = fromPos.y == toPos.y, html = ""; - function add(left, top, right, height) { - html += '
'; - } + var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment(); + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; + var add = function(left, top, right, height) { + var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" + : "right: " + right + "px"; + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; " + rstyle + "; height: " + height + "px")); + }; if (sel.from.ch && fromPos.y >= 0) { - var right = sameLine ? lineSpace.clientWidth - toPos.x : 0; + var right = sameLine ? clientWidth - toPos.x : 0; add(fromPos.x, fromPos.y, right, th); } var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); - var middleHeight = Math.min(toPos.y, lineSpace.clientHeight) - middleStart; + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; if (middleHeight > 0.2 * th) add(0, middleStart, 0, middleHeight); - if ((!sameLine || !sel.from.ch) && toPos.y < lineSpace.clientHeight - .5 * th) - add(0, toPos.y, lineSpace.clientWidth - toPos.x, th); - selectionDiv.innerHTML = html; + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) + add(0, toPos.y, clientWidth - toPos.x, th); + removeChildrenAndAdd(selectionDiv, fragment); cursor.style.display = "none"; selectionDiv.style.display = ""; } @@ -1074,13 +1298,32 @@ var CodeMirror = (function() { if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} // Skip over hidden lines. - if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch); + if (from.line != oldFrom) { + var from1 = skipHidden(from, oldFrom, sel.from.ch); + // If there is no non-hidden line left, force visibility on current line + if (!from1) setLineHidden(from.line, false); + else from = from1; + } if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); if (posEq(from, to)) sel.inverted = false; else if (posEq(from, sel.to)) sel.inverted = false; else if (posEq(to, sel.from)) sel.inverted = true; + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { + var head = sel.inverted ? from : to; + if (head.line != sel.from.line && sel.from.line < doc.size) { + var oldLine = getLine(sel.from.line); + if (/^\s+$/.test(oldLine.text)) + setTimeout(operation(function() { + if (oldLine.parent && /^\s+$/.test(oldLine.text)) { + var no = lineNo(oldLine); + replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); + } + }, 10)); + } + } + sel.from = from; sel.to = to; selectionChanged = true; } @@ -1091,13 +1334,14 @@ var CodeMirror = (function() { var line = getLine(lNo); if (!line.hidden) { var ch = pos.ch; - if (ch > oldCh || ch > line.text.length) ch = line.text.length; + if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; return {line: lNo, ch: ch}; } lNo += dir; } } var line = getLine(pos.line); + var toEnd = pos.ch == line.text.length && pos.ch != oldCh; if (!line.hidden) return pos; if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); else return getNonHidden(-1) || getNonHidden(1); @@ -1157,26 +1401,37 @@ var CodeMirror = (function() { else replaceRange("", sel.from, findPosH(dir, unit)); userSelChange = true; } - var goalColumn = null; function moveV(dir, unit) { var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); if (goalColumn != null) pos.x = goalColumn; - if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); - else if (unit == "line") dist = textHeight(); - var target = coordsChar(pos.x, pos.y + dist * dir + 2); + if (unit == "page") { + var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); + var target = coordsChar(pos.x, pos.y + screen * dir); + } else if (unit == "line") { + var th = textHeight(); + var target = coordsChar(pos.x, pos.y + .5 * th + dir * th); + } + if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y; setCursor(target.line, target.ch, true); goalColumn = pos.x; } - function selectWordAt(pos) { + function findWordAt(pos) { var line = getLine(pos.line).text; var start = pos.ch, end = pos.ch; - while (start > 0 && isWordChar(line.charAt(start - 1))) --start; - while (end < line.length && isWordChar(line.charAt(end))) ++end; - setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); + if (line) { + if (pos.after === false || end == line.length) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar) ? isWordChar : + /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : + function(ch) {return !/\s/.test(ch) && isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; } function selectLine(line) { - setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); } function indentSelected(mode) { if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); @@ -1193,35 +1448,34 @@ var CodeMirror = (function() { var line = getLine(n), curSpace = line.indentation(options.tabSize), curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "smart") { + indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass) how = "prev"; + } if (how == "prev") { if (n) indentation = getLine(n-1).indentation(options.tabSize); else indentation = 0; } - else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); else if (how == "add") indentation = curSpace + options.indentUnit; else if (how == "subtract") indentation = curSpace - options.indentUnit; indentation = Math.max(0, indentation); var diff = indentation - curSpace; - if (!diff) { - if (sel.from.line != n && sel.to.line != n) return; - var indentString = curSpaceString; - } - else { - var indentString = "", pos = 0; - if (options.indentWithTabs) - for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} - while (pos < indentation) {++pos; indentString += " ";} - } + var indentString = "", pos = 0; + if (options.indentWithTabs) + for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); - replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + if (indentString != curSpaceString) + replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + line.stateAfter = null; } function loadMode() { mode = CodeMirror.getMode(options, options.mode); doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); - work = [0]; - startWorker(); + frontier = 0; + startWorker(100); } function gutterChanged() { var visible = options.gutter || options.lineNumbers; @@ -1238,86 +1492,93 @@ var CodeMirror = (function() { var guess = Math.ceil(line.text.length / perLine) || 1; if (guess != 1) updateLineHeight(line, guess); }); - lineSpace.style.width = code.style.width = ""; + lineSpace.style.minWidth = widthForcer.style.left = ""; } else { wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); - maxWidth = null; maxLine = ""; + computeMaxLength(); doc.iter(0, doc.size, function(line) { if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); - if (line.text.length > maxLine.length) maxLine = line.text; }); } changes.push({from: 0, to: doc.size}); } - function computeTabText() { - for (var str = '', i = 0; i < options.tabSize; ++i) str += " "; - return str + ""; - } - function tabsChanged() { - tabText = computeTabText(); - updateDisplay(true); - } function themeChanged() { - scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") + + scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); } + function keyMapChanged() { + var style = keyMap[options.keyMap].style; + wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); + } - function TextMarker() { this.set = []; } + function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; } TextMarker.prototype.clear = operation(function() { - var min = Infinity, max = -Infinity; - for (var i = 0, e = this.set.length; i < e; ++i) { - var line = this.set[i], mk = line.marked; - if (!mk || !line.parent) continue; - var lineN = lineNo(line); - min = Math.min(min, lineN); max = Math.max(max, lineN); - for (var j = 0; j < mk.length; ++j) - if (mk[j].set == this.set) mk.splice(j--, 1); + var min, max; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) min = lineNo(line); + if (span.to != null) max = lineNo(line); + line.markedSpans = removeMarkedSpan(line.markedSpans, span); } - if (min != Infinity) - changes.push({from: min, to: max + 1}); + if (min != null) changes.push({from: min, to: max + 1}); + this.lines.length = 0; + this.explicitlyCleared = true; }); TextMarker.prototype.find = function() { var from, to; - for (var i = 0, e = this.set.length; i < e; ++i) { - var line = this.set[i], mk = line.marked; - for (var j = 0; j < mk.length; ++j) { - var mark = mk[j]; - if (mark.set == this.set) { - if (mark.from != null || mark.to != null) { - var found = lineNo(line); - if (found != null) { - if (mark.from != null) from = {line: found, ch: mark.from}; - if (mark.to != null) to = {line: found, ch: mark.to}; - } - } - } + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null || span.to != null) { + var found = lineNo(line); + if (span.from != null) from = {line: found, ch: span.from}; + if (span.to != null) to = {line: found, ch: span.to}; } } - return {from: from, to: to}; + if (this.type == "bookmark") return from; + return from && {from: from, to: to}; }; - function markText(from, to, className) { + function markText(from, to, className, options) { from = clipPos(from); to = clipPos(to); - var tm = new TextMarker(); - function add(line, from, to, className) { - getLine(line).addMark(new MarkedText(from, to, className, tm.set)); - } - if (from.line == to.line) add(from.line, from.ch, to.ch, className); - else { - add(from.line, from.ch, null, className); - for (var i = from.line + 1, e = to.line; i < e; ++i) - add(i, null, null, className); - add(to.line, null, to.ch, className); - } + var marker = new TextMarker("range", className); + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + marker[opt] = options[opt]; + var curLine = from.line; + doc.iter(curLine, to.line + 1, function(line) { + var span = {from: curLine == from.line ? from.ch : null, + to: curLine == to.line ? to.ch : null, + marker: marker}; + line.markedSpans = (line.markedSpans || []).concat([span]); + marker.lines.push(line); + ++curLine; + }); changes.push({from: from.line, to: to.line + 1}); - return tm; + return marker; } function setBookmark(pos) { pos = clipPos(pos); - var bm = new Bookmark(pos.ch); - getLine(pos.line).addMark(bm); - return bm; + var marker = new TextMarker("bookmark"), line = getLine(pos.line); + history.addChange(pos.line, 1, [newHL(line.text, line.markedSpans)], true); + var span = {from: pos.ch, to: pos.ch, marker: marker}; + line.markedSpans = (line.markedSpans || []).concat([span]); + marker.lines.push(line); + return marker; + } + + function findMarksAt(pos) { + pos = clipPos(pos); + var markers = [], spans = getLine(pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker); + } + return markers; } function addGutterMarker(line, text, className) { @@ -1341,10 +1602,11 @@ var CodeMirror = (function() { else return null; return line; } - function setLineClass(handle, className) { + function setLineClass(handle, className, bgClassName) { return changeLine(handle, function(line) { - if (line.className != className) { + if (line.className != className || line.bgClassName != bgClassName) { line.className = className; + line.bgClassName = bgClassName; return true; } }); @@ -1353,11 +1615,20 @@ var CodeMirror = (function() { return changeLine(handle, function(line, no) { if (line.hidden != hidden) { line.hidden = hidden; + if (!options.lineWrapping) { + if (hidden && line.text.length == maxLine.text.length) { + updateMaxLine = true; + } else if (!hidden && line.text.length > maxLine.text.length) { + maxLine = line; updateMaxLine = false; + } + } updateLineHeight(line, hidden ? 0 : 1); var fline = sel.from.line, tline = sel.to.line; if (hidden && (fline == no || tline == no)) { var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + // Can't hide the last visible line, we'd have no place to put the cursor + if (!to) return; setSelection(from, to); } return (gutterDirty = true); @@ -1371,70 +1642,25 @@ var CodeMirror = (function() { var n = line; line = getLine(line); if (!line) return null; - } - else { + } else { var n = lineNo(line); if (n == null) return null; } var marker = line.gutterMarker; return {line: n, handle: line, text: line.text, markerText: marker && marker.text, - markerClass: marker && marker.style, lineClass: line.className}; + markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; } - function stringWidth(str) { - measure.innerHTML = "
x
"; - measure.firstChild.firstChild.firstChild.nodeValue = str; - return measure.firstChild.firstChild.offsetWidth || 10; - } - // These are used to go from pixel positions to character - // positions, taking varying character widths into account. - function charFromX(line, x) { - if (x <= 0) return 0; - var lineObj = getLine(line), text = lineObj.text; - function getX(len) { - measure.innerHTML = "
" + lineObj.getHTML(tabText, len) + "
"; - return measure.firstChild.firstChild.offsetWidth; - } - var from = 0, fromX = 0, to = text.length, toX; - // Guess a suitable upper bound for our search. - var estimated = Math.min(to, Math.ceil(x / charWidth())); - for (;;) { - var estX = getX(estimated); - if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); - else {toX = estX; to = estimated; break;} - } - if (x > toX) return to; - // Try to guess a suitable lower bound as well. - estimated = Math.floor(to * 0.8); estX = getX(estimated); - if (estX < x) {from = estimated; fromX = estX;} - // Do a binary search between these bounds. - for (;;) { - if (to - from <= 1) return (toX - x > x - fromX) ? from : to; - var middle = Math.ceil((from + to) / 2), middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX;} - else {from = middle; fromX = middleX;} - } - } - - var tempId = Math.floor(Math.random() * 0xffffff).toString(16); function measureLine(line, ch) { if (ch == 0) return {top: 0, left: 0}; - var extra = ""; - // Include extra text at the end to make sure the measured line is wrapped in the right way. - if (options.lineWrapping) { - var end = line.text.indexOf(" ", ch + 2); - extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0))); - } - measure.innerHTML = "
" + line.getHTML(tabText, ch) +
-        '' + htmlEscape(line.text.charAt(ch) || " ") + "" +
-        extra + "
"; - var elt = document.getElementById("CodeMirror-temp-" + tempId); - var top = elt.offsetTop, left = elt.offsetLeft; + var pre = lineContent(line, ch); + removeChildrenAndAdd(measure, pre); + var anchor = pre.anchor; + var top = anchor.offsetTop, left = anchor.offsetLeft; // Older IEs report zero offsets for spans directly after a wrap if (ie && top == 0 && left == 0) { - var backup = document.createElement("span"); - backup.innerHTML = "x"; - elt.parentNode.insertBefore(backup, elt.nextSibling); + var backup = elt("span", "x"); + anchor.parentNode.insertBefore(backup, anchor.nextSibling); top = backup.offsetTop; } return {top: top, left: left}; @@ -1451,17 +1677,19 @@ var CodeMirror = (function() { } // Coords must be lineSpace-local function coordsChar(x, y) { - if (y < 0) y = 0; var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); + if (heightPos < 0) return {line: 0, ch: 0}; var lineNo = lineAtHeight(doc, heightPos); if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; var lineObj = getLine(lineNo), text = lineObj.text; var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; + var wrongLine = false; function getX(len) { var sp = measureLine(lineObj, len); if (tw) { var off = Math.round(sp.top / th); + wrongLine = off != innerOff; return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); } return sp.left; @@ -1480,9 +1708,12 @@ var CodeMirror = (function() { if (estX < x) {from = estimated; fromX = estX;} // Do a binary search between these bounds. for (;;) { - if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to}; + if (to - from <= 1) { + var after = x - fromX < toX - x; + return {line: lineNo, ch: after ? from : to, after: after}; + } var middle = Math.ceil((from + to) / 2), middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX;} + if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; } else {from = middle; fromX = middleX;} } } @@ -1491,26 +1722,32 @@ var CodeMirror = (function() { return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; } - var cachedHeight, cachedHeightFor, measureText; + var cachedHeight, cachedHeightFor, measurePre; function textHeight() { - if (measureText == null) { - measureText = "
";
-        for (var i = 0; i < 49; ++i) measureText += "x
"; - measureText += "x
"; + if (measurePre == null) { + measurePre = elt("pre"); + for (var i = 0; i < 49; ++i) { + measurePre.appendChild(document.createTextNode("x")); + measurePre.appendChild(elt("br")); + } + measurePre.appendChild(document.createTextNode("x")); } var offsetHeight = lineDiv.clientHeight; if (offsetHeight == cachedHeightFor) return cachedHeight; cachedHeightFor = offsetHeight; - measure.innerHTML = measureText; + removeChildrenAndAdd(measure, measurePre.cloneNode(true)); cachedHeight = measure.firstChild.offsetHeight / 50 || 1; - measure.innerHTML = ""; + removeChildren(measure); return cachedHeight; } var cachedWidth, cachedWidthFor = 0; function charWidth() { if (scroller.clientWidth == cachedWidthFor) return cachedWidth; cachedWidthFor = scroller.clientWidth; - return (cachedWidth = stringWidth("x")); + var anchor = elt("span", "x"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(measure, pre); + return (cachedWidth = anchor.offsetWidth || 10); } function paddingTop() {return lineSpace.offsetTop;} function paddingLeft() {return lineSpace.offsetLeft;} @@ -1527,9 +1764,10 @@ var CodeMirror = (function() { var offL = eltOffset(lineSpace, true); return coordsChar(x - offL.left, y - offL.top); } + var detectingSelectAll; function onContextMenu(e) { - var pos = posFromMouse(e); - if (!pos || window.opera) return; // Opera is difficult. + var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; + if (!pos || opera) return; // Opera is difficult. if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) operation(setCursor)(pos.line, pos.ch); @@ -1538,18 +1776,30 @@ var CodeMirror = (function() { input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; - leaveInputAlone = true; - var val = input.value = getSelection(); focusInput(); - input.select(); + resetInput(true); + // Adds "Select all" to context menu in FF + if (posEq(sel.from, sel.to)) input.value = prevInput = " "; + function rehide() { - var newVal = splitLines(input.value).join("\n"); - if (newVal != val) operation(replaceSelection)(newVal, "end"); inputDiv.style.position = "relative"; input.style.cssText = oldCSS; - leaveInputAlone = false; - resetInput(true); + if (ie_lt9) scrollbar.scrollTop = scrollPos; slowPoll(); + + // Try to detect the user choosing select-all + if (input.selectionStart != null) { + clearTimeout(detectingSelectAll); + var extval = input.value = " " + (posEq(sel.from, sel.to) ? "" : input.value), i = 0; + prevInput = " "; + input.selectionStart = 1; input.selectionEnd = extval.length; + detectingSelectAll = setTimeout(function poll(){ + if (prevInput == " " && input.selectionStart == 0) + operation(commands.selectAll)(instance); + else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); + else resetInput(); + }, 200); + } } if (gecko) { @@ -1558,8 +1808,7 @@ var CodeMirror = (function() { mouseup(); setTimeout(rehide, 20); }, true); - } - else { + } else { setTimeout(rehide, 50); } } @@ -1571,7 +1820,7 @@ var CodeMirror = (function() { cursor.style.visibility = ""; blinker = setInterval(function() { cursor.style.visibility = (on = !on) ? "" : "hidden"; - }, 650); + }, options.cursorBlinkRate); } var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; @@ -1589,7 +1838,7 @@ var CodeMirror = (function() { var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { var text = st[i]; - if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;} + if (st[i+1] != style) {pos += d * text.length; continue;} for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { var match = matching[cur]; @@ -1634,66 +1883,39 @@ var CodeMirror = (function() { return minline; } function getStateBefore(n) { - var start = findStartLine(n), state = start && getLine(start-1).stateAfter; + var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter; if (!state) state = startState(mode); else state = copyState(mode, state); - doc.iter(start, n, function(line) { - line.highlight(mode, state, options.tabSize); - line.stateAfter = copyState(mode, state); + doc.iter(pos, n, function(line) { + line.process(mode, state, options.tabSize); + line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null; }); - if (start < n) changes.push({from: start, to: n}); - if (n < doc.size && !getLine(n).stateAfter) work.push(n); return state; } - function highlightLines(start, end) { - var state = getStateBefore(start); - doc.iter(start, end, function(line) { - line.highlight(mode, state, options.tabSize); - line.stateAfter = copyState(mode, state); - }); - } function highlightWorker() { - var end = +new Date + options.workTime; - var foundWork = work.length; - while (work.length) { - if (!getLine(showingFrom).stateAfter) var task = showingFrom; - else var task = work.pop(); - if (task >= doc.size) continue; - var start = findStartLine(task), state = start && getLine(start-1).stateAfter; - if (state) state = copyState(mode, state); - else state = startState(mode); - - var unchanged = 0, compare = mode.compareStates, realChange = false, - i = start, bail = false; - doc.iter(i, doc.size, function(line) { - var hadState = line.stateAfter; - if (+new Date > end) { - work.push(i); - startWorker(options.workDelay); - if (realChange) changes.push({from: task, to: i + 1}); - return (bail = true); - } - var changed = line.highlight(mode, state, options.tabSize); - if (changed) realChange = true; + if (frontier >= showingTo) return; + var end = +new Date + options.workTime, state = copyState(mode, getStateBefore(frontier)); + var startFrontier = frontier; + doc.iter(frontier, showingTo, function(line) { + if (frontier >= showingFrom) { // Visible + line.highlight(mode, state, options.tabSize); line.stateAfter = copyState(mode, state); - if (compare) { - if (hadState && compare(hadState, state)) return true; - } else { - if (changed !== false || !hadState) unchanged = 0; - else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) - return true; - } - ++i; - }); - if (bail) return; - if (realChange) changes.push({from: task, to: i + 1}); - } - if (foundWork && options.onHighlightComplete) - options.onHighlightComplete(instance); + } else { + line.process(mode, state, options.tabSize); + line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null; + } + ++frontier; + if (+new Date > end) { + startWorker(options.workDelay); + return true; + } + }); + if (showingTo > startFrontier && frontier >= showingFrom) + operation(function() {changes.push({from: startFrontier, to: frontier});})(); } function startWorker(time) { - if (!work.length) return; - highlight.set(time, operation(highlightWorker)); + if (frontier < showingTo) + highlight.set(time, highlightWorker); } // Operations are used to wrap changes in such a way that each @@ -1705,18 +1927,30 @@ var CodeMirror = (function() { changes = []; selectionChanged = false; callbacks = []; } function endOperation() { - var reScroll = false, updated; - if (selectionChanged) reScroll = !scrollCursorIntoView(); - if (changes.length) updated = updateDisplay(changes, true); - else { + if (updateMaxLine) computeMaxLength(); + if (maxLineChanged && !options.lineWrapping) { + var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left; + if (!ie_lt8) { + widthForcer.style.left = left + "px"; + lineSpace.style.minWidth = (left + cursorWidth) + "px"; + } + maxLineChanged = false; + } + var newScrollPos, updated; + if (selectionChanged) { + var coords = calculateCursorCoords(); + newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot); + } + if (changes.length || newScrollPos && newScrollPos.scrollTop != null) + updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop); + if (!updated) { if (selectionChanged) updateSelection(); if (gutterDirty) updateGutter(); } - if (reScroll) scrollCursorIntoView(); - if (selectionChanged) {scrollEditorIntoView(); restartBlink();} + if (newScrollPos) scrollCursorIntoView(); + if (selectionChanged) restartBlink(); - if (focused && !leaveInputAlone && - (updateInput === true || (updateInput !== false && selectionChanged))) + if (focused && (updateInput === true || (updateInput !== false && selectionChanged))) resetInput(userSelChange); if (selectionChanged && options.matchBrackets) @@ -1724,11 +1958,11 @@ var CodeMirror = (function() { if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} if (posEq(sel.from, sel.to)) matchBrackets(false); }), 20); - var tc = textChanged, cbs = callbacks; // these can be reset by callbacks - if (selectionChanged && options.onCursorActivity) + var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks + if (textChanged && options.onChange && instance) + options.onChange(instance, textChanged); + if (sc && options.onCursorActivity) options.onCursorActivity(instance); - if (tc && options.onChange && instance) - options.onChange(instance, tc); for (var i = 0; i < cbs.length; ++i) cbs[i](instance); if (updated && options.onUpdate) options.onUpdate(instance); } @@ -1742,10 +1976,16 @@ var CodeMirror = (function() { }; } + function compoundChange(f) { + history.startCompound(); + try { return f(); } finally { history.endCompound(); } + } + for (var ext in extensions) if (extensions.propertyIsEnumerable(ext) && !instance.propertyIsEnumerable(ext)) instance[ext] = extensions[ext]; + for (var i = 0; i < initHooks.length; ++i) initHooks[i](instance); return instance; } // (end of function CodeMirror) @@ -1761,26 +2001,31 @@ var CodeMirror = (function() { keyMap: "default", extraKeys: null, electricChars: true, + autoClearEmptyLines: false, onKeyEvent: null, + onDragEvent: null, lineWrapping: false, lineNumbers: false, gutter: false, fixedGutter: false, firstLineNumber: 1, readOnly: false, + dragDrop: true, onChange: null, onCursorActivity: null, + onViewportChange: null, onGutterClick: null, - onHighlightComplete: null, onUpdate: null, onFocus: null, onBlur: null, onScroll: null, matchBrackets: false, + cursorBlinkRate: 530, workTime: 100, workDelay: 200, pollInterval: 100, undoDepth: 40, tabindex: null, - document: window.document + autofocus: null, + lineNumberFormatter: function(integer) { return integer; } }; var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); @@ -1788,27 +2033,37 @@ var CodeMirror = (function() { var win = /Win/.test(navigator.platform); // Known modes, by name and by MIME - var modes = {}, mimeModes = {}; + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; CodeMirror.defineMode = function(name, mode) { if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } modes[name] = mode; }; CodeMirror.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; - CodeMirror.getMode = function(options, spec) { + CodeMirror.resolveMode = function(spec) { if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - else if (spec != null) - var mname = spec.name, config = spec; - var mfactory = modes[mname]; - if (!mfactory) { - if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); - return CodeMirror.getMode(options, "text/plain"); + else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) + return CodeMirror.resolveMode("application/xml"); + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) if (exts.hasOwnProperty(prop)) modeObj[prop] = exts[prop]; } - return mfactory(options, config || {}); + modeObj.name = spec.name; + return modeObj; }; CodeMirror.listModes = function() { var list = []; @@ -1828,6 +2083,16 @@ var CodeMirror = (function() { extensions[name] = func; }; + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + for (var prop in properties) if (properties.hasOwnProperty(prop)) + exts[prop] = properties[prop]; + }; + var commands = CodeMirror.commands = { selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, killLine: function(cm) { @@ -1865,6 +2130,10 @@ var CodeMirror = (function() { indentMore: function(cm) {cm.indentSelection("add");}, indentLess: function(cm) {cm.indentSelection("subtract");}, insertTab: function(cm) {cm.replaceSelection("\t", "end");}, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.replaceSelection("\t", "end"); + }, transposeChars: function(cm) { var cur = cm.getCursor(), line = cm.getLine(cur.line); if (cur.ch > 0 && cur.ch < line.length - 1) @@ -1882,7 +2151,7 @@ var CodeMirror = (function() { keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess", + "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto", "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" }; // Note that the save and find-related commands aren't defined by @@ -1893,6 +2162,7 @@ var CodeMirror = (function() { "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", fallthrough: "basic" }; keyMap.macDefault = { @@ -1901,6 +2171,7 @@ var CodeMirror = (function() { "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", fallthrough: ["basic", "emacsy"] }; keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; @@ -1911,31 +2182,55 @@ var CodeMirror = (function() { "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" }; - function lookupKey(name, extraMap, map) { - function lookup(name, map, ft) { + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + function lookupKey(name, extraMap, map, handle, stop) { + function lookup(map) { + map = getKeyMap(map); var found = map[name]; - if (found != null) return found; - if (ft == null) ft = map.fallthrough; - if (ft == null) return map.catchall; - if (typeof ft == "string") return lookup(name, keyMap[ft]); - for (var i = 0, e = ft.length; i < e; ++i) { - found = lookup(name, keyMap[ft[i]]); - if (found != null) return found; + if (found === false) { + if (stop) stop(); + return true; } - return null; + if (found != null && handle(found)) return true; + if (map.nofallthrough) { + if (stop) stop(); + return true; + } + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0, e = fallthrough.length; i < e; ++i) { + if (lookup(fallthrough[i])) return true; + } + return false; } - return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]); + if (extraMap && lookup(extraMap)) return true; + return lookup(map); } function isModifierKey(event) { var name = keyNames[e_prop(event, "keyCode")]; return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; } + CodeMirror.isModifierKey = isModifierKey; CodeMirror.fromTextArea = function(textarea, options) { if (!options) options = {}; options.value = textarea.value; if (!options.tabindex && textarea.tabindex) options.tabindex = textarea.tabindex; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = document.body; + // doc.activeElement occasionally throws on IE + try { hasFocus = document.activeElement; } catch(e) {} + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } function save() {textarea.value = instance.getValue();} if (textarea.form) { @@ -1943,13 +2238,12 @@ var CodeMirror = (function() { var rmSubmit = connect(textarea.form, "submit", save, true); if (typeof textarea.form.submit == "function") { var realSubmit = textarea.form.submit; - function wrappedSubmit() { + textarea.form.submit = function wrappedSubmit() { save(); textarea.form.submit = realSubmit; textarea.form.submit(); textarea.form.submit = wrappedSubmit; - } - textarea.form.submit = wrappedSubmit; + }; } } @@ -1972,6 +2266,18 @@ var CodeMirror = (function() { return instance; }; + var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); + var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); + var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); + var quirksMode = ie && document.documentMode == 5; + var webkit = /WebKit\//.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var opera = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var khtml = /KHTML\//.test(navigator.userAgent); + var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent); + // Utility functions for working with state. Exported because modes // sometimes need to do this. function copyState(mode, state) { @@ -1990,6 +2296,14 @@ var CodeMirror = (function() { return mode.startState ? mode.startState(a1, a2) : true; } CodeMirror.startState = startState; + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; // The character stream used by a mode's parser. function StringStream(string, tabSize) { @@ -2000,7 +2314,7 @@ var CodeMirror = (function() { StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, sol: function() {return this.pos == 0;}, - peek: function() {return this.string.charAt(this.pos);}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, next: function() { if (this.pos < this.string.length) return this.string.charAt(this.pos++); @@ -2031,14 +2345,14 @@ var CodeMirror = (function() { indentation: function() {return countColumn(this.string, null, this.tabSize);}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { - function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { if (consume !== false) this.pos += pattern.length; return true; } - } - else { + } else { var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; if (match && consume !== false) this.pos += match[0].length; return match; } @@ -2047,200 +2361,174 @@ var CodeMirror = (function() { }; CodeMirror.StringStream = StringStream; - function MarkedText(from, to, className, set) { - this.from = from; this.to = to; this.style = className; this.set = set; + function MarkedSpan(from, to, marker) { + this.from = from; this.to = to; this.marker = marker; } - MarkedText.prototype = { - attach: function(line) { this.set.push(line); }, - detach: function(line) { - var ix = indexOf(this.set, line); - if (ix > -1) this.set.splice(ix, 1); - }, - split: function(pos, lenBefore) { - if (this.to <= pos && this.to != null) return null; - var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; - var to = this.to == null ? null : this.to - pos + lenBefore; - return new MarkedText(from, to, this.style, this.set); - }, - dup: function() { return new MarkedText(null, null, this.style, this.set); }, - clipTo: function(fromOpen, from, toOpen, to, diff) { - if (this.from != null && this.from >= from) - this.from = Math.max(to, this.from) + diff; - if (this.to != null && this.to > from) - this.to = to < this.to ? this.to + diff : from; - if (fromOpen && to > this.from && (to < this.to || this.to == null)) - this.from = null; - if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) - this.to = null; - }, - isDead: function() { return this.from != null && this.to != null && this.from >= this.to; }, - sameSet: function(x) { return this.set == x.set; } - }; - function Bookmark(pos) { - this.from = pos; this.to = pos; this.line = null; + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } } - Bookmark.prototype = { - attach: function(line) { this.line = line; }, - detach: function(line) { if (this.line == line) this.line = null; }, - split: function(pos, lenBefore) { - if (pos < this.from) { - this.from = this.to = (this.from - pos) + lenBefore; - return this; - } - }, - isDead: function() { return this.from > this.to; }, - clipTo: function(fromOpen, from, toOpen, to, diff) { - if ((fromOpen || from < this.from) && (toOpen || to > this.to)) { - this.from = 0; this.to = -1; - } else if (this.from > from) { - this.from = this.to = Math.max(to, this.from) + diff; - } - }, - sameSet: function(x) { return false; }, - find: function() { - if (!this.line || !this.line.parent) return null; - return {line: lineNo(this.line), ch: this.from}; - }, - clear: function() { - if (this.line) { - var found = indexOf(this.line.marked, this); - if (found != -1) this.line.marked.splice(found, 1); - this.line = null; + + function removeMarkedSpan(spans, span) { + var r; + for (var i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + + function markedSpansBefore(old, startCh, endCh) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || marker.type == "bookmark" && span.from == startCh && span.from != endCh) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push({from: span.from, + to: endsAfter ? null : span.to, + marker: marker}); } } - }; + return nw; + } + + function markedSpansAfter(old, endCh) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || marker.type == "bookmark" && span.from == endCh) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, + to: span.to == null ? null : span.to - endCh, + marker: marker}); + } + } + return nw; + } + + function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { + if (!oldFirst && !oldLast) return newText; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh); + var last = markedSpansAfter(oldLast, endCh); + + // Next, merge those two ends + var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + + var newMarkers = [newHL(newText[0], first)]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = newText.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); + for (var i = 0; i < gap; ++i) + newMarkers.push(newHL(newText[i+1], gapMarkers)); + newMarkers.push(newHL(lst(newText), last)); + } + return newMarkers; + } + + // hl stands for history-line, a data structure that can be either a + // string (line without markers) or a {text, markedSpans} object. + function hlText(val) { return typeof val == "string" ? val : val.text; } + function hlSpans(val) { + if (typeof val == "string") return null; + var spans = val.markedSpans, out = null; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } + + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) { + var lines = spans[i].marker.lines; + var ix = indexOf(lines, line); + lines.splice(ix, 1); + } + line.markedSpans = null; + } + + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + var marker = spans[i].marker.lines.push(line); + line.markedSpans = spans; + } + + // When measuring the position of the end of a line, different + // browsers require different approaches. If an empty span is added, + // many browsers report bogus offsets. Of those, some (Webkit, + // recent IE) will accept a space without moving the whole span to + // the next line when wrapping it, others work with a zero-width + // space. + var eolSpanContent = " "; + if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b"; + else if (opera) eolSpanContent = ""; // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function Line(text, styles) { - this.styles = styles || [text, null]; + function Line(text, markedSpans) { this.text = text; this.height = 1; - this.marked = this.gutterMarker = this.className = this.handlers = null; - this.stateAfter = this.parent = this.hidden = null; - } - Line.inheritMarks = function(text, orig) { - var ln = new Line(text), mk = orig && orig.marked; - if (mk) { - for (var i = 0; i < mk.length; ++i) { - if (mk[i].to == null && mk[i].style) { - var newmk = ln.marked || (ln.marked = []), mark = mk[i]; - var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln); - } - } - } - return ln; + attachMarkedSpans(this, markedSpans); } Line.prototype = { - // Replace a piece of a line, keeping the styles around it intact. - replace: function(from, to_, text) { - var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_; - copyStyles(0, from, this.styles, st); - if (text) st.push(text, null); - copyStyles(to, this.text.length, this.styles, st); - this.styles = st; - this.text = this.text.slice(0, from) + text + this.text.slice(to); - this.stateAfter = null; - if (mk) { - var diff = text.length - (to - from); - for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - mark.clipTo(from == null, from || 0, to_ == null, to, diff); - if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);} - } - } - }, - // Split a part off a line, keeping styles and markers intact. - split: function(pos, textBefore) { - var st = [textBefore, null], mk = this.marked; - copyStyles(pos, this.text.length, this.styles, st); - var taken = new Line(textBefore + this.text.slice(pos), st); - if (mk) { - for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - var newmark = mark.split(pos, textBefore.length); - if (newmark) { - if (!taken.marked) taken.marked = []; - taken.marked.push(newmark); newmark.attach(taken); - } - } - } - return taken; - }, - append: function(line) { - var mylen = this.text.length, mk = line.marked, mymk = this.marked; - this.text += line.text; - copyStyles(0, line.text.length, line.styles, this.styles); - if (mymk) { - for (var i = 0; i < mymk.length; ++i) - if (mymk[i].to == null) mymk[i].to = mylen; - } - if (mk && mk.length) { - if (!mymk) this.marked = mymk = []; - outer: for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - if (!mark.from) { - for (var j = 0; j < mymk.length; ++j) { - var mymark = mymk[j]; - if (mymark.to == mylen && mymark.sameSet(mark)) { - mymark.to = mark.to == null ? null : mark.to + mylen; - if (mymark.isDead()) { - mymark.detach(this); - mk.splice(i--, 1); - } - continue outer; - } - } - } - mymk.push(mark); - mark.attach(this); - mark.from += mylen; - if (mark.to != null) mark.to += mylen; - } - } - }, - fixMarkEnds: function(other) { - var mk = this.marked, omk = other.marked; - if (!mk) return; - for (var i = 0; i < mk.length; ++i) { - var mark = mk[i], close = mark.to == null; - if (close && omk) { - for (var j = 0; j < omk.length; ++j) - if (omk[j].sameSet(mark)) {close = false; break;} - } - if (close) mark.to = this.text.length; - } - }, - fixMarkStarts: function() { - var mk = this.marked; - if (!mk) return; - for (var i = 0; i < mk.length; ++i) - if (mk[i].from == null) mk[i].from = 0; - }, - addMark: function(mark) { - mark.attach(this); - if (this.marked == null) this.marked = []; - this.marked.push(mark); - this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);}); + update: function(text, markedSpans) { + this.text = text; + this.stateAfter = this.styles = null; + detachMarkedSpans(this); + attachMarkedSpans(this, markedSpans); }, // Run the given mode's parser over a line, update the styles // array, which contains alternating fragments of text and CSS // classes. highlight: function(mode, state, tabSize) { - var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0; - var changed = false, curWord = st[0], prevWord; + var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []); + var pos = st.length = 0; if (this.text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol()) { - var style = mode.token(stream, state); - var substr = this.text.slice(stream.start, stream.pos); + var style = mode.token(stream, state), substr = stream.current(); stream.start = stream.pos; - if (pos && st[pos-1] == style) + if (pos && st[pos-1] == style) { st[pos-2] += substr; - else if (substr) { - if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true; + } else if (substr) { st[pos++] = substr; st[pos++] = style; - prevWord = curWord; curWord = st[pos]; } // Give up when line is ridiculously long if (stream.pos > 5000) { @@ -2248,17 +2536,19 @@ var CodeMirror = (function() { break; } } - if (st.length != pos) {st.length = pos; changed = true;} - if (pos && st[pos-2] != prevWord) changed = true; - // Short lines with simple highlights return null, and are - // counted as changed by the driver because they are likely to - // highlight the same way in various contexts. - return changed || (st.length < 5 && this.text.length < 10 ? null : false); + }, + process: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize); + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol() && stream.pos <= 5000) { + mode.token(stream, state); + stream.start = stream.pos; + } }, // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). - getTokenAt: function(mode, state, ch) { - var txt = this.text, stream = new StringStream(txt); + getTokenAt: function(mode, state, tabSize, ch) { + var txt = this.text, stream = new StringStream(txt, tabSize); while (stream.pos < ch && !stream.eol()) { stream.start = stream.pos; var style = mode.token(stream, state); @@ -2272,50 +2562,112 @@ var CodeMirror = (function() { indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, // Produces an HTML fragment for the line, taking selection, // marking, and highlighting into account. - getHTML: function(tabText, endAt) { - var html = [], first = true; - function span(text, style) { + getContent: function(tabSize, wrapAt, compensateForWrapping) { + var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; + var pre = elt("pre"); + function span_(html, text, style) { if (!text) return; // Work around a bug where, in some compat modes, IE ignores leading spaces if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); first = false; - if (style) html.push('', htmlEscape(text).replace(/\t/g, tabText), ""); - else html.push(htmlEscape(text).replace(/\t/g, tabText)); + if (!specials.test(text)) { + col += text.length; + var content = document.createTextNode(text); + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + specials.lastIndex = pos; + var m = specials.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); + col += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabWidth = tabSize - col % tabSize; + content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + col += tabWidth; + } else { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + m[0].charCodeAt(0).toString(16); + content.appendChild(token); + col += 1; + } + } + } + if (style) html.appendChild(elt("span", [content], style)); + else html.appendChild(content); } - var st = this.styles, allText = this.text, marked = this.marked; + var span = span_; + if (wrapAt != null) { + var outPos = 0, anchor = pre.anchor = elt("span"); + span = function(html, text, style) { + var l = text.length; + if (wrapAt >= outPos && wrapAt < outPos + l) { + var cut = wrapAt - outPos; + if (cut) { + span_(html, text.slice(0, cut), style); + // See comment at the definition of spanAffectsWrapping + if (compensateForWrapping) { + var view = text.slice(cut - 1, cut + 1); + if (spanAffectsWrapping.test(view)) html.appendChild(elt("wbr")); + else if (!ie_lt8 && /\w\w/.test(view)) html.appendChild(document.createTextNode("\u200d")); + } + } + html.appendChild(anchor); + span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style); + if (opera) span_(html, text.slice(cut + 1), style); + wrapAt--; + outPos += l; + } else { + outPos += l; + span_(html, text, style); + if (outPos == wrapAt && outPos == len) { + setTextContent(anchor, eolSpanContent); + html.appendChild(anchor); + } + // Stop outputting HTML when gone sufficiently far beyond measure + else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){}; + } + }; + } + + var st = this.styles, allText = this.text, marked = this.markedSpans; var len = allText.length; - if (endAt != null) len = Math.min(endAt, len); function styleToClass(style) { if (!style) return null; return "cm-" + style.replace(/ +/g, " cm-"); } - - if (!allText && endAt == null) - span(" "); - else if (!marked || !marked.length) + if (!allText && wrapAt == null) { + span(pre, " "); + } else if (!marked || !marked.length) { for (var i = 0, ch = 0; ch < len; i+=2) { var str = st[i], style = st[i+1], l = str.length; if (ch + l > len) str = str.slice(0, len - ch); ch += l; - span(str, styleToClass(style)); + span(pre, str, styleToClass(style)); } - else { + } else { + marked.sort(function(a, b) { return a.from - b.from; }); var pos = 0, i = 0, text = "", style, sg = 0; var nextChange = marked[0].from || 0, marks = [], markpos = 0; - function advanceMarks() { + var advanceMarks = function() { var m; while (markpos < marked.length && ((m = marked[markpos]).from == pos || m.from == null)) { - if (m.style != null) marks.push(m); + if (m.marker.type == "range") marks.push(m); ++markpos; } nextChange = markpos < marked.length ? marked[markpos].from : Infinity; for (var i = 0; i < marks.length; ++i) { - var to = marks[i].to || Infinity; + var to = marks[i].to; + if (to == null) to = Infinity; if (to == pos) marks.splice(i--, 1); else nextChange = Math.min(to, nextChange); } - } + }; var m = 0; while (pos < len) { if (nextChange == pos) advanceMarks(); @@ -2324,9 +2676,13 @@ var CodeMirror = (function() { if (text) { var end = pos + text.length; var appliedStyle = style; - for (var j = 0; j < marks.length; ++j) - appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style; - span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); + for (var j = 0; j < marks.length; ++j) { + var mark = marks[j]; + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; + if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; + if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; + } + span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle); if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} pos = end; } @@ -2334,29 +2690,13 @@ var CodeMirror = (function() { } } } - return html.join(""); + return pre; }, cleanUp: function() { this.parent = null; - if (this.marked) - for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); + detachMarkedSpans(this); } }; - // Utility used by replace and split above - function copyStyles(from, to, source, dest) { - for (var i = 0, pos = 0, state = 0; pos < to; i+=2) { - var part = source[i], end = pos + part.length; - if (state == 0) { - if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); - if (end >= from) state = 1; - } - else if (state == 1) { - if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); - else dest.push(part, source[i+1]); - } - pos = end; - } - } // Data structure that holds the sequence of lines. function LeafChunk(lines) { @@ -2385,7 +2725,7 @@ var CodeMirror = (function() { }, insertHeight: function(at, lines, height) { this.height += height; - this.lines.splice.apply(this.lines, [at, 0].concat(lines)); + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; }, iterN: function(at, n, op) { @@ -2551,33 +2891,36 @@ var CodeMirror = (function() { function History() { this.time = 0; this.done = []; this.undone = []; + this.compound = 0; + this.closed = false; } History.prototype = { addChange: function(start, added, old) { this.undone.length = 0; - var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var time = +new Date, cur = lst(this.done), last = cur && lst(cur); var dtime = time - this.time; - if (dtime > 400 || !last) { - this.done.push([{start: start, added: added, old: old}]); - } else if (last.start > start + added || last.start + last.added < start - last.added + last.old.length) { + + if (cur && !this.closed && this.compound) { cur.push({start: start, added: added, old: old}); + } else if (dtime > 400 || !last || this.closed || + last.start > start + old.length || last.start + last.added < start) { + this.done.push([{start: start, added: added, old: old}]); + this.closed = false; } else { - var oldoff = 0; - if (start < last.start) { - for (var i = last.start - start - 1; i >= 0; --i) - last.old.unshift(old[i]); - last.added += last.start - start; - last.start = start; - } - else if (last.start < start) { - oldoff = start - last.start; - added += oldoff; - } - for (var i = last.added - oldoff, e = old.length; i < e; ++i) - last.old.push(old[i]); - if (last.added < added) last.added = added; + var startBefore = Math.max(0, last.start - start), + endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); + for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); + for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); + if (startBefore) last.start = start; + last.added += added - (old.length - startBefore - endAfter); } this.time = time; + }, + startCompound: function() { + if (!this.compound++) this.closed = true; + }, + endCompound: function() { + if (!--this.compound) this.closed = true; } }; @@ -2603,10 +2946,14 @@ var CodeMirror = (function() { function e_target(e) {return e.target || e.srcElement;} function e_button(e) { - if (e.which) return e.which; - else if (e.button & 1) return 1; - else if (e.button & 2) return 3; - else if (e.button & 4) return 2; + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; } // Allow 3rd-party code to override event properties by adding an override @@ -2622,8 +2969,7 @@ var CodeMirror = (function() { if (typeof node.addEventListener == "function") { node.addEventListener(type, handler, false); if (disconnect) return function() {node.removeEventListener(type, handler, false);}; - } - else { + } else { var wrapHandler = function(event) {handler(event || window.event);}; node.attachEvent("on" + type, wrapHandler); if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; @@ -2634,26 +2980,36 @@ var CodeMirror = (function() { function Delayed() {this.id = null;} Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + // Detect drag-and-drop var dragAndDrop = function() { - // IE8 has ondragstart and ondrop properties, but doesn't seem to - // actually support ondragstart the way it's supposed to work. - if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false; - var div = document.createElement('div'); - return "draggable" in div; + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie_lt9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; }(); - var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); - var ie = /MSIE \d/.test(navigator.userAgent); - var webkit = /WebKit\//.test(navigator.userAgent); - - var lineSep = "\n"; // Feature-detect whether newlines in textareas are converted to \r\n - (function () { - var te = document.createElement("textarea"); + var lineSep = function () { + var te = elt("textarea"); te.value = "foo\nbar"; - if (te.value.indexOf("\r") > -1) lineSep = "\r\n"; - }()); + if (te.value.indexOf("\r") > -1) return "\r\n"; + return "\n"; + }(); + + // For a reason I have yet to figure out, some browsers disallow + // word wrapping between certain characters *only* if a new inline + // element is started between them. This makes it hard to reliably + // measure the position of things, since that requires inserting an + // extra span. This terribly fragile set of regexps matches the + // character combinations that suffer from this phenomenon on the + // various browsers. + var spanAffectsWrapping = /^$/; // Won't match any two-character string + if (gecko) spanAffectsWrapping = /$'/; + else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; + else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; // Counts the column offset in a string, taking tabs into account. // Used mostly to find indentation. @@ -2669,31 +3025,7 @@ var CodeMirror = (function() { return n; } - function computedStyle(elt) { - if (elt.currentStyle) return elt.currentStyle; - return window.getComputedStyle(elt, null); - } - - // Find the position of an element by following the offsetParent chain. - // If screen==true, it returns screen (rather than page) coordinates. function eltOffset(node, screen) { - var bod = node.ownerDocument.body; - var x = 0, y = 0, skipBody = false; - for (var n = node; n; n = n.offsetParent) { - var ol = n.offsetLeft, ot = n.offsetTop; - // Firefox reports weird inverted offsets when the body has a border. - if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); } - else { x += ol, y += ot; } - if (screen && computedStyle(n).position == "fixed") - skipBody = true; - } - var e = screen && !skipBody ? null : bod; - for (var n = node.parentNode; n != e; n = n.parentNode) - if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} - return {left: x, top: y}; - } - // Use the faster and saner getBoundingClientRect method when possible. - if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) { // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } @@ -2709,12 +3041,21 @@ var CodeMirror = (function() { } } return box; - }; + } - // Get a node's text content. function eltText(node) { return node.textContent || node.innerText || node.nodeValue || ""; } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + function selectInput(node) { if (ios) { // Mobile Safari apparently has a bug where select() is broken. node.selectionStart = 0; @@ -2727,26 +3068,27 @@ var CodeMirror = (function() { function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} function copyPos(x) {return {line: x.line, ch: x.ch};} - var escapeElement = document.createElement("pre"); - function htmlEscape(str) { - escapeElement.textContent = str; - return escapeElement.innerHTML; + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") setTextContent(e, content); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + function removeChildren(e) { + e.innerHTML = ""; + return e; + } + function removeChildrenAndAdd(parent, e) { + removeChildren(parent).appendChild(e); + } + function setTextContent(e, str) { + if (ie_lt9) { + e.innerHTML = ""; + e.appendChild(document.createTextNode(str)); + } else e.textContent = str; } - // Recent (late 2011) Opera betas insert bogus newlines at the start - // of the textContent, so we strip those. - if (htmlEscape("a") == "\na") - htmlEscape = function(str) { - escapeElement.textContent = str; - return escapeElement.innerHTML.slice(1); - }; - // Some IEs don't preserve tabs through innerHTML - else if (htmlEscape("\t") != "\t") - htmlEscape = function(str) { - escapeElement.innerHTML = ""; - escapeElement.appendChild(document.createTextNode(str)); - return escapeElement.innerHTML; - }; - CodeMirror.htmlEscape = htmlEscape; // Used to position the cursor after an undo/redo by finding the // last edited character. @@ -2764,21 +3106,31 @@ var CodeMirror = (function() { if (collection[i] == elt) return i; return -1; } + var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; function isWordChar(ch) { - return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase(); + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); } // See if "".split is the broken IE version, if so, provide an // alternative way to split lines. var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { - var pos = 0, nl, result = []; - while ((nl = string.indexOf("\n", pos)) > -1) { - result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); - pos = nl + 1; + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } } - result.push(string.slice(pos)); return result; - } : function(string){return string.split(/\r?\n/);}; + } : function(string){return string.split(/\r\n?|\n/);}; CodeMirror.splitLines = splitLines; var hasSelection = window.getSelection ? function(te) { @@ -2799,10 +3151,10 @@ var CodeMirror = (function() { var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",", - 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp", - 63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right", - 63233: "Down", 63302: "Insert", 63272: "Delete"}; + 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", + 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", + 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; CodeMirror.keyNames = keyNames; (function() { // Number keys @@ -2813,5 +3165,7 @@ var CodeMirror = (function() { for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; })(); + CodeMirror.version = "2.35 +"; + return CodeMirror; })(); diff --git a/lib/codemirror.min.js b/lib/codemirror.min.js deleted file mode 100644 index 37c06a2..0000000 --- a/lib/codemirror.min.js +++ /dev/null @@ -1 +0,0 @@ -var CodeMirror=function(){function a(d,e){function bS(a){return a>=0&&a=c.to||b.linee-400&&T(bv.pos,d))return B(a),setTimeout(cx,20),cU(d.line);if(bu&&bu.time>e-400&&T(bu.pos,d))return bv={time:e,pos:d},B(a),cT(d);bu={time:e,pos:d};var g=d,h;if(J&&!f.readOnly&&!T(bs.from,bs.to)&&!U(d,bs.from)&&!U(bs.to,d)){M&&(bf.draggable=!0);var i=H(z,"mouseup",dO(function(b){M&&(bf.draggable=!1),bx=!1,i(),Math.abs(a.clientX-b.clientX)+Math.abs(a.clientY-b.clientY)<10&&(B(b),cL(d.line,d.ch,!0),cx())}),!0);bx=!0;return}B(a),cL(d.line,d.ch,!0);var k=H(z,"mousemove",dO(function(a){clearTimeout(h),B(a),j(a)}),!0),i=H(z,"mouseup",dO(function(a){clearTimeout(h);var b=dB(a);b&&cI(d,b),B(a),cx(),bA=!0,k(),i()}),!0)}function bZ(a){for(var b=E(a);b!=C;b=b.parentNode)if(b.parentNode==be)return B(a);var c=dB(a);if(!c)return;bv={time:+(new Date),pos:c},B(a),cT(c)}function b$(a){a.preventDefault();var b=dB(a,!0),c=a.dataTransfer.files;if(!b||f.readOnly)return;if(c&&c.length&&window.FileReader&&window.File){function d(a,c){var d=new FileReader;d.onload=function(){g[c]=d.result,++h==e&&(b=cN(b),dO(function(){var a=cm(g.join(""),b,b);cI(b,a)})())},d.readAsText(a)}var e=c.length,g=Array(e),h=0;for(var i=0;i-1&&setTimeout(dO(function(){cW(bs.to.line,"smart")}),75)}ct()}function ce(a){if(f.onKeyEvent&&f.onKeyEvent(bT,A(a)))return;G(a,"keyCode")==16&&(bt=null)}function cf(){if(f.readOnly=="nocursor")return;br||(f.onFocus&&f.onFocus(bT),br=!0,C.className.search(/\bCodeMirror-focused\b/)==-1&&(C.className+=" CodeMirror-focused"),bF||cw(!0)),cs(),dD()}function cg(){br&&(f.onBlur&&f.onBlur(bT),br=!1,bM&&dO(function(){bM&&(bM(),bM=null)})(),C.className=C.className.replace(" CodeMirror-focused","")),clearInterval(bn),setTimeout(function(){br||(bt=null)},150)}function ch(a,b,c,d,e){if(bz)return;if(bQ){var g=[];bp.iter(a.line,b.line+1,function(a){g.push(a.text)}),bQ.addChange(a.line,c.length,g);while(bQ.done.length>f.undoDepth)bQ.done.shift()}cl(a,b,c,d,e)}function ci(a,b,c){var d=a.pop(),e=d?d.length:0,f=[];for(var g=c>0?0:e-1,h=c>0?e:-1;g!=h;g+=c){var i=d[g],j=[],k=i.start+i.added;bp.iter(i.start,k,function(a){j.push(a.text)}),f.push({start:i.start,added:i.old.length,old:j});var l=cN({line:i.start+i.old.length-1,ch:Y(j[j.length-1],i.old[i.old.length-1])});cl({line:i.start,ch:0},{line:k-1,ch:bU(k-1).text.length},i.old,l,l)}bA=!0,b.push(f)}function cj(){ci(bQ.done,bQ.undone,-1)}function ck(){ci(bQ.undone,bQ.done,1)}function cl(a,b,c,d,e){function y(a){return a<=Math.min(b.line,b.line+s)?a:a+s}if(bz)return;var g=!1,h=bN.length;f.lineWrapping||bp.iter(a.line,b.line,function(a){if(a.text.length==h)return g=!0,!0});if(a.line!=b.line||c.length>1)bG=!0;var i=b.line-a.line,j=bU(a.line),k=bU(b.line);if(a.ch==0&&b.ch==0&&c[c.length-1]==""){var l=[],m=null;a.line?(m=bU(a.line-1),m.fixMarkEnds(k)):k.fixMarkStarts();for(var n=0,o=c.length-1;n1&&bp.remove(a.line+1,i-1,bH),bp.insert(a.line+1,l)}if(f.lineWrapping){var p=P.clientWidth/dy()-3;bp.iter(a.line,a.line+c.length,function(a){if(a.hidden)return;var b=Math.ceil(a.text.length/p)||1;b!=a.height&&bV(a,b)})}else bp.iter(a.line,n+c.length,function(a){var b=a.text;b.length>h&&(bN=b,h=b.length,bO=null,g=!1)}),g&&(h=0,bN="",bO=null,bp.iter(0,bp.size,function(a){var b=a.text;b.length>h&&(h=b.length,bN=b)}));var r=[],s=c.length-i-1;for(var n=0,t=bq.length;nb.line&&r.push(u+s)}var v=a.line+Math.min(c.length,500);dI(a.line,v),r.push(v),bq=r,dK(100),bC.push({from:a.line,to:b.line+1,diff:s});var w={from:a,to:b,text:c};if(bD){for(var x=bD;x.next;x=x.next);x.next=w}else bD=w;cJ(d,e,y(bs.from.line),y(bs.to.line)),P.clientHeight&&(S.style.height=bp.height*dv()+2*dz()+"px")}function cm(a,b,c){function d(d){if(U(d,b))return d;if(!U(c,d))return e;var f=d.line+a.length-(c.line-b.line)-1,g=d.ch;return d.line==c.line&&(g+=a[a.length-1].length-(c.ch-(c.line==b.line?b.ch:0))),{line:f,ch:g}}b=cN(b),c?c=cN(c):c=b,a=_(a);var e;return co(a,b,c,function(a){return e=a,{from:d(bs.from),to:d(bs.to)}}),e}function cn(a,b){co(_(a),bs.from,bs.to,function(a){return b=="end"?{from:a,to:a}:b=="start"?{from:bs.from,to:bs.from}:{from:bs.from,to:a}})}function co(a,b,c,d){var e=a.length==1?a[0].length+b.ch:a[a.length-1].length,f=d({line:b.line+a.length-1,ch:e});ch(b,c,a,f.from,f.to)}function cp(a,b){var c=a.line,d=b.line;if(c==d)return bU(c).text.slice(a.ch,b.ch);var e=[bU(c).text.slice(a.ch)];return bp.iter(c+1,d,function(a){e.push(a.text)}),e.push(bU(d).text.slice(0,b.ch)),e.join("\n")}function cq(){return cp(bs.from,bs.to)}function cs(){if(cr)return;bl.set(f.pollInterval,function(){dL(),cv(),br&&cs(),dM()})}function ct(){function b(){dL();var c=cv();!c&&!a?(a=!0,bl.set(60,b)):(cr=!1,cs()),dM()}var a=!1;cr=!0,bl.set(20,b)}function cv(){if(bF||!br||ba(O)||f.readOnly)return!1;var a=O.value;if(a==cu)return!1;bt=null;var b=0,c=Math.min(cu.length,a.length);while(bb)&&bh.scrollIntoView()}function cz(){var a=dp(bs.inverted?bs.from:bs.to),b=f.lineWrapping?Math.min(a.x,bf.offsetWidth):a.x;return cA(b,a.y,b,a.yBot)}function cA(a,b,c,d){var e=dA(),g=dz(),h=dv();b+=g,d+=g,a+=e,c+=e;var i=P.clientHeight,j=P.scrollTop,k=!1,l=!0;bj+i&&(P.scrollTop=d+h-i,k=!0);var m=P.clientWidth,n=P.scrollLeft,o=f.fixedGutter?bd.clientWidth:0;return am+n-3&&(P.scrollLeft=c+10-m,k=!0,c>S.clientWidth&&(l=!1)),k&&f.onScroll&&f.onScroll(bT),l}function cB(){var a=dv(),b=P.scrollTop-dz(),c=Math.max(0,Math.floor(b/a)),d=Math.ceil((b+P.clientHeight)/a);return{from:w(bp,c),to:w(bp,d)}}function cC(a,b){if(!P.clientWidth){bJ=bK=bI=0;return}var c=cB();if(a!==!0&&a.length==0&&c.from>bJ&&c.toe&&bK-e<20&&(e=Math.min(bp.size,bK));var g=a===!0?[]:cD([{from:bJ,to:bK,domStart:0}],a),h=0;for(var i=0;ie&&(j.to=e),j.from>=j.to?g.splice(i--,1):h+=j.to-j.from}if(h==e-d)return;g.sort(function(a,b){return a.domStart-b.domStart});var k=dv(),l=bd.style.display;bj.style.display="none",cE(d,e,g),bj.style.display=bd.style.display="";var m=d!=bJ||e!=bK||bL!=P.clientHeight+k;m&&(bL=P.clientHeight+k),bJ=d,bK=e,bI=x(bp,d),bc.style.top=bI*k+"px",P.clientHeight&&(S.style.height=bp.height*k+2*dz()+"px");if(bj.childNodes.length!=bK-bJ)throw new Error("BAD PATCH! "+JSON.stringify(g)+" size="+(bK-bJ)+" nodes="+bj.childNodes.length);if(f.lineWrapping){bO=P.clientWidth;var n=bj.firstChild,o=!1;bp.iter(bJ,bK,function(a){if(!a.hidden){var b=Math.round(n.offsetHeight/k)||1;a.height!=b&&(bV(a,b),bG=o=!0)}n=n.nextSibling}),o&&(S.style.height=bp.height*k+2*dz()+"px")}else bO==null&&(bO=dk(bN)),bO>P.clientWidth?(bf.style.width=bO+"px",S.style.width="",S.style.width=P.scrollWidth+"px"):bf.style.width=S.style.width="";return bd.style.display=l,(m||bG)&&cF(),cG(),!b&&f.onUpdate&&f.onUpdate(bT),!0}function cD(a,b){for(var c=0,d=b.length||0;c=j.to?f.push(j):(e.from>j.from&&f.push({from:j.from,to:e.from,domStart:j.domStart}),e.toe)f=d(f),e++;for(var j=0,k=i.to-i.from;jj){if(a.hidden)var b=m.innerHTML="
";else{var b="
"+a.getHTML(bP)+"
";a.className&&(b='
 
'+b+"
")}m.innerHTML=b,bj.insertBefore(m.firstChild,f)}else f=f.nextSibling;++j})}function cF(){if(!f.gutter&&!f.lineNumbers)return;var a=bc.offsetHeight,b=P.clientHeight;bd.style.height=(a-b<2?b:a)+"px";var c=[],d=bJ;bp.iter(bJ,Math.max(bK,bJ+1),function(a){if(a.hidden)c.push("
");else{var b=a.gutterMarker,e=f.lineNumbers?d+f.firstLineNumber:null;b&&b.text?e=b.text.replace("%N%",e!=null?e:""):e==null&&(e="\u00a0"),c.push(b&&b.style?'
':"
",e);for(var g=1;g ");c.push("
")}++d}),bd.style.display="none",be.innerHTML=c.join("");var e=String(bp.size).length,g=be.firstChild,h=R(g),i="";while(h.length+i.length'}if(bs.from.ch&&b.y>=0){var l=i?bf.clientWidth-c.x:0;k(b.x,b.y,l,e)}var m=Math.max(0,b.y+(bs.from.ch?e:0)),n=Math.min(c.y,bf.clientHeight)-m;n>.2*e&&k(0,m,0,n),(!i||!bs.from.ch)&&c.yc||g>f.text.length)g=f.text.length;return{line:d,ch:g}}d+=b}}var e=bU(a.line);return e.hidden?a.line>=b?d(1)||d(-1):d(-1)||d(1):a}function cL(a,b,c){var d=cN({line:a,ch:b||0});(c?cI:cJ)(d,d)}function cM(a){return Math.max(0,Math.min(a,bp.size-1))}function cN(a){if(a.line<0)return{line:0,ch:0};if(a.line>=bp.size)return{line:bp.size-1,ch:bU(bp.size-1).text.length};var b=a.ch,c=bU(a.line).text.length;return b==null||b>c?{line:a.line,ch:c}:b<0?{line:a.line,ch:0}:a}function cO(a,b){function g(){for(var b=d+a,c=a<0?-1:bp.size;b!=c;b+=a){var e=bU(b);if(!e.hidden)return d=b,f=e,!0}}function h(b){if(e==(a<0?0:f.text.length))if(!b&&g())e=a<0?f.text.length:0;else return!1;else e+=a;return!0}var c=bs.inverted?bs.from:bs.to,d=c.line,e=c.ch,f=bU(d);if(b=="char")h();else if(b=="column")h(!0);else if(b=="word"){var i=!1;for(;;){if(a<0&&!h())break;if($(f.text.charAt(e)))i=!0;else if(i){a<0&&(a=1,h());break}if(a>0&&!h())break}}return{line:d,ch:e}}function cP(a,b){var c=a<0?bs.from:bs.to;if(bt||T(bs.from,bs.to))c=cO(a,b);cL(c.line,c.ch,!0)}function cQ(a,b){T(bs.from,bs.to)?a<0?cm("",cO(a,b),bs.to):cm("",bs.from,cO(a,b)):cm("",bs.from,bs.to),bB=!0}function cS(a,b){var c=0,d=dp(bs.inverted?bs.from:bs.to,!0);cR!=null&&(d.x=cR),b=="page"?c=Math.min(P.clientHeight,window.innerHeight||document.documentElement.clientHeight):b=="line"&&(c=dv());var e=dq(d.x,d.y+c*a+2);cL(e.line,e.ch,!0),cR=d.x}function cT(a){var b=bU(a.line).text,c=a.ch,d=a.ch;while(c>0&&$(b.charAt(c-1)))--c;while(dbN.length&&(bN=a.text)});bC.push({from:0,to:bp.size})}function c$(){for(var a='',b=0;b"}function c_(){bP=c$(),cC(!0)}function da(){P.className=P.className.replace(/\s*cm-s-\w+/g,"")+f.theme.replace(/(^|\s)\s*/g," cm-s-")}function db(){this.set=[]}function dc(a,b,c){function e(a,b,c,e){bU(a).addMark(new o(b,c,e,d.set))}a=cN(a),b=cN(b);var d=new db;if(a.line==b.line)e(a.line,a.ch,b.ch,c);else{e(a.line,a.ch,null,c);for(var f=a.line+1,g=b.line;f
",bg.firstChild.firstChild.offsetWidth}if(b<=0)return 0;var c=bU(a),d=c.text,f=0,g=0,h=d.length,i,j=Math.min(h,Math.ceil(b/dy()));for(;;){var k=e(j);if(k<=b&&ji)return h;j=Math.floor(h*.8),k=e(j),kb-g?f:h;var l=Math.ceil((f+h)/2),m=e(l);m>b?(h=l,i=m):(f=l,g=m)}}function dn(a,b){if(b==0)return{top:0,left:0};var c="";if(f.lineWrapping){var d=a.text.indexOf(" ",b+2);c=X(a.text.slice(b+1,d<0?a.text.length:d+(L?5:0)))}bg.innerHTML="
"+a.getHTML(bP,b)+''+X(a.text.charAt(b)||" ")+""+c+"
";var e=document.getElementById("CodeMirror-temp-"+dm),g=e.offsetTop,h=e.offsetLeft;if(L&&g==0&&h==0){var i=document.createElement("span");i.innerHTML="x",e.parentNode.insertBefore(i,e.nextSibling),g=i.offsetTop}return{top:g,left:h}}function dp(a,b){var c,d=dv(),e=d*(x(bp,a.line)-(b?bI:0));if(a.ch==0)c=0;else{var g=dn(bU(a.line),a.ch);c=g.left,f.lineWrapping&&(e+=Math.max(0,g.top))}return{x:c,y:e,yBot:e+d}}function dq(a,b){function l(a){var b=dn(h,a);if(j){var d=Math.round(b.top/c);return Math.max(0,b.left+(d-k)*P.clientWidth)}return b.left}b<0&&(b=0);var c=dv(),d=dy(),e=bI+Math.floor(b/c),g=w(bp,e);if(g>=bp.size)return{line:bp.size-1,ch:bU(bp.size-1).text.length};var h=bU(g),i=h.text,j=f.lineWrapping,k=j?e-x(bp,g):0;if(a<=0&&k==0)return{line:g,ch:0};var m=0,n=0,o=i.length,p,q=Math.min(o,Math.ceil((a+k*P.clientWidth*.9)/d));for(;;){var r=l(q);if(r<=a&&qp)return{line:g,ch:o};q=Math.floor(o*.8),r=l(q),ra-n?m:o};var s=Math.ceil((m+o)/2),t=l(s);t>a?(o=s,p=t):(m=s,n=t)}}function dr(a){var b=dp(a,!0),c=Q(bf);return{x:c.left+b.x,y:c.top+b.y,yBot:c.top+b.yBot}}function dv(){if(du==null){du="
";for(var a=0;a<49;++a)du+="x
";du+="x
"}var b=bj.clientHeight;return b==dt?ds:(dt=b,bg.innerHTML=du,ds=bg.firstChild.offsetHeight/50||1,bg.innerHTML="",ds)}function dy(){return P.clientWidth==dx?dw:(dx=P.clientWidth,dw=dk("x"))}function dz(){return bf.offsetTop}function dA(){return bf.offsetLeft}function dB(a,b){var c=Q(P,!0),d,e;try{d=a.clientX,e=a.clientY}catch(a){return null}if(!b&&(d-c.left>P.clientWidth||e-c.top>P.clientHeight))return null;var f=Q(bf,!0);return dq(d-f.left,e-f.top)}function dC(a){function e(){var a=_(O.value).join("\n");a!=d&&dO(cn)(a,"end"),N.style.position="relative",O.style.cssText=c,bF=!1,cw(!0),cs()}var b=dB(a);if(!b||window.opera)return;(T(bs.from,bs.to)||U(b,bs.from)||!U(b,bs.to))&&dO(cL)(b.line,b.ch);var c=O.style.cssText;N.style.position="absolute",O.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(a.clientY-5)+"px; left: "+(a.clientX-5)+"px; z-index: 1000; background: white; "+"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",bF=!0;var d=O.value=cq();cx(),O.select();if(K){D(a);var f=H(window,"mouseup",function(){f(),setTimeout(e,20)},!0)}else setTimeout(e,50)}function dD(){clearInterval(bn);var a=!0;bh.style.visibility="",bn=setInterval(function(){bh.style.visibility=(a=!a)?"":"hidden"},650)}function dF(a){function p(a,b,c){if(!a.text)return;var d=a.styles,e=g?0:a.text.length-1,f;for(var i=g?0:d.length-2,j=g?d.length:-2;i!=j;i+=2*h){var k=d[i];if(d[i+1]!=null&&d[i+1]!=m){e+=h*k.length;continue}for(var l=g?0:k.length-1,p=g?k.length:-1;l!=p;l+=h,e+=h)if(e>=b&&e"==g)n.push(f);else{if(n.pop()!=q.charAt(0))return{pos:e,match:!1};if(!n.length)return{pos:e,match:!0}}}}}var b=bs.inverted?bs.from:bs.to,c=bU(b.line),d=b.ch-1,e=d>=0&&dE[c.text.charAt(d)]||dE[c.text.charAt(++d)];if(!e)return;var f=e.charAt(0),g=e.charAt(1)==">",h=g?1:-1,i=c.styles;for(var j=d+1,k=0,l=i.length;ke;--d){if(d==0)return 0;var g=bU(d-1);if(g.stateAfter)return d;var h=g.indentation(f.tabSize);if(c==null||b>h)c=d-1,b=h}return c}function dH(a){var b=dG(a),c=b&&bU(b-1).stateAfter;return c?c=l(bo,c):c=m(bo),bp.iter(b,a,function(a){a.highlight(bo,c,f.tabSize),a.stateAfter=l(bo,c)}),b=bp.size)continue;var d=dG(c),e=d&&bU(d-1).stateAfter;e?e=l(bo,e):e=m(bo);var g=0,h=bo.compareStates,i=!1,j=d,k=!1;bp.iter(j,bp.size,function(b){var d=b.stateAfter;if(+(new Date)>a)return bq.push(j),dK(f.workDelay),i&&bC.push({from:c,to:j+1}),k=!0;var m=b.highlight(bo,e,f.tabSize);m&&(i=!0),b.stateAfter=l(bo,e);if(h){if(d&&h(d,e))return!0}else if(m!==!1||!d)g=0;else if(++g>3&&(!bo.indent||bo.indent(d,"")==bo.indent(e,"")))return!0;++j});if(k)return;i&&bC.push({from:c,to:j+1})}b&&f.onHighlightComplete&&f.onHighlightComplete(bT)}function dK(a){if(!bq.length)return;bm.set(a,dO(dJ))}function dL(){bA=bB=bD=null,bC=[],bE=!1,bH=[]}function dM(){var a=!1,b;bE&&(a=!cz()),bC.length?b=cC(bC,!0):(bE&&cG(),bG&&cF()),a&&cz(),bE&&(cy(),dD()),br&&!bF&&(bA===!0||bA!==!1&&bE)&&cw(bB),bE&&f.matchBrackets&&setTimeout(dO(function(){bM&&(bM(),bM=null),T(bs.from,bs.to)&&dF(!1)}),20);var c=bD,d=bH;bE&&f.onCursorActivity&&f.onCursorActivity(bT),c&&f.onChange&&bT&&f.onChange(bT,c);for(var e=0;eh&&a.y>b.offsetHeight&&(f=a.y-b.offsetHeight),g+b.offsetWidth>i&&(g=i-b.offsetWidth)}b.style.top=f+dz()+"px",b.style.left=b.style.right="",e=="right"?(g=S.clientWidth-b.offsetWidth,b.style.right="0px"):(e=="left"?g=0:e=="middle"&&(g=(S.clientWidth-b.offsetWidth)/2),b.style.left=g+dA()+"px"),c&&cA(g,f,g+b.offsetWidth,f+b.offsetHeight)},lineCount:function(){return bp.size},clipPos:cN,getCursor:function(a){return a==null&&(a=bs.inverted),V(a?bs.from:bs.to)},somethingSelected:function(){return!T(bs.from,bs.to)},setCursor:dO(function(a,b,c){b==null&&typeof a.line=="number"?cL(a.line,a.ch,c):cL(a,b,c)}),setSelection:dO(function(a,b,c){(c?cI:cJ)(cN(a),cN(b||a))}),getLine:function(a){if(bS(a))return bU(a).text},getLineHandle:function(a){if(bS(a))return bU(a)},setLine:dO(function(a,b){bS(a)&&cm(b,{line:a,ch:0},{line:a,ch:bU(a).text.length})}),removeLine:dO(function(a){bS(a)&&cm("",{line:a,ch:0},cN({line:a+1,ch:0}))}),replaceRange:dO(cm),getRange:function(a,b){return cp(cN(a),cN(b))},execCommand:function(a){return h[a](bT)},moveH:dO(cP),deleteH:dO(cQ),moveV:dO(cS),toggleOverwrite:function(){by=!by},posFromIndex:function(a){var b=0,c;return bp.iter(0,bp.size,function(d){var e=d.text.length+1;if(e>a)return c=a,!0;a-=e,++b}),cN({line:b,ch:c})},indexFromPos:function(a){if(a.line<0||a.ch<0)return 0;var b=a.ch;return bp.iter(0,a.line,function(a){b+=a.text.length+1}),b},scrollTo:function(a,b){a!=null&&(P.scrollTop=a),b!=null&&(P.scrollLeft=b),cC([])},operation:function(a){return dO(a)()},refresh:function(){cC(!0),P.scrollHeight>bw&&(P.scrollTop=bw)},getInputField:function(){return O},getWrapperElement:function(){return C},getScrollerElement:function(){return P},getGutterElement:function(){return bd}},cb=null,cr=!1,cu="",cR=null;db.prototype.clear=dO(function(){var a=Infinity,b=-Infinity;for(var c=0,d=this.set.length;c",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"},dN=0;for(var dP in g)g.propertyIsEnumerable(dP)&&!bT.propertyIsEnumerable(dP)&&(bT[dP]=g[dP]);return bT}function j(a,b,c){function d(a,b,c){var e=b[a];if(e!=null)return e;c==null&&(c=b.fallthrough);if(c==null)return b.catchall;if(typeof c=="string")return d(a,i[c]);for(var f=0,g=c.length;fa&&d.push(h.slice(a-f,Math.min(h.length,b-f)),c[e+1]),i>=a&&(g=1)):g==1&&(i>b?d.push(h.slice(0,b-f),c[e+1]):d.push(h,c[e+1])),f=i}}function s(a){this.lines=a,this.parent=null;for(var b=0,c=a.length,d=0;b=0&&d>=0;--c,--d)if(a.charAt(c)!=b.charAt(d))break;return d+1}function Z(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;c0&&b.ch=this.string.length},sol:function(){return this.pos==0},peek:function(){return this.string.charAt(this.pos)},next:function(){if(this.posb},eatSpace:function(){var a=this.pos;while(/[\s\u00a0]/.test(this.string.charAt(this.pos)))++this.pos;return this.pos>a},skipToEnd:function(){this.pos=this.string.length},skipTo:function(a){var b=this.string.indexOf(a,this.pos);if(b>-1)return this.pos=b,!0},backUp:function(a){this.pos-=a},column:function(){return O(this.string,this.start,this.tabSize)},indentation:function(){return O(this.string,null,this.tabSize)},match:function(a,b,c){if(typeof a!="string"){var e=this.string.slice(this.pos).match(a);return e&&b!==!1&&(this.pos+=e[0].length),e}function d(a){return c?a.toLowerCase():a}if(d(this.string).indexOf(d(a),this.pos)==this.pos)return b!==!1&&(this.pos+=a.length),!0},current:function(){return this.string.slice(this.start,this.pos)}},a.StringStream=n,o.prototype={attach:function(a){this.set.push(a)},detach:function(a){var b=Z(this.set,a);b>-1&&this.set.splice(b,1)},split:function(a,b){if(this.to<=a&&this.to!=null)return null;var c=this.from=b&&(this.from=Math.max(d,this.from)+e),this.to!=null&&this.to>b&&(this.to=dthis.from&&(dthis.from||this.from==null)&&(this.to=null)},isDead:function(){return this.from!=null&&this.to!=null&&this.from>=this.to},sameSet:function(a){return this.set==a.set}},p.prototype={attach:function(a){this.line=a},detach:function(a){this.line==a&&(this.line=null)},split:function(a,b){if(athis.to},clipTo:function(a,b,c,d,e){(a||bthis.to)?(this.from=0,this.to=-1):this.from>b&&(this.from=this.to=Math.max(d,this.from)+e)},sameSet:function(a){return!1},find:function(){return!this.line||!this.line.parent?null:{line:v(this.line),ch:this.from}},clear:function(){if(this.line){var a=Z(this.line.marked,this);a!=-1&&this.line.marked.splice(a,1),this.line=null}}},q.inheritMarks=function(a,b){var c=new q(a),d=b&&b.marked;if(d)for(var e=0;e5e3){e[f++]=this.text.slice(d.pos),e[f++]=null;break}}return e.length!=f&&(e.length=f,g=!0),f&&e[f-2]!=i&&(g=!0),g||(e.length<5&&this.text.length<10?null:!1)},getTokenAt:function(a,b,c){var d=this.text,e=new n(d);while(e.pos',X(b).replace(/\t/g,a),""):c.push(X(b).replace(/\t/g,a))}function j(a){return a?"cm-"+a.replace(/ +/g," cm-"):null}var c=[],d=!0,f=this.styles,g=this.text,h=this.marked,i=g.length;b!=null&&(i=Math.min(b,i));if(!g&&b==null)e(" ");else if(!h||!h.length)for(var k=0,l=0;li&&(m=m.slice(0,i-l)),l+=o,e(m,j(n))}else{var p=0,k=0,q="",n,r=0,s=h[0].from||0,t=[],u=0;function v(){var a;while(ux?q.slice(0,x-p):q,z);if(y>=x){q=q.slice(x-p),p=x;break}p=y}q=f[k++],n=j(f[k++])}}}return c.join("")},cleanUp:function(){this.parent=null;if(this.marked)for(var a=0,b=this.marked.length;a50){while(f.lines.length>50){var h=f.lines.splice(f.lines.length-25,25),i=new s(h);f.height-=i.height,this.children.splice(d+1,0,i),i.parent=this}this.maybeSpill()}break}a-=g}},maybeSpill:function(){if(this.children.length<=10)return;var a=this;do{var b=a.children.splice(a.children.length-5,5),c=new t(b);if(!a.parent){var d=new t(a.children);d.parent=a,a.children=[d,c],a=d}else{a.size-=c.size,a.height-=c.height;var e=Z(a.parent.children,a);a.parent.children.splice(e+1,0,c)}c.parent=a.parent}while(a.children.length>10);a.parent.maybeSpill()},iter:function(a,b,c){this.iterN(a,b-a,c)},iterN:function(a,b,c){for(var d=0,e=this.children.length;d400||!f)this.done.push([{start:a,added:b,old:c}]);else if(f.start>a+b||f.start+f.added=0;--i)f.old.unshift(c[i]);f.added+=f.start-a,f.start=a}else f.start-1&&(N="\r\n")})(),document.documentElement.getBoundingClientRect!=null&&(Q=function(a,b){try{var c=a.getBoundingClientRect();c={top:c.top,left:c.left}}catch(d){c={top:0,left:0}}if(!b)if(window.pageYOffset==null){var e=document.documentElement||document.body.parentNode;e.scrollTop==null&&(e=document.body),c.top+=e.scrollTop,c.left+=e.scrollLeft}else c.top+=window.pageYOffset,c.left+=window.pageXOffset;return c});var W=document.createElement("pre");X("a")=="\na"?X=function(a){return W.textContent=a,W.innerHTML.slice(1)}:X("\t")!="\t"&&(X=function(a){return W.innerHTML="",W.appendChild(document.createTextNode(a)),W.innerHTML}),a.htmlEscape=X;var _="\n\nb".split(/\n/).length!=3?function(a){var b=0,c,d=[];while((c=a.indexOf("\n",b))>-1)d.push(a.slice(b,a.charAt(c-1)=="\r"?c-1:c)),b=c+1;return d.push(a.slice(b)),d}:function(a){return a.split(/\r?\n/)};a.splitLines=_;var ba=window.getSelection?function(a){try{return a.selectionStart!=a.selectionEnd}catch(b){return!1}}:function(a){try{var b=a.ownerDocument.selection.createRange()}catch(c){}return!b||b.parentElement()!=a?!1:b.compareEndPoints("StartToEnd",b)!=0};a.defineMode("null",function(){return{token:function(a){a.skipToEnd()}}}),a.defineMIME("text/plain","null");var bb={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",91:"Mod",92:"Mod",93:"Mod",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63276:"PageUp",63277:"PageDown",63275:"End",63273:"Home",63234:"Left",63232:"Up",63235:"Right",63233:"Down",63302:"Insert",63272:"Delete"};return a.keyNames=bb,function(){for(var a=0;a<10;a++)bb[a+48]=String(a);for(var a=65;a<=90;a++)bb[a]=String.fromCharCode(a);for(var a=1;a<=12;a++)bb[a+111]=bb[a+63235]="F"+a}(),a}() diff --git a/lib/mergely.css b/lib/mergely.css index 8c73504..26dc42f 100644 --- a/lib/mergely.css +++ b/lib/mergely.css @@ -1,8 +1,3 @@ -/** - * Copyright (c) 2012 by Jamie Peabody, http://www.mergely.com - * All rights reserved. - * Version: 2.5 2012-07-31 - */ /* required */ .mergely-column textarea { width: 80px; height: 200px; } .mergely-column { float: left; } @@ -16,18 +11,23 @@ .mergely-column { border: 1px solid #ccc; } .mergely-active { border: 1px solid #4ba3fa; } -/* markup */ -.mergely-c-start { border-top: 1px solid #ccc !important; } -.mergely-c-end { border-bottom: 1px solid #ccc !important; } -.mergely-a-start { border-top: 1px solid #4ba3fa !important; background-color: #ddeeff !important; } -.mergely-a-mid { background-color: #ddeeff !important; } -.mergely-a-end { border-bottom: 1px solid #4ba3fa !important; background-color: #ddeeff !important; } -.mergely-d-start { border-top: 1px solid #ff7f7f !important; background-color: #f9e9e9 !important; } -.mergely-d-mid { background-color: #f9e9e9 !important; } -.mergely-d-end { border-bottom: 1px solid #ff7f7f !important; background-color: #f9e9e9 !important; } -.mergely-c-rem { text-decoration: line-through; color: #888; background-color: #f9e9e9; } -.mergely-c-add { background-color: #ddeeff; } -.mergely-d-start-rhs { border-top: 1px solid #ff7f7f !important; } -.mergely-d-end-rhs { border-bottom: 1px solid #ff7f7f !important; } -.mergely-a-start-lhs { border-top: 1px solid #4ba3fa !important; } -.mergely-a-end-lhs { border-bottom: 1px solid #4ba3fa !important; } + +.mergely.a.rhs.start { border-top: 1px solid #4ba3fa; } +.mergely.a.lhs.start.end, +.mergely.a.rhs.end { border-bottom: 1px solid #4ba3fa; } +.mergely.a.rhs { background-color: #ddeeff; } + +.mergely.d.lhs { background-color: #f9e9e9; } +.mergely.d.lhs.end, +.mergely.d.rhs.start.end { border-bottom: 1px solid #ff7f7f; } +.mergely.d.lhs.start { border-top: 1px solid #ff7f7f; } + +.mergely.c.lhs, +.mergely.c.rhs { background-color: #fcfcfc; } +.mergely.c.lhs.start, +.mergely.c.rhs.start { border-top: 1px solid #ccc; } +.mergely.c.lhs.end, +.mergely.c.rhs.end { border-bottom: 1px solid #ccc; } + +.mergely.ch.a.rhs { background-color: #ddeeff; } +.mergely.ch.d.lhs { background-color: #f9e9e9; text-decoration: line-through; color: #888; } diff --git a/lib/mergely.js b/lib/mergely.js index 331da4c..eaccb46 100644 --- a/lib/mergely.js +++ b/lib/mergely.js @@ -1,17 +1,12 @@ -/** - * Copyright (c) 2012 by Jamie Peabody, http://www.mergely.com - * All rights reserved. - * Version: 2.5 2012-07-31 - */ Mgly = {}; -Object.size = function(obj) { - var size = 0, key; - for (key in obj) { - if (obj.hasOwnProperty(key)) size++; - } - return size; -}; +Mgly.sizeOf = function(obj) { + var size = 0, key; + for (key in obj) { + if (obj.hasOwnProperty(key)) size++; + } + return size; +} Mgly.LCS = function(x, y) { //http://en.wikipedia.org/wiki/Longest_common_subsequence_problem @@ -38,66 +33,62 @@ Mgly.LCS = function(x, y) { } this.ready = 1; } - -Mgly.LCS.prototype.clear = function() { - this.ready = 0; -} - -Mgly.LCS.prototype._diff = function(i, j, added, removed) { - var x = this.x; - var y = this.y; - var C = this.C; - if (this.ready && i > 0 && j > 0 && (x[i] == y[j])) { - this._diff(i - 1, j - 1, added, removed); - } - else { - if (j > 0 && (i == 0 || (C[i][j - 1] >= C[i-1][j]))) { - this._diff(i, j - 1, added, removed); - if (added) added(j - 1, y[j]); +$.extend(Mgly.LCS.prototype, { + clear: function() { this.ready = 0; }, + diff: function(added, removed) { + this._diff(this.x.length - 1, this.y.length - 1, added, removed); + }, + _diff: function(i, j, added, removed){ + var x = this.x; + var y = this.y; + var C = this.C; + if (this.ready && i > 0 && j > 0 && (x[i] == y[j])) { + this._diff(i - 1, j - 1, added, removed); } - else if (i > 0 && (j == 0 || (C[i][j - 1] < C[i - 1][j]))) { - this._diff(i - 1, j, added, removed); - if (removed) removed(i - 1, x[i]); - } - } -} - -Mgly.LCS.prototype.diff = function(added, removed) { - this._diff(this.x.length - 1, this.y.length - 1, added, removed); -} - -Mgly.LCS.prototype._lcs = function(string1, string2) { - // init max value - var longest = 0; - // init 2D array with 0 - var table = Array(string1.length); - for(a = 0; a <= string1.length; a++){ - table[a] = Array(string2.length); - for(b = 0; b <= string2.length; b++){ - table[a][b] = 0; - } - } - // fill table - for(var i = 0; i < string1.length; i++) { - for(var j = 0; j < string2.length; j++) { - if(string1[i]==string2[j]) { - if(table[i][j] == 0){ - table[i+1][j+1] = 1; - } - else { - table[i+1][j+1] = table[i][j] + 1; - } - if(table[i+1][j+1] > longest){ - longest = table[i+1][j+1]; - } - } - else { - table[i+1][j+1] = 0; + else { + if (j > 0 && (i == 0 || (C[i][j - 1] >= C[i-1][j]))) { + this._diff(i, j - 1, added, removed); + if (added) added(j - 1, y[j]); + } + else if (i > 0 && (j == 0 || (C[i][j - 1] < C[i - 1][j]))) { + this._diff(i - 1, j, added, removed); + if (removed) removed(i - 1, x[i]); } } + }, + _lcs: function(string1, string2) { + // init max value + var longest = 0; + // init 2D array with 0 + var table = Array(string1.length); + for(a = 0; a <= string1.length; a++){ + table[a] = Array(string2.length); + for(b = 0; b <= string2.length; b++){ + table[a][b] = 0; + } + } + // fill table + for(var i = 0; i < string1.length; i++) { + for(var j = 0; j < string2.length; j++) { + if(string1[i]==string2[j]) { + if(table[i][j] == 0){ + table[i+1][j+1] = 1; + } + else { + table[i+1][j+1] = table[i][j] + 1; + } + if(table[i+1][j+1] > longest){ + longest = table[i+1][j+1]; + } + } + else { + table[i+1][j+1] = 0; + } + } + } + return longest; } - return longest; -} +}); Mgly.diff = function(lhs, rhs, retain_lines) { this.diff_codes = {}; @@ -110,12 +101,12 @@ Mgly.diff = function(lhs, rhs, retain_lines) { var lhs_data = new Object(); lhs_data.data = this._diff_codes(lhs_lines); lhs_data.modified = {}; - lhs_data.length = Object.size(lhs_data.data); + lhs_data.length = Mgly.sizeOf(lhs_data.data); var rhs_data = new Object(); rhs_data.data = this._diff_codes(rhs_lines); rhs_data.modified = {}; - rhs_data.length = Object.size(rhs_data.data); + rhs_data.length = Mgly.sizeOf(rhs_data.data); var max = (lhs_data.length + rhs_data.length + 1); var vector_d = Array( 2 * max + 2 ); @@ -130,233 +121,232 @@ Mgly.diff = function(lhs, rhs, retain_lines) { this.rhs_lines = rhs_lines; } }; - -Mgly.diff.prototype.changes = function() { - return this.items; -} - -Mgly.diff.prototype.normal_form = function() { - var nf = ''; - for (var index in this.items) { - 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'; +$.extend(Mgly.diff.prototype, { + changes: function() { return this.items; }, + normal_form: function() { + var nf = ''; + for (var index in this.items) { + 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; -} - -Mgly.diff.prototype._diff_codes = function(lines) { - var code = this.max_code; - var codes = {}; - for (var i = 0; i < lines.length; ++i) { - var line = lines[i]; - 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; -} - -Mgly.diff.prototype._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]) ) { - ++lhs_lower; - ++rhs_lower; - } - while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs.data[lhs_upper - 1] == rhs.data[rhs_upper - 1]) ) { - --lhs_upper; - --rhs_upper; - } - if (lhs_lower == lhs_upper) { - while (rhs_lower < rhs_upper) { - rhs.modified[ rhs_lower++ ] = true; - } - } - else if (rhs_lower == rhs_upper) { - while (lhs_lower < lhs_upper) { - lhs.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); - } -} - -Mgly.diff.prototype._sms = function(lhs, lhs_lower, lhs_upper, rhs, rhs_lower, rhs_upper, vector_u, vector_d) { - var max = lhs.length + rhs.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 = new Object(); - 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 + return nf; + }, + _diff_codes: function(lines) { + var code = this.max_code; + var codes = {}; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + var aCode = this.diff_codes[line]; + if (aCode != undefined) { + codes[i] = aCode; } else { - x = vector_d[ offset_down + k - 1 ] + 1;//right - if ((k < (kdown + d)) && (vector_d[ offset_down + k + 1 ] >= x)) { + 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]) ) { + ++lhs_lower; + ++rhs_lower; + } + while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs.data[lhs_upper - 1] == rhs.data[rhs_upper - 1]) ) { + --lhs_upper; + --rhs_upper; + } + if (lhs_lower == lhs_upper) { + while (rhs_lower < rhs_upper) { + rhs.modified[ rhs_lower++ ] = true; + } + } + else if (rhs_lower == rhs_upper) { + while (lhs_lower < lhs_upper) { + lhs.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); + } + }, + _sms: function(lhs, lhs_lower, lhs_upper, rhs, rhs_lower, rhs_upper, vector_u, vector_d) { + var max = lhs.length + rhs.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 = new Object(); + 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 } - } - 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])) { - 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); + 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])) { + 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)) + // 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 - } - y = x - k; - while ((x > lhs_lower) && (y > rhs_lower) && (lhs.data[x - 1] == rhs.data[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); + } 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])) { + // 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."; -} - -Mgly.diff.prototype._optimize = function(data) { - var start = 0, end = 0; - while (start < data.length) { - while ((start < data.length) && (data.modified[start] == undefined || data.modified[start] == false)) { - start++; - } - end = start; - while ((end < data.length) && (data.modified[end] == true)) { - end++; - } - if ((end < data.length) && (data.data[start] == data.data[end])) { - data.modified[start] = false; - data.modified[end] = true; - } - else { - start = end; - } - } -} - -Mgly.diff.prototype._create_diffs = function(lhs_data, rhs_data) { - 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])) { - // 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])) - lhs_line++; - - while (rhs_line < rhs_data.length && (lhs_line >= lhs_data.length || rhs_data.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); + throw "the algorithm should never come here."; + }, + _optimize: function(data) { + var start = 0, end = 0; + while (start < data.length) { + while ((start < data.length) && (data.modified[start] == undefined || data.modified[start] == false)) { + start++; + } + end = start; + while ((end < data.length) && (data.modified[end] == true)) { + end++; + } + if ((end < data.length) && (data.data[start] == data.data[end])) { + data.modified[start] = false; + data.modified[end] = true; + } + else { + start = end; } } + }, + _create_diffs: function(lhs_data, rhs_data) { + 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])) { + // 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])) + lhs_line++; + + while (rhs_line < rhs_data.length && (lhs_line >= lhs_data.length || rhs_data.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); + } + } + } + return items; } - 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); } }; -$.extend(Mgly.mergely.prototype, -{ +$.extend(Mgly.mergely.prototype, { name: "mergely", //http://jupiterjs.com/news/writing-the-perfect-jquery-plugin init: function(el, options) { this.settings = { + autoupdate: true, autoresize: true, fadein: 'fast', editor_width: '400px', editor_height: '400px', resize_timeout: 500, change_timeout: 150, - fgcolor: '#4ba3fa', - bgcolor: '#eee', + fgcolor: {a:'#4ba3fa',c:'#cccccc',d:'#ff7f7f'}, + _bgcolor: '#eee', lhs: function(setValue) { }, rhs: function(setValue) { }, loaded: function() { }, @@ -369,34 +359,37 @@ $.extend(Mgly.mergely.prototype, if (this.height) h = this.height(h); var content_width = w / 2.0 - 2 * 8 - 8; var content_height = h; - var tthis = $(el); - tthis.find('.mergely-column, .CodeMirror-scroll').css({ 'width': content_width + 'px' }); - tthis.find('.mergely-column, .mergely-canvas, .mergely-margin, .mergely-column textarea, .CodeMirror-scroll').css({ 'height': content_height + 'px' }); - tthis.find('.mergely-canvas').css({ 'height': content_height + 'px' }); - tthis.find('.mergely-column textarea').css({ 'width': content_width + 'px' }); - tthis.css({ 'width': w + 'px', 'height': h + 'px' }); - if (tthis.css('display') == 'none') { - if (this.fadein != false) tthis.fadeIn(this.fadein); - else tthis.show(); + var self = $(el); + self.find('.mergely-column').css({ 'width': content_width + 'px' }); + self.find('.mergely-column, .mergely-canvas, .mergely-margin, .mergely-column textarea, .CodeMirror-scroll').css({ 'height': content_height + 'px' }); + self.find('.mergely-canvas').css({ 'height': content_height + 'px' }); + self.find('.mergely-column textarea').css({ 'width': content_width + 'px' }); + self.css({ 'width': w + 'px', 'height': h + 'px' }); + if (self.css('display') == 'none') { + if (this.fadein != false) self.fadeIn(this.fadein); + else self.show(); if (this.loaded) this.loaded(); } if (this.resized) this.resized(); }, - _debug: 'calc', //scroll,draw,calc,diff,markup + _debug: '', //scroll,draw,calc,diff,markup resized: function() { } }; - this.cmsettings = { + var cmsettings = { mode: 'text/plain', readOnly: false, lineWrapping: false, lineNumbers: true } + this.lhs_cmsettings = {}; + this.rhs_cmsettings = {}; // save this element for faster queries this.element = $(el); // save options if there are any - if (options.cmsettings) $.extend(this.cmsettings, options.cmsettings); + if (options && options.cmsettings) $.extend(this.lhs_cmsettings, cmsettings, options.cmsettings, options.lhs_cmsettings); + if (options && options.cmsettings) $.extend(this.rhs_cmsettings, cmsettings, options.cmsettings, options.rhs_cmsettings); if (options) $.extend(this.settings, options); // bind if the element is destroyed @@ -409,45 +402,56 @@ $.extend(Mgly.mergely.prototype, }, // bind events to this instance's methods bind: function() { - var tthis = this; + var rhstx = $('#' + this.id + '-rhs').get(0); + if (!rhstx) { + console.error('rhs textarea not defined - Mergely not initialized properly'); + return; + } + var lhstx = $('#' + this.id + '-lhs').get(0); + if (!rhstx) { + console.error('lhs textarea not defined - Mergely not initialized properly'); + return; + } + var self = this; this.editor = []; var lhs_cmsettings = jQuery.extend({ - onChange: function () { tthis._changing(tthis.id + '-lhs', tthis.id + '-rhs'); }, - onScroll: function () { tthis._scrolling(tthis.id + '-lhs'); } - }, this.cmsettings); - this.editor[this.id + '-lhs'] = CodeMirror.fromTextArea( - $('#' + this.id + '-lhs').get(0), lhs_cmsettings + onChange: function () { if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); }, + onScroll: function () { self._scrolling(self.id + '-lhs'); } + }, this.lhs_cmsettings); + this.editor[this.id + '-lhs'] = CodeMirror.fromTextArea(lhstx, lhs_cmsettings ); var rhs_cmsettings = jQuery.extend({ - onChange: function () { tthis._changing(tthis.id + '-lhs', tthis.id + '-rhs'); }, - onScroll: function () { tthis._scrolling(tthis.id + '-rhs'); } - }, this.cmsettings); - this.editor[this.id + '-rhs'] = CodeMirror.fromTextArea( - $('#' + this.id + '-rhs').get(0), rhs_cmsettings + onChange: function () { if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); }, + onScroll: function () { self._scrolling(self.id + '-rhs'); } + }, this.rhs_cmsettings); + this.editor[this.id + '-rhs'] = CodeMirror.fromTextArea(rhstx, rhs_cmsettings ); // resize var sz_timeout1 = null; + var sz = function() { + self.em_height = null; //recalculate + if (self.settings.resize) self.settings.resize(); + self.editor[self.id + '-lhs'].refresh(); + self.editor[self.id + '-rhs'].refresh(); + self._changing(self.id + '-lhs', self.id + '-rhs'); + } $(window).resize( function () { if (sz_timeout1) clearTimeout(sz_timeout1); - sz_timeout1 = setTimeout(function () { - tthis.em_height = null; //recalculate - if (tthis.settings.resize) tthis.settings.resize(); - tthis.editor[tthis.id + '-lhs'].refresh(); - tthis.editor[tthis.id + '-rhs'].refresh(); - tthis._changing(tthis.id + '-lhs', tthis.id + '-rhs'); - }, tthis.settings.resize_timeout); + sz_timeout1 = setTimeout(sz, self.settings.resize_timeout); } ); + sz(); }, 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.element.unbind('destroyed', this.teardown); this.teardown(); - }, + }, teardown: function() { this.unbind(); }, @@ -457,7 +461,26 @@ $.extend(Mgly.mergely.prototype, rhs: function(text) { this.editor[this.id + '-rhs'].setValue(text); }, + update: function() { + this._changing(this.id + '-lhs', this.id + '-rhs'); + }, + scrollTo: function(side, num) { + var le = this.editor[this.id + '-lhs']; + var re = this.editor[this.id + '-rhs']; + if (side == 'lhs') { + le.setCursor(num); + le.centerOnCursor(); + } + else { + re.setCursor(num); + re.centerOnCursor(); + } + }, + options: function(opts) { + $.extend(this.settings, opts); + }, swap: function() { + if (this.lhs_cmsettings.readOnly || this.rhs_cmsettings.readOnly) return; var le = this.editor[this.id + '-lhs']; var re = this.editor[this.id + '-rhs']; var tmp = re.getValue(); @@ -467,8 +490,8 @@ $.extend(Mgly.mergely.prototype, merge: function(side) { var le = this.editor[this.id + '-lhs']; var re = this.editor[this.id + '-rhs']; - if (side == 'lhs') le.setValue(re.getValue()); - else re.setValue(le.getValue()); + if (side == 'lhs' && !this.lhs_cmsettings.readOnly) le.setValue(re.getValue()); + else if (!this.rhs_cmsettings.readOnly) re.setValue(le.getValue()); }, get: function(side) { var ed = this.editor[this.id + '-' + side]; @@ -477,25 +500,30 @@ $.extend(Mgly.mergely.prototype, return t; }, clear: function(side) { + if (side == 'lhs' && this.lhs_cmsettings.readOnly) return; + if (side == 'rhs' && this.rhs_cmsettings.readOnly) return; var ed = this.editor[this.id + '-' + side]; ed.setValue(''); }, + cm: function(side) { + return this.editor[this.id + '-' + side]; + }, search: function(side, query) { var le = this.editor[this.id + '-lhs']; var re = this.editor[this.id + '-rhs']; var editor; if (side == 'lhs') editor = le; else editor = re; - 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; - } - if (this.cursor[this.id].findNext()) { - editor.setSelection(this.cursor[this.id].from(), this.cursor[this.id].to()); - } - else { - this.cursor[this.id] = editor.getSearchCursor(query, { line: 0, ch: 0 }, false); - } + 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; + } + if (this.cursor[this.id].findNext()) { + editor.setSelection(this.cursor[this.id].from(), this.cursor[this.id].to()); + } + else { + this.cursor[this.id] = editor.getSearchCursor(query, { line: 0, ch: 0 }, false); + } }, resize: function() { this.settings.resize(); @@ -517,37 +545,30 @@ $.extend(Mgly.mergely.prototype, this.prev_query = []; this.cursor = []; this.change_exp = new RegExp(/(\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/); - var merge_left_button; - var merge_right_button; + var merge_lhs_button; + var merge_rhs_button; if ($.button != undefined) { //jquery ui - merge_left_button = ''; - merge_right_button = ''; + merge_lhs_button = ''; + merge_rhs_button = ''; } else { // homebrew var style = 'width:1em;height:1em;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;'; - merge_left_button = '
<
'; - merge_right_button = '
>
'; + merge_lhs_button = '
<
'; + merge_rhs_button = '
>
'; } - this.merge_right_button = $(merge_right_button); - this.merge_left_button = $(merge_left_button); - if (this.merge_right_button.corner) this.merge_right_button.corner('3px'); - if (this.merge_left_button.corner) this.merge_left_button.corner('3px'); - + this.merge_rhs_button = $(merge_rhs_button); + this.merge_lhs_button = $(merge_lhs_button); + if (this.merge_rhs_button.corner) this.merge_rhs_button.corner('3px'); + if (this.merge_lhs_button.corner) this.merge_lhs_button.corner('3px'); + // create the textarea and canvas elements - $(this.element).append($('
')); + $(this.element).append($('
')); $(this.element).append($('
')); $(this.element).append($('
')); $(this.element).append($('
')); - $(this.element).append($('
')); - this.bind(); - if (this.settings.lhs) this.settings.lhs( this.editor[this.id + '-lhs'].setValue ); - if (this.settings.rhs) this.settings.rhs( this.editor[this.id + '-rhs'].setValue ); - - // resize only after bind - $(window).resize(); - + $(this.element).append($('
')); //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; }'; @@ -555,8 +576,72 @@ $.extend(Mgly.mergely.prototype, cmstyle += this.id + ' .CodeMirror-scroll { height: 100%; overflow: auto; }'; } $('').appendTo('head'); + this.bind(); + if (this.settings.lhs) this.settings.lhs( this.editor[this.id + '-lhs'].setValue ); + if (this.settings.rhs) this.settings.rhs( this.editor[this.id + '-rhs'].setValue ); + + // resize only after bind + this.settings.resize(); + + // merge + var self = this; + var ed = {lhs:this.editor[this.id + '-lhs'], rhs:this.editor[this.id + '-rhs']}; + $('.merge-button').live('click', function(ev){ + console.log('lhs hover over', ev, this); + // side of mouseenter + var side = 'rhs'; + var oside = 'lhs'; + var parent = $(this).parents('#' + self.id + '-editor-lhs'); + if (parent.length) { + side = 'lhs'; + oside = 'rhs'; + } + var pos = ed[side].coordsChar({x:ev.pageX, y:ev.pageY}); + console.log('pos', side, pos); + + // get the change id + var cid = null; + var info = ed[side].lineInfo(pos.line); + $.each(info.bgClass.split(' '), function(i, clazz) { + console.log('clazz', i, clazz); + if (clazz.indexOf('cid-') == 0) { + cid = parseInt(clazz.split('-')[1]); + return false; + } + }); + var change = self.changes[cid]; + console.log('change', change); + + var line = {lhs: ed['lhs'].lineInfo(change['lhs-line-to']), rhs: ed['rhs'].lineInfo(change['rhs-line-to'])}; + var text = ed[side].getRange( + { 'line': change[side + '-line-from'], 'ch': 0 }, + { 'line': change[side + '-line-to'], 'ch': line[side].text.length }); + + if (change['op'] == 'c') { + ed[oside].replaceRange( text, + { 'line': change[oside + '-line-from'], 'ch': 0 }, + { 'line': change[oside + '-line-to'], 'ch': line[oside].text.length }); + } + else {// 'a' or 'd' + var from = parseInt(change[oside + '-line-from']); + var to = parseInt(change[oside + '-line-to']); + for (var i = to; i >= from; --i) { + ed[oside].removeLine(i); + } + } + //reset + ed['lhs'].setValue(ed['lhs'].getValue()); + ed['rhs'].setValue(ed['rhs'].getValue()); + return false; + }); }, + _scrolling: function(editor_name) { + if (self._skipscroll === true) { + // scrolling one side causes the other to event - ignore it + self._skipscroll = false; + return; + } var scroller = $(this.editor[editor_name].getScrollerElement()); if (this.midway == undefined) { this.midway = (scroller.height() / 2.0 + scroller.offset().top).toFixed(2); @@ -566,6 +651,7 @@ $.extend(Mgly.mergely.prototype, 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); @@ -586,23 +672,26 @@ $.extend(Mgly.mergely.prototype, last_change = change; if (midline.line >= last_change[this_side+'-line-to']) { top_adjust += - (change[this_side+'-y-end'] - change[this_side+'-y-start']) - (change[other_side+'-y-end'] - change[other_side+'-y-start']); + (change[this_side+'-y-end'] - change[this_side+'-y-start']) - + (change[other_side+'-y-end'] - change[other_side+'-y-start']); } } } var scroll = true; if (last_change) { - this.trace('scroll', 'last visible change', last_change); + this.trace('scroll', 'last change before midline', last_change); if ((last_change[this_side+'-line-from'] < midline.line) && (last_change[this_side+'-line-to'] > midline.line)) { scroll = false; } } + this.trace('scroll', 'scroll', scroll); if (scroll) { // scroll the other side this.trace('scroll', 'scrolling other side', top_to - top_adjust); var scroller = $(this.editor[name].getScrollerElement()); + self._skipscroll = true;//disable next event scroller.scrollTop(top_to - top_adjust).scrollLeft(left_to); } else this.trace('scroll', 'not scrolling other side'); @@ -612,10 +701,10 @@ $.extend(Mgly.mergely.prototype, } }, _changing: function(editor_name1, editor_name2) { - var tthis = this; + var self = this; if (this.changed_timeout != null) clearTimeout(this.changed_timeout); this.changed_timeout = setTimeout(function(){ - tthis._changed(editor_name1, editor_name2); + self._changed(editor_name1, editor_name2); }, this.settings.change_timeout); }, _changed: function(editor_name1, editor_name2) { @@ -624,7 +713,7 @@ $.extend(Mgly.mergely.prototype, editor.operation(function() { for (var i = 0, l = editor.lineCount(); i < l; ++i) { editor.clearMarker(i); - editor.setLineClass(i, null); + editor.setLineClass(i, null, null); } }); } @@ -666,352 +755,322 @@ $.extend(Mgly.mergely.prototype, if (to.length == 1) change['rhs-line-to'] = to[0] - 1; else change['rhs-line-to'] = to[1] - 1; // TODO: optimize for changes that are adds/removes + if (change['lhs-line-from'] < 0) change['lhs-line-from'] = 0; + if (change['lhs-line-to'] < 0) change['lhs-line-to'] = 0; + if (change['rhs-line-from'] < 0) change['rhs-line-from'] = 0; + if (change['rhs-line-to'] < 0) change['rhs-line-to'] = 0; change['op'] = test[2]; changes[change_id++] = change; this.trace('diff', 'change', change); } return changes; }, + _get_extents: function() { + return { + 'top-offset': this.draw_top_offset, + 'em-height': this.em_height, + 'draw-lhs-min': this.draw_lhs_min, + 'draw-rhs-max': this.draw_rhs_max + }; + }, _calculate_offsets: function (editor_name1, editor_name2, changes) { - if (this.draw_top_offset == null) { - var topnode = this.element.find('.CodeMirror-gutter-text pre').first(); - var top_offset = topnode.offset().top; - if (!top_offset) { - // try again - return; - } - this.em_height = topnode.get(0).offsetHeight; + if (this.em_height == null) { + + //var topnode = this.element.find('.CodeMirror-gutter-text pre').first(); + //this.em_height = topnode.get(0).offsetHeight; // this is the distance from the top of the screen - this.draw_top_offset = 6.5 - top_offset; - if (!this.em_height) { - var test = $('
 
'); - this.em_height = parseInt(test.css('line-height')) - 2; - console.warn('Failed to calculate offsets, trying brute-force:', this.em_height); - } + + var topnode = $('#' + this.id + '-lhs-margin').first(); + var top_offset = topnode.offset().top; + if (!top_offset) return;//try again + this.draw_top_offset = 0.5 - top_offset; + + this.em_height = $('.CodeMirror-lines pre').get(0).offsetHeight 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 = $('#' + editor_name1 + '-' + editor_name2 + '-canvas'); - if (!c.width()) { - console.error('canvas width is 0'); - return; - } + var c = $('#' + 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_rhs_max = $('#' + editor_name1 + '-' + editor_name2 + '-canvas').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}); } for (var i in changes) { var change = changes[i]; - /* - FIXME - var lhslc = this.editor[editor_name1].lineCount(); - if (change['lhs-line-to'] + 1 >= lhslc) { - console.warn('here'); + var valign = 2; + var ls = this.editor[editor_name1].charCoords({line: change['lhs-line-from']}); + var le = this.editor[editor_name1].charCoords({line: change['lhs-line-to']}); + var rs = this.editor[editor_name2].charCoords({line: change['rhs-line-from']}); + var re = this.editor[editor_name2].charCoords({line: change['rhs-line-to']}); + 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 (change['rhs-line-from'] > 0) { + ls.y = ls.yBot; + ls.yBot += this.em_height; + le = ls; + } } - */ - change['lhs-y-start'] = this.draw_top_offset + this.editor[editor_name1].charCoords({line: change['lhs-line-from'], ch:0}).y; - change['lhs-y-end'] = this.draw_top_offset + this.editor[editor_name1].charCoords({line: change['lhs-line-to']+1, ch:0}).y; - change['rhs-y-start'] = this.draw_top_offset + this.editor[editor_name2].charCoords({line: change['rhs-line-from'], ch:0}).y; - change['rhs-y-end'] = this.draw_top_offset + this.editor[editor_name2].charCoords({line: change['rhs-line-to']+1, ch:0}).y; - if (change['op'] == 'd') { - change['rhs-y-start'] = change['rhs-y-end']; + 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 + if (change['lhs-line-from'] > 0) { + rs.y = rs.yBot; + rs.yBot += this.em_height; + re = rs; + } } - else if (change['op'] == 'a') { - change['lhs-y-start'] = change['lhs-y-end']; + change['lhs-y-start'] = this.draw_top_offset + ls.y; + if (change['op'] == 'c' || change['op'] == 'd') { + change['lhs-y-end'] = this.draw_top_offset + le.yBot - valign; } - //this.trace('calc', 'change offsets calculated', i, change); + else { + change['lhs-y-end'] = this.draw_top_offset + le.y - valign; + } + change['rhs-y-start'] = this.draw_top_offset + rs.y; + if (change['op'] == 'c' || change['op'] == 'a') { + change['rhs-y-end'] = this.draw_top_offset + re.yBot - valign; + } + else { + change['rhs-y-end'] = this.draw_top_offset + re.y - valign; + } + this.trace('calc', 'change calculated', i, change); } + return changes; }, _markup_changes: function (editor_name1, editor_name2, changes) { $('.merge-button').remove(); // clear - var editor = this.editor[editor_name1]; - editor.operation(function() { + + var self = this; + var led = this.editor[editor_name1]; + var red = this.editor[editor_name2]; + led.operation(function() { for (var i in changes) { var change = changes[i]; - var class_start = 'mergely-' + change['op'] + '-start'; - var class_end = 'mergely-' + change['op'] + '-end'; - var class_start_end = class_start + ' ' + class_end; + var clazz = 'mergely ' + change['op'] + ' cid-' + i + ' '; + // lhs markup if (change['lhs-line-from'] == change['lhs-line-to']) { - if (change['op'] == 'c') { - editor.setLineClass(change['lhs-line-from'], class_start_end); - //editor.setMarker(change['lhs-line-from'], '%N%', class_start_end); - } - else if (change['op'] == 'a') { - editor.setLineClass(change['lhs-line-from'], class_end + '-lhs'); - //editor.setMarker(change['lhs-line-from'], '%N%', class_end + '-lhs'); + 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 (change['rhs-line-from'] > 0) { + var cl = clazz + 'lhs start end'; + led.setLineClass(change['lhs-line-from'], null, cl); + } + else { + var cl = clazz + 'lhs start'; + led.setLineClass(change['lhs-line-from'], null, cl); + } } else if (change['op'] == 'd') { - editor.setLineClass(change['lhs-line-from'], class_start_end + ' mergely-c-rem'); - //editor.setMarker(change['lhs-line-from'], '%N%', class_start_end + ' mergely-c-rem'); + var cl = clazz + 'lhs start end'; + led.setLineClass(change['lhs-line-from'], null, cl); + } + else if (change['op'] == 'c') { + var cl = clazz + 'lhs start end'; + led.setLineClass(change['lhs-line-from'], null, cl); } } else { - editor.setLineClass(change['lhs-line-from'], class_start + ((change['op'] == 'd')? ' mergely-c-rem' : '') ); - editor.setLineClass(change['lhs-line-to'], class_end + ((change['op'] == 'd')? ' mergely-c-rem' : '') ); - //editor.setMarker(change['lhs-line-from'], '%N%', class_start + ((change['op'] == 'd')? ' mergely-c-rem' : '') ); - //editor.setMarker(change['lhs-line-to'], '%N%', class_end + ((change['op'] == 'd')? ' mergely-c-rem' : '') ); - - if (change['op'] == 'd') {// fill in deletes between from/to - for (var line = change['lhs-line-from'] + 1; line < change['lhs-line-to']; ++line) { - editor.setLineClass(line, 'mergely-c-rem mergely-d-mid'); - //editor.setMarker(line, '%N%', 'mergely-c-rem mergely-d-mid'); + var cl = clazz + 'lhs start'; + led.setLineClass(change['lhs-line-from'], null, cl); + cl = clazz + 'lhs end'; + led.setLineClass(change['lhs-line-to'], null, cl); + } + + if (change['op'] == 'd') { + // apply delete to cross-out + var from = change['lhs-line-from']; + var to = change['lhs-line-to']; + var to_ln = led.lineInfo(to); + if (to_ln) { + var func = led.markText({line:from, ch:0}, {line:to, ch:to_ln.text.length}, 'mergely ch d lhs'); + self.change_funcs.push(func); + } + } + else if (change['op'] == 'c') { + // apply LCS changes to each line + for (var j = change['lhs-line-from'], k = change['rhs-line-from'], i = 0; + ((j >= 0) && (j <= change['lhs-line-to'])) || ((k >= 0) && (k <= change['rhs-line-to'])); + ++j, ++k) { + if (k + i > change['rhs-line-to']) { + // lhs continues past rhs, mark lhs as deleted + var lhs_line = led.getLine( j ); + var func = led.markText({line:j, ch:0}, {line:j, ch:lhs_line.length}, 'mergely ch d lhs'); + self.change_funcs.push(func); + continue; + } + if (j + i > change['lhs-line-to']) { + // rhs continues past lhs, mark rhs as added + var rhs_line = red.getLine( k ); + var func = led.markText({line:k, ch:0}, {line:k, ch:lhs_line.length}, 'mergely ch a rhs'); + self.change_funcs.push(func); + 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); + var max = Math.max(lhs_line.length, rhs_line.length); + if (max == 0) max = 1; + var percent = ((1.0)*lcs.length / max) * 100; + if (percent < 10) lcs.clear(); + lcs.diff( + added = function (index, c) { + if (rhs_start.ch < 0) { + rhs_start.line = k; + rhs_start.ch = index; + rhs_stop.line = k; + rhs_stop.ch = index; + } + else if (index == rhs_stop.ch + 1) { + rhs_stop.ch = index; + } + else { + if ((rhs_start.ch >= 0) && (rhs_stop.ch >= rhs_start.ch)) { + rhs_stop.ch += 1; + var func = self.editor[editor_name2].markText(rhs_start, rhs_stop, 'mergely ch a rhs'); + self.change_funcs.push(func); + } + //reset + rhs_start.ch = -1; + rhs_stop.ch = -1; + if (c != '\n') this.added(index, c);//call again + } + }, + removed = function (index, c) { + if (lhs_start.ch < 0) { + lhs_start.line = j; + lhs_start.ch = index; + lhs_stop.line = j; + lhs_stop.ch = index; + } + else if (index == lhs_stop.ch + 1) { + lhs_stop.ch = index; + } + else { + if ((lhs_start.ch >= 0) && (lhs_stop.ch >= lhs_start.ch)) { + lhs_stop.ch += 1; + var func = self.editor[editor_name1].markText(lhs_start, lhs_stop, 'mergely ch d lhs'); + self.change_funcs.push(func); + } + //reset + lhs_start.ch = -1; + lhs_stop.ch = -1; + if (c != '\n') this.removed(index, c);//call again + } + } + ); + if ((rhs_start.ch >= 0) && (rhs_stop.ch >= rhs_start.ch)) { + rhs_stop.ch += 1; + var func = red.markText(rhs_start, rhs_stop, 'mergely ch a rhs'); + self.change_funcs.push(func); + } + if ((lhs_start.ch >= 0) && (lhs_stop.ch >= lhs_start.ch)) { + lhs_stop.ch += 1; + var func = led.markText(lhs_start, lhs_stop, 'mergely ch d lhs'); + self.change_funcs.push(func); } } } + + // for each line in-between the changed lines, from and to, apply 'bg' class + for (var i = change['lhs-line-from'] + 1; i < change['lhs-line-to']; ++i) { + var cl = clazz + 'lhs'; + led.setLineClass(i, null, cl); + } + + // add widgets + 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.addWidget( + {'line': change['rhs-line-from'], 'ch': 0}, lhs_button.get(0), false, 'over', 'right'); + + 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.addWidget( + {'line': change['lhs-line-from'], 'ch': 0}, rhs_button.get(0), false, 'over', 'right'); } }); - - var editor = this.editor[editor_name2]; - editor.operation(function() { + red.operation(function() { for (var i in changes) { var change = changes[i]; - var class_start = 'mergely-' + change['op'] + '-start'; - var class_end = 'mergely-' + change['op'] + '-end'; - var class_start_end = class_start + ' ' + class_end; + var clazz = 'mergely ' + change['op'] + ' cid-' + i + ' '; + // rhs markup if (change['rhs-line-from'] == change['rhs-line-to']) { - if (change['op'] == 'c') { - editor.setLineClass(change['rhs-line-from'], class_start_end); - //editor.setMarker(change['rhs-line-from'], '%N%', class_start_end); + if (change['op'] == 'a') { + var cl = clazz + 'bg rhs start end'; + red.setLineClass(change['rhs-line-from'], null, cl); } - else if (change['op'] == 'a') { - editor.setLineClass(change['rhs-line-from'], class_start_end); - //editor.setMarker(change['rhs-line-from'], '%N%', class_start_end); + else if (change['op'] == 'd'){ + if (change['lhs-line-from'] > 0) { + // deletes (left), normally start from the end of the rhs, + // except for the case when the start of the lhs is 0 + var cl = clazz + 'rhs start end'; + red.setLineClass(change['rhs-line-from'], null, cl); + + // this will be sweet if gutters worked the same as lines + //red.setMarker(change['rhs-line-from'], null, cl) + } + else { + // case where there are no lines on the rhs or where the lhs change + // finishes before the rhs change + var cl = clazz + 'rhs start'; + red.setLineClass(change['rhs-line-from'], null, cl); + + // this will be sweet if gutters worked the same as lines + //red.setMarker(change['rhs-line-from'], null, cl) + } } - else if (change['op'] == 'd') { - editor.setLineClass(change['rhs-line-from'], class_end + '-rhs'); - //editor.setMarker(change['rhs-line-from'], '%N%', class_end + '-rhs'); + else if (change['op'] == 'c') { + var cl = clazz + 'rhs start end'; + red.setLineClass(change['rhs-line-from'], null, cl); + + // this will be sweet if gutters worked the same as lines + //red.setMarker(change['rhs-line-from'], null, cl) } } else { - editor.setLineClass(change['rhs-line-from'], class_start); - editor.setLineClass(change['rhs-line-to'], class_end); - //editor.setMarker(change['rhs-line-from'], '%N%', class_start); - //editor.setMarker(change['rhs-line-to'], '%N%', class_end); - - if (change['op'] == 'a') {// fill in deletes between from/to - for (var line = change['rhs-line-from'] + 1; line < change['rhs-line-to']; ++line) { - editor.setLineClass(line, 'mergely-c-add mergely-a-mid'); - //editor.setMarker(line, '%N%', 'mergely-c-add mergely-a-mid'); - } + var cl = clazz + 'rhs start'; + red.setLineClass(change['rhs-line-from'], null, cl); + cl = clazz + 'rhs end'; + red.setLineClass(change['rhs-line-to'], null, cl); + } + for (var i = change['rhs-line-from'] + 1; i <= change['rhs-line-to']; ++i) { + var cl = clazz + 'rhs'; + if (change['op'] == 'c') { + // these lines are added + cl = 'mergely a cid-' + i + ' rhs c'; + //if (i == change['rhs-line-to']) cl += ' c'; } + if (i == change['rhs-line-to']) cl += ' end'; + red.setLineClass(i, '', cl); } } }); - - for (var i in changes) { - var change = changes[i]; - var class_start = 'mergely-' + change['op'] + '-start'; - var class_end = 'mergely-' + change['op'] + '-end'; - var class_start_end = class_start + ' ' + class_end; - - if (!this.cmsettings.readOnly) { - var button = this.merge_right_button.clone(); - if (button.button) button.button({icons: {primary: 'ui-icon-triangle-1-e'}, text: false}); - button.addClass('merge-button'); - button.attr('id', 'merge-right-' + i); - // the strangeness here is to get around creating closures in a loop, which do - // not work. to get around, need to wrap and call the function. - var tthis = this; - $(button).get(0).onclick = (function (change) { - return function() { - var lhs_line = tthis.editor[editor_name1].lineInfo(change['lhs-line-to']); - var rhs_line = tthis.editor[editor_name2].lineInfo(change['rhs-line-to']); - var text = tthis.editor[editor_name1].getRange( - { 'line': change['lhs-line-from'], 'ch': 0 }, - { 'line': change['lhs-line-to'], 'ch': lhs_line.text.length }); - if (change['op'] == 'c') { - tthis.editor[editor_name2].replaceRange( - text, { 'line': change['rhs-line-from'], 'ch': 0 }, - { 'line': change['rhs-line-to'], 'ch': rhs_line.text.length }); - } - else if (change['op'] == 'a' ) { - var from = parseInt(change['rhs-line-from']); - var to = parseInt(change['rhs-line-to']); - for (var i = to; i >= from; --i) { - tthis.editor[editor_name2].removeLine(i); - } - } - else { - //must force a newline - text = text + '\n'; - tthis.editor[editor_name2].replaceRange( - text, { 'line': change['rhs-line-from'] + 1, 'ch': 0 }); - } - - //reset - tthis.editor[editor_name1].setValue(tthis.editor[editor_name1].getValue()); - tthis.editor[editor_name2].setValue(tthis.editor[editor_name2].getValue()); - return false; - } - })(change); - - this.trace('markup', 'lhs adding button', change['lhs-line-from']); - this.editor[editor_name1].addWidget( - { 'line': change['lhs-line-from'], 'ch': 0 }, button.get(0), false, 'over', 'right') - - button = this.merge_left_button.clone(); - if (button.button) button.button({icons: {primary: 'ui-icon-triangle-1-w'}, text: false}); - button.addClass('merge-button'); - button.attr('id', 'merge-left-' + i); - // the strangeness here is to get around creating closures in a loop, which do - // not work. to get around, need to wrap and call the function. - var tthis = this; - $(button).get(0).onclick = (function (change) { - return function () { - var lhs_line = tthis.editor[editor_name1].lineInfo(change['lhs-line-to']); - var rhs_line = tthis.editor[editor_name2].lineInfo(change['rhs-line-to']); - var text = tthis.editor[editor_name2].getRange( - { 'line': change['rhs-line-from'], 'ch': 0 }, - { 'line': change['rhs-line-to'], 'ch': rhs_line.text.length }); - if (change['op'] == 'c') { - tthis.editor[editor_name1].replaceRange( - text, { 'line': change['lhs-line-from'], 'ch': 0 }, - { 'line': change['lhs-line-to'], 'ch': lhs_line.text.length }); - } - else if (change['op'] == 'd' ) { - var from = parseInt(change['lhs-line-from']); - var to = parseInt(change['lhs-line-to']); - for (var i = to; i >= from; --i) { - tthis.editor[editor_name1].removeLine(i); - } - } - else { - //must force a newline - text = text + '\n'; - tthis.editor[editor_name1].replaceRange( - text, { 'line': change['lhs-line-from'] + 1, 'ch': 0 }); - } - //reset - tthis.editor[editor_name1].setValue(tthis.editor[editor_name1].getValue()); - tthis.editor[editor_name2].setValue(tthis.editor[editor_name2].getValue()); - return false; - } - })(change); - - this.trace('markup', 'rhs adding button', change['rhs-line-from']); - this.editor[editor_name2].addWidget( - { 'line': change['rhs-line-from'], 'ch': 0 }, button.get(0), false, 'over', 'right') - } - // - // LCS or line changes - // - if (change['op'] == 'a') { - var from = change['rhs-line-from']; - var to = change['rhs-line-to']; - var to_ln = this.editor[editor_name2].lineInfo(to); - if (to_ln) { - var func = this.editor[editor_name2].markText({line:from, ch:0}, {line:to, ch:to_ln.text.length}, 'mergely-c-add'); - this.change_funcs.push(func); - } - continue; - } - else if (change['op'] == 'd') { - var from = change['lhs-line-from']; - var to = change['lhs-line-to']; - var to_ln = this.editor[editor_name1].lineInfo(to); - if (to_ln) { - var func = this.editor[editor_name1].markText({line:from, ch:0}, {line:to, ch:to_ln.text.length}, 'mergely-c-rem'); - this.change_funcs.push(func); - } - continue; - } - - var tthis = this; - - for (var j = change['lhs-line-from'], k = change['rhs-line-from'], i = 0; - ((j >= 0) && (j <= change['lhs-line-to'])) || ((k >= 0) && (k <= change['rhs-line-to'])); - ++j, ++k) { - if (k + i > change['rhs-line-to']) { - // lhs continues past rhs, mark lhs as deleted - var lhs_line = this.editor[editor_name1].getLine( j ); - var func = this.editor[editor_name1].markText({line:j, ch:0}, {line:j, ch:lhs_line.length}, 'mergely-c-rem'); - this.change_funcs.push(func); - continue; - } - if (j + i > change['lhs-line-to']) { - // rhs continues past lhs, mark rhs as added - var rhs_line = this.editor[editor_name2].getLine( k ); - var func = this.editor[editor_name1].markText({line:k, ch:0}, {line:k, ch:lhs_line.length}, 'mergely-c-add'); - this.change_funcs.push(func); - continue; - } - - var lhs_line = this.editor[editor_name1].getLine( j ); - var rhs_line = this.editor[editor_name2].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); - var max = Math.max(lhs_line.length, rhs_line.length); - if (max == 0) max = 1; - var percent = ((1.0)*lcs.length / max) * 100; - if (percent < 10) lcs.clear(); - lcs.diff( - added = function (index, c) { - if (rhs_start.ch < 0) { - rhs_start.line = k; - rhs_start.ch = index; - rhs_stop.line = k; - rhs_stop.ch = index; - } - else if (index == rhs_stop.ch + 1) { - rhs_stop.ch = index; - } - else { - if ((rhs_start.ch >= 0) && (rhs_stop.ch >= rhs_start.ch)) { - rhs_stop.ch += 1; - var func = tthis.editor[editor_name2].markText(rhs_start, rhs_stop, 'mergely-c-add'); - tthis.change_funcs.push(func); - } - //reset - rhs_start.ch = -1; - rhs_stop.ch = -1; - if (c != '\n') this.added(index, c);//call again - } - }, - removed = function (index, c) { - if (lhs_start.ch < 0) { - lhs_start.line = j; - lhs_start.ch = index; - lhs_stop.line = j; - lhs_stop.ch = index; - } - else if (index == lhs_stop.ch + 1) { - lhs_stop.ch = index; - } - else { - if ((lhs_start.ch >= 0) && (lhs_stop.ch >= lhs_start.ch)) { - lhs_stop.ch += 1; - var func = tthis.editor[editor_name1].markText(lhs_start, lhs_stop, 'mergely-c-rem'); - tthis.change_funcs.push(func); - } - //reset - lhs_start.ch = -1; - lhs_stop.ch = -1; - if (c != '\n') this.removed(index, c);//call again - } - } - ); - if ((rhs_start.ch >= 0) && (rhs_stop.ch >= rhs_start.ch)) { - rhs_stop.ch += 1; - var func = this.editor[editor_name2].markText(rhs_start, rhs_stop, 'mergely-c-add'); - this.change_funcs.push(func); - } - if ((lhs_start.ch >= 0) && (lhs_stop.ch >= lhs_start.ch)) { - lhs_stop.ch += 1; - var func = this.editor[editor_name1].markText(lhs_start, lhs_stop, 'mergely-c-rem'); - this.change_funcs.push(func); - } - } - - - } }, _draw_diff: function(editor_name1, editor_name2, changes) { var visible_page_height = $(this.editor[editor_name1].getScrollerElement()).height(); @@ -1048,13 +1107,13 @@ $.extend(Mgly.mergely.prototype, var ctx_rhs = mcanvas_rhs.getContext('2d'); ctx_lhs.beginPath(); - ctx_lhs.fillStyle = this.settings.bgcolor; + ctx_lhs.fillStyle = this.settings._bgcolor; ctx_lhs.strokeStyle = '#888'; ctx_lhs.fillRect(0, 0, 6.5, visible_page_height); ctx_lhs.strokeRect(0, 0, 6.5, visible_page_height); ctx_rhs.beginPath(); - ctx_rhs.fillStyle = this.settings.bgcolor; + ctx_rhs.fillStyle = this.settings._bgcolor; ctx_rhs.strokeStyle = '#888'; ctx_rhs.fillRect(0, 0, 6.5, visible_page_height); ctx_rhs.strokeRect(0, 0, 6.5, visible_page_height); @@ -1069,7 +1128,7 @@ $.extend(Mgly.mergely.prototype, // draw left box ctx.beginPath(); - ctx.strokeStyle = this.settings.fgcolor; + ctx.strokeStyle = this.settings.fgcolor[change['op']]; ctx.lineWidth = 1; ctx.moveTo(this.draw_lhs_min, lhs_y_start); ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_start); @@ -1089,19 +1148,6 @@ $.extend(Mgly.mergely.prototype, ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_start + (rhs_y_end + 1 - rhs_y_start) / 2.0); ctx.stroke(); - // margin indicators - /* - if (this.em_height * lhs_lines > visible_page_height) { - // margin indicators need to be re-calculated per-ratio - lhs_y_start = change['lhs-line-from'] * (visible_page_height / lhs_lines); - lhs_y_end = (change['lhs-line-to'] + 1) * (visible_page_height / lhs_lines); - } - if (this.em_height * rhs_lines > visible_page_height) { - // margin indicators need to be re-calculated per-ratio - rhs_y_start = change['rhs-line-from'] * (visible_page_height / rhs_lines); - rhs_y_end = (change['rhs-line-to'] + 1) * (visible_page_height / rhs_lines); - } - */ this.trace('draw', change); // margin indicators lhs_y_start = ((change['lhs-y-start'] + lhs_scroller.scrollTop()) * visible_page_ratio); @@ -1111,14 +1157,14 @@ $.extend(Mgly.mergely.prototype, 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; + ctx_lhs.fillStyle = this.settings.fgcolor[change['op']]; ctx_lhs.strokeStyle = '#000'; 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; + ctx_rhs.fillStyle = this.settings.fgcolor[change['op']]; ctx_rhs.strokeStyle = '#000'; ctx_rhs.lineWidth = 0.5; ctx_rhs.fillRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5)); @@ -1154,7 +1200,7 @@ $.extend(Mgly.mergely.prototype, trace: function(name) { if(this.settings._debug.indexOf(name) >= 0) { arguments[0] = name+':'; - console.log(this, [].slice.apply(arguments)); + console.log([].slice.apply(arguments)); } } }); @@ -1175,7 +1221,6 @@ $.pluginMaker = function(plugin) { rc = instance[options].apply(instance, after); } else if (instance.update) { // call update on the instance - alert('here'); return instance.update.apply(instance, args); } } else { diff --git a/lib/mergely.min.js b/lib/mergely.min.js deleted file mode 100644 index a4fca48..0000000 --- a/lib/mergely.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) 2012 by Jamie Peabody, http://www.mergely.com - * All rights reserved. - * Version: 2.5 2012-07-31 - */ -Mgly={},Object.size=function(e){var t=0,n;for(n in e)e.hasOwnProperty(n)&&t++;return t},Mgly.LCS=function(e,t){this.length=this._lcs(e,t);var n=[];e=e.split(""),t=t.split(""),e.unshift(""),t.unshift(""),this.C=n,this.x=e,this.y=t;var r=0,i=0;for(r=0;r0&&t>0&&i[e]==s[t]?this._diff(e-1,t-1,n,r):t>0&&(e==0||o[e][t-1]>=o[e-1][t])?(this._diff(e,t-1,n,r),n&&n(t-1,s[t])):e>0&&(t==0||o[e][t-1]n&&(n=r[i+1][s+1])):r[i+1][s+1]=0;return n},Mgly.diff=function(e,t,n){this.diff_codes={},this.max_code=0;var r=e.split("\n"),i=t.split("\n");e.length==0&&(r=[]),t.length==0&&(i=[]);var s=new Object;s.data=this._diff_codes(r),s.modified={},s.length=Object.size(s.data);var o=new Object;o.data=this._diff_codes(i),o.modified={},o.length=Object.size(o.data);var u=s.length+o.length+1,a=Array(2*u+2),f=Array(2*u+2);this._lcs(s,0,s.length,o,0,o.length,f,a),this._optimize(s),this._optimize(o),this.items=this._create_diffs(s,o),n&&(this.lhs_lines=r,this.rhs_lines=i)},Mgly.diff.prototype.changes=function(){return this.items},Mgly.diff.prototype.normal_form=function(){var e="";for(var t in this.items){var n=this.items[t],r="",i="",s="c";n.lhs_deleted_count==0&&n.rhs_inserted_count>0?s="a":n.lhs_deleted_count>0&&n.rhs_inserted_count==0&&(s="d"),n.lhs_deleted_count==1?r=n.lhs_start+1:n.lhs_deleted_count==0?r=n.lhs_start:r=n.lhs_start+1+","+(n.lhs_start+n.lhs_deleted_count),n.rhs_inserted_count==1?i=n.rhs_start+1:n.rhs_inserted_count==0?i=n.rhs_start:i=n.rhs_start+1+","+(n.rhs_start+n.rhs_inserted_count),e+=r+s+i+"\n";if(this.rhs_lines&&this.lhs_lines){for(var o=n.lhs_start;o "+this.rhs_lines[o]+"\n"}}return e},Mgly.diff.prototype._diff_codes=function(e){var t=this.max_code,n={};for(var r=0;r=b&&(b=u[p+y+1])),w=b-y;while(bl-g&&o[d+y-1]t&&w>i&&e.data[b-1]==r.data[w-1])b--,w--;o[d+y]=b;if(!h&&f-g<=y&&y<=f+g&&o[d+y]<=u[p+y])return m.x=u[p+y],m.y=u[p+y]-y,m}}throw"the algorithm should never come here."},Mgly.diff.prototype._optimize=function(e){var t=0,n=0;while(t=t.length||e.modified[s]))s++;while(o=e.length||t.modified[o]))o++;if(r',i='';else{var s="width:1em;height:1em;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;";r='
<
',i='
>
'}this.merge_right_button=$(i),this.merge_left_button=$(r),this.merge_right_button.corner&&this.merge_right_button.corner("3px"),this.merge_left_button.corner&&this.merge_left_button.corner("3px"),$(this.element).append($('
')),$(this.element).append($('
')),$(this.element).append($('
')),$(this.element).append($('
')),$(this.element).append($('
')),this.bind(),this.settings.lhs&&this.settings.lhs(this.editor[this.id+"-lhs"].setValue),this.settings.rhs&&this.settings.rhs(this.editor[this.id+"-rhs"].setValue),$(window).resize();var o="#"+this.id+" .CodeMirror-gutter-text { padding: 5px 0 0 0; }"+"#"+this.id+" .CodeMirror-lines pre, "+"#"+this.id+" .CodeMirror-gutter-text pre { line-height: 18px; }";this.settings.autoresize&&(o+=this.id+" .CodeMirror-scroll { height: 100%; overflow: auto; }"),$('").appendTo("head")},_scrolling:function(e){var t=$(this.editor[e].getScrollerElement());this.midway==undefined&&(this.midway=(t.height()/2+t.offset().top).toFixed(2));var n=this.editor[e].coordsChar({x:0,y:this.midway}),r=t.scrollTop(),i=t.scrollLeft();this.trace("scroll","midway",this.midway),this.trace("scroll","midline",n),this.trace("scroll","top_to",r),this.trace("scroll","left_to",i);for(var s in this.editor){if(e==s)continue;var o=e.replace(this.id+"-",""),u=s.replace(this.id+"-",""),a=0,f=null;for(var l in this.changes){var c=this.changes[l];n.line>=c[o+"-line-from"]&&(f=c,n.line>=f[o+"-line-to"]&&(a+=c[o+"-y-end"]-c[o+"-y-start"]-(c[u+"-y-end"]-c[u+"-y-start"])))}var h=!0;f&&(this.trace("scroll","last visible change",f),f[o+"-line-from"]n.line&&(h=!1));if(h){this.trace("scroll","scrolling other side",r-a);var t=$(this.editor[s].getScrollerElement());t.scrollTop(r-a).scrollLeft(i)}else this.trace("scroll","not scrolling other side");this._calculate_offsets(this.id+"-lhs",this.id+"-rhs",this.changes),this._draw_diff(this.id+"-lhs",this.id+"-rhs",this.changes),this.trace("scroll","scrolled")}},_changing:function(e,t){var n=this;this.changed_timeout!=null&&clearTimeout(this.changed_timeout),this.changed_timeout=setTimeout(function(){n._changed(e,t)},this.settings.change_timeout)},_changed:function(e,t){for(var n in this.editor){var r=this.editor[n];r.operation(function(){for(var e=0,t=r.lineCount();e
 
');this.em_height=parseInt(s.css("line-height"))-2,console.warn("Failed to calculate offsets, trying brute-force:",this.em_height)}this.em_height||(console.warn("Failed to calculate offsets, using 18 by default"),this.em_height=18),this.draw_lhs_min=.5;var o=$("#"+e+"-"+t+"-canvas");if(!o.width()){console.error("canvas width is 0");return}this.draw_rhs_max=$("#"+e+"-"+t+"-canvas").width()-.5,this.draw_lhs_width=5,this.draw_rhs_width=5,this.trace("calc","change offsets calculated",{top_offset:i,lhs_min:this.draw_lhs_min,rhs_max:this.draw_rhs_max,lhs_width:this.draw_lhs_width,rhs_width:this.draw_rhs_width})}for(var u in n){var a=n[u];a["lhs-y-start"]=this.draw_top_offset+this.editor[e].charCoords({line:a["lhs-line-from"],ch:0}).y,a["lhs-y-end"]=this.draw_top_offset+this.editor[e].charCoords({line:a["lhs-line-to"]+1,ch:0}).y,a["rhs-y-start"]=this.draw_top_offset+this.editor[t].charCoords({line:a["rhs-line-from"],ch:0}).y,a["rhs-y-end"]=this.draw_top_offset+this.editor[t].charCoords({line:a["rhs-line-to"]+1,ch:0}).y,a["op"]=="d"?a["rhs-y-start"]=a["rhs-y-end"]:a["op"]=="a"&&(a["lhs-y-start"]=a["lhs-y-end"])}},_markup_changes:function(e,t,n){$(".merge-button").remove();var r=this.editor[e];r.operation(function(){for(var e in n){var t=n[e],i="mergely-"+t.op+"-start",s="mergely-"+t.op+"-end",o=i+" "+s;if(t["lhs-line-from"]==t["lhs-line-to"])t["op"]=="c"?r.setLineClass(t["lhs-line-from"],o):t["op"]=="a"?r.setLineClass(t["lhs-line-from"],s+"-lhs"):t["op"]=="d"&&r.setLineClass(t["lhs-line-from"],o+" mergely-c-rem");else{r.setLineClass(t["lhs-line-from"],i+(t["op"]=="d"?" mergely-c-rem":"")),r.setLineClass(t["lhs-line-to"],s+(t["op"]=="d"?" mergely-c-rem":""));if(t["op"]=="d")for(var u=t["lhs-line-from"]+1;u=o;--a)l.editor[t].removeLine(a)}else s+="\n",l.editor[t].replaceRange(s,{line:n["rhs-line-from"]+1,ch:0});return l.editor[e].setValue(l.editor[e].getValue()),l.editor[t].setValue(l.editor[t].getValue()),!1}}(s),this.trace("markup","lhs adding button",s["lhs-line-from"]),this.editor[e].addWidget({line:s["lhs-line-from"],ch:0},f.get(0),!1,"over","right"),f=this.merge_left_button.clone(),f.button&&f.button({icons:{primary:"ui-icon-triangle-1-w"},text:!1}),f.addClass("merge-button"),f.attr("id","merge-left-"+i);var l=this;$(f).get(0).onclick=function(n){return function(){var r=l.editor[e].lineInfo(n["lhs-line-to"]),i=l.editor[t].lineInfo(n["rhs-line-to"]),s=l.editor[t].getRange({line:n["rhs-line-from"],ch:0},{line:n["rhs-line-to"],ch:i.text.length});if(n["op"]=="c")l.editor[e].replaceRange(s,{line:n["lhs-line-from"],ch:0},{line:n["lhs-line-to"],ch:r.text.length});else if(n["op"]=="d"){var o=parseInt(n["lhs-line-from"]),u=parseInt(n["lhs-line-to"]);for(var a=u;a>=o;--a)l.editor[e].removeLine(a)}else s+="\n",l.editor[e].replaceRange(s,{line:n["lhs-line-from"]+1,ch:0});return l.editor[e].setValue(l.editor[e].getValue()),l.editor[t].setValue(l.editor[t].getValue()),!1}}(s),this.trace("markup","rhs adding button",s["rhs-line-from"]),this.editor[t].addWidget({line:s["rhs-line-from"],ch:0},f.get(0),!1,"over","right")}if(s["op"]=="a"){var c=s["rhs-line-from"],h=s["rhs-line-to"],p=this.editor[t].lineInfo(h);if(p){var d=this.editor[t].markText({line:c,ch:0},{line:h,ch:p.text.length},"mergely-c-add");this.change_funcs.push(d)}continue}if(s["op"]=="d"){var c=s["lhs-line-from"],h=s["lhs-line-to"],p=this.editor[e].lineInfo(h);if(p){var d=this.editor[e].markText({line:c,ch:0},{line:h,ch:p.text.length},"mergely-c-rem");this.change_funcs.push(d)}continue}var l=this;for(var v=s["lhs-line-from"],m=s["rhs-line-from"],i=0;v>=0&&v<=s["lhs-line-to"]||m>=0&&m<=s["rhs-line-to"];++v,++m){if(m+i>s["rhs-line-to"]){var g=this.editor[e].getLine(v),d=this.editor[e].markText({line:v,ch:0},{line:v,ch:g.length},"mergely-c-rem");this.change_funcs.push(d);continue}if(v+i>s["lhs-line-to"]){var y=this.editor[t].getLine(m),d=this.editor[e].markText({line:m,ch:0},{line:m,ch:g.length},"mergely-c-add");this.change_funcs.push(d);continue}var g=this.editor[e].getLine(v),y=this.editor[t].getLine(m),b={line:-1,ch:-1},w={line:-1,ch:-1},E={line:-1,ch:-1},S={line:-1,ch:-1},x=new Mgly.LCS(g,y),T=Math.max(g.length,y.length);T==0&&(T=1);var N=1*x.length/T*100;N<10&&x.clear(),x.diff(added=function(e,n){if(E.ch<0)E.line=m,E.ch=e,S.line=m,S.ch=e;else if(e==S.ch+1)S.ch=e;else{if(E.ch>=0&&S.ch>=E.ch){S.ch+=1;var r=l.editor[t].markText(E,S,"mergely-c-add");l.change_funcs.push(r)}E.ch=-1,S.ch=-1,n!="\n"&&this.added(e,n)}},removed=function(t,n){if(b.ch<0)b.line=v,b.ch=t,w.line=v,w.ch=t;else if(t==w.ch+1)w.ch=t;else{if(b.ch>=0&&w.ch>=b.ch){w.ch+=1;var r=l.editor[e].markText(b,w,"mergely-c-rem");l.change_funcs.push(r)}b.ch=-1,w.ch=-1,n!="\n"&&this.removed(t,n)}});if(E.ch>=0&&S.ch>=E.ch){S.ch+=1;var d=this.editor[t].markText(E,S,"mergely-c-add");this.change_funcs.push(d)}if(b.ch>=0&&w.ch>=b.ch){w.ch+=1;var d=this.editor[e].markText(b,w,"mergely-c-rem");this.change_funcs.push(d)}}}},_draw_diff:function(e,t,n){var r=$(this.editor[e].getScrollerElement()).height(),i=$(this.editor[e].getScrollerElement()).children(":first-child").height(),s=r/i,o=r/i,u=$(this.editor[e].getScrollerElement()),a=$(this.editor[t].getScrollerElement()),f=this.editor[e].lineCount(),l=this.editor[t].lineCount();this.trace("draw","visible_page_height",r),this.trace("draw","gutter_height",i),this.trace("draw","visible_page_ratio",s),this.trace("draw","lhs-scroller-top",u.scrollTop()),this.trace("draw","rhs-scroller-top",a.scrollTop());var c=document.getElementById(e+"-"+t+"-canvas");if(c==undefined)throw"Failed to find: "+e+"-"+t+"-canvas";$.each($("canvas"),function(){$(this).get(0).height=r});var h=$("#"+this.id+"-lhs-margin"),p=$("#"+this.id+"-rhs-margin");h.unbind("click"),p.unbind("click");var d=h.get(0),v=p.get(0),m=$(h).offset(),g=$(p).offset(),y=c.getContext("2d"),b=d.getContext("2d"),w=v.getContext("2d");b.beginPath(),b.fillStyle=this.settings.bgcolor,b.strokeStyle="#888",b.fillRect(0,0,6.5,r),b.strokeRect(0,0,6.5,r),w.beginPath(),w.fillStyle=this.settings.bgcolor,w.strokeStyle="#888",w.fillRect(0,0,6.5,r),w.strokeRect(0,0,6.5,r);for(var E in n){var S=n[E],x=S["lhs-y-start"],T=S["lhs-y-end"],N=S["rhs-y-start"],C=S["rhs-y-end"];y.beginPath(),y.strokeStyle=this.settings.fgcolor,y.lineWidth=1,y.moveTo(this.draw_lhs_min,x),y.lineTo(this.draw_lhs_min+this.draw_lhs_width,x),y.lineTo(this.draw_lhs_min+this.draw_lhs_width,T+1),y.lineTo(this.draw_lhs_min,T+1),y.stroke(),y.moveTo(this.draw_rhs_max,N),y.lineTo(this.draw_rhs_max-this.draw_rhs_width,N),y.lineTo(this.draw_rhs_max-this.draw_rhs_width,C+1),y.lineTo(this.draw_rhs_max,C+1),y.stroke(),y.moveTo(this.draw_lhs_min+this.draw_lhs_width,x+(T+1-x)/2),y.lineTo(this.draw_rhs_max-this.draw_rhs_width,N+(C+1-N)/2),y.stroke(),this.trace("draw",S),x=(S["lhs-y-start"]+u.scrollTop())*s,T=(S["lhs-y-end"]+u.scrollTop())*s+1,N=(S["rhs-y-start"]+a.scrollTop())*s,C=(S["rhs-y-end"]+a.scrollTop())*s+1,this.trace("draw","marker calculated",x,T,N,C),b.beginPath(),b.fillStyle=this.settings.fgcolor,b.strokeStyle="#000",b.lineWidth=.5,b.fillRect(1.5,x,4.5,Math.max(T-x,5)),b.strokeRect(1.5,x,4.5,Math.max(T-x,5)),w.beginPath(),w.fillStyle=this.settings.fgcolor,w.strokeStyle="#000",w.lineWidth=.5,w.fillRect(1.5,N,4.5,Math.max(C-N,5)),w.strokeRect(1.5,N,4.5,Math.max(C-N,5))}b.fillStyle="rgba(0, 0, 200, 0.5)",w.fillStyle="rgba(0, 0, 200, 0.5)";var k=h.height()*s,L=u.scrollTop()/i*h.height();this.trace("draw","cls.height",h.height()),this.trace("draw","lhs_scroller.scrollTop()",u.scrollTop()),this.trace("draw","gutter_height",i),this.trace("draw","visible_page_ratio",s),this.trace("draw","from",L,"to",k),b.fillRect(1.5,L,4.5,k),w.fillRect(1.5,L,4.5,k),h.click(function(e){var t=e.pageY-m.top-k/2,n=Math.max(0,t/d.height*u.get(0).scrollHeight);u.scrollTop(n)}),p.click(function(e){var t=e.pageY-g.top-k/2,n=Math.max(0,t/v.height*a.get(0).scrollHeight);a.scrollTop(n)})},trace:function(e){this.settings._debug.indexOf(e)>=0&&(arguments[0]=e+":",console.log(this,[].slice.apply(arguments)))}}),$.pluginMaker=function(e){$.fn[e.prototype.name]=function(t){var n=$.makeArray(arguments),r=n.slice(1),i=undefined;this.each(function(){var s=$.data(this,e.prototype.name);if(s){if(typeof t=="string")i=s[t].apply(s,r);else if(s.update)return alert("here"),s.update.apply(s,n)}else new e(this,t)});if(i!=undefined)return i}},$.pluginMaker(Mgly.mergely) \ No newline at end of file diff --git a/lib/searchcursor.js b/lib/searchcursor.js new file mode 100755 index 0000000..1750aad --- /dev/null +++ b/lib/searchcursor.js @@ -0,0 +1,119 @@ +(function(){ + function SearchCursor(cm, query, pos, caseFold) { + this.atOccurrence = false; this.cm = cm; + if (caseFold == null && typeof query == "string") caseFold = false; + + pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0}; + this.pos = {from: pos, to: pos}; + + // The matches method is filled in based on the type of query. + // It takes a position and a direction, and returns an object + // describing the next occurrence of the query, or null if no + // more matches were found. + if (typeof query != "string") { // Regexp match + if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); + this.matches = function(reverse, pos) { + if (reverse) { + query.lastIndex = 0; + var line = cm.getLine(pos.line).slice(0, pos.ch), match = query.exec(line), start = 0; + while (match) { + start += match.index + 1; + line = line.slice(start); + query.lastIndex = 0; + var newmatch = query.exec(line); + if (newmatch) match = newmatch; + else break; + } + start--; + } else { + query.lastIndex = pos.ch; + var line = cm.getLine(pos.line), match = query.exec(line), + start = match && match.index; + } + if (match) + return {from: {line: pos.line, ch: start}, + to: {line: pos.line, ch: start + match[0].length}, + match: match}; + }; + } else { // String query + if (caseFold) query = query.toLowerCase(); + var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; + var target = query.split("\n"); + // Different methods for single-line and multi-line queries + if (target.length == 1) + this.matches = function(reverse, pos) { + var line = fold(cm.getLine(pos.line)), len = query.length, match; + if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1) + : (match = line.indexOf(query, pos.ch)) != -1) + return {from: {line: pos.line, ch: match}, + to: {line: pos.line, ch: match + len}}; + }; + else + this.matches = function(reverse, pos) { + var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln)); + var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match)); + if (reverse ? offsetA >= pos.ch || offsetA != match.length + : offsetA <= pos.ch || offsetA != line.length - match.length) + return; + for (;;) { + if (reverse ? !ln : ln == cm.lineCount() - 1) return; + line = fold(cm.getLine(ln += reverse ? -1 : 1)); + match = target[reverse ? --idx : ++idx]; + if (idx > 0 && idx < target.length - 1) { + if (line != match) return; + else continue; + } + var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length); + if (reverse ? offsetB != line.length - match.length : offsetB != match.length) + return; + var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB}; + return {from: reverse ? end : start, to: reverse ? start : end}; + } + }; + } + } + + SearchCursor.prototype = { + findNext: function() {return this.find(false);}, + findPrevious: function() {return this.find(true);}, + + find: function(reverse) { + var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to); + function savePosAndFail(line) { + var pos = {line: line, ch: 0}; + self.pos = {from: pos, to: pos}; + self.atOccurrence = false; + return false; + } + + for (;;) { + if (this.pos = this.matches(reverse, pos)) { + this.atOccurrence = true; + return this.pos.match || true; + } + if (reverse) { + if (!pos.line) return savePosAndFail(0); + pos = {line: pos.line-1, ch: this.cm.getLine(pos.line-1).length}; + } + else { + var maxLine = this.cm.lineCount(); + if (pos.line == maxLine - 1) return savePosAndFail(maxLine); + pos = {line: pos.line+1, ch: 0}; + } + } + }, + + from: function() {if (this.atOccurrence) return this.pos.from;}, + to: function() {if (this.atOccurrence) return this.pos.to;}, + + replace: function(newText) { + var self = this; + if (this.atOccurrence) + self.pos.to = this.cm.replaceRange(newText, self.pos.from, self.pos.to); + } + }; + + CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { + return new SearchCursor(this, query, pos, caseFold); + }); +})(); diff --git a/test/index.html b/test/index.html new file mode 100755 index 0000000..588dfd1 --- /dev/null +++ b/test/index.html @@ -0,0 +1,34 @@ + + + + + Tests + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModuleTest NameStateRetryStack
+ + diff --git a/test/lib/jsunit/jsunit.css b/test/lib/jsunit/jsunit.css new file mode 100755 index 0000000..97428d7 --- /dev/null +++ b/test/lib/jsunit/jsunit.css @@ -0,0 +1,15 @@ +body { font-family: courier new; font-size: 0.75em; } +table { border-collapse: collapse; width: 100%; text-align: left; vertical-align: top; } + +.stack span, +.failed span, +.passed span { + width: 60px; + display: block; + text-align: center; + font-size: 0.85em; + border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; +} +.failed span { background-color: #FFCC66; border: 1px solid black; color: black; cursor: help; } +.passed span { background-color: #66BB66; border: 1px solid green; color: green; } +.stack span { background-color: #efefef; border: 1px solid #444; color: #444; cursor: help; } diff --git a/test/lib/jsunit/jsunit.js b/test/lib/jsunit/jsunit.js new file mode 100755 index 0000000..74f1e04 --- /dev/null +++ b/test/lib/jsunit/jsunit.js @@ -0,0 +1,198 @@ +var JsUnit = (function () { + var JsUnit = { + modules : {}, + config : { + onStartModule: function(module) { }, + onEndModule: function(module) { }, + onStartTest: function(module, test) { }, + onEndTest: function(module, test) { }, + onResult: function(module, test) { }, + } + }; + JsUnit.PASSED = 'passed'; + JsUnit.FAILED = 'failed'; + + JsUnit._mixin = function(src, dest) { + return $.extend({}, src, dest); + } + + JsUnit.module = function(name) { + this.modules[name] = {name: name, tests:[]}; + this._module = name; + } + + JsUnit.test = function(name, run) { + var module = this.modules[this._module]; + module.tests.push({ + name: name, + run: run, + assertions: [] + }); + } + + JsUnit._assert = function(result, value, message, passed) { + var stack = []; + if (!passed) { + if (printStackTrace != undefined) { + var trace = printStackTrace(); + for (key in trace) { + stack.push({ + method: trace[key].split('@')[0], + location: trace[key].split('@')[1] + }); + } + } + else { + console.warn('missing required library:','https://raw.github.com/eriwen/javascript-stacktrace/master/stacktrace.js'); + } + } + this._running.assertions.push({ + expected: value, + message: message, + stack: stack, + passed: function() { return passed; } + }); + } + + JsUnit.equal = function(result, value, message) { + JsUnit._assert(result, value, message || 'Result was not equal as expected', result == value); + } + + JsUnit.notEqual = function(result, value, message) { + JsUnit._assert(result, value, message || 'Result did not evaluate to false as expected', result != value); + } + + JsUnit.okay = function(result, message) { + JsUnit._assert(result, null, message || 'Result did not evaluate to true as expected', !(!result)); + } + JsUnit.throws = function(func, message) { + try { + func(); + } + catch (err) { + return; + } + JsUnit.okay(false, message || 'Did not throw as expected'); + } + + JsUnit.run = function(config) { + var settings = this._mixin(this.config, config); + for (var mkey in this.modules) { + var module = this.modules[mkey]; + settings.onStartModule(module); + for (var tkey in module.tests) { + var test = module.tests[tkey]; + var tthis = this; + test.rerun = function() { + try { + settings.onStartTest(module, this); + tthis._running = this; + this.assertions = []; + var success = true; + this.passed = function() { return success; } + this.run(); + for (var akey in this.assertions) { + var assertion = this.assertions[akey]; + success &= assertion.passed(); + } + settings.onResult(module, this); + settings.onEndTest(module, this); + } + catch (ex) { + console.log('Exception', ex); + this.ex = ex; + success = false; + settings.onResult(module, this); + settings.onEndTest(module, this); + } + } + test.rerun(); + } + settings.onEndModule(module); + } + } + + JsUnit.getCurrentUrl = function(changes) { + var options = { + protocol: window.document.location.protocol, + host: window.document.location.hostname, + port: window.document.location.port, + path: window.document.location.pathname, + user: '', + pass: '' + }; + options = JsUnit._mixin(options, changes); + var userpass = (options.user && options.pass) ? (options.user + ':' + options.pass + '@') : ''; + if (options.protocol.substr(-1) != ':') options.protocol += ':'; + return options.protocol + '//' + userpass + options.host + ':' + options.port + options.path; + } + + JsUnit.include = function(file) { + if (document.createElement && document.getElementsByTagName) { + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.setAttribute('src', file); + script.setAttribute('async', 'async'); + head.appendChild(script); + } + else { + console.error('Failed to include', file); + } + } + + JsUnit.start = function() { + this.run({ + onStartModule: function(module) { + var test; + for (var i = 0; i < module.tests.length; ++i) { + // add all the tests + test = module.tests[i]; + var node = $('' + module.name + '' + test.name + ''); + $('#tests').append(node); + // closure + (function(y) { + $('#' + y.name + ' button').click(function(){ + //test = module.tests[i]; + console.log('test', y.name); + console.log('test.rerun', y, y.rerun); + console.log('rerunning', y); + y.rerun(); + return false; + }); + })(test); + } + }, + + onResult: function(module, test) { + var id = '#' + test.name; + var message = ''; + var stack = ''; + var state = 'passed'; + if (!test.passed()) { + $(id + ' .stack').html('stack'); + state = 'failed'; + if (test.ex) { + message += test.ex + '\n' + test.ex.stack; + } + for (var i in test.assertions) { + var assertion = test.assertions[i]; + if (!assertion.passed()) { + message += assertion.message.replace(/&/g, "&").replace(/>/g, ">").replace(/'; + } + } + } + } + $(id + ' .state').attr('class', ' state ' + state); + $(id + ' .details').attr('title', message).html(state); + $(id + ' .stack').attr('title', stack); + $(id + ' .stack').tipTip({delay: 100, fadeIn:0, fadeOut:100, defaultPosition: 'left', maxWidth: '640px'}); + $(id + ' .details').tipTip({delay: 100, fadeIn:0, fadeOut:100, defaultPosition: 'right'}) + } + }); + } + return JsUnit; +}()); diff --git a/test/lib/jsunit/stacktrace.js b/test/lib/jsunit/stacktrace.js new file mode 100755 index 0000000..397c831 --- /dev/null +++ b/test/lib/jsunit/stacktrace.js @@ -0,0 +1,416 @@ +// Domain Public by Eric Wendelin http://eriwen.com/ (2008) +// Luke Smith http://lucassmith.name/ (2008) +// Loic Dachary (2008) +// Johan Euphrosine (2008) +// Oyvind Sean Kinsey http://kinsey.no/blog (2010) +// Victor Homyakov (2010) + +/** + * Main function giving a function stack trace with a forced or passed in Error + * + * @cfg {Error} e The error to create a stacktrace from (optional) + * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions + * @return {Array} of Strings with functions, lines, files, and arguments where possible + */ +function printStackTrace(options) { + options = options || {guess: true}; + var ex = options.e || null, guess = !!options.guess; + var p = new printStackTrace.implementation(), result = p.run(ex); + return (guess) ? p.guessAnonymousFunctions(result) : result; +} + +printStackTrace.implementation = function() { +}; + +printStackTrace.implementation.prototype = { + /** + * @param {Error} ex The error to create a stacktrace from (optional) + * @param {String} mode Forced mode (optional, mostly for unit tests) + */ + run: function(ex, mode) { + ex = ex || this.createException(); + // examine exception properties w/o debugger + //for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);} + mode = mode || this.mode(ex); + if (mode === 'other') { + return this.other(arguments.callee); + } else { + return this[mode](ex); + } + }, + + createException: function() { + try { + this.undef(); + } catch (e) { + return e; + } + }, + + /** + * Mode could differ for different exception, e.g. + * exceptions in Chrome may or may not have arguments or stack. + * + * @return {String} mode of operation for the exception + */ + mode: function(e) { + if (e['arguments'] && e.stack) { + return 'chrome'; + } else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) { + // e.message.indexOf("Backtrace:") > -1 -> opera + // !e.stacktrace -> opera + if (!e.stacktrace) { + return 'opera9'; // use e.message + } + // 'opera#sourceloc' in e -> opera9, opera10a + if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) { + return 'opera9'; // use e.message + } + // e.stacktrace && !e.stack -> opera10a + if (!e.stack) { + return 'opera10a'; // use e.stacktrace + } + // e.stacktrace && e.stack -> opera10b + if (e.stacktrace.indexOf("called from line") < 0) { + return 'opera10b'; // use e.stacktrace, format differs from 'opera10a' + } + // e.stacktrace && e.stack -> opera11 + return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b' + } else if (e.stack) { + return 'firefox'; + } + return 'other'; + }, + + /** + * Given a context, function name, and callback function, overwrite it so that it calls + * printStackTrace() first with a callback and then runs the rest of the body. + * + * @param {Object} context of execution (e.g. window) + * @param {String} functionName to instrument + * @param {Function} function to call with a stack trace on invocation + */ + instrumentFunction: function(context, functionName, callback) { + context = context || window; + var original = context[functionName]; + context[functionName] = function instrumented() { + callback.call(this, printStackTrace().slice(4)); + return context[functionName]._instrumented.apply(this, arguments); + }; + context[functionName]._instrumented = original; + }, + + /** + * Given a context and function name of a function that has been + * instrumented, revert the function to it's original (non-instrumented) + * state. + * + * @param {Object} context of execution (e.g. window) + * @param {String} functionName to de-instrument + */ + deinstrumentFunction: function(context, functionName) { + if (context[functionName].constructor === Function && + context[functionName]._instrumented && + context[functionName]._instrumented.constructor === Function) { + context[functionName] = context[functionName]._instrumented; + } + }, + + /** + * Given an Error object, return a formatted Array based on Chrome's stack string. + * + * @param e - Error object to inspect + * @return Array of function calls, files and line numbers + */ + chrome: function(e) { + var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, ''). + replace(/^\s+(at eval )?at\s+/gm, ''). + replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2'). + replace(/^Object.\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n'); + stack.pop(); + return stack; + }, + + /** + * Given an Error object, return a formatted Array based on Firefox's stack string. + * + * @param e - Error object to inspect + * @return Array of function calls, files and line numbers + */ + firefox: function(e) { + return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n'); + }, + + opera11: function(e) { + // "Error thrown at line 42, column 12 in () in file://localhost/G:/js/stacktrace.js:\n" + // "Error thrown at line 42, column 12 in () in file://localhost/G:/js/stacktrace.js:\n" + // "called from line 7, column 4 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n" + // "called from line 15, column 3 in file://localhost/G:/js/test/functional/testcase1.html:\n" + var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/; + var lines = e.stacktrace.split('\n'), result = []; + + for (var i = 0, len = lines.length; i < len; i += 2) { + var match = lineRE.exec(lines[i]); + if (match) { + var location = match[4] + ':' + match[1] + ':' + match[2]; + var fnName = match[3] || "global code"; + fnName = fnName.replace(//, "$1").replace(//, ANON); + result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, '')); + } + } + + return result; + }, + + opera10b: function(e) { + // "([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" + + // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" + + // "@file://localhost/G:/js/test/functional/testcase1.html:15" + var lineRE = /^(.*)@(.+):(\d+)$/; + var lines = e.stacktrace.split('\n'), result = []; + + for (var i = 0, len = lines.length; i < len; i++) { + var match = lineRE.exec(lines[i]); + if (match) { + var fnName = match[1]? (match[1] + '()') : "global code"; + result.push(fnName + '@' + match[2] + ':' + match[3]); + } + } + + return result; + }, + + /** + * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. + * + * @param e - Error object to inspect + * @return Array of function calls, files and line numbers + */ + opera10a: function(e) { + // " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n" + // " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n" + var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i; + var lines = e.stacktrace.split('\n'), result = []; + + for (var i = 0, len = lines.length; i < len; i += 2) { + var match = lineRE.exec(lines[i]); + if (match) { + var fnName = match[3] || ANON; + result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, '')); + } + } + + return result; + }, + + // Opera 7.x-9.2x only! + opera9: function(e) { + // " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n" + // " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" + var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i; + var lines = e.message.split('\n'), result = []; + + for (var i = 2, len = lines.length; i < len; i += 2) { + var match = lineRE.exec(lines[i]); + if (match) { + result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, '')); + } + } + + return result; + }, + + // Safari, IE, and others + other: function(curr) { + var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10; + while (curr && curr['arguments'] && stack.length < maxStackSize) { + fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; + args = Array.prototype.slice.call(curr['arguments'] || []); + stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; + curr = curr.caller; + } + return stack; + }, + + /** + * Given arguments array as a String, subsituting type names for non-string types. + * + * @param {Arguments} object + * @return {Array} of Strings with stringified arguments + */ + stringifyArguments: function(args) { + var result = []; + var slice = Array.prototype.slice; + for (var i = 0; i < args.length; ++i) { + var arg = args[i]; + if (arg === undefined) { + result[i] = 'undefined'; + } else if (arg === null) { + result[i] = 'null'; + } else if (arg.constructor) { + if (arg.constructor === Array) { + if (arg.length < 3) { + result[i] = '[' + this.stringifyArguments(arg) + ']'; + } else { + result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']'; + } + } else if (arg.constructor === Object) { + result[i] = '#object'; + } else if (arg.constructor === Function) { + result[i] = '#function'; + } else if (arg.constructor === String) { + result[i] = '"' + arg + '"'; + } else if (arg.constructor === Number) { + result[i] = arg; + } + } + } + return result.join(','); + }, + + sourceCache: {}, + + /** + * @return the text from a given URL + */ + ajax: function(url) { + var req = this.createXMLHTTPObject(); + if (req) { + try { + req.open('GET', url, false); + //req.overrideMimeType('text/plain'); + //req.overrideMimeType('text/javascript'); + req.send(null); + //return req.status == 200 ? req.responseText : ''; + return req.responseText; + } catch (e) { + } + } + return ''; + }, + + /** + * Try XHR methods in order and store XHR factory. + * + * @return XHR function or equivalent + */ + createXMLHTTPObject: function() { + var xmlhttp, XMLHttpFactories = [ + function() { + return new XMLHttpRequest(); + }, function() { + return new ActiveXObject('Msxml2.XMLHTTP'); + }, function() { + return new ActiveXObject('Msxml3.XMLHTTP'); + }, function() { + return new ActiveXObject('Microsoft.XMLHTTP'); + } + ]; + for (var i = 0; i < XMLHttpFactories.length; i++) { + try { + xmlhttp = XMLHttpFactories[i](); + // Use memoization to cache the factory + this.createXMLHTTPObject = XMLHttpFactories[i]; + return xmlhttp; + } catch (e) { + } + } + }, + + /** + * Given a URL, check if it is in the same domain (so we can get the source + * via Ajax). + * + * @param url source url + * @return False if we need a cross-domain request + */ + isSameDomain: function(url) { + return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs. + }, + + /** + * Get source code from given URL if in the same domain. + * + * @param url JS source URL + * @return Array of source code lines + */ + getSource: function(url) { + // TODO reuse source from script tags? + if (!(url in this.sourceCache)) { + this.sourceCache[url] = this.ajax(url).split('\n'); + } + return this.sourceCache[url]; + }, + + guessAnonymousFunctions: function(stack) { + for (var i = 0; i < stack.length; ++i) { + var reStack = /\{anonymous\}\(.*\)@(.*)/, + reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/, + frame = stack[i], ref = reStack.exec(frame); + + if (ref) { + var m = reRef.exec(ref[1]); + if (m) { // If falsey, we did not get any file/line information + var file = m[1], lineno = m[2], charno = m[3] || 0; + if (file && this.isSameDomain(file) && lineno) { + var functionName = this.guessAnonymousFunction(file, lineno, charno); + stack[i] = frame.replace('{anonymous}', functionName); + } + } + } + } + return stack; + }, + + guessAnonymousFunction: function(url, lineNo, charNo) { + var ret; + try { + ret = this.findFunctionName(this.getSource(url), lineNo); + } catch (e) { + ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString(); + } + return ret; + }, + + findFunctionName: function(source, lineNo) { + // FIXME findFunctionName fails for compressed source + // (more than one function on the same line) + // TODO use captured args + // function {name}({args}) m[1]=name m[2]=args + var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/; + // {name} = function ({args}) TODO args capture + // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/ + var reFunctionExpression = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function\b/; + // {name} = eval() + var reFunctionEvaluation = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(?:eval|new Function)\b/; + // Walk backwards in the source lines until we find + // the line which matches one of the patterns above + var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos; + for (var i = 0; i < maxLines; ++i) { + // lineNo is 1-based, source[] is 0-based + line = source[lineNo - i - 1]; + commentPos = line.indexOf('//'); + if (commentPos >= 0) { + line = line.substr(0, commentPos); + } + // TODO check other types of comments? Commented code may lead to false positive + if (line) { + code = line + code; + m = reFunctionExpression.exec(code); + if (m && m[1]) { + return m[1]; + } + m = reFunctionDeclaration.exec(code); + if (m && m[1]) { + //return m[1] + "(" + (m[2] || "") + ")"; + return m[1]; + } + m = reFunctionEvaluation.exec(code); + if (m && m[1]) { + return m[1]; + } + } + } + return '(?)'; + } +}; diff --git a/test/lib/jsunit/tipTip.css b/test/lib/jsunit/tipTip.css new file mode 100755 index 0000000..3ed8386 --- /dev/null +++ b/test/lib/jsunit/tipTip.css @@ -0,0 +1,113 @@ +/* TipTip CSS - Version 1.2 */ + +#tiptip_holder { + display: none; + position: absolute; + top: 0; + left: 0; + z-index: 99999; +} + +#tiptip_holder.tip_top { + padding-bottom: 5px; +} + +#tiptip_holder.tip_bottom { + padding-top: 5px; +} + +#tiptip_holder.tip_right { + padding-left: 5px; +} + +#tiptip_holder.tip_left { + padding-right: 5px; +} + +#tiptip_content { + font-size: 11px; + color: #fff; + text-shadow: 0 0 2px #000; + padding: 4px 8px; + border: 1px solid rgba(255,255,255,0.25); + background-color: rgb(25,25,25); + background-color: rgba(25,25,25,0.92); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(transparent), to(#000)); + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + box-shadow: 0 0 2px #ccc; + -webkit-box-shadow: 0 0 2px #ccc; + -moz-box-shadow: 0 0 2px #ccc; +} + +#tiptip_arrow, #tiptip_arrow_inner { + position: absolute; + border-color: transparent; + border-style: solid; + border-width: 6px; + height: 0; + width: 0; +} + +#tiptip_holder.tip_top #tiptip_arrow { + border-top-color: #fff; + border-top-color: rgba(255,255,255,0.35); +} + +#tiptip_holder.tip_bottom #tiptip_arrow { + border-bottom-color: #fff; + border-bottom-color: rgba(255,255,255,0.35); +} + +#tiptip_holder.tip_right #tiptip_arrow { + border-right-color: #fff; + border-right-color: rgba(255,255,255,0.35); +} + +#tiptip_holder.tip_left #tiptip_arrow { + border-left-color: #fff; + border-left-color: rgba(255,255,255,0.35); +} + +#tiptip_holder.tip_top #tiptip_arrow_inner { + margin-top: -7px; + margin-left: -6px; + border-top-color: rgb(25,25,25); + border-top-color: rgba(25,25,25,0.92); +} + +#tiptip_holder.tip_bottom #tiptip_arrow_inner { + margin-top: -5px; + margin-left: -6px; + border-bottom-color: rgb(25,25,25); + border-bottom-color: rgba(25,25,25,0.92); +} + +#tiptip_holder.tip_right #tiptip_arrow_inner { + margin-top: -6px; + margin-left: -5px; + border-right-color: rgb(25,25,25); + border-right-color: rgba(25,25,25,0.92); +} + +#tiptip_holder.tip_left #tiptip_arrow_inner { + margin-top: -6px; + margin-left: -7px; + border-left-color: rgb(25,25,25); + border-left-color: rgba(25,25,25,0.92); +} + +/* Webkit Hacks */ +@media screen and (-webkit-min-device-pixel-ratio:0) { + #tiptip_content { + padding: 4px 8px 5px 8px; + background-color: rgba(45,45,45,0.88); + } + #tiptip_holder.tip_bottom #tiptip_arrow_inner { + border-bottom-color: rgba(45,45,45,0.88); + } + #tiptip_holder.tip_top #tiptip_arrow_inner { + border-top-color: rgba(20,20,20,0.92); + } +} \ No newline at end of file diff --git a/test/lib/jsunit/tipTip.js b/test/lib/jsunit/tipTip.js new file mode 100755 index 0000000..7eacf35 --- /dev/null +++ b/test/lib/jsunit/tipTip.js @@ -0,0 +1,191 @@ + /* + * TipTip + * Copyright 2010 Drew Wilson + * www.drewwilson.com + * code.drewwilson.com/entry/tiptip-jquery-plugin + * + * Version 1.3 - Updated: Mar. 23, 2010 + * + * This Plug-In will create a custom tooltip to replace the default + * browser tooltip. It is extremely lightweight and very smart in + * that it detects the edges of the browser window and will make sure + * the tooltip stays within the current window size. As a result the + * tooltip will adjust itself to be displayed above, below, to the left + * or to the right depending on what is necessary to stay within the + * browser window. It is completely customizable as well via CSS. + * + * This TipTip jQuery plug-in is dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +(function($){ + $.fn.tipTip = function(options) { + var defaults = { + activation: "hover", + keepAlive: false, + maxWidth: "200px", + edgeOffset: 3, + defaultPosition: "bottom", + delay: 400, + fadeIn: 200, + fadeOut: 200, + attribute: "title", + content: false, // HTML or String to fill TipTIp with + enter: function(){}, + exit: function(){} + }; + var opts = $.extend(defaults, options); + + // Setup tip tip elements and render them to the DOM + if($("#tiptip_holder").length <= 0){ + var tiptip_holder = $('
'); + var tiptip_content = $('
'); + var tiptip_arrow = $('
'); + $("body").append(tiptip_holder.html(tiptip_content).prepend(tiptip_arrow.html('
'))); + } else { + var tiptip_holder = $("#tiptip_holder"); + var tiptip_content = $("#tiptip_content"); + var tiptip_arrow = $("#tiptip_arrow"); + } + + return this.each(function(){ + var org_elem = $(this); + if(opts.content){ + var org_title = opts.content; + } else { + var org_title = org_elem.attr(opts.attribute); + } + if(org_title != ""){ + if(!opts.content){ + org_elem.removeAttr(opts.attribute); //remove original Attribute + } + var timeout = false; + + if(opts.activation == "hover"){ + org_elem.hover(function(){ + active_tiptip(); + }, function(){ + if(!opts.keepAlive){ + deactive_tiptip(); + } + }); + if(opts.keepAlive){ + tiptip_holder.hover(function(){}, function(){ + deactive_tiptip(); + }); + } + } else if(opts.activation == "focus"){ + org_elem.focus(function(){ + active_tiptip(); + }).blur(function(){ + deactive_tiptip(); + }); + } else if(opts.activation == "click"){ + org_elem.click(function(){ + active_tiptip(); + return false; + }).hover(function(){},function(){ + if(!opts.keepAlive){ + deactive_tiptip(); + } + }); + if(opts.keepAlive){ + tiptip_holder.hover(function(){}, function(){ + deactive_tiptip(); + }); + } + } + + function active_tiptip(){ + opts.enter.call(this); + tiptip_content.html(org_title); + tiptip_holder.hide().removeAttr("class").css("margin","0"); + tiptip_arrow.removeAttr("style"); + + var top = parseInt(org_elem.offset()['top']); + var left = parseInt(org_elem.offset()['left']); + var org_width = parseInt(org_elem.outerWidth()); + var org_height = parseInt(org_elem.outerHeight()); + var tip_w = tiptip_holder.outerWidth(); + var tip_h = tiptip_holder.outerHeight(); + var w_compare = Math.round((org_width - tip_w) / 2); + var h_compare = Math.round((org_height - tip_h) / 2); + var marg_left = Math.round(left + w_compare); + var marg_top = Math.round(top + org_height + opts.edgeOffset); + var t_class = ""; + var arrow_top = ""; + var arrow_left = Math.round(tip_w - 12) / 2; + + if(opts.defaultPosition == "bottom"){ + t_class = "_bottom"; + } else if(opts.defaultPosition == "top"){ + t_class = "_top"; + } else if(opts.defaultPosition == "left"){ + t_class = "_left"; + } else if(opts.defaultPosition == "right"){ + t_class = "_right"; + } + + var right_compare = (w_compare + left) < parseInt($(window).scrollLeft()); + var left_compare = (tip_w + left) > parseInt($(window).width()); + + if((right_compare && w_compare < 0) || (t_class == "_right" && !left_compare) || (t_class == "_left" && left < (tip_w + opts.edgeOffset + 5))){ + t_class = "_right"; + arrow_top = Math.round(tip_h - 13) / 2; + arrow_left = -12; + marg_left = Math.round(left + org_width + opts.edgeOffset); + marg_top = Math.round(top + h_compare); + } else if((left_compare && w_compare < 0) || (t_class == "_left" && !right_compare)){ + t_class = "_left"; + arrow_top = Math.round(tip_h - 13) / 2; + arrow_left = Math.round(tip_w); + marg_left = Math.round(left - (tip_w + opts.edgeOffset + 5)); + marg_top = Math.round(top + h_compare); + } + + var top_compare = (top + org_height + opts.edgeOffset + tip_h + 8) > parseInt($(window).height() + $(window).scrollTop()); + var bottom_compare = ((top + org_height) - (opts.edgeOffset + tip_h + 8)) < 0; + + if(top_compare || (t_class == "_bottom" && top_compare) || (t_class == "_top" && !bottom_compare)){ + if(t_class == "_top" || t_class == "_bottom"){ + t_class = "_top"; + } else { + t_class = t_class+"_top"; + } + arrow_top = tip_h; + marg_top = Math.round(top - (tip_h + 5 + opts.edgeOffset)); + } else if(bottom_compare | (t_class == "_top" && bottom_compare) || (t_class == "_bottom" && !top_compare)){ + if(t_class == "_top" || t_class == "_bottom"){ + t_class = "_bottom"; + } else { + t_class = t_class+"_bottom"; + } + arrow_top = -12; + marg_top = Math.round(top + org_height + opts.edgeOffset); + } + + if(t_class == "_right_top" || t_class == "_left_top"){ + marg_top = marg_top + 5; + } else if(t_class == "_right_bottom" || t_class == "_left_bottom"){ + marg_top = marg_top - 5; + } + if(t_class == "_left_top" || t_class == "_left_bottom"){ + marg_left = marg_left + 5; + } + tiptip_arrow.css({"margin-left": arrow_left+"px", "margin-top": arrow_top+"px"}); + tiptip_holder.css({"margin-left": marg_left+"px", "margin-top": marg_top+"px"}).attr("class","tip"+t_class); + + if (timeout){ clearTimeout(timeout); } + timeout = setTimeout(function(){ tiptip_holder.stop(true,true).fadeIn(opts.fadeIn); }, opts.delay); + } + + function deactive_tiptip(){ + opts.exit.call(this); + if (timeout){ clearTimeout(timeout); } + tiptip_holder.fadeOut(opts.fadeOut); + } + } + }); + } +})(jQuery); \ No newline at end of file diff --git a/test/lib/require/require.js b/test/lib/require/require.js new file mode 100755 index 0000000..b6090d1 --- /dev/null +++ b/test/lib/require/require.js @@ -0,0 +1,31 @@ +/* + RequireJS 1.0.2 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved. + Available via the MIT or new BSD license. + see: http://github.com/jrburke/requirejs for details +*/ +var requirejs,require,define; +(function(){function J(a){return M.call(a)==="[object Function]"}function E(a){return M.call(a)==="[object Array]"}function Z(a,c,h){for(var k in c)if(!(k in K)&&(!(k in a)||h))a[k]=c[k];return d}function N(a,c,d){a=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+a);if(d)a.originalError=d;return a}function $(a,c,d){var k,j,q;for(k=0;q=c[k];k++){q=typeof q==="string"?{name:q}:q;j=q.location;if(d&&(!j||j.indexOf("/")!==0&&j.indexOf(":")===-1))j=d+"/"+(j||q.name);a[q.name]={name:q.name,location:j|| +q.name,main:(q.main||"main").replace(ea,"").replace(aa,"")}}}function V(a,c){a.holdReady?a.holdReady(c):c?a.readyWait+=1:a.ready(!0)}function fa(a){function c(b,l){var f,a;if(b&&b.charAt(0)===".")if(l){p.pkgs[l]?l=[l]:(l=l.split("/"),l=l.slice(0,l.length-1));f=b=l.concat(b.split("/"));var c;for(a=0;c=f[a];a++)if(c===".")f.splice(a,1),a-=1;else if(c==="..")if(a===1&&(f[2]===".."||f[0]===".."))break;else a>0&&(f.splice(a-1,2),a-=2);a=p.pkgs[f=b[0]];b=b.join("/");a&&b===f+"/"+a.main&&(b=f)}else b.indexOf("./")=== +0&&(b=b.substring(2));return b}function h(b,l){var f=b?b.indexOf("!"):-1,a=null,d=l?l.name:null,i=b,e,h;f!==-1&&(a=b.substring(0,f),b=b.substring(f+1,b.length));a&&(a=c(a,d));b&&(a?e=(f=m[a])&&f.normalize?f.normalize(b,function(b){return c(b,d)}):c(b,d):(e=c(b,d),h=E[e],h||(h=g.nameToUrl(e,null,l),E[e]=h)));return{prefix:a,name:e,parentMap:l,url:h,originalName:i,fullName:a?a+"!"+(e||""):e}}function k(){var b=!0,l=p.priorityWait,f,a;if(l){for(a=0;f=l[a];a++)if(!s[f]){b=!1;break}b&&delete p.priorityWait}return b} +function j(b,l,f){return function(){var a=ga.call(arguments,0),c;if(f&&J(c=a[a.length-1]))c.__requireJsBuild=!0;a.push(l);return b.apply(null,a)}}function q(b,l){var a=j(g.require,b,l);Z(a,{nameToUrl:j(g.nameToUrl,b),toUrl:j(g.toUrl,b),defined:j(g.requireDefined,b),specified:j(g.requireSpecified,b),isBrowser:d.isBrowser});return a}function o(b){var l,a,c,C=b.callback,i=b.map,e=i.fullName,ba=b.deps;c=b.listeners;if(C&&J(C)){if(p.catchError.define)try{a=d.execCb(e,b.callback,ba,m[e])}catch(k){l=k}else a= +d.execCb(e,b.callback,ba,m[e]);if(e)(C=b.cjsModule)&&C.exports!==void 0&&C.exports!==m[e]?a=m[e]=b.cjsModule.exports:a===void 0&&b.usingExports?a=m[e]:(m[e]=a,F[e]&&(Q[e]=!0))}else e&&(a=m[e]=C,F[e]&&(Q[e]=!0));if(D[b.id])delete D[b.id],b.isDone=!0,g.waitCount-=1,g.waitCount===0&&(I=[]);delete R[e];if(d.onResourceLoad&&!b.placeholder)d.onResourceLoad(g,i,b.depArray);if(l)return a=(e?h(e).url:"")||l.fileName||l.sourceURL,c=l.moduleTree,l=N("defineerror",'Error evaluating module "'+e+'" at location "'+ +a+'":\n'+l+"\nfileName:"+a+"\nlineNumber: "+(l.lineNumber||l.line),l),l.moduleName=e,l.moduleTree=c,d.onError(l);for(l=0;C=c[l];l++)C(a)}function r(b,a){return function(f){b.depDone[a]||(b.depDone[a]=!0,b.deps[a]=f,b.depCount-=1,b.depCount||o(b))}}function u(b,a){var f=a.map,c=f.fullName,h=f.name,i=L[b]||(L[b]=m[b]),e;if(!a.loading)a.loading=!0,e=function(b){a.callback=function(){return b};o(a);s[a.id]=!0;w()},e.fromText=function(b,a){var l=O;s[b]=!1;g.scriptCount+=1;g.fake[b]=!0;l&&(O=!1);d.exec(a); +l&&(O=!0);g.completeLoad(b)},c in m?e(m[c]):i.load(h,q(f.parentMap,!0),e,p)}function v(b){D[b.id]||(D[b.id]=b,I.push(b),g.waitCount+=1)}function B(b){this.listeners.push(b)}function t(b,a){var f=b.fullName,c=b.prefix,d=c?L[c]||(L[c]=m[c]):null,i,e;f&&(i=R[f]);if(!i&&(e=!0,i={id:(c&&!d?M++ +"__p@:":"")+(f||"__r@"+M++),map:b,depCount:0,depDone:[],depCallbacks:[],deps:[],listeners:[],add:B},y[i.id]=!0,f&&(!c||L[c])))R[f]=i;c&&!d?(f=t(h(c),!0),f.add(function(){var a=h(b.originalName,b.parentMap),a=t(a, +!0);i.placeholder=!0;a.add(function(b){i.callback=function(){return b};o(i)})})):e&&a&&(s[i.id]=!1,g.paused.push(i),v(i));return i}function x(b,a,f,c){var b=h(b,c),d=b.name,i=b.fullName,e=t(b),k=e.id,j=e.deps,n;if(i){if(i in m||s[k]===!0||i==="jquery"&&p.jQuery&&p.jQuery!==f().fn.jquery)return;y[k]=!0;s[k]=!0;i==="jquery"&&f&&S(f())}e.depArray=a;e.callback=f;for(f=0;f0)){if(p.priorityWait)if(k())w();else return;for(j in s)if(!(j in K)&&(c=!0,!s[j]))if(a)b+=j+" ";else{h=!0;break}if(c||g.waitCount){if(a&&b)return j=N("timeout","Load timeout for modules: "+b),j.requireType="timeout",j.requireModules=b,d.onError(j);if(h||g.scriptCount){if((G||ca)&&!W)W=setTimeout(function(){W=0;A()},50)}else{if(g.waitCount){for(H= +0;b=I[H];H++)z(b,{});g.paused.length&&w();X<5&&(X+=1,A())}X=0;d.checkReadyState()}}}}var g,w,p={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},catchError:{}},P=[],y={require:!0,exports:!0,module:!0},E={},m={},s={},D={},I=[],T={},M=0,R={},L={},F={},Q={},Y=0;S=function(b){if(!g.jQuery&&(b=b||(typeof jQuery!=="undefined"?jQuery:null))&&!(p.jQuery&&b.fn.jquery!==p.jQuery)&&("holdReady"in b||"readyWait"in b))if(g.jQuery=b,n(["jquery",[],function(){return jQuery}]),g.scriptCount)V(b,!0),g.jQueryIncremented= +!0};w=function(){var b,a,c,h,j,i;Y+=1;if(g.scriptCount<=0)g.scriptCount=0;for(;P.length;)if(b=P.shift(),b[0]===null)return d.onError(N("mismatch","Mismatched anonymous define() module: "+b[b.length-1]));else n(b);if(!p.priorityWait||k())for(;g.paused.length;){j=g.paused;g.pausedCount+=j.length;g.paused=[];for(h=0;b=j[h];h++)a=b.map,c=a.url,i=a.fullName,a.prefix?u(a.prefix,b):!T[c]&&!s[i]&&(d.load(g,i,c),c.indexOf("empty:")!==0&&(T[c]=!0));g.startTime=(new Date).getTime();g.pausedCount-=j.length}Y=== +1&&A();Y-=1};g={contextName:a,config:p,defQueue:P,waiting:D,waitCount:0,specified:y,loaded:s,urlMap:E,urlFetched:T,scriptCount:0,defined:m,paused:[],pausedCount:0,plugins:L,needFullExec:F,fake:{},fullExec:Q,managerCallbacks:R,makeModuleMap:h,normalize:c,configure:function(b){var a,c,d;b.baseUrl&&b.baseUrl.charAt(b.baseUrl.length-1)!=="/"&&(b.baseUrl+="/");a=p.paths;d=p.pkgs;Z(p,b,!0);if(b.paths){for(c in b.paths)c in K||(a[c]=b.paths[c]);p.paths=a}if((a=b.packagePaths)||b.packages){if(a)for(c in a)c in +K||$(d,a[c],c);b.packages&&$(d,b.packages);p.pkgs=d}if(b.priority)c=g.requireWait,g.requireWait=!1,g.takeGlobalQueue(),w(),g.require(b.priority),w(),g.requireWait=c,p.priorityWait=b.priority;if(b.deps||b.callback)g.require(b.deps||[],b.callback)},requireDefined:function(b,a){return h(b,a).fullName in m},requireSpecified:function(b,a){return h(b,a).fullName in y},require:function(b,c,f){if(typeof b==="string"){if(J(c))return d.onError(N("requireargs","Invalid require call"));if(d.get)return d.get(g, +b,c);c=h(b,c);b=c.fullName;return!(b in m)?d.onError(N("notloaded","Module name '"+c.fullName+"' has not been loaded yet for context: "+a)):m[b]}(b&&b.length||c)&&x(null,b,c,f);if(!g.requireWait)for(;!g.scriptCount&&g.paused.length;)g.takeGlobalQueue(),w();return g.require},takeGlobalQueue:function(){U.length&&(ha.apply(g.defQueue,[g.defQueue.length-1,0].concat(U)),U=[])},completeLoad:function(b){var a;for(g.takeGlobalQueue();P.length;)if(a=P.shift(),a[0]===null){a[0]=b;break}else if(a[0]===b)break; +else n(a),a=null;a?n(a):n([b,[],b==="jquery"&&typeof jQuery!=="undefined"?function(){return jQuery}:null]);S();d.isAsync&&(g.scriptCount-=1);w();d.isAsync||(g.scriptCount-=1)},toUrl:function(a,c){var d=a.lastIndexOf("."),h=null;d!==-1&&(h=a.substring(d,a.length),a=a.substring(0,d));return g.nameToUrl(a,h,c)},nameToUrl:function(a,h,f){var j,k,i,e,m=g.config,a=c(a,f&&f.fullName);if(d.jsExtRegExp.test(a))h=a+(h?h:"");else{j=m.paths;k=m.pkgs;f=a.split("/");for(e=f.length;e>0;e--)if(i=f.slice(0,e).join("/"), +j[i]){f.splice(0,e,j[i]);break}else if(i=k[i]){a=a===i.name?i.location+"/"+i.main:i.location;f.splice(0,e,a);break}h=f.join("/")+(h||".js");h=(h.charAt(0)==="/"||h.match(/^\w+:/)?"":m.baseUrl)+h}return m.urlArgs?h+((h.indexOf("?")===-1?"?":"&")+m.urlArgs):h}};g.jQueryCheck=S;g.resume=w;return g}function ia(){var a,c,d;if(n&&n.readyState==="interactive")return n;a=document.getElementsByTagName("script");for(c=a.length-1;c>-1&&(d=a[c]);c--)if(d.readyState==="interactive")return n=d;return null}var ja= +/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ka=/require\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/^\.\//,aa=/\.js$/,M=Object.prototype.toString,r=Array.prototype,ga=r.slice,ha=r.splice,G=!!(typeof window!=="undefined"&&navigator&&document),ca=!G&&typeof importScripts!=="undefined",la=G&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,da=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",K={},t={},U=[],n=null,X=0,O=!1,d,r={},I,v,x,y,u,z,A,H,B,S,W;if(typeof define==="undefined"){if(typeof requirejs!== +"undefined")if(J(requirejs))return;else r=requirejs,requirejs=void 0;typeof require!=="undefined"&&!J(require)&&(r=require,require=void 0);d=requirejs=function(a,c,d){var k="_",j;!E(a)&&typeof a!=="string"&&(j=a,E(c)?(a=c,c=d):a=[]);if(j&&j.context)k=j.context;d=t[k]||(t[k]=fa(k));j&&d.configure(j);return d.require(a,c)};d.config=function(a){return d(a)};require||(require=d);d.toUrl=function(a){return t._.toUrl(a)};d.version="1.0.2";d.jsExtRegExp=/^\/|:|\?|\.js$/;v=d.s={contexts:t,skipAsync:{}};if(d.isAsync= +d.isBrowser=G)if(x=v.head=document.getElementsByTagName("head")[0],y=document.getElementsByTagName("base")[0])x=v.head=y.parentNode;d.onError=function(a){throw a;};d.load=function(a,c,h){d.resourcesReady(!1);a.scriptCount+=1;d.attach(h,a,c);if(a.jQuery&&!a.jQueryIncremented)V(a.jQuery,!0),a.jQueryIncremented=!0};define=function(a,c,d){var k,j;typeof a!=="string"&&(d=c,c=a,a=null);E(c)||(d=c,c=[]);!c.length&&J(d)&&d.length&&(d.toString().replace(ja,"").replace(ka,function(a,d){c.push(d)}),c=(d.length=== +1?["require"]:["require","exports","module"]).concat(c));if(O&&(k=I||ia()))a||(a=k.getAttribute("data-requiremodule")),j=t[k.getAttribute("data-requirecontext")];(j?j.defQueue:U).push([a,c,d])};define.amd={multiversion:!0,plugins:!0,jQuery:!0};d.exec=function(a){return eval(a)};d.execCb=function(a,c,d,k){return c.apply(k,d)};d.addScriptToDom=function(a){I=a;y?x.insertBefore(a,y):x.appendChild(a);I=null};d.onScriptLoad=function(a){var c=a.currentTarget||a.srcElement,h;if(a.type==="load"||c&&la.test(c.readyState))n= +null,a=c.getAttribute("data-requirecontext"),h=c.getAttribute("data-requiremodule"),t[a].completeLoad(h),c.detachEvent&&!da?c.detachEvent("onreadystatechange",d.onScriptLoad):c.removeEventListener("load",d.onScriptLoad,!1)};d.attach=function(a,c,h,k,j,n){var o;if(G)return k=k||d.onScriptLoad,o=c&&c.config&&c.config.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),o.type=j||"text/javascript",o.charset="utf-8",o.async=!v.skipAsync[a],c&&o.setAttribute("data-requirecontext", +c.contextName),o.setAttribute("data-requiremodule",h),o.attachEvent&&!da?(O=!0,n?o.onreadystatechange=function(){if(o.readyState==="loaded")o.onreadystatechange=null,o.attachEvent("onreadystatechange",k),n(o)}:o.attachEvent("onreadystatechange",k)):o.addEventListener("load",k,!1),o.src=a,n||d.addScriptToDom(o),o;else ca&&(importScripts(a),c.completeLoad(h));return null};if(G){u=document.getElementsByTagName("script");for(H=u.length-1;H>-1&&(z=u[H]);H--){if(!x)x=z.parentNode;if(A=z.getAttribute("data-main")){if(!r.baseUrl)u= +A.split("/"),z=u.pop(),u=u.length?u.join("/")+"/":"./",r.baseUrl=u,A=z.replace(aa,"");r.deps=r.deps?r.deps.concat(A):[A];break}}}d.checkReadyState=function(){var a=v.contexts,c;for(c in a)if(!(c in K)&&a[c].waitCount)return;d.resourcesReady(!0)};d.resourcesReady=function(a){var c,h;d.resourcesDone=a;if(d.resourcesDone)for(h in a=v.contexts,a)if(!(h in K)&&(c=a[h],c.jQueryIncremented))V(c.jQuery,!1),c.jQueryIncremented=!1};d.pageLoaded=function(){if(document.readyState!=="complete")document.readyState= +"complete"};if(G&&document.addEventListener&&!document.readyState)document.readyState="loading",window.addEventListener("load",d.pageLoaded,!1);d(r);if(d.isAsync&&typeof setTimeout!=="undefined")B=v.contexts[r.context||"_"],B.requireWait=!0,setTimeout(function(){B.requireWait=!1;B.takeGlobalQueue();B.jQueryCheck();B.scriptCount||B.resume();d.checkReadyState()},0)}})(); diff --git a/test/lib/require/text.js b/test/lib/require/text.js new file mode 100755 index 0000000..0caaacf --- /dev/null +++ b/test/lib/require/text.js @@ -0,0 +1,11 @@ +/* + RequireJS text 0.27.0 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved. + Available via the MIT or new BSD license. + see: http://github.com/jrburke/requirejs for details +*/ +(function(){var k=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"],n=/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,o=/]*>\s*([\s\S]+)\s*<\/body>/im,i=typeof location!=="undefined"&&location.href,p=i&&location.protocol&&location.protocol.replace(/\:/,""),q=i&&location.hostname,r=i&&(location.port||void 0),j=[];define(function(){var g,h,l;typeof window!=="undefined"&&window.navigator&&window.document?h=function(a,b){var c=g.createXhr();c.open("GET",a,!0);c.onreadystatechange= +function(){c.readyState===4&&b(c.responseText)};c.send(null)}:typeof process!=="undefined"&&process.versions&&process.versions.node?(l=require.nodeRequire("fs"),h=function(a,b){b(l.readFileSync(a,"utf8"))}):typeof Packages!=="undefined"&&(h=function(a,b){var c=new java.io.File(a),e=java.lang.System.getProperty("line.separator"),c=new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(c),"utf-8")),d,f,g="";try{d=new java.lang.StringBuffer;(f=c.readLine())&&f.length()&& +f.charAt(0)===65279&&(f=f.substring(1));for(d.append(f);(f=c.readLine())!==null;)d.append(e),d.append(f);g=String(d.toString())}finally{c.close()}b(g)});return g={version:"0.27.0",strip:function(a){if(a){var a=a.replace(n,""),b=a.match(o);b&&(a=b[1])}else a="";return a},jsEscape:function(a){return a.replace(/(['\\])/g,"\\$1").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r")},createXhr:function(){var a,b,c;if(typeof XMLHttpRequest!== +"undefined")return new XMLHttpRequest;else for(b=0;b<3;b++){c=k[b];try{a=new ActiveXObject(c)}catch(e){}if(a){k=[c];break}}if(!a)throw Error("createXhr(): XMLHttpRequest not available");return a},get:h,parseName:function(a){var b=!1,c=a.indexOf("."),e=a.substring(0,c),a=a.substring(c+1,a.length),c=a.indexOf("!");c!==-1&&(b=a.substring(c+1,a.length),b=b==="strip",a=a.substring(0,c));return{moduleName:e,ext:a,strip:b}},xdRegExp:/^((\w+)\:)?\/\/([^\/\\]+)/,useXhr:function(a,b,c,e){var d=g.xdRegExp.exec(a), +f;if(!d)return!0;a=d[2];d=d[3];d=d.split(":");f=d[1];d=d[0];return(!a||a===b)&&(!d||d===c)&&(!f&&!d||f===e)},finishLoad:function(a,b,c,e,d){c=b?g.strip(c):c;d.isBuild&&d.inlineText&&(j[a]=c);e(c)},load:function(a,b,c,e){var d=g.parseName(a),f=d.moduleName+"."+d.ext,m=b.toUrl(f),h=e&&e.text&&e.text.useXhr||g.useXhr;!i||h(m,p,q,r)?g.get(m,function(b){g.finishLoad(a,d.strip,b,c,e)}):b([f],function(a){g.finishLoad(d.moduleName+"."+d.ext,d.strip,a,c,e)})},write:function(a,b,c){if(b in j){var e=g.jsEscape(j[b]); +c.asModule(a+"!"+b,"define(function () { return '"+e+"';});\n")}},writeFile:function(a,b,c,e,d){var b=g.parseName(b),f=b.moduleName+"."+b.ext,h=c.toUrl(b.moduleName+"."+b.ext)+".js";g.load(f,c,function(){var b=function(a){return e(h,a)};b.asModule=function(a,b){return e.asModule(a,h,b)};g.write(a,f,b,d)},d)}}})})(); diff --git a/test/macbeth.js b/test/macbeth.js new file mode 100755 index 0000000..7d36b24 --- /dev/null +++ b/test/macbeth.js @@ -0,0 +1,3411 @@ +macbeth = ["The Tragedy of Macbeth", +"Shakespeare homepage | Macbeth | Entire play", +"ACT I", +"", +"SCENE I. A desert place.", +"", +"Thunder and lightning. Enter three Witches", +"First Witch", +"When shall we three meet again", +"In thunder, lightning, or in rain?", +"Second Witch", +"When the hurlyburly's done,", +"When the battle's lost and won.", +"Third Witch", +"That will be ere the set of sun.", +"First Witch", +"Where the place?", +"Second Witch", +"Upon the heath.", +"Third Witch", +"There to meet with Macbeth.", +"First Witch", +"I come, Graymalkin!", +"Second Witch", +"Paddock calls.", +"Third Witch", +"Anon.", +"ALL", +"Fair is foul, and foul is fair:", +"Hover through the fog and filthy air.", +"Exeunt", +"", +"SCENE II. A camp near Forres.", +"", +"Alarum within. Enter DUNCAN, MALCOLM, DONALBAIN, LENNOX, with Attendants, meeting a bleeding Sergeant", +"DUNCAN", +"What bloody man is that? He can report,", +"As seemeth by his plight, of the revolt", +"The newest state.", +"MALCOLM", +"This is the sergeant", +"Who like a good and hardy soldier fought", +"'Gainst my captivity. Hail, brave friend!", +"Say to the king the knowledge of the broil", +"As thou didst leave it.", +"Sergeant", +"Doubtful it stood;", +"As two spent swimmers, that do cling together", +"And choke their art. The merciless Macdonwald--", +"Worthy to be a rebel, for to that", +"The multiplying villanies of nature", +"Do swarm upon him--from the western isles", +"Of kerns and gallowglasses is supplied;", +"And fortune, on his damned quarrel smiling,", +"Show'd like a rebel's whore: but all's too weak:", +"For brave Macbeth--well he deserves that name--", +"Disdaining fortune, with his brandish'd steel,", +"Which smoked with bloody execution,", +"Like valour's minion carved out his passage", +"Till he faced the slave;", +"Which ne'er shook hands, nor bade farewell to him,", +"Till he unseam'd him from the nave to the chaps,", +"And fix'd his head upon our battlements.", +"DUNCAN", +"O valiant cousin! worthy gentleman!", +"Sergeant", +"As whence the sun 'gins his reflection", +"Shipwrecking storms and direful thunders break,", +"So from that spring whence comfort seem'd to come", +"Discomfort swells. Mark, king of Scotland, mark:", +"No sooner justice had with valour arm'd", +"Compell'd these skipping kerns to trust their heels,", +"But the Norweyan lord surveying vantage,", +"With furbish'd arms and new supplies of men", +"Began a fresh assault.", +"DUNCAN", +"Dismay'd not this", +"Our captains, Macbeth and Banquo?", +"Sergeant", +"Yes;", +"As sparrows eagles, or the hare the lion.", +"If I say sooth, I must report they were", +"As cannons overcharged with double cracks, so they", +"Doubly redoubled strokes upon the foe:", +"Except they meant to bathe in reeking wounds,", +"Or memorise another Golgotha,", +"I cannot tell.", +"But I am faint, my gashes cry for help.", +"DUNCAN", +"So well thy words become thee as thy wounds;", +"They smack of honour both. Go get him surgeons.", +"Exit Sergeant, attended", +"", +"Who comes here?", +"Enter ROSS", +"", +"MALCOLM", +"The worthy thane of Ross.", +"LENNOX", +"What a haste looks through his eyes! So should he look", +"That seems to speak things strange.", +"ROSS", +"God save the king!", +"DUNCAN", +"Whence camest thou, worthy thane?", +"ROSS", +"From Fife, great king;", +"Where the Norweyan banners flout the sky", +"And fan our people cold. Norway himself,", +"With terrible numbers,", +"Assisted by that most disloyal traitor", +"The thane of Cawdor, began a dismal conflict;", +"Till that Bellona's bridegroom, lapp'd in proof,", +"Confronted him with self-comparisons,", +"Point against point rebellious, arm 'gainst arm.", +"Curbing his lavish spirit: and, to conclude,", +"The victory fell on us.", +"DUNCAN", +"Great happiness!", +"ROSS", +"That now", +"Sweno, the Norways' king, craves composition:", +"Nor would we deign him burial of his men", +"Till he disbursed at Saint Colme's inch", +"Ten thousand dollars to our general use.", +"DUNCAN", +"No more that thane of Cawdor shall deceive", +"Our bosom interest: go pronounce his present death,", +"And with his former title greet Macbeth.", +"ROSS", +"I'll see it done.", +"DUNCAN", +"What he hath lost noble Macbeth hath won.", +"Exeunt", +"", +"SCENE III. A heath near Forres.", +"", +"Thunder. Enter the three Witches", +"First Witch", +"Where hast thou been, sister?", +"Second Witch", +"Killing swine.", +"Third Witch", +"Sister, where thou?", +"First Witch", +"A sailor's wife had chestnuts in her lap,", +"And munch'd, and munch'd, and munch'd:--", +"'Give me,' quoth I:", +"'Aroint thee, witch!' the rump-fed ronyon cries.", +"Her husband's to Aleppo gone, master o' the Tiger:", +"But in a sieve I'll thither sail,", +"And, like a rat without a tail,", +"I'll do, I'll do, and I'll do.", +"Second Witch", +"I'll give thee a wind.", +"First Witch", +"Thou'rt kind.", +"Third Witch", +"And I another.", +"First Witch", +"I myself have all the other,", +"And the very ports they blow,", +"All the quarters that they know", +"I' the shipman's card.", +"I will drain him dry as hay:", +"Sleep shall neither night nor day", +"Hang upon his pent-house lid;", +"He shall live a man forbid:", +"Weary se'nnights nine times nine", +"Shall he dwindle, peak and pine:", +"Though his bark cannot be lost,", +"Yet it shall be tempest-tost.", +"Look what I have.", +"Second Witch", +"Show me, show me.", +"First Witch", +"Here I have a pilot's thumb,", +"Wreck'd as homeward he did come.", +"Drum within", +"", +"Third Witch", +"A drum, a drum!", +"Macbeth doth come.", +"ALL", +"The weird sisters, hand in hand,", +"Posters of the sea and land,", +"Thus do go about, about:", +"Thrice to thine and thrice to mine", +"And thrice again, to make up nine.", +"Peace! the charm's wound up.", +"Enter MACBETH and BANQUO", +"", +"MACBETH", +"So foul and fair a day I have not seen.", +"BANQUO", +"How far is't call'd to Forres? What are these", +"So wither'd and so wild in their attire,", +"That look not like the inhabitants o' the earth,", +"And yet are on't? Live you? or are you aught", +"That man may question? You seem to understand me,", +"By each at once her chappy finger laying", +"Upon her skinny lips: you should be women,", +"And yet your beards forbid me to interpret", +"That you are so.", +"MACBETH", +"Speak, if you can: what are you?", +"First Witch", +"All hail, Macbeth! hail to thee, thane of Glamis!", +"Second Witch", +"All hail, Macbeth, hail to thee, thane of Cawdor!", +"Third Witch", +"All hail, Macbeth, thou shalt be king hereafter!", +"BANQUO", +"Good sir, why do you start; and seem to fear", +"Things that do sound so fair? I' the name of truth,", +"Are ye fantastical, or that indeed", +"Which outwardly ye show? My noble partner", +"You greet with present grace and great prediction", +"Of noble having and of royal hope,", +"That he seems rapt withal: to me you speak not.", +"If you can look into the seeds of time,", +"And say which grain will grow and which will not,", +"Speak then to me, who neither beg nor fear", +"Your favours nor your hate.", +"First Witch", +"Hail!", +"Second Witch", +"Hail!", +"Third Witch", +"Hail!", +"First Witch", +"Lesser than Macbeth, and greater.", +"Second Witch", +"Not so happy, yet much happier.", +"Third Witch", +"Thou shalt get kings, though thou be none:", +"So all hail, Macbeth and Banquo!", +"First Witch", +"Banquo and Macbeth, all hail!", +"MACBETH", +"Stay, you imperfect speakers, tell me more:", +"By Sinel's death I know I am thane of Glamis;", +"But how of Cawdor? the thane of Cawdor lives,", +"A prosperous gentleman; and to be king", +"Stands not within the prospect of belief,", +"No more than to be Cawdor. Say from whence", +"You owe this strange intelligence? or why", +"Upon this blasted heath you stop our way", +"With such prophetic greeting? Speak, I charge you.", +"Witches vanish", +"", +"BANQUO", +"The earth hath bubbles, as the water has,", +"And these are of them. Whither are they vanish'd?", +"MACBETH", +"Into the air; and what seem'd corporal melted", +"As breath into the wind. Would they had stay'd!", +"BANQUO", +"Were such things here as we do speak about?", +"Or have we eaten on the insane root", +"That takes the reason prisoner?", +"MACBETH", +"Your children shall be kings.", +"BANQUO", +"You shall be king.", +"MACBETH", +"And thane of Cawdor too: went it not so?", +"BANQUO", +"To the selfsame tune and words. Who's here?", +"Enter ROSS and ANGUS", +"", +"ROSS", +"The king hath happily received, Macbeth,", +"The news of thy success; and when he reads", +"Thy personal venture in the rebels' fight,", +"His wonders and his praises do contend", +"Which should be thine or his: silenced with that,", +"In viewing o'er the rest o' the selfsame day,", +"He finds thee in the stout Norweyan ranks,", +"Nothing afeard of what thyself didst make,", +"Strange images of death. As thick as hail", +"Came post with post; and every one did bear", +"Thy praises in his kingdom's great defence,", +"And pour'd them down before him.", +"ANGUS", +"We are sent", +"To give thee from our royal master thanks;", +"Only to herald thee into his sight,", +"Not pay thee.", +"ROSS", +"And, for an earnest of a greater honour,", +"He bade me, from him, call thee thane of Cawdor:", +"In which addition, hail, most worthy thane!", +"For it is thine.", +"BANQUO", +"What, can the devil speak true?", +"MACBETH", +"The thane of Cawdor lives: why do you dress me", +"In borrow'd robes?", +"ANGUS", +"Who was the thane lives yet;", +"But under heavy judgment bears that life", +"Which he deserves to lose. Whether he was combined", +"With those of Norway, or did line the rebel", +"With hidden help and vantage, or that with both", +"He labour'd in his country's wreck, I know not;", +"But treasons capital, confess'd and proved,", +"Have overthrown him.", +"MACBETH", +"[Aside] Glamis, and thane of Cawdor!", +"The greatest is behind.", +"To ROSS and ANGUS", +"", +"Thanks for your pains.", +"To BANQUO", +"", +"Do you not hope your children shall be kings,", +"When those that gave the thane of Cawdor to me", +"Promised no less to them?", +"BANQUO", +"That trusted home", +"Might yet enkindle you unto the crown,", +"Besides the thane of Cawdor. But 'tis strange:", +"And oftentimes, to win us to our harm,", +"The instruments of darkness tell us truths,", +"Win us with honest trifles, to betray's", +"In deepest consequence.", +"Cousins, a word, I pray you.", +"MACBETH", +"[Aside] Two truths are told,", +"As happy prologues to the swelling act", +"Of the imperial theme.--I thank you, gentlemen.", +"Aside", +"", +"Cannot be ill, cannot be good: if ill,", +"Why hath it given me earnest of success,", +"Commencing in a truth? I am thane of Cawdor:", +"If good, why do I yield to that suggestion", +"Whose horrid image doth unfix my hair", +"And make my seated heart knock at my ribs,", +"Against the use of nature? Present fears", +"Are less than horrible imaginings:", +"My thought, whose murder yet is but fantastical,", +"Shakes so my single state of man that function", +"Is smother'd in surmise, and nothing is", +"But what is not.", +"BANQUO", +"Look, how our partner's rapt.", +"MACBETH", +"[Aside] If chance will have me king, why, chance may crown me,", +"Without my stir.", +"BANQUO", +"New horrors come upon him,", +"Like our strange garments, cleave not to their mould", +"But with the aid of use.", +"MACBETH", +"[Aside] Come what come may,", +"Time and the hour runs through the roughest day.", +"BANQUO", +"Worthy Macbeth, we stay upon your leisure.", +"MACBETH", +"Give me your favour: my dull brain was wrought", +"With things forgotten. Kind gentlemen, your pains", +"Are register'd where every day I turn", +"The leaf to read them. Let us toward the king.", +"Think upon what hath chanced, and, at more time,", +"The interim having weigh'd it, let us speak", +"Our free hearts each to other.", +"BANQUO", +"Very gladly.", +"MACBETH", +"Till then, enough. Come, friends.", +"Exeunt", +"", +"SCENE IV. Forres. The palace.", +"", +"Flourish. Enter DUNCAN, MALCOLM, DONALBAIN, LENNOX, and Attendants", +"DUNCAN", +"Is execution done on Cawdor? Are not", +"Those in commission yet return'd?", +"MALCOLM", +"My liege,", +"They are not yet come back. But I have spoke", +"With one that saw him die: who did report", +"That very frankly he confess'd his treasons,", +"Implored your highness' pardon and set forth", +"A deep repentance: nothing in his life", +"Became him like the leaving it; he died", +"As one that had been studied in his death", +"To throw away the dearest thing he owed,", +"As 'twere a careless trifle.", +"DUNCAN", +"There's no art", +"To find the mind's construction in the face:", +"He was a gentleman on whom I built", +"An absolute trust.", +"Enter MACBETH, BANQUO, ROSS, and ANGUS", +"", +"O worthiest cousin!", +"The sin of my ingratitude even now", +"Was heavy on me: thou art so far before", +"That swiftest wing of recompense is slow", +"To overtake thee. Would thou hadst less deserved,", +"That the proportion both of thanks and payment", +"Might have been mine! only I have left to say,", +"More is thy due than more than all can pay.", +"MACBETH", +"The service and the loyalty I owe,", +"In doing it, pays itself. Your highness' part", +"Is to receive our duties; and our duties", +"Are to your throne and state children and servants,", +"Which do but what they should, by doing every thing", +"Safe toward your love and honour.", +"DUNCAN", +"Welcome hither:", +"I have begun to plant thee, and will labour", +"To make thee full of growing. Noble Banquo,", +"That hast no less deserved, nor must be known", +"No less to have done so, let me enfold thee", +"And hold thee to my heart.", +"BANQUO", +"There if I grow,", +"The harvest is your own.", +"DUNCAN", +"My plenteous joys,", +"Wanton in fulness, seek to hide themselves", +"In drops of sorrow. Sons, kinsmen, thanes,", +"And you whose places are the nearest, know", +"We will establish our estate upon", +"Our eldest, Malcolm, whom we name hereafter", +"The Prince of Cumberland; which honour must", +"Not unaccompanied invest him only,", +"But signs of nobleness, like stars, shall shine", +"On all deservers. From hence to Inverness,", +"And bind us further to you.", +"MACBETH", +"The rest is labour, which is not used for you:", +"I'll be myself the harbinger and make joyful", +"The hearing of my wife with your approach;", +"So humbly take my leave.", +"DUNCAN", +"My worthy Cawdor!", +"MACBETH", +"[Aside] The Prince of Cumberland! that is a step", +"On which I must fall down, or else o'erleap,", +"For in my way it lies. Stars, hide your fires;", +"Let not light see my black and deep desires:", +"The eye wink at the hand; yet let that be,", +"Which the eye fears, when it is done, to see.", +"Exit", +"", +"DUNCAN", +"True, worthy Banquo; he is full so valiant,", +"And in his commendations I am fed;", +"It is a banquet to me. Let's after him,", +"Whose care is gone before to bid us welcome:", +"It is a peerless kinsman.", +"Flourish. Exeunt", +"", +"SCENE V. Inverness. Macbeth's castle.", +"", +"Enter LADY MACBETH, reading a letter", +"LADY MACBETH", +"'They met me in the day of success: and I have", +"learned by the perfectest report, they have more in", +"them than mortal knowledge. When I burned in desire", +"to question them further, they made themselves air,", +"into which they vanished. Whiles I stood rapt in", +"the wonder of it, came missives from the king, who", +"all-hailed me 'Thane of Cawdor;' by which title,", +"before, these weird sisters saluted me, and referred", +"me to the coming on of time, with 'Hail, king that", +"shalt be!' This have I thought good to deliver", +"thee, my dearest partner of greatness, that thou", +"mightst not lose the dues of rejoicing, by being", +"ignorant of what greatness is promised thee. Lay it", +"to thy heart, and farewell.'", +"Glamis thou art, and Cawdor; and shalt be", +"What thou art promised: yet do I fear thy nature;", +"It is too full o' the milk of human kindness", +"To catch the nearest way: thou wouldst be great;", +"Art not without ambition, but without", +"The illness should attend it: what thou wouldst highly,", +"That wouldst thou holily; wouldst not play false,", +"And yet wouldst wrongly win: thou'ldst have, great Glamis,", +"That which cries 'Thus thou must do, if thou have it;", +"And that which rather thou dost fear to do", +"Than wishest should be undone.' Hie thee hither,", +"That I may pour my spirits in thine ear;", +"And chastise with the valour of my tongue", +"All that impedes thee from the golden round,", +"Which fate and metaphysical aid doth seem", +"To have thee crown'd withal.", +"Enter a Messenger", +"", +"What is your tidings?", +"Messenger", +"The king comes here to-night.", +"LADY MACBETH", +"Thou'rt mad to say it:", +"Is not thy master with him? who, were't so,", +"Would have inform'd for preparation.", +"Messenger", +"So please you, it is true: our thane is coming:", +"One of my fellows had the speed of him,", +"Who, almost dead for breath, had scarcely more", +"Than would make up his message.", +"LADY MACBETH", +"Give him tending;", +"He brings great news.", +"Exit Messenger", +"", +"The raven himself is hoarse", +"That croaks the fatal entrance of Duncan", +"Under my battlements. Come, you spirits", +"That tend on mortal thoughts, unsex me here,", +"And fill me from the crown to the toe top-full", +"Of direst cruelty! make thick my blood;", +"Stop up the access and passage to remorse,", +"That no compunctious visitings of nature", +"Shake my fell purpose, nor keep peace between", +"The effect and it! Come to my woman's breasts,", +"And take my milk for gall, you murdering ministers,", +"Wherever in your sightless substances", +"You wait on nature's mischief! Come, thick night,", +"And pall thee in the dunnest smoke of hell,", +"That my keen knife see not the wound it makes,", +"Nor heaven peep through the blanket of the dark,", +"To cry 'Hold, hold!'", +"Enter MACBETH", +"", +"Great Glamis! worthy Cawdor!", +"Greater than both, by the all-hail hereafter!", +"Thy letters have transported me beyond", +"This ignorant present, and I feel now", +"The future in the instant.", +"MACBETH", +"My dearest love,", +"Duncan comes here to-night.", +"LADY MACBETH", +"And when goes hence?", +"MACBETH", +"To-morrow, as he purposes.", +"LADY MACBETH", +"O, never", +"Shall sun that morrow see!", +"Your face, my thane, is as a book where men", +"May read strange matters. To beguile the time,", +"Look like the time; bear welcome in your eye,", +"Your hand, your tongue: look like the innocent flower,", +"But be the serpent under't. He that's coming", +"Must be provided for: and you shall put", +"This night's great business into my dispatch;", +"Which shall to all our nights and days to come", +"Give solely sovereign sway and masterdom.", +"MACBETH", +"We will speak further.", +"LADY MACBETH", +"Only look up clear;", +"To alter favour ever is to fear:", +"Leave all the rest to me.", +"Exeunt", +"", +"SCENE VI. Before Macbeth's castle.", +"", +"Hautboys and torches. Enter DUNCAN, MALCOLM, DONALBAIN, BANQUO, LENNOX, MACDUFF, ROSS, ANGUS, and Attendants", +"DUNCAN", +"This castle hath a pleasant seat; the air", +"Nimbly and sweetly recommends itself", +"Unto our gentle senses.", +"BANQUO", +"This guest of summer,", +"The temple-haunting martlet, does approve,", +"By his loved mansionry, that the heaven's breath", +"Smells wooingly here: no jutty, frieze,", +"Buttress, nor coign of vantage, but this bird", +"Hath made his pendent bed and procreant cradle:", +"Where they most breed and haunt, I have observed,", +"The air is delicate.", +"Enter LADY MACBETH", +"", +"DUNCAN", +"See, see, our honour'd hostess!", +"The love that follows us sometime is our trouble,", +"Which still we thank as love. Herein I teach you", +"How you shall bid God 'ild us for your pains,", +"And thank us for your trouble.", +"LADY MACBETH", +"All our service", +"In every point twice done and then done double", +"Were poor and single business to contend", +"Against those honours deep and broad wherewith", +"Your majesty loads our house: for those of old,", +"And the late dignities heap'd up to them,", +"We rest your hermits.", +"DUNCAN", +"Where's the thane of Cawdor?", +"We coursed him at the heels, and had a purpose", +"To be his purveyor: but he rides well;", +"And his great love, sharp as his spur, hath holp him", +"To his home before us. Fair and noble hostess,", +"We are your guest to-night.", +"LADY MACBETH", +"Your servants ever", +"Have theirs, themselves and what is theirs, in compt,", +"To make their audit at your highness' pleasure,", +"Still to return your own.", +"DUNCAN", +"Give me your hand;", +"Conduct me to mine host: we love him highly,", +"And shall continue our graces towards him.", +"By your leave, hostess.", +"Exeunt", +"", +"SCENE VII. Macbeth's castle.", +"", +"Hautboys and torches. Enter a Sewer, and divers Servants with dishes and service, and pass over the stage. Then enter MACBETH", +"MACBETH", +"If it were done when 'tis done, then 'twere well", +"It were done quickly: if the assassination", +"Could trammel up the consequence, and catch", +"With his surcease success; that but this blow", +"Might be the be-all and the end-all here,", +"But here, upon this bank and shoal of time,", +"We'ld jump the life to come. But in these cases", +"We still have judgment here; that we but teach", +"Bloody instructions, which, being taught, return", +"To plague the inventor: this even-handed justice", +"Commends the ingredients of our poison'd chalice", +"To our own lips. He's here in double trust;", +"First, as I am his kinsman and his subject,", +"Strong both against the deed; then, as his host,", +"Who should against his murderer shut the door,", +"Not bear the knife myself. Besides, this Duncan", +"Hath borne his faculties so meek, hath been", +"So clear in his great office, that his virtues", +"Will plead like angels, trumpet-tongued, against", +"The deep damnation of his taking-off;", +"And pity, like a naked new-born babe,", +"Striding the blast, or heaven's cherubim, horsed", +"Upon the sightless couriers of the air,", +"Shall blow the horrid deed in every eye,", +"That tears shall drown the wind. I have no spur", +"To prick the sides of my intent, but only", +"Vaulting ambition, which o'erleaps itself", +"And falls on the other.", +"Enter LADY MACBETH", +"", +"How now! what news?", +"LADY MACBETH", +"He has almost supp'd: why have you left the chamber?", +"MACBETH", +"Hath he ask'd for me?", +"LADY MACBETH", +"Know you not he has?", +"MACBETH", +"We will proceed no further in this business:", +"He hath honour'd me of late; and I have bought", +"Golden opinions from all sorts of people,", +"Which would be worn now in their newest gloss,", +"Not cast aside so soon.", +"LADY MACBETH", +"Was the hope drunk", +"Wherein you dress'd yourself? hath it slept since?", +"And wakes it now, to look so green and pale", +"At what it did so freely? From this time", +"Such I account thy love. Art thou afeard", +"To be the same in thine own act and valour", +"As thou art in desire? Wouldst thou have that", +"Which thou esteem'st the ornament of life,", +"And live a coward in thine own esteem,", +"Letting 'I dare not' wait upon 'I would,'", +"Like the poor cat i' the adage?", +"MACBETH", +"Prithee, peace:", +"I dare do all that may become a man;", +"Who dares do more is none.", +"LADY MACBETH", +"What beast was't, then,", +"That made you break this enterprise to me?", +"When you durst do it, then you were a man;", +"And, to be more than what you were, you would", +"Be so much more the man. Nor time nor place", +"Did then adhere, and yet you would make both:", +"They have made themselves, and that their fitness now", +"Does unmake you. I have given suck, and know", +"How tender 'tis to love the babe that milks me:", +"I would, while it was smiling in my face,", +"Have pluck'd my nipple from his boneless gums,", +"And dash'd the brains out, had I so sworn as you", +"Have done to this.", +"MACBETH", +"If we should fail?", +"LADY MACBETH", +"We fail!", +"But screw your courage to the sticking-place,", +"And we'll not fail. When Duncan is asleep--", +"Whereto the rather shall his day's hard journey", +"Soundly invite him--his two chamberlains", +"Will I with wine and wassail so convince", +"That memory, the warder of the brain,", +"Shall be a fume, and the receipt of reason", +"A limbeck only: when in swinish sleep", +"Their drenched natures lie as in a death,", +"What cannot you and I perform upon", +"The unguarded Duncan? what not put upon", +"His spongy officers, who shall bear the guilt", +"Of our great quell?", +"MACBETH", +"Bring forth men-children only;", +"For thy undaunted mettle should compose", +"Nothing but males. Will it not be received,", +"When we have mark'd with blood those sleepy two", +"Of his own chamber and used their very daggers,", +"That they have done't?", +"LADY MACBETH", +"Who dares receive it other,", +"As we shall make our griefs and clamour roar", +"Upon his death?", +"MACBETH", +"I am settled, and bend up", +"Each corporal agent to this terrible feat.", +"Away, and mock the time with fairest show:", +"False face must hide what the false heart doth know.", +"Exeunt", +"", +"ACT II", +"", +"SCENE I. Court of Macbeth's castle.", +"", +"Enter BANQUO, and FLEANCE bearing a torch before him", +"BANQUO", +"How goes the night, boy?", +"FLEANCE", +"The moon is down; I have not heard the clock.", +"BANQUO", +"And she goes down at twelve.", +"FLEANCE", +"I take't, 'tis later, sir.", +"BANQUO", +"Hold, take my sword. There's husbandry in heaven;", +"Their candles are all out. Take thee that too.", +"A heavy summons lies like lead upon me,", +"And yet I would not sleep: merciful powers,", +"Restrain in me the cursed thoughts that nature", +"Gives way to in repose!", +"Enter MACBETH, and a Servant with a torch", +"", +"Give me my sword.", +"Who's there?", +"MACBETH", +"A friend.", +"BANQUO", +"What, sir, not yet at rest? The king's a-bed:", +"He hath been in unusual pleasure, and", +"Sent forth great largess to your offices.", +"This diamond he greets your wife withal,", +"By the name of most kind hostess; and shut up", +"In measureless content.", +"MACBETH", +"Being unprepared,", +"Our will became the servant to defect;", +"Which else should free have wrought.", +"BANQUO", +"All's well.", +"I dreamt last night of the three weird sisters:", +"To you they have show'd some truth.", +"MACBETH", +"I think not of them:", +"Yet, when we can entreat an hour to serve,", +"We would spend it in some words upon that business,", +"If you would grant the time.", +"BANQUO", +"At your kind'st leisure.", +"MACBETH", +"If you shall cleave to my consent, when 'tis,", +"It shall make honour for you.", +"BANQUO", +"So I lose none", +"In seeking to augment it, but still keep", +"My bosom franchised and allegiance clear,", +"I shall be counsell'd.", +"MACBETH", +"Good repose the while!", +"BANQUO", +"Thanks, sir: the like to you!", +"Exeunt BANQUO and FLEANCE", +"", +"MACBETH", +"Go bid thy mistress, when my drink is ready,", +"She strike upon the bell. Get thee to bed.", +"Exit Servant", +"", +"Is this a dagger which I see before me,", +"The handle toward my hand? Come, let me clutch thee.", +"I have thee not, and yet I see thee still.", +"Art thou not, fatal vision, sensible", +"To feeling as to sight? or art thou but", +"A dagger of the mind, a false creation,", +"Proceeding from the heat-oppressed brain?", +"I see thee yet, in form as palpable", +"As this which now I draw.", +"Thou marshall'st me the way that I was going;", +"And such an instrument I was to use.", +"Mine eyes are made the fools o' the other senses,", +"Or else worth all the rest; I see thee still,", +"And on thy blade and dudgeon gouts of blood,", +"Which was not so before. There's no such thing:", +"It is the bloody business which informs", +"Thus to mine eyes. Now o'er the one halfworld", +"Nature seems dead, and wicked dreams abuse", +"The curtain'd sleep; witchcraft celebrates", +"Pale Hecate's offerings, and wither'd murder,", +"Alarum'd by his sentinel, the wolf,", +"Whose howl's his watch, thus with his stealthy pace.", +"With Tarquin's ravishing strides, towards his design", +"Moves like a ghost. Thou sure and firm-set earth,", +"Hear not my steps, which way they walk, for fear", +"Thy very stones prate of my whereabout,", +"And take the present horror from the time,", +"Which now suits with it. Whiles I threat, he lives:", +"Words to the heat of deeds too cold breath gives.", +"A bell rings", +"", +"I go, and it is done; the bell invites me.", +"Hear it not, Duncan; for it is a knell", +"That summons thee to heaven or to hell.", +"Exit", +"", +"SCENE II. The same.", +"", +"Enter LADY MACBETH", +"LADY MACBETH", +"That which hath made them drunk hath made me bold;", +"What hath quench'd them hath given me fire.", +"Hark! Peace!", +"It was the owl that shriek'd, the fatal bellman,", +"Which gives the stern'st good-night. He is about it:", +"The doors are open; and the surfeited grooms", +"Do mock their charge with snores: I have drugg'd", +"their possets,", +"That death and nature do contend about them,", +"Whether they live or die.", +"MACBETH", +"[Within] Who's there? what, ho!", +"LADY MACBETH", +"Alack, I am afraid they have awaked,", +"And 'tis not done. The attempt and not the deed", +"Confounds us. Hark! I laid their daggers ready;", +"He could not miss 'em. Had he not resembled", +"My father as he slept, I had done't.", +"Enter MACBETH", +"", +"My husband!", +"MACBETH", +"I have done the deed. Didst thou not hear a noise?", +"LADY MACBETH", +"I heard the owl scream and the crickets cry.", +"Did not you speak?", +"MACBETH", +"When?", +"LADY MACBETH", +"Now.", +"MACBETH", +"As I descended?", +"LADY MACBETH", +"Ay.", +"MACBETH", +"Hark!", +"Who lies i' the second chamber?", +"LADY MACBETH", +"Donalbain.", +"MACBETH", +"This is a sorry sight.", +"Looking on his hands", +"", +"LADY MACBETH", +"A foolish thought, to say a sorry sight.", +"MACBETH", +"There's one did laugh in's sleep, and one cried", +"'Murder!'", +"That they did wake each other: I stood and heard them:", +"But they did say their prayers, and address'd them", +"Again to sleep.", +"LADY MACBETH", +"There are two lodged together.", +"MACBETH", +"One cried 'God bless us!' and 'Amen' the other;", +"As they had seen me with these hangman's hands.", +"Listening their fear, I could not say 'Amen,'", +"When they did say 'God bless us!'", +"LADY MACBETH", +"Consider it not so deeply.", +"MACBETH", +"But wherefore could not I pronounce 'Amen'?", +"I had most need of blessing, and 'Amen'", +"Stuck in my throat.", +"LADY MACBETH", +"These deeds must not be thought", +"After these ways; so, it will make us mad.", +"MACBETH", +"Methought I heard a voice cry 'Sleep no more!", +"Macbeth does murder sleep', the innocent sleep,", +"Sleep that knits up the ravell'd sleeve of care,", +"The death of each day's life, sore labour's bath,", +"Balm of hurt minds, great nature's second course,", +"Chief nourisher in life's feast,--", +"LADY MACBETH", +"What do you mean?", +"MACBETH", +"Still it cried 'Sleep no more!' to all the house:", +"'Glamis hath murder'd sleep, and therefore Cawdor", +"Shall sleep no more; Macbeth shall sleep no more.'", +"LADY MACBETH", +"Who was it that thus cried? Why, worthy thane,", +"You do unbend your noble strength, to think", +"So brainsickly of things. Go get some water,", +"And wash this filthy witness from your hand.", +"Why did you bring these daggers from the place?", +"They must lie there: go carry them; and smear", +"The sleepy grooms with blood.", +"MACBETH", +"I'll go no more:", +"I am afraid to think what I have done;", +"Look on't again I dare not.", +"LADY MACBETH", +"Infirm of purpose!", +"Give me the daggers: the sleeping and the dead", +"Are but as pictures: 'tis the eye of childhood", +"That fears a painted devil. If he do bleed,", +"I'll gild the faces of the grooms withal;", +"For it must seem their guilt.", +"Exit. Knocking within", +"", +"MACBETH", +"Whence is that knocking?", +"How is't with me, when every noise appals me?", +"What hands are here? ha! they pluck out mine eyes.", +"Will all great Neptune's ocean wash this blood", +"Clean from my hand? No, this my hand will rather", +"The multitudinous seas in incarnadine,", +"Making the green one red.", +"Re-enter LADY MACBETH", +"", +"LADY MACBETH", +"My hands are of your colour; but I shame", +"To wear a heart so white.", +"Knocking within", +"", +"I hear a knocking", +"At the south entry: retire we to our chamber;", +"A little water clears us of this deed:", +"How easy is it, then! Your constancy", +"Hath left you unattended.", +"Knocking within", +"", +"Hark! more knocking.", +"Get on your nightgown, lest occasion call us,", +"And show us to be watchers. Be not lost", +"So poorly in your thoughts.", +"MACBETH", +"To know my deed, 'twere best not know myself.", +"Knocking within", +"", +"Wake Duncan with thy knocking! I would thou couldst!", +"Exeunt", +"", +"SCENE III. The same.", +"", +"Knocking within. Enter a Porter", +"Porter", +"Here's a knocking indeed! If a", +"man were porter of hell-gate, he should have", +"old turning the key.", +"Knocking within", +"", +"Knock,", +"knock, knock! Who's there, i' the name of", +"Beelzebub? Here's a farmer, that hanged", +"himself on the expectation of plenty: come in", +"time; have napkins enow about you; here", +"you'll sweat for't.", +"Knocking within", +"", +"Knock,", +"knock! Who's there, in the other devil's", +"name? Faith, here's an equivocator, that could", +"swear in both the scales against either scale;", +"who committed treason enough for God's sake,", +"yet could not equivocate to heaven: O, come", +"in, equivocator.", +"Knocking within", +"", +"Knock,", +"knock, knock! Who's there? Faith, here's an", +"English tailor come hither, for stealing out of", +"a French hose: come in, tailor; here you may", +"roast your goose.", +"Knocking within", +"", +"Knock,", +"knock; never at quiet! What are you? But", +"this place is too cold for hell. I'll devil-porter", +"it no further: I had thought to have let in", +"some of all professions that go the primrose", +"way to the everlasting bonfire.", +"Knocking within", +"", +"Anon, anon! I pray you, remember the porter.", +"Opens the gate", +"", +"Enter MACDUFF and LENNOX", +"", +"MACDUFF", +"Was it so late, friend, ere you went to bed,", +"That you do lie so late?", +"Porter", +"'Faith sir, we were carousing till the", +"second cock: and drink, sir, is a great", +"provoker of three things.", +"MACDUFF", +"What three things does drink especially provoke?", +"Porter", +"Marry, sir, nose-painting, sleep, and", +"urine. Lechery, sir, it provokes, and unprovokes;", +"it provokes the desire, but it takes", +"away the performance: therefore, much drink", +"may be said to be an equivocator with lechery:", +"it makes him, and it mars him; it sets", +"him on, and it takes him off; it persuades him,", +"and disheartens him; makes him stand to, and", +"not stand to; in conclusion, equivocates him", +"in a sleep, and, giving him the lie, leaves him.", +"MACDUFF", +"I believe drink gave thee the lie last night.", +"Porter", +"That it did, sir, i' the very throat on", +"me: but I requited him for his lie; and, I", +"think, being too strong for him, though he took", +"up my legs sometime, yet I made a shift to cast", +"him.", +"MACDUFF", +"Is thy master stirring?", +"Enter MACBETH", +"", +"Our knocking has awaked him; here he comes.", +"LENNOX", +"Good morrow, noble sir.", +"MACBETH", +"Good morrow, both.", +"MACDUFF", +"Is the king stirring, worthy thane?", +"MACBETH", +"Not yet.", +"MACDUFF", +"He did command me to call timely on him:", +"I have almost slipp'd the hour.", +"MACBETH", +"I'll bring you to him.", +"MACDUFF", +"I know this is a joyful trouble to you;", +"But yet 'tis one.", +"MACBETH", +"The labour we delight in physics pain.", +"This is the door.", +"MACDUFF", +"I'll make so bold to call,", +"For 'tis my limited service.", +"Exit", +"", +"LENNOX", +"Goes the king hence to-day?", +"MACBETH", +"He does: he did appoint so.", +"LENNOX", +"The night has been unruly: where we lay,", +"Our chimneys were blown down; and, as they say,", +"Lamentings heard i' the air; strange screams of death,", +"And prophesying with accents terrible", +"Of dire combustion and confused events", +"New hatch'd to the woeful time: the obscure bird", +"Clamour'd the livelong night: some say, the earth", +"Was feverous and did shake.", +"MACBETH", +"'Twas a rough night.", +"LENNOX", +"My young remembrance cannot parallel", +"A fellow to it.", +"Re-enter MACDUFF", +"", +"MACDUFF", +"O horror, horror, horror! Tongue nor heart", +"Cannot conceive nor name thee!", +"MACBETH LENNOX", +"What's the matter.", +"MACDUFF", +"Confusion now hath made his masterpiece!", +"Most sacrilegious murder hath broke ope", +"The Lord's anointed temple, and stole thence", +"The life o' the building!", +"MACBETH", +"What is 't you say? the life?", +"LENNOX", +"Mean you his majesty?", +"MACDUFF", +"Approach the chamber, and destroy your sight", +"With a new Gorgon: do not bid me speak;", +"See, and then speak yourselves.", +"Exeunt MACBETH and LENNOX", +"", +"Awake, awake!", +"Ring the alarum-bell. Murder and treason!", +"Banquo and Donalbain! Malcolm! awake!", +"Shake off this downy sleep, death's counterfeit,", +"And look on death itself! up, up, and see", +"The great doom's image! Malcolm! Banquo!", +"As from your graves rise up, and walk like sprites,", +"To countenance this horror! Ring the bell.", +"Bell rings", +"", +"Enter LADY MACBETH", +"", +"LADY MACBETH", +"What's the business,", +"That such a hideous trumpet calls to parley", +"The sleepers of the house? speak, speak!", +"MACDUFF", +"O gentle lady,", +"'Tis not for you to hear what I can speak:", +"The repetition, in a woman's ear,", +"Would murder as it fell.", +"Enter BANQUO", +"", +"O Banquo, Banquo,", +"Our royal master 's murder'd!", +"LADY MACBETH", +"Woe, alas!", +"What, in our house?", +"BANQUO", +"Too cruel any where.", +"Dear Duff, I prithee, contradict thyself,", +"And say it is not so.", +"Re-enter MACBETH and LENNOX, with ROSS", +"", +"MACBETH", +"Had I but died an hour before this chance,", +"I had lived a blessed time; for, from this instant,", +"There 's nothing serious in mortality:", +"All is but toys: renown and grace is dead;", +"The wine of life is drawn, and the mere lees", +"Is left this vault to brag of.", +"Enter MALCOLM and DONALBAIN", +"", +"DONALBAIN", +"What is amiss?", +"MACBETH", +"You are, and do not know't:", +"The spring, the head, the fountain of your blood", +"Is stopp'd; the very source of it is stopp'd.", +"MACDUFF", +"Your royal father 's murder'd.", +"MALCOLM", +"O, by whom?", +"LENNOX", +"Those of his chamber, as it seem'd, had done 't:", +"Their hands and faces were an badged with blood;", +"So were their daggers, which unwiped we found", +"Upon their pillows:", +"They stared, and were distracted; no man's life", +"Was to be trusted with them.", +"MACBETH", +"O, yet I do repent me of my fury,", +"That I did kill them.", +"MACDUFF", +"Wherefore did you so?", +"MACBETH", +"Who can be wise, amazed, temperate and furious,", +"Loyal and neutral, in a moment? No man:", +"The expedition my violent love", +"Outrun the pauser, reason. Here lay Duncan,", +"His silver skin laced with his golden blood;", +"And his gash'd stabs look'd like a breach in nature", +"For ruin's wasteful entrance: there, the murderers,", +"Steep'd in the colours of their trade, their daggers", +"Unmannerly breech'd with gore: who could refrain,", +"That had a heart to love, and in that heart", +"Courage to make 's love kno wn?", +"LADY MACBETH", +"Help me hence, ho!", +"MACDUFF", +"Look to the lady.", +"MALCOLM", +"[Aside to DONALBAIN] Why do we hold our tongues,", +"That most may claim this argument for ours?", +"DONALBAIN", +"[Aside to MALCOLM] What should be spoken here,", +"where our fate,", +"Hid in an auger-hole, may rush, and seize us?", +"Let 's away;", +"Our tears are not yet brew'd.", +"MALCOLM", +"[Aside to DONALBAIN] Nor our strong sorrow", +"Upon the foot of motion.", +"BANQUO", +"Look to the lady:", +"LADY MACBETH is carried out", +"", +"And when we have our naked frailties hid,", +"That suffer in exposure, let us meet,", +"And question this most bloody piece of work,", +"To know it further. Fears and scruples shake us:", +"In the great hand of God I stand; and thence", +"Against the undivulged pretence I fight", +"Of treasonous malice.", +"MACDUFF", +"And so do I.", +"ALL", +"So all.", +"MACBETH", +"Let's briefly put on manly readiness,", +"And meet i' the hall together.", +"ALL", +"Well contented.", +"Exeunt all but Malcolm and Donalbain.", +"", +"MALCOLM", +"What will you do? Let's not consort with them:", +"To show an unfelt sorrow is an office", +"Which the false man does easy. I'll to England.", +"DONALBAIN", +"To Ireland, I; our separated fortune", +"Shall keep us both the safer: where we are,", +"There's daggers in men's smiles: the near in blood,", +"The nearer bloody.", +"MALCOLM", +"This murderous shaft that's shot", +"Hath not yet lighted, and our safest way", +"Is to avoid the aim. Therefore, to horse;", +"And let us not be dainty of leave-taking,", +"But shift away: there's warrant in that theft", +"Which steals itself, when there's no mercy left.", +"Exeunt", +"", +"SCENE IV. Outside Macbeth's castle.", +"", +"Enter ROSS and an old Man", +"Old Man", +"Threescore and ten I can remember well:", +"Within the volume of which time I have seen", +"Hours dreadful and things strange; but this sore night", +"Hath trifled former knowings.", +"ROSS", +"Ah, good father,", +"Thou seest, the heavens, as troubled with man's act,", +"Threaten his bloody stage: by the clock, 'tis day,", +"And yet dark night strangles the travelling lamp:", +"Is't night's predominance, or the day's shame,", +"That darkness does the face of earth entomb,", +"When living light should kiss it?", +"Old Man", +"'Tis unnatural,", +"Even like the deed that's done. On Tuesday last,", +"A falcon, towering in her pride of place,", +"Was by a mousing owl hawk'd at and kill'd.", +"ROSS", +"And Duncan's horses--a thing most strange and certain--", +"Beauteous and swift, the minions of their race,", +"Turn'd wild in nature, broke their stalls, flung out,", +"Contending 'gainst obedience, as they would make", +"War with mankind.", +"Old Man", +"'Tis said they eat each other.", +"ROSS", +"They did so, to the amazement of mine eyes", +"That look'd upon't. Here comes the good Macduff.", +"Enter MACDUFF", +"", +"How goes the world, sir, now?", +"MACDUFF", +"Why, see you not?", +"ROSS", +"Is't known who did this more than bloody deed?", +"MACDUFF", +"Those that Macbeth hath slain.", +"ROSS", +"Alas, the day!", +"What good could they pretend?", +"MACDUFF", +"They were suborn'd:", +"Malcolm and Donalbain, the king's two sons,", +"Are stol'n away and fled; which puts upon them", +"Suspicion of the deed.", +"ROSS", +"'Gainst nature still!", +"Thriftless ambition, that wilt ravin up", +"Thine own life's means! Then 'tis most like", +"The sovereignty will fall upon Macbeth.", +"MACDUFF", +"He is already named, and gone to Scone", +"To be invested.", +"ROSS", +"Where is Duncan's body?", +"MACDUFF", +"Carried to Colmekill,", +"The sacred storehouse of his predecessors,", +"And guardian of their bones.", +"ROSS", +"Will you to Scone?", +"MACDUFF", +"No, cousin, I'll to Fife.", +"ROSS", +"Well, I will thither.", +"MACDUFF", +"Well, may you see things well done there: adieu!", +"Lest our old robes sit easier than our new!", +"ROSS", +"Farewell, father.", +"Old Man", +"God's benison go with you; and with those", +"That would make good of bad, and friends of foes!", +"Exeunt", +"", +"ACT III", +"", +"SCENE I. Forres. The palace.", +"", +"Enter BANQUO", +"BANQUO", +"Thou hast it now: king, Cawdor, Glamis, all,", +"As the weird women promised, and, I fear,", +"Thou play'dst most foully for't: yet it was said", +"It should not stand in thy posterity,", +"But that myself should be the root and father", +"Of many kings. If there come truth from them--", +"As upon thee, Macbeth, their speeches shine--", +"Why, by the verities on thee made good,", +"May they not be my oracles as well,", +"And set me up in hope? But hush! no more.", +"Sennet sounded. Enter MACBETH, as king, LADY MACBETH, as queen, LENNOX, ROSS, Lords, Ladies, and Attendants", +"", +"MACBETH", +"Here's our chief guest.", +"LADY MACBETH", +"If he had been forgotten,", +"It had been as a gap in our great feast,", +"And all-thing unbecoming.", +"MACBETH", +"To-night we hold a solemn supper sir,", +"And I'll request your presence.", +"BANQUO", +"Let your highness", +"Command upon me; to the which my duties", +"Are with a most indissoluble tie", +"For ever knit.", +"MACBETH", +"Ride you this afternoon?", +"BANQUO", +"Ay, my good lord.", +"MACBETH", +"We should have else desired your good advice,", +"Which still hath been both grave and prosperous,", +"In this day's council; but we'll take to-morrow.", +"Is't far you ride?", +"BANQUO", +"As far, my lord, as will fill up the time", +"'Twixt this and supper: go not my horse the better,", +"I must become a borrower of the night", +"For a dark hour or twain.", +"MACBETH", +"Fail not our feast.", +"BANQUO", +"My lord, I will not.", +"MACBETH", +"We hear, our bloody cousins are bestow'd", +"In England and in Ireland, not confessing", +"Their cruel parricide, filling their hearers", +"With strange invention: but of that to-morrow,", +"When therewithal we shall have cause of state", +"Craving us jointly. Hie you to horse: adieu,", +"Till you return at night. Goes Fleance with you?", +"BANQUO", +"Ay, my good lord: our time does call upon 's.", +"MACBETH", +"I wish your horses swift and sure of foot;", +"And so I do commend you to their backs. Farewell.", +"Exit BANQUO", +"", +"Let every man be master of his time", +"Till seven at night: to make society", +"The sweeter welcome, we will keep ourself", +"Till supper-time alone: while then, God be with you!", +"Exeunt all but MACBETH, and an attendant", +"", +"Sirrah, a word with you: attend those men", +"Our pleasure?", +"ATTENDANT", +"They are, my lord, without the palace gate.", +"MACBETH", +"Bring them before us.", +"Exit Attendant", +"", +"To be thus is nothing;", +"But to be safely thus.--Our fears in Banquo", +"Stick deep; and in his royalty of nature", +"Reigns that which would be fear'd: 'tis much he dares;", +"And, to that dauntless temper of his mind,", +"He hath a wisdom that doth guide his valour", +"To act in safety. There is none but he", +"Whose being I do fear: and, under him,", +"My Genius is rebuked; as, it is said,", +"Mark Antony's was by Caesar. He chid the sisters", +"When first they put the name of king upon me,", +"And bade them speak to him: then prophet-like", +"They hail'd him father to a line of kings:", +"Upon my head they placed a fruitless crown,", +"And put a barren sceptre in my gripe,", +"Thence to be wrench'd with an unlineal hand,", +"No son of mine succeeding. If 't be so,", +"For Banquo's issue have I filed my mind;", +"For them the gracious Duncan have I murder'd;", +"Put rancours in the vessel of my peace", +"Only for them; and mine eternal jewel", +"Given to the common enemy of man,", +"To make them kings, the seed of Banquo kings!", +"Rather than so, come fate into the list.", +"And champion me to the utterance! Who's there!", +"Re-enter Attendant, with two Murderers", +"", +"Now go to the door, and stay there till we call.", +"Exit Attendant", +"", +"Was it not yesterday we spoke together?", +"First Murderer", +"It was, so please your highness.", +"MACBETH", +"Well then, now", +"Have you consider'd of my speeches? Know", +"That it was he in the times past which held you", +"So under fortune, which you thought had been", +"Our innocent self: this I made good to you", +"In our last conference, pass'd in probation with you,", +"How you were borne in hand, how cross'd,", +"the instruments,", +"Who wrought with them, and all things else that might", +"To half a soul and to a notion crazed", +"Say 'Thus did Banquo.'", +"First Murderer", +"You made it known to us.", +"MACBETH", +"I did so, and went further, which is now", +"Our point of second meeting. Do you find", +"Your patience so predominant in your nature", +"That you can let this go? Are you so gospell'd", +"To pray for this good man and for his issue,", +"Whose heavy hand hath bow'd you to the grave", +"And beggar'd yours for ever?", +"First Murderer", +"We are men, my liege.", +"MACBETH", +"Ay, in the catalogue ye go for men;", +"As hounds and greyhounds, mongrels, spaniels, curs,", +"Shoughs, water-rugs and demi-wolves, are clept", +"All by the name of dogs: the valued file", +"Distinguishes the swift, the slow, the subtle,", +"The housekeeper, the hunter, every one", +"According to the gift which bounteous nature", +"Hath in him closed; whereby he does receive", +"Particular addition. from the bill", +"That writes them all alike: and so of men.", +"Now, if you have a station in the file,", +"Not i' the worst rank of manhood, say 't;", +"And I will put that business in your bosoms,", +"Whose execution takes your enemy off,", +"Grapples you to the heart and love of us,", +"Who wear our health but sickly in his life,", +"Which in his death were perfect.", +"Second Murderer", +"I am one, my liege,", +"Whom the vile blows and buffets of the world", +"Have so incensed that I am reckless what", +"I do to spite the world.", +"First Murderer", +"And I another", +"So weary with disasters, tugg'd with fortune,", +"That I would set my lie on any chance,", +"To mend it, or be rid on't.", +"MACBETH", +"Both of you", +"Know Banquo was your enemy.", +"Both Murderers", +"True, my lord.", +"MACBETH", +"So is he mine; and in such bloody distance,", +"That every minute of his being thrusts", +"Against my near'st of life: and though I could", +"With barefaced power sweep him from my sight", +"And bid my will avouch it, yet I must not,", +"For certain friends that are both his and mine,", +"Whose loves I may not drop, but wail his fall", +"Who I myself struck down; and thence it is,", +"That I to your assistance do make love,", +"Masking the business from the common eye", +"For sundry weighty reasons.", +"Second Murderer", +"We shall, my lord,", +"Perform what you command us.", +"First Murderer", +"Though our lives--", +"MACBETH", +"Your spirits shine through you. Within this hour at most", +"I will advise you where to plant yourselves;", +"Acquaint you with the perfect spy o' the time,", +"The moment on't; for't must be done to-night,", +"And something from the palace; always thought", +"That I require a clearness: and with him--", +"To leave no rubs nor botches in the work--", +"Fleance his son, that keeps him company,", +"Whose absence is no less material to me", +"Than is his father's, must embrace the fate", +"Of that dark hour. Resolve yourselves apart:", +"I'll come to you anon.", +"Both Murderers", +"We are resolved, my lord.", +"MACBETH", +"I'll call upon you straight: abide within.", +"Exeunt Murderers", +"", +"It is concluded. Banquo, thy soul's flight,", +"If it find heaven, must find it out to-night.", +"Exit", +"", +"SCENE II. The palace.", +"", +"Enter LADY MACBETH and a Servant", +"LADY MACBETH", +"Is Banquo gone from court?", +"Servant", +"Ay, madam, but returns again to-night.", +"LADY MACBETH", +"Say to the king, I would attend his leisure", +"For a few words.", +"Servant", +"Madam, I will.", +"Exit", +"", +"LADY MACBETH", +"Nought's had, all's spent,", +"Where our desire is got without content:", +"'Tis safer to be that which we destroy", +"Than by destruction dwell in doubtful joy.", +"Enter MACBETH", +"", +"How now, my lord! why do you keep alone,", +"Of sorriest fancies your companions making,", +"Using those thoughts which should indeed have died", +"With them they think on? Things without all remedy", +"Should be without regard: what's done is done.", +"MACBETH", +"We have scotch'd the snake, not kill'd it:", +"She'll close and be herself, whilst our poor malice", +"Remains in danger of her former tooth.", +"But let the frame of things disjoint, both the", +"worlds suffer,", +"Ere we will eat our meal in fear and sleep", +"In the affliction of these terrible dreams", +"That shake us nightly: better be with the dead,", +"Whom we, to gain our peace, have sent to peace,", +"Than on the torture of the mind to lie", +"In restless ecstasy. Duncan is in his grave;", +"After life's fitful fever he sleeps well;", +"Treason has done his worst: nor steel, nor poison,", +"Malice domestic, foreign levy, nothing,", +"Can touch him further.", +"LADY MACBETH", +"Come on;", +"Gentle my lord, sleek o'er your rugged looks;", +"Be bright and jovial among your guests to-night.", +"MACBETH", +"So shall I, love; and so, I pray, be you:", +"Let your remembrance apply to Banquo;", +"Present him eminence, both with eye and tongue:", +"Unsafe the while, that we", +"Must lave our honours in these flattering streams,", +"And make our faces vizards to our hearts,", +"Disguising what they are.", +"LADY MACBETH", +"You must leave this.", +"MACBETH", +"O, full of scorpions is my mind, dear wife!", +"Thou know'st that Banquo, and his Fleance, lives.", +"LADY MACBETH", +"But in them nature's copy's not eterne.", +"MACBETH", +"There's comfort yet; they are assailable;", +"Then be thou jocund: ere the bat hath flown", +"His cloister'd flight, ere to black Hecate's summons", +"The shard-borne beetle with his drowsy hums", +"Hath rung night's yawning peal, there shall be done", +"A deed of dreadful note.", +"LADY MACBETH", +"What's to be done?", +"MACBETH", +"Be innocent of the knowledge, dearest chuck,", +"Till thou applaud the deed. Come, seeling night,", +"Scarf up the tender eye of pitiful day;", +"And with thy bloody and invisible hand", +"Cancel and tear to pieces that great bond", +"Which keeps me pale! Light thickens; and the crow", +"Makes wing to the rooky wood:", +"Good things of day begin to droop and drowse;", +"While night's black agents to their preys do rouse.", +"Thou marvell'st at my words: but hold thee still;", +"Things bad begun make strong themselves by ill.", +"So, prithee, go with me.", +"Exeunt", +"", +"SCENE III. A park near the palace.", +"", +"Enter three Murderers", +"First Murderer", +"But who did bid thee join with us?", +"Third Murderer", +"Macbeth.", +"Second Murderer", +"He needs not our mistrust, since he delivers", +"Our offices and what we have to do", +"To the direction just.", +"First Murderer", +"Then stand with us.", +"The west yet glimmers with some streaks of day:", +"Now spurs the lated traveller apace", +"To gain the timely inn; and near approaches", +"The subject of our watch.", +"Third Murderer", +"Hark! I hear horses.", +"BANQUO", +"[Within] Give us a light there, ho!", +"Second Murderer", +"Then 'tis he: the rest", +"That are within the note of expectation", +"Already are i' the court.", +"First Murderer", +"His horses go about.", +"Third Murderer", +"Almost a mile: but he does usually,", +"So all men do, from hence to the palace gate", +"Make it their walk.", +"Second Murderer", +"A light, a light!", +"Enter BANQUO, and FLEANCE with a torch", +"", +"Third Murderer", +"'Tis he.", +"First Murderer", +"Stand to't.", +"BANQUO", +"It will be rain to-night.", +"First Murderer", +"Let it come down.", +"They set upon BANQUO", +"", +"BANQUO", +"O, treachery! Fly, good Fleance, fly, fly, fly!", +"Thou mayst revenge. O slave!", +"Dies. FLEANCE escapes", +"", +"Third Murderer", +"Who did strike out the light?", +"First Murderer", +"Wast not the way?", +"Third Murderer", +"There's but one down; the son is fled.", +"Second Murderer", +"We have lost", +"Best half of our affair.", +"First Murderer", +"Well, let's away, and say how much is done.", +"Exeunt", +"", +"SCENE IV. The same. Hall in the palace.", +"", +"A banquet prepared. Enter MACBETH, LADY MACBETH, ROSS, LENNOX, Lords, and Attendants", +"MACBETH", +"You know your own degrees; sit down: at first", +"And last the hearty welcome.", +"Lords", +"Thanks to your majesty.", +"MACBETH", +"Ourself will mingle with society,", +"And play the humble host.", +"Our hostess keeps her state, but in best time", +"We will require her welcome.", +"LADY MACBETH", +"Pronounce it for me, sir, to all our friends;", +"For my heart speaks they are welcome.", +"First Murderer appears at the door", +"", +"MACBETH", +"See, they encounter thee with their hearts' thanks.", +"Both sides are even: here I'll sit i' the midst:", +"Be large in mirth; anon we'll drink a measure", +"The table round.", +"Approaching the door", +"", +"There's blood on thy face.", +"First Murderer", +"'Tis Banquo's then.", +"MACBETH", +"'Tis better thee without than he within.", +"Is he dispatch'd?", +"First Murderer", +"My lord, his throat is cut; that I did for him.", +"MACBETH", +"Thou art the best o' the cut-throats: yet he's good", +"That did the like for Fleance: if thou didst it,", +"Thou art the nonpareil.", +"First Murderer", +"Most royal sir,", +"Fleance is 'scaped.", +"MACBETH", +"Then comes my fit again: I had else been perfect,", +"Whole as the marble, founded as the rock,", +"As broad and general as the casing air:", +"But now I am cabin'd, cribb'd, confined, bound in", +"To saucy doubts and fears. But Banquo's safe?", +"First Murderer", +"Ay, my good lord: safe in a ditch he bides,", +"With twenty trenched gashes on his head;", +"The least a death to nature.", +"MACBETH", +"Thanks for that:", +"There the grown serpent lies; the worm that's fled", +"Hath nature that in time will venom breed,", +"No teeth for the present. Get thee gone: to-morrow", +"We'll hear, ourselves, again.", +"Exit Murderer", +"", +"LADY MACBETH", +"My royal lord,", +"You do not give the cheer: the feast is sold", +"That is not often vouch'd, while 'tis a-making,", +"'Tis given with welcome: to feed were best at home;", +"From thence the sauce to meat is ceremony;", +"Meeting were bare without it.", +"MACBETH", +"Sweet remembrancer!", +"Now, good digestion wait on appetite,", +"And health on both!", +"LENNOX", +"May't please your highness sit.", +"The GHOST OF BANQUO enters, and sits in MACBETH's place", +"", +"MACBETH", +"Here had we now our country's honour roof'd,", +"Were the graced person of our Banquo present;", +"Who may I rather challenge for unkindness", +"Than pity for mischance!", +"ROSS", +"His absence, sir,", +"Lays blame upon his promise. Please't your highness", +"To grace us with your royal company.", +"MACBETH", +"The table's full.", +"LENNOX", +"Here is a place reserved, sir.", +"MACBETH", +"Where?", +"LENNOX", +"Here, my good lord. What is't that moves your highness?", +"MACBETH", +"Which of you have done this?", +"Lords", +"What, my good lord?", +"MACBETH", +"Thou canst not say I did it: never shake", +"Thy gory locks at me.", +"ROSS", +"Gentlemen, rise: his highness is not well.", +"LADY MACBETH", +"Sit, worthy friends: my lord is often thus,", +"And hath been from his youth: pray you, keep seat;", +"The fit is momentary; upon a thought", +"He will again be well: if much you note him,", +"You shall offend him and extend his passion:", +"Feed, and regard him not. Are you a man?", +"MACBETH", +"Ay, and a bold one, that dare look on that", +"Which might appal the devil.", +"LADY MACBETH", +"O proper stuff!", +"This is the very painting of your fear:", +"This is the air-drawn dagger which, you said,", +"Led you to Duncan. O, these flaws and starts,", +"Impostors to true fear, would well become", +"A woman's story at a winter's fire,", +"Authorized by her grandam. Shame itself!", +"Why do you make such faces? When all's done,", +"You look but on a stool.", +"MACBETH", +"Prithee, see there! behold! look! lo!", +"how say you?", +"Why, what care I? If thou canst nod, speak too.", +"If charnel-houses and our graves must send", +"Those that we bury back, our monuments", +"Shall be the maws of kites.", +"GHOST OF BANQUO vanishes", +"", +"LADY MACBETH", +"What, quite unmann'd in folly?", +"MACBETH", +"If I stand here, I saw him.", +"LADY MACBETH", +"Fie, for shame!", +"MACBETH", +"Blood hath been shed ere now, i' the olden time,", +"Ere human statute purged the gentle weal;", +"Ay, and since too, murders have been perform'd", +"Too terrible for the ear: the times have been,", +"That, when the brains were out, the man would die,", +"And there an end; but now they rise again,", +"With twenty mortal murders on their crowns,", +"And push us from our stools: this is more strange", +"Than such a murder is.", +"LADY MACBETH", +"My worthy lord,", +"Your noble friends do lack you.", +"MACBETH", +"I do forget.", +"Do not muse at me, my most worthy friends,", +"I have a strange infirmity, which is nothing", +"To those that know me. Come, love and health to all;", +"Then I'll sit down. Give me some wine; fill full.", +"I drink to the general joy o' the whole table,", +"And to our dear friend Banquo, whom we miss;", +"Would he were here! to all, and him, we thirst,", +"And all to all.", +"Lords", +"Our duties, and the pledge.", +"Re-enter GHOST OF BANQUO", +"", +"MACBETH", +"Avaunt! and quit my sight! let the earth hide thee!", +"Thy bones are marrowless, thy blood is cold;", +"Thou hast no speculation in those eyes", +"Which thou dost glare with!", +"LADY MACBETH", +"Think of this, good peers,", +"But as a thing of custom: 'tis no other;", +"Only it spoils the pleasure of the time.", +"MACBETH", +"What man dare, I dare:", +"Approach thou like the rugged Russian bear,", +"The arm'd rhinoceros, or the Hyrcan tiger;", +"Take any shape but that, and my firm nerves", +"Shall never tremble: or be alive again,", +"And dare me to the desert with thy sword;", +"If trembling I inhabit then, protest me", +"The baby of a girl. Hence, horrible shadow!", +"Unreal mockery, hence!", +"GHOST OF BANQUO vanishes", +"", +"Why, so: being gone,", +"I am a man again. Pray you, sit still.", +"LADY MACBETH", +"You have displaced the mirth, broke the good meeting,", +"With most admired disorder.", +"MACBETH", +"Can such things be,", +"And overcome us like a summer's cloud,", +"Without our special wonder? You make me strange", +"Even to the disposition that I owe,", +"When now I think you can behold such sights,", +"And keep the natural ruby of your cheeks,", +"When mine is blanched with fear.", +"ROSS", +"What sights, my lord?", +"LADY MACBETH", +"I pray you, speak not; he grows worse and worse;", +"Question enrages him. At once, good night:", +"Stand not upon the order of your going,", +"But go at once.", +"LENNOX", +"Good night; and better health", +"Attend his majesty!", +"LADY MACBETH", +"A kind good night to all!", +"Exeunt all but MACBETH and LADY MACBETH", +"", +"MACBETH", +"It will have blood; they say, blood will have blood:", +"Stones have been known to move and trees to speak;", +"Augurs and understood relations have", +"By magot-pies and choughs and rooks brought forth", +"The secret'st man of blood. What is the night?", +"LADY MACBETH", +"Almost at odds with morning, which is which.", +"MACBETH", +"How say'st thou, that Macduff denies his person", +"At our great bidding?", +"LADY MACBETH", +"Did you send to him, sir?", +"MACBETH", +"I hear it by the way; but I will send:", +"There's not a one of them but in his house", +"I keep a servant fee'd. I will to-morrow,", +"And betimes I will, to the weird sisters:", +"More shall they speak; for now I am bent to know,", +"By the worst means, the worst. For mine own good,", +"All causes shall give way: I am in blood", +"Stepp'd in so far that, should I wade no more,", +"Returning were as tedious as go o'er:", +"Strange things I have in head, that will to hand;", +"Which must be acted ere they may be scann'd.", +"LADY MACBETH", +"You lack the season of all natures, sleep.", +"MACBETH", +"Come, we'll to sleep. My strange and self-abuse", +"Is the initiate fear that wants hard use:", +"We are yet but young in deed.", +"Exeunt", +"", +"SCENE V. A Heath.", +"", +"Thunder. Enter the three Witches meeting HECATE", +"First Witch", +"Why, how now, Hecate! you look angerly.", +"HECATE", +"Have I not reason, beldams as you are,", +"Saucy and overbold? How did you dare", +"To trade and traffic with Macbeth", +"In riddles and affairs of death;", +"And I, the mistress of your charms,", +"The close contriver of all harms,", +"Was never call'd to bear my part,", +"Or show the glory of our art?", +"And, which is worse, all you have done", +"Hath been but for a wayward son,", +"Spiteful and wrathful, who, as others do,", +"Loves for his own ends, not for you.", +"But make amends now: get you gone,", +"And at the pit of Acheron", +"Meet me i' the morning: thither he", +"Will come to know his destiny:", +"Your vessels and your spells provide,", +"Your charms and every thing beside.", +"I am for the air; this night I'll spend", +"Unto a dismal and a fatal end:", +"Great business must be wrought ere noon:", +"Upon the corner of the moon", +"There hangs a vaporous drop profound;", +"I'll catch it ere it come to ground:", +"And that distill'd by magic sleights", +"Shall raise such artificial sprites", +"As by the strength of their illusion", +"Shall draw him on to his confusion:", +"He shall spurn fate, scorn death, and bear", +"He hopes 'bove wisdom, grace and fear:", +"And you all know, security", +"Is mortals' chiefest enemy.", +"Music and a song within: 'Come away, come away,' & c", +"", +"Hark! I am call'd; my little spirit, see,", +"Sits in a foggy cloud, and stays for me.", +"Exit", +"", +"First Witch", +"Come, let's make haste; she'll soon be back again.", +"Exeunt", +"", +"SCENE VI. Forres. The palace.", +"", +"Enter LENNOX and another Lord", +"LENNOX", +"My former speeches have but hit your thoughts,", +"Which can interpret further: only, I say,", +"Things have been strangely borne. The", +"gracious Duncan", +"Was pitied of Macbeth: marry, he was dead:", +"And the right-valiant Banquo walk'd too late;", +"Whom, you may say, if't please you, Fleance kill'd,", +"For Fleance fled: men must not walk too late.", +"Who cannot want the thought how monstrous", +"It was for Malcolm and for Donalbain", +"To kill their gracious father? damned fact!", +"How it did grieve Macbeth! did he not straight", +"In pious rage the two delinquents tear,", +"That were the slaves of drink and thralls of sleep?", +"Was not that nobly done? Ay, and wisely too;", +"For 'twould have anger'd any heart alive", +"To hear the men deny't. So that, I say,", +"He has borne all things well: and I do think", +"That had he Duncan's sons under his key--", +"As, an't please heaven, he shall not--they", +"should find", +"What 'twere to kill a father; so should Fleance.", +"But, peace! for from broad words and 'cause he fail'd", +"His presence at the tyrant's feast, I hear", +"Macduff lives in disgrace: sir, can you tell", +"Where he bestows himself?", +"Lord", +"The son of Duncan,", +"From whom this tyrant holds the due of birth", +"Lives in the English court, and is received", +"Of the most pious Edward with such grace", +"That the malevolence of fortune nothing", +"Takes from his high respect: thither Macduff", +"Is gone to pray the holy king, upon his aid", +"To wake Northumberland and warlike Siward:", +"That, by the help of these--with Him above", +"To ratify the work--we may again", +"Give to our tables meat, sleep to our nights,", +"Free from our feasts and banquets bloody knives,", +"Do faithful homage and receive free honours:", +"All which we pine for now: and this report", +"Hath so exasperate the king that he", +"Prepares for some attempt of war.", +"LENNOX", +"Sent he to Macduff?", +"Lord", +"He did: and with an absolute 'Sir, not I,'", +"The cloudy messenger turns me his back,", +"And hums, as who should say 'You'll rue the time", +"That clogs me with this answer.'", +"LENNOX", +"And that well might", +"Advise him to a caution, to hold what distance", +"His wisdom can provide. Some holy angel", +"Fly to the court of England and unfold", +"His message ere he come, that a swift blessing", +"May soon return to this our suffering country", +"Under a hand accursed!", +"Lord", +"I'll send my prayers with him.", +"Exeunt", +"", +"ACT IV", +"", +"SCENE I. A cavern. In the middle, a boiling cauldron.", +"", +"Thunder. Enter the three Witches", +"First Witch", +"Thrice the brinded cat hath mew'd.", +"Second Witch", +"Thrice and once the hedge-pig whined.", +"Third Witch", +"Harpier cries 'Tis time, 'tis time.", +"First Witch", +"Round about the cauldron go;", +"In the poison'd entrails throw.", +"Toad, that under cold stone", +"Days and nights has thirty-one", +"Swelter'd venom sleeping got,", +"Boil thou first i' the charmed pot.", +"ALL", +"Double, double toil and trouble;", +"Fire burn, and cauldron bubble.", +"Second Witch", +"Fillet of a fenny snake,", +"In the cauldron boil and bake;", +"Eye of newt and toe of frog,", +"Wool of bat and tongue of dog,", +"Adder's fork and blind-worm's sting,", +"Lizard's leg and owlet's wing,", +"For a charm of powerful trouble,", +"Like a hell-broth boil and bubble.", +"ALL", +"Double, double toil and trouble;", +"Fire burn and cauldron bubble.", +"Third Witch", +"Scale of dragon, tooth of wolf,", +"Witches' mummy, maw and gulf", +"Of the ravin'd salt-sea shark,", +"Root of hemlock digg'd i' the dark,", +"Liver of blaspheming Jew,", +"Gall of goat, and slips of yew", +"Silver'd in the moon's eclipse,", +"Nose of Turk and Tartar's lips,", +"Finger of birth-strangled babe", +"Ditch-deliver'd by a drab,", +"Make the gruel thick and slab:", +"Add thereto a tiger's chaudron,", +"For the ingredients of our cauldron.", +"ALL", +"Double, double toil and trouble;", +"Fire burn and cauldron bubble.", +"Second Witch", +"Cool it with a baboon's blood,", +"Then the charm is firm and good.", +"Enter HECATE to the other three Witches", +"", +"HECATE", +"O well done! I commend your pains;", +"And every one shall share i' the gains;", +"And now about the cauldron sing,", +"Live elves and fairies in a ring,", +"Enchanting all that you put in.", +"Music and a song: 'Black spirits,' & c", +"", +"HECATE retires", +"", +"Second Witch", +"By the pricking of my thumbs,", +"Something wicked this way comes.", +"Open, locks,", +"Whoever knocks!", +"Enter MACBETH", +"", +"MACBETH", +"How now, you secret, black, and midnight hags!", +"What is't you do?", +"ALL", +"A deed without a name.", +"MACBETH", +"I conjure you, by that which you profess,", +"Howe'er you come to know it, answer me:", +"Though you untie the winds and let them fight", +"Against the churches; though the yesty waves", +"Confound and swallow navigation up;", +"Though bladed corn be lodged and trees blown down;", +"Though castles topple on their warders' heads;", +"Though palaces and pyramids do slope", +"Their heads to their foundations; though the treasure", +"Of nature's germens tumble all together,", +"Even till destruction sicken; answer me", +"To what I ask you.", +"First Witch", +"Speak.", +"Second Witch", +"Demand.", +"Third Witch", +"We'll answer.", +"First Witch", +"Say, if thou'dst rather hear it from our mouths,", +"Or from our masters?", +"MACBETH", +"Call 'em; let me see 'em.", +"First Witch", +"Pour in sow's blood, that hath eaten", +"Her nine farrow; grease that's sweaten", +"From the murderer's gibbet throw", +"Into the flame.", +"ALL", +"Come, high or low;", +"Thyself and office deftly show!", +"Thunder. First Apparition: an armed Head", +"", +"MACBETH", +"Tell me, thou unknown power,--", +"First Witch", +"He knows thy thought:", +"Hear his speech, but say thou nought.", +"First Apparition", +"Macbeth! Macbeth! Macbeth! beware Macduff;", +"Beware the thane of Fife. Dismiss me. Enough.", +"Descends", +"", +"MACBETH", +"Whate'er thou art, for thy good caution, thanks;", +"Thou hast harp'd my fear aright: but one", +"word more,--", +"First Witch", +"He will not be commanded: here's another,", +"More potent than the first.", +"Thunder. Second Apparition: A bloody Child", +"", +"Second Apparition", +"Macbeth! Macbeth! Macbeth!", +"MACBETH", +"Had I three ears, I'ld hear thee.", +"Second Apparition", +"Be bloody, bold, and resolute; laugh to scorn", +"The power of man, for none of woman born", +"Shall harm Macbeth.", +"Descends", +"", +"MACBETH", +"Then live, Macduff: what need I fear of thee?", +"But yet I'll make assurance double sure,", +"And take a bond of fate: thou shalt not live;", +"That I may tell pale-hearted fear it lies,", +"And sleep in spite of thunder.", +"Thunder. Third Apparition: a Child crowned, with a tree in his hand", +"", +"What is this", +"That rises like the issue of a king,", +"And wears upon his baby-brow the round", +"And top of sovereignty?", +"ALL", +"Listen, but speak not to't.", +"Third Apparition", +"Be lion-mettled, proud; and take no care", +"Who chafes, who frets, or where conspirers are:", +"Macbeth shall never vanquish'd be until", +"Great Birnam wood to high Dunsinane hill", +"Shall come against him.", +"Descends", +"", +"MACBETH", +"That will never be", +"Who can impress the forest, bid the tree", +"Unfix his earth-bound root? Sweet bodements! good!", +"Rebellion's head, rise never till the wood", +"Of Birnam rise, and our high-placed Macbeth", +"Shall live the lease of nature, pay his breath", +"To time and mortal custom. Yet my heart", +"Throbs to know one thing: tell me, if your art", +"Can tell so much: shall Banquo's issue ever", +"Reign in this kingdom?", +"ALL", +"Seek to know no more.", +"MACBETH", +"I will be satisfied: deny me this,", +"And an eternal curse fall on you! Let me know.", +"Why sinks that cauldron? and what noise is this?", +"Hautboys", +"", +"First Witch", +"Show!", +"Second Witch", +"Show!", +"Third Witch", +"Show!", +"ALL", +"Show his eyes, and grieve his heart;", +"Come like shadows, so depart!", +"A show of Eight Kings, the last with a glass in his hand; GHOST OF BANQUO following", +"", +"MACBETH", +"Thou art too like the spirit of Banquo: down!", +"Thy crown does sear mine eye-balls. And thy hair,", +"Thou other gold-bound brow, is like the first.", +"A third is like the former. Filthy hags!", +"Why do you show me this? A fourth! Start, eyes!", +"What, will the line stretch out to the crack of doom?", +"Another yet! A seventh! I'll see no more:", +"And yet the eighth appears, who bears a glass", +"Which shows me many more; and some I see", +"That two-fold balls and treble scepters carry:", +"Horrible sight! Now, I see, 'tis true;", +"For the blood-bolter'd Banquo smiles upon me,", +"And points at them for his.", +"Apparitions vanish", +"", +"What, is this so?", +"First Witch", +"Ay, sir, all this is so: but why", +"Stands Macbeth thus amazedly?", +"Come, sisters, cheer we up his sprites,", +"And show the best of our delights:", +"I'll charm the air to give a sound,", +"While you perform your antic round:", +"That this great king may kindly say,", +"Our duties did his welcome pay.", +"Music. The witches dance and then vanish, with HECATE", +"", +"MACBETH", +"Where are they? Gone? Let this pernicious hour", +"Stand aye accursed in the calendar!", +"Come in, without there!", +"Enter LENNOX", +"", +"LENNOX", +"What's your grace's will?", +"MACBETH", +"Saw you the weird sisters?", +"LENNOX", +"No, my lord.", +"MACBETH", +"Came they not by you?", +"LENNOX", +"No, indeed, my lord.", +"MACBETH", +"Infected be the air whereon they ride;", +"And damn'd all those that trust them! I did hear", +"The galloping of horse: who was't came by?", +"LENNOX", +"'Tis two or three, my lord, that bring you word", +"Macduff is fled to England.", +"MACBETH", +"Fled to England!", +"LENNOX", +"Ay, my good lord.", +"MACBETH", +"Time, thou anticipatest my dread exploits:", +"The flighty purpose never is o'ertook", +"Unless the deed go with it; from this moment", +"The very firstlings of my heart shall be", +"The firstlings of my hand. And even now,", +"To crown my thoughts with acts, be it thought and done:", +"The castle of Macduff I will surprise;", +"Seize upon Fife; give to the edge o' the sword", +"His wife, his babes, and all unfortunate souls", +"That trace him in his line. No boasting like a fool;", +"This deed I'll do before this purpose cool.", +"But no more sights!--Where are these gentlemen?", +"Come, bring me where they are.", +"Exeunt", +"", +"SCENE II. Fife. Macduff's castle.", +"", +"Enter LADY MACDUFF, her Son, and ROSS", +"LADY MACDUFF", +"What had he done, to make him fly the land?", +"ROSS", +"You must have patience, madam.", +"LADY MACDUFF", +"He had none:", +"His flight was madness: when our actions do not,", +"Our fears do make us traitors.", +"ROSS", +"You know not", +"Whether it was his wisdom or his fear.", +"LADY MACDUFF", +"Wisdom! to leave his wife, to leave his babes,", +"His mansion and his titles in a place", +"From whence himself does fly? He loves us not;", +"He wants the natural touch: for the poor wren,", +"The most diminutive of birds, will fight,", +"Her young ones in her nest, against the owl.", +"All is the fear and nothing is the love;", +"As little is the wisdom, where the flight", +"So runs against all reason.", +"ROSS", +"My dearest coz,", +"I pray you, school yourself: but for your husband,", +"He is noble, wise, judicious, and best knows", +"The fits o' the season. I dare not speak", +"much further;", +"But cruel are the times, when we are traitors", +"And do not know ourselves, when we hold rumour", +"From what we fear, yet know not what we fear,", +"But float upon a wild and violent sea", +"Each way and move. I take my leave of you:", +"Shall not be long but I'll be here again:", +"Things at the worst will cease, or else climb upward", +"To what they were before. My pretty cousin,", +"Blessing upon you!", +"LADY MACDUFF", +"Father'd he is, and yet he's fatherless.", +"ROSS", +"I am so much a fool, should I stay longer,", +"It would be my disgrace and your discomfort:", +"I take my leave at once.", +"Exit", +"", +"LADY MACDUFF", +"Sirrah, your father's dead;", +"And what will you do now? How will you live?", +"Son", +"As birds do, mother.", +"LADY MACDUFF", +"What, with worms and flies?", +"Son", +"With what I get, I mean; and so do they.", +"LADY MACDUFF", +"Poor bird! thou'ldst never fear the net nor lime,", +"The pitfall nor the gin.", +"Son", +"Why should I, mother? Poor birds they are not set for.", +"My father is not dead, for all your saying.", +"LADY MACDUFF", +"Yes, he is dead; how wilt thou do for a father?", +"Son", +"Nay, how will you do for a husband?", +"LADY MACDUFF", +"Why, I can buy me twenty at any market.", +"Son", +"Then you'll buy 'em to sell again.", +"LADY MACDUFF", +"Thou speak'st with all thy wit: and yet, i' faith,", +"With wit enough for thee.", +"Son", +"Was my father a traitor, mother?", +"LADY MACDUFF", +"Ay, that he was.", +"Son", +"What is a traitor?", +"LADY MACDUFF", +"Why, one that swears and lies.", +"Son", +"And be all traitors that do so?", +"LADY MACDUFF", +"Every one that does so is a traitor, and must be hanged.", +"Son", +"And must they all be hanged that swear and lie?", +"LADY MACDUFF", +"Every one.", +"Son", +"Who must hang them?", +"LADY MACDUFF", +"Why, the honest men.", +"Son", +"Then the liars and swearers are fools,", +"for there are liars and swearers enow to beat", +"the honest men and hang up them.", +"LADY MACDUFF", +"Now, God help thee, poor monkey!", +"But how wilt thou do for a father?", +"Son", +"If he were dead, you'ld weep for", +"him: if you would not, it were a good sign", +"that I should quickly have a new father.", +"LADY MACDUFF", +"Poor prattler, how thou talk'st!", +"Enter a Messenger", +"", +"Messenger", +"Bless you, fair dame! I am not to you known,", +"Though in your state of honour I am perfect.", +"I doubt some danger does approach you nearly:", +"If you will take a homely man's advice,", +"Be not found here; hence, with your little ones.", +"To fright you thus, methinks, I am too savage;", +"To do worse to you were fell cruelty,", +"Which is too nigh your person. Heaven preserve you!", +"I dare abide no longer.", +"Exit", +"", +"LADY MACDUFF", +"Whither should I fly?", +"I have done no harm. But I remember now", +"I am in this earthly world; where to do harm", +"Is often laudable, to do good sometime", +"Accounted dangerous folly: why then, alas,", +"Do I put up that womanly defence,", +"To say I have done no harm?", +"Enter Murderers", +"", +"What are these faces?", +"First Murderer", +"Where is your husband?", +"LADY MACDUFF", +"I hope, in no place so unsanctified", +"Where such as thou mayst find him.", +"First Murderer", +"He's a traitor.", +"Son", +"Thou liest, thou shag-hair'd villain!", +"First Murderer", +"What, you egg!", +"Stabbing him", +"", +"Young fry of treachery!", +"Son", +"He has kill'd me, mother:", +"Run away, I pray you!", +"Dies", +"", +"Exit LADY MACDUFF, crying 'Murder!' Exeunt Murderers, following her", +"", +"SCENE III. England. Before the King's palace.", +"", +"Enter MALCOLM and MACDUFF", +"MALCOLM", +"Let us seek out some desolate shade, and there", +"Weep our sad bosoms empty.", +"MACDUFF", +"Let us rather", +"Hold fast the mortal sword, and like good men", +"Bestride our down-fall'n birthdom: each new morn", +"New widows howl, new orphans cry, new sorrows", +"Strike heaven on the face, that it resounds", +"As if it felt with Scotland and yell'd out", +"Like syllable of dolour.", +"MALCOLM", +"What I believe I'll wail,", +"What know believe, and what I can redress,", +"As I shall find the time to friend, I will.", +"What you have spoke, it may be so perchance.", +"This tyrant, whose sole name blisters our tongues,", +"Was once thought honest: you have loved him well.", +"He hath not touch'd you yet. I am young;", +"but something", +"You may deserve of him through me, and wisdom", +"To offer up a weak poor innocent lamb", +"To appease an angry god.", +"MACDUFF", +"I am not treacherous.", +"MALCOLM", +"But Macbeth is.", +"A good and virtuous nature may recoil", +"In an imperial charge. But I shall crave", +"your pardon;", +"That which you are my thoughts cannot transpose:", +"Angels are bright still, though the brightest fell;", +"Though all things foul would wear the brows of grace,", +"Yet grace must still look so.", +"MACDUFF", +"I have lost my hopes.", +"MALCOLM", +"Perchance even there where I did find my doubts.", +"Why in that rawness left you wife and child,", +"Those precious motives, those strong knots of love,", +"Without leave-taking? I pray you,", +"Let not my jealousies be your dishonours,", +"But mine own safeties. You may be rightly just,", +"Whatever I shall think.", +"MACDUFF", +"Bleed, bleed, poor country!", +"Great tyranny! lay thou thy basis sure,", +"For goodness dare not cheque thee: wear thou", +"thy wrongs;", +"The title is affeer'd! Fare thee well, lord:", +"I would not be the villain that thou think'st", +"For the whole space that's in the tyrant's grasp,", +"And the rich East to boot.", +"MALCOLM", +"Be not offended:", +"I speak not as in absolute fear of you.", +"I think our country sinks beneath the yoke;", +"It weeps, it bleeds; and each new day a gash", +"Is added to her wounds: I think withal", +"There would be hands uplifted in my right;", +"And here from gracious England have I offer", +"Of goodly thousands: but, for all this,", +"When I shall tread upon the tyrant's head,", +"Or wear it on my sword, yet my poor country", +"Shall have more vices than it had before,", +"More suffer and more sundry ways than ever,", +"By him that shall succeed.", +"MACDUFF", +"What should he be?", +"MALCOLM", +"It is myself I mean: in whom I know", +"All the particulars of vice so grafted", +"That, when they shall be open'd, black Macbeth", +"Will seem as pure as snow, and the poor state", +"Esteem him as a lamb, being compared", +"With my confineless harms.", +"MACDUFF", +"Not in the legions", +"Of horrid hell can come a devil more damn'd", +"In evils to top Macbeth.", +"MALCOLM", +"I grant him bloody,", +"Luxurious, avaricious, false, deceitful,", +"Sudden, malicious, smacking of every sin", +"That has a name: but there's no bottom, none,", +"In my voluptuousness: your wives, your daughters,", +"Your matrons and your maids, could not fill up", +"The cistern of my lust, and my desire", +"All continent impediments would o'erbear", +"That did oppose my will: better Macbeth", +"Than such an one to reign.", +"MACDUFF", +"Boundless intemperance", +"In nature is a tyranny; it hath been", +"The untimely emptying of the happy throne", +"And fall of many kings. But fear not yet", +"To take upon you what is yours: you may", +"Convey your pleasures in a spacious plenty,", +"And yet seem cold, the time you may so hoodwink.", +"We have willing dames enough: there cannot be", +"That vulture in you, to devour so many", +"As will to greatness dedicate themselves,", +"Finding it so inclined.", +"MALCOLM", +"With this there grows", +"In my most ill-composed affection such", +"A stanchless avarice that, were I king,", +"I should cut off the nobles for their lands,", +"Desire his jewels and this other's house:", +"And my more-having would be as a sauce", +"To make me hunger more; that I should forge", +"Quarrels unjust against the good and loyal,", +"Destroying them for wealth.", +"MACDUFF", +"This avarice", +"Sticks deeper, grows with more pernicious root", +"Than summer-seeming lust, and it hath been", +"The sword of our slain kings: yet do not fear;", +"Scotland hath foisons to fill up your will.", +"Of your mere own: all these are portable,", +"With other graces weigh'd.", +"MALCOLM", +"But I have none: the king-becoming graces,", +"As justice, verity, temperance, stableness,", +"Bounty, perseverance, mercy, lowliness,", +"Devotion, patience, courage, fortitude,", +"I have no relish of them, but abound", +"In the division of each several crime,", +"Acting it many ways. Nay, had I power, I should", +"Pour the sweet milk of concord into hell,", +"Uproar the universal peace, confound", +"All unity on earth.", +"MACDUFF", +"O Scotland, Scotland!", +"MALCOLM", +"If such a one be fit to govern, speak:", +"I am as I have spoken.", +"MACDUFF", +"Fit to govern!", +"No, not to live. O nation miserable,", +"With an untitled tyrant bloody-scepter'd,", +"When shalt thou see thy wholesome days again,", +"Since that the truest issue of thy throne", +"By his own interdiction stands accursed,", +"And does blaspheme his breed? Thy royal father", +"Was a most sainted king: the queen that bore thee,", +"Oftener upon her knees than on her feet,", +"Died every day she lived. Fare thee well!", +"These evils thou repeat'st upon thyself", +"Have banish'd me from Scotland. O my breast,", +"Thy hope ends here!", +"MALCOLM", +"Macduff, this noble passion,", +"Child of integrity, hath from my soul", +"Wiped the black scruples, reconciled my thoughts", +"To thy good truth and honour. Devilish Macbeth", +"By many of these trains hath sought to win me", +"Into his power, and modest wisdom plucks me", +"From over-credulous haste: but God above", +"Deal between thee and me! for even now", +"I put myself to thy direction, and", +"Unspeak mine own detraction, here abjure", +"The taints and blames I laid upon myself,", +"For strangers to my nature. I am yet", +"Unknown to woman, never was forsworn,", +"Scarcely have coveted what was mine own,", +"At no time broke my faith, would not betray", +"The devil to his fellow and delight", +"No less in truth than life: my first false speaking", +"Was this upon myself: what I am truly,", +"Is thine and my poor country's to command:", +"Whither indeed, before thy here-approach,", +"Old Siward, with ten thousand warlike men,", +"Already at a point, was setting forth.", +"Now we'll together; and the chance of goodness", +"Be like our warranted quarrel! Why are you silent?", +"MACDUFF", +"Such welcome and unwelcome things at once", +"'Tis hard to reconcile.", +"Enter a Doctor", +"", +"MALCOLM", +"Well; more anon.--Comes the king forth, I pray you?", +"Doctor", +"Ay, sir; there are a crew of wretched souls", +"That stay his cure: their malady convinces", +"The great assay of art; but at his touch--", +"Such sanctity hath heaven given his hand--", +"They presently amend.", +"MALCOLM", +"I thank you, doctor.", +"Exit Doctor", +"", +"MACDUFF", +"What's the disease he means?", +"MALCOLM", +"'Tis call'd the evil:", +"A most miraculous work in this good king;", +"Which often, since my here-remain in England,", +"I have seen him do. How he solicits heaven,", +"Himself best knows: but strangely-visited people,", +"All swoln and ulcerous, pitiful to the eye,", +"The mere despair of surgery, he cures,", +"Hanging a golden stamp about their necks,", +"Put on with holy prayers: and 'tis spoken,", +"To the succeeding royalty he leaves", +"The healing benediction. With this strange virtue,", +"He hath a heavenly gift of prophecy,", +"And sundry blessings hang about his throne,", +"That speak him full of grace.", +"Enter ROSS", +"", +"MACDUFF", +"See, who comes here?", +"MALCOLM", +"My countryman; but yet I know him not.", +"MACDUFF", +"My ever-gentle cousin, welcome hither.", +"MALCOLM", +"I know him now. Good God, betimes remove", +"The means that makes us strangers!", +"ROSS", +"Sir, amen.", +"MACDUFF", +"Stands Scotland where it did?", +"ROSS", +"Alas, poor country!", +"Almost afraid to know itself. It cannot", +"Be call'd our mother, but our grave; where nothing,", +"But who knows nothing, is once seen to smile;", +"Where sighs and groans and shrieks that rend the air", +"Are made, not mark'd; where violent sorrow seems", +"A modern ecstasy; the dead man's knell", +"Is there scarce ask'd for who; and good men's lives", +"Expire before the flowers in their caps,", +"Dying or ere they sicken.", +"MACDUFF", +"O, relation", +"Too nice, and yet too true!", +"MALCOLM", +"What's the newest grief?", +"ROSS", +"That of an hour's age doth hiss the speaker:", +"Each minute teems a new one.", +"MACDUFF", +"How does my wife?", +"ROSS", +"Why, well.", +"MACDUFF", +"And all my children?", +"ROSS", +"Well too.", +"MACDUFF", +"The tyrant has not batter'd at their peace?", +"ROSS", +"No; they were well at peace when I did leave 'em.", +"MACDUFF", +"But not a niggard of your speech: how goes't?", +"ROSS", +"When I came hither to transport the tidings,", +"Which I have heavily borne, there ran a rumour", +"Of many worthy fellows that were out;", +"Which was to my belief witness'd the rather,", +"For that I saw the tyrant's power a-foot:", +"Now is the time of help; your eye in Scotland", +"Would create soldiers, make our women fight,", +"To doff their dire distresses.", +"MALCOLM", +"Be't their comfort", +"We are coming thither: gracious England hath", +"Lent us good Siward and ten thousand men;", +"An older and a better soldier none", +"That Christendom gives out.", +"ROSS", +"Would I could answer", +"This comfort with the like! But I have words", +"That would be howl'd out in the desert air,", +"Where hearing should not latch them.", +"MACDUFF", +"What concern they?", +"The general cause? or is it a fee-grief", +"Due to some single breast?", +"ROSS", +"No mind that's honest", +"But in it shares some woe; though the main part", +"Pertains to you alone.", +"MACDUFF", +"If it be mine,", +"Keep it not from me, quickly let me have it.", +"ROSS", +"Let not your ears despise my tongue for ever,", +"Which shall possess them with the heaviest sound", +"That ever yet they heard.", +"MACDUFF", +"Hum! I guess at it.", +"ROSS", +"Your castle is surprised; your wife and babes", +"Savagely slaughter'd: to relate the manner,", +"Were, on the quarry of these murder'd deer,", +"To add the death of you.", +"MALCOLM", +"Merciful heaven!", +"What, man! ne'er pull your hat upon your brows;", +"Give sorrow words: the grief that does not speak", +"Whispers the o'er-fraught heart and bids it break.", +"MACDUFF", +"My children too?", +"ROSS", +"Wife, children, servants, all", +"That could be found.", +"MACDUFF", +"And I must be from thence!", +"My wife kill'd too?", +"ROSS", +"I have said.", +"MALCOLM", +"Be comforted:", +"Let's make us medicines of our great revenge,", +"To cure this deadly grief.", +"MACDUFF", +"He has no children. All my pretty ones?", +"Did you say all? O hell-kite! All?", +"What, all my pretty chickens and their dam", +"At one fell swoop?", +"MALCOLM", +"Dispute it like a man.", +"MACDUFF", +"I shall do so;", +"But I must also feel it as a man:", +"I cannot but remember such things were,", +"That were most precious to me. Did heaven look on,", +"And would not take their part? Sinful Macduff,", +"They were all struck for thee! naught that I am,", +"Not for their own demerits, but for mine,", +"Fell slaughter on their souls. Heaven rest them now!", +"MALCOLM", +"Be this the whetstone of your sword: let grief", +"Convert to anger; blunt not the heart, enrage it.", +"MACDUFF", +"O, I could play the woman with mine eyes", +"And braggart with my tongue! But, gentle heavens,", +"Cut short all intermission; front to front", +"Bring thou this fiend of Scotland and myself;", +"Within my sword's length set him; if he 'scape,", +"Heaven forgive him too!", +"MALCOLM", +"This tune goes manly.", +"Come, go we to the king; our power is ready;", +"Our lack is nothing but our leave; Macbeth", +"Is ripe for shaking, and the powers above", +"Put on their instruments. Receive what cheer you may:", +"The night is long that never finds the day.", +"Exeunt", +"", +"ACT V", +"", +"SCENE I. Dunsinane. Ante-room in the castle.", +"", +"Enter a Doctor of Physic and a Waiting-Gentlewoman", +"Doctor", +"I have two nights watched with you, but can perceive", +"no truth in your report. When was it she last walked?", +"Gentlewoman", +"Since his majesty went into the field, I have seen", +"her rise from her bed, throw her night-gown upon", +"her, unlock her closet, take forth paper, fold it,", +"write upon't, read it, afterwards seal it, and again", +"return to bed; yet all this while in a most fast sleep.", +"Doctor", +"A great perturbation in nature, to receive at once", +"the benefit of sleep, and do the effects of", +"watching! In this slumbery agitation, besides her", +"walking and other actual performances, what, at any", +"time, have you heard her say?", +"Gentlewoman", +"That, sir, which I will not report after her.", +"Doctor", +"You may to me: and 'tis most meet you should.", +"Gentlewoman", +"Neither to you nor any one; having no witness to", +"confirm my speech.", +"Enter LADY MACBETH, with a taper", +"", +"Lo you, here she comes! This is her very guise;", +"and, upon my life, fast asleep. Observe her; stand close.", +"Doctor", +"How came she by that light?", +"Gentlewoman", +"Why, it stood by her: she has light by her", +"continually; 'tis her command.", +"Doctor", +"You see, her eyes are open.", +"Gentlewoman", +"Ay, but their sense is shut.", +"Doctor", +"What is it she does now? Look, how she rubs her hands.", +"Gentlewoman", +"It is an accustomed action with her, to seem thus", +"washing her hands: I have known her continue in", +"this a quarter of an hour.", +"LADY MACBETH", +"Yet here's a spot.", +"Doctor", +"Hark! she speaks: I will set down what comes from", +"her, to satisfy my remembrance the more strongly.", +"LADY MACBETH", +"Out, damned spot! out, I say!--One: two: why,", +"then, 'tis time to do't.--Hell is murky!--Fie, my", +"lord, fie! a soldier, and afeard? What need we", +"fear who knows it, when none can call our power to", +"account?--Yet who would have thought the old man", +"to have had so much blood in him.", +"Doctor", +"Do you mark that?", +"LADY MACBETH", +"The thane of Fife had a wife: where is she now?--", +"What, will these hands ne'er be clean?--No more o'", +"that, my lord, no more o' that: you mar all with", +"this starting.", +"Doctor", +"Go to, go to; you have known what you should not.", +"Gentlewoman", +"She has spoke what she should not, I am sure of", +"that: heaven knows what she has known.", +"LADY MACBETH", +"Here's the smell of the blood still: all the", +"perfumes of Arabia will not sweeten this little", +"hand. Oh, oh, oh!", +"Doctor", +"What a sigh is there! The heart is sorely charged.", +"Gentlewoman", +"I would not have such a heart in my bosom for the", +"dignity of the whole body.", +"Doctor", +"Well, well, well,--", +"Gentlewoman", +"Pray God it be, sir.", +"Doctor", +"This disease is beyond my practise: yet I have known", +"those which have walked in their sleep who have died", +"holily in their beds.", +"LADY MACBETH", +"Wash your hands, put on your nightgown; look not so", +"pale.--I tell you yet again, Banquo's buried; he", +"cannot come out on's grave.", +"Doctor", +"Even so?", +"LADY MACBETH", +"To bed, to bed! there's knocking at the gate:", +"come, come, come, come, give me your hand. What's", +"done cannot be undone.--To bed, to bed, to bed!", +"Exit", +"", +"Doctor", +"Will she go now to bed?", +"Gentlewoman", +"Directly.", +"Doctor", +"Foul whisperings are abroad: unnatural deeds", +"Do breed unnatural troubles: infected minds", +"To their deaf pillows will discharge their secrets:", +"More needs she the divine than the physician.", +"God, God forgive us all! Look after her;", +"Remove from her the means of all annoyance,", +"And still keep eyes upon her. So, good night:", +"My mind she has mated, and amazed my sight.", +"I think, but dare not speak.", +"Gentlewoman", +"Good night, good doctor.", +"Exeunt", +"", +"SCENE II. The country near Dunsinane.", +"", +"Drum and colours. Enter MENTEITH, CAITHNESS, ANGUS, LENNOX, and Soldiers", +"MENTEITH", +"The English power is near, led on by Malcolm,", +"His uncle Siward and the good Macduff:", +"Revenges burn in them; for their dear causes", +"Would to the bleeding and the grim alarm", +"Excite the mortified man.", +"ANGUS", +"Near Birnam wood", +"Shall we well meet them; that way are they coming.", +"CAITHNESS", +"Who knows if Donalbain be with his brother?", +"LENNOX", +"For certain, sir, he is not: I have a file", +"Of all the gentry: there is Siward's son,", +"And many unrough youths that even now", +"Protest their first of manhood.", +"MENTEITH", +"What does the tyrant?", +"CAITHNESS", +"Great Dunsinane he strongly fortifies:", +"Some say he's mad; others that lesser hate him", +"Do call it valiant fury: but, for certain,", +"He cannot buckle his distemper'd cause", +"Within the belt of rule.", +"ANGUS", +"Now does he feel", +"His secret murders sticking on his hands;", +"Now minutely revolts upbraid his faith-breach;", +"Those he commands move only in command,", +"Nothing in love: now does he feel his title", +"Hang loose about him, like a giant's robe", +"Upon a dwarfish thief.", +"MENTEITH", +"Who then shall blame", +"His pester'd senses to recoil and start,", +"When all that is within him does condemn", +"Itself for being there?", +"CAITHNESS", +"Well, march we on,", +"To give obedience where 'tis truly owed:", +"Meet we the medicine of the sickly weal,", +"And with him pour we in our country's purge", +"Each drop of us.", +"LENNOX", +"Or so much as it needs,", +"To dew the sovereign flower and drown the weeds.", +"Make we our march towards Birnam.", +"Exeunt, marching", +"", +"SCENE III. Dunsinane. A room in the castle.", +"", +"Enter MACBETH, Doctor, and Attendants", +"MACBETH", +"Bring me no more reports; let them fly all:", +"Till Birnam wood remove to Dunsinane,", +"I cannot taint with fear. What's the boy Malcolm?", +"Was he not born of woman? The spirits that know", +"All mortal consequences have pronounced me thus:", +"'Fear not, Macbeth; no man that's born of woman", +"Shall e'er have power upon thee.' Then fly,", +"false thanes,", +"And mingle with the English epicures:", +"The mind I sway by and the heart I bear", +"Shall never sag with doubt nor shake with fear.", +"Enter a Servant", +"", +"The devil damn thee black, thou cream-faced loon!", +"Where got'st thou that goose look?", +"Servant", +"There is ten thousand--", +"MACBETH", +"Geese, villain!", +"Servant", +"Soldiers, sir.", +"MACBETH", +"Go prick thy face, and over-red thy fear,", +"Thou lily-liver'd boy. What soldiers, patch?", +"Death of thy soul! those linen cheeks of thine", +"Are counsellors to fear. What soldiers, whey-face?", +"Servant", +"The English force, so please you.", +"MACBETH", +"Take thy face hence.", +"Exit Servant", +"", +"Seyton!--I am sick at heart,", +"When I behold--Seyton, I say!--This push", +"Will cheer me ever, or disseat me now.", +"I have lived long enough: my way of life", +"Is fall'n into the sear, the yellow leaf;", +"And that which should accompany old age,", +"As honour, love, obedience, troops of friends,", +"I must not look to have; but, in their stead,", +"Curses, not loud but deep, mouth-honour, breath,", +"Which the poor heart would fain deny, and dare not. Seyton!", +"Enter SEYTON", +"", +"SEYTON", +"What is your gracious pleasure?", +"MACBETH", +"What news more?", +"SEYTON", +"All is confirm'd, my lord, which was reported.", +"MACBETH", +"I'll fight till from my bones my flesh be hack'd.", +"Give me my armour.", +"SEYTON", +"'Tis not needed yet.", +"MACBETH", +"I'll put it on.", +"Send out more horses; skirr the country round;", +"Hang those that talk of fear. Give me mine armour.", +"How does your patient, doctor?", +"Doctor", +"Not so sick, my lord,", +"As she is troubled with thick coming fancies,", +"That keep her from her rest.", +"MACBETH", +"Cure her of that.", +"Canst thou not minister to a mind diseased,", +"Pluck from the memory a rooted sorrow,", +"Raze out the written troubles of the brain", +"And with some sweet oblivious antidote", +"Cleanse the stuff'd bosom of that perilous stuff", +"Which weighs upon the heart?", +"Doctor", +"Therein the patient", +"Must minister to himself.", +"MACBETH", +"Throw physic to the dogs; I'll none of it.", +"Come, put mine armour on; give me my staff.", +"Seyton, send out. Doctor, the thanes fly from me.", +"Come, sir, dispatch. If thou couldst, doctor, cast", +"The water of my land, find her disease,", +"And purge it to a sound and pristine health,", +"I would applaud thee to the very echo,", +"That should applaud again.--Pull't off, I say.--", +"What rhubarb, cyme, or what purgative drug,", +"Would scour these English hence? Hear'st thou of them?", +"Doctor", +"Ay, my good lord; your royal preparation", +"Makes us hear something.", +"MACBETH", +"Bring it after me.", +"I will not be afraid of death and bane,", +"Till Birnam forest come to Dunsinane.", +"Doctor", +"[Aside] Were I from Dunsinane away and clear,", +"Profit again should hardly draw me here.", +"Exeunt", +"", +"SCENE IV. Country near Birnam wood.", +"", +"Drum and colours. Enter MALCOLM, SIWARD and YOUNG SIWARD, MACDUFF, MENTEITH, CAITHNESS, ANGUS, LENNOX, ROSS, and Soldiers, marching", +"MALCOLM", +"Cousins, I hope the days are near at hand", +"That chambers will be safe.", +"MENTEITH", +"We doubt it nothing.", +"SIWARD", +"What wood is this before us?", +"MENTEITH", +"The wood of Birnam.", +"MALCOLM", +"Let every soldier hew him down a bough", +"And bear't before him: thereby shall we shadow", +"The numbers of our host and make discovery", +"Err in report of us.", +"Soldiers", +"It shall be done.", +"SIWARD", +"We learn no other but the confident tyrant", +"Keeps still in Dunsinane, and will endure", +"Our setting down before 't.", +"MALCOLM", +"'Tis his main hope:", +"For where there is advantage to be given,", +"Both more and less have given him the revolt,", +"And none serve with him but constrained things", +"Whose hearts are absent too.", +"MACDUFF", +"Let our just censures", +"Attend the true event, and put we on", +"Industrious soldiership.", +"SIWARD", +"The time approaches", +"That will with due decision make us know", +"What we shall say we have and what we owe.", +"Thoughts speculative their unsure hopes relate,", +"But certain issue strokes must arbitrate:", +"Towards which advance the war.", +"Exeunt, marching", +"", +"SCENE V. Dunsinane. Within the castle.", +"", +"Enter MACBETH, SEYTON, and Soldiers, with drum and colours", +"MACBETH", +"Hang out our banners on the outward walls;", +"The cry is still 'They come:' our castle's strength", +"Will laugh a siege to scorn: here let them lie", +"Till famine and the ague eat them up:", +"Were they not forced with those that should be ours,", +"We might have met them dareful, beard to beard,", +"And beat them backward home.", +"A cry of women within", +"", +"What is that noise?", +"SEYTON", +"It is the cry of women, my good lord.", +"Exit", +"", +"MACBETH", +"I have almost forgot the taste of fears;", +"The time has been, my senses would have cool'd", +"To hear a night-shriek; and my fell of hair", +"Would at a dismal treatise rouse and stir", +"As life were in't: I have supp'd full with horrors;", +"Direness, familiar to my slaughterous thoughts", +"Cannot once start me.", +"Re-enter SEYTON", +"", +"Wherefore was that cry?", +"SEYTON", +"The queen, my lord, is dead.", +"MACBETH", +"She should have died hereafter;", +"There would have been a time for such a word.", +"To-morrow, and to-morrow, and to-morrow,", +"Creeps in this petty pace from day to day", +"To the last syllable of recorded time,", +"And all our yesterdays have lighted fools", +"The way to dusty death. Out, out, brief candle!", +"Life's but a walking shadow, a poor player", +"That struts and frets his hour upon the stage", +"And then is heard no more: it is a tale", +"Told by an idiot, full of sound and fury,", +"Signifying nothing.", +"Enter a Messenger", +"", +"Thou comest to use thy tongue; thy story quickly.", +"Messenger", +"Gracious my lord,", +"I should report that which I say I saw,", +"But know not how to do it.", +"MACBETH", +"Well, say, sir.", +"Messenger", +"As I did stand my watch upon the hill,", +"I look'd toward Birnam, and anon, methought,", +"The wood began to move.", +"MACBETH", +"Liar and slave!", +"Messenger", +"Let me endure your wrath, if't be not so:", +"Within this three mile may you see it coming;", +"I say, a moving grove.", +"MACBETH", +"If thou speak'st false,", +"Upon the next tree shalt thou hang alive,", +"Till famine cling thee: if thy speech be sooth,", +"I care not if thou dost for me as much.", +"I pull in resolution, and begin", +"To doubt the equivocation of the fiend", +"That lies like truth: 'Fear not, till Birnam wood", +"Do come to Dunsinane:' and now a wood", +"Comes toward Dunsinane. Arm, arm, and out!", +"If this which he avouches does appear,", +"There is nor flying hence nor tarrying here.", +"I gin to be aweary of the sun,", +"And wish the estate o' the world were now undone.", +"Ring the alarum-bell! Blow, wind! come, wrack!", +"At least we'll die with harness on our back.", +"Exeunt", +"", +"SCENE VI. Dunsinane. Before the castle.", +"", +"Drum and colours. Enter MALCOLM, SIWARD, MACDUFF, and their Army, with boughs", +"MALCOLM", +"Now near enough: your leafy screens throw down.", +"And show like those you are. You, worthy uncle,", +"Shall, with my cousin, your right-noble son,", +"Lead our first battle: worthy Macduff and we", +"Shall take upon 's what else remains to do,", +"According to our order.", +"SIWARD", +"Fare you well.", +"Do we but find the tyrant's power to-night,", +"Let us be beaten, if we cannot fight.", +"MACDUFF", +"Make all our trumpets speak; give them all breath,", +"Those clamorous harbingers of blood and death.", +"Exeunt", +"", +"SCENE VII. Another part of the field.", +"", +"Alarums. Enter MACBETH", +"MACBETH", +"They have tied me to a stake; I cannot fly,", +"But, bear-like, I must fight the course. What's he", +"That was not born of woman? Such a one", +"Am I to fear, or none.", +"Enter YOUNG SIWARD", +"", +"YOUNG SIWARD", +"What is thy name?", +"MACBETH", +"Thou'lt be afraid to hear it.", +"YOUNG SIWARD", +"No; though thou call'st thyself a hotter name", +"Than any is in hell.", +"MACBETH", +"My name's Macbeth.", +"YOUNG SIWARD", +"The devil himself could not pronounce a title", +"More hateful to mine ear.", +"MACBETH", +"No, nor more fearful.", +"YOUNG SIWARD", +"Thou liest, abhorred tyrant; with my sword", +"I'll prove the lie thou speak'st.", +"They fight and YOUNG SIWARD is slain", +"", +"MACBETH", +"Thou wast born of woman", +"But swords I smile at, weapons laugh to scorn,", +"Brandish'd by man that's of a woman born.", +"Exit", +"", +"Alarums. Enter MACDUFF", +"", +"MACDUFF", +"That way the noise is. Tyrant, show thy face!", +"If thou be'st slain and with no stroke of mine,", +"My wife and children's ghosts will haunt me still.", +"I cannot strike at wretched kerns, whose arms", +"Are hired to bear their staves: either thou, Macbeth,", +"Or else my sword with an unbatter'd edge", +"I sheathe again undeeded. There thou shouldst be;", +"By this great clatter, one of greatest note", +"Seems bruited. Let me find him, fortune!", +"And more I beg not.", +"Exit. Alarums", +"", +"Enter MALCOLM and SIWARD", +"", +"SIWARD", +"This way, my lord; the castle's gently render'd:", +"The tyrant's people on both sides do fight;", +"The noble thanes do bravely in the war;", +"The day almost itself professes yours,", +"And little is to do.", +"MALCOLM", +"We have met with foes", +"That strike beside us.", +"SIWARD", +"Enter, sir, the castle.", +"Exeunt. Alarums", +"", +"SCENE VIII. Another part of the field.", +"", +"Enter MACBETH", +"MACBETH", +"Why should I play the Roman fool, and die", +"On mine own sword? whiles I see lives, the gashes", +"Do better upon them.", +"Enter MACDUFF", +"", +"MACDUFF", +"Turn, hell-hound, turn!", +"MACBETH", +"Of all men else I have avoided thee:", +"But get thee back; my soul is too much charged", +"With blood of thine already.", +"MACDUFF", +"I have no words:", +"My voice is in my sword: thou bloodier villain", +"Than terms can give thee out!", +"They fight", +"", +"MACBETH", +"Thou losest labour:", +"As easy mayst thou the intrenchant air", +"With thy keen sword impress as make me bleed:", +"Let fall thy blade on vulnerable crests;", +"I bear a charmed life, which must not yield,", +"To one of woman born.", +"MACDUFF", +"Despair thy charm;", +"And let the angel whom thou still hast served", +"Tell thee, Macduff was from his mother's womb", +"Untimely ripp'd.", +"MACBETH", +"Accursed be that tongue that tells me so,", +"For it hath cow'd my better part of man!", +"And be these juggling fiends no more believed,", +"That palter with us in a double sense;", +"That keep the word of promise to our ear,", +"And break it to our hope. I'll not fight with thee.", +"MACDUFF", +"Then yield thee, coward,", +"And live to be the show and gaze o' the time:", +"We'll have thee, as our rarer monsters are,", +"Painted on a pole, and underwrit,", +"'Here may you see the tyrant.'", +"MACBETH", +"I will not yield,", +"To kiss the ground before young Malcolm's feet,", +"And to be baited with the rabble's curse.", +"Though Birnam wood be come to Dunsinane,", +"And thou opposed, being of no woman born,", +"Yet I will try the last. Before my body", +"I throw my warlike shield. Lay on, Macduff,", +"And damn'd be him that first cries, 'Hold, enough!'", +"Exeunt, fighting. Alarums", +"", +"Retreat. Flourish. Enter, with drum and colours, MALCOLM, SIWARD, ROSS, the other Thanes, and Soldiers", +"", +"MALCOLM", +"I would the friends we miss were safe arrived.", +"SIWARD", +"Some must go off: and yet, by these I see,", +"So great a day as this is cheaply bought.", +"MALCOLM", +"Macduff is missing, and your noble son.", +"ROSS", +"Your son, my lord, has paid a soldier's debt:", +"He only lived but till he was a man;", +"The which no sooner had his prowess confirm'd", +"In the unshrinking station where he fought,", +"But like a man he died.", +"SIWARD", +"Then he is dead?", +"ROSS", +"Ay, and brought off the field: your cause of sorrow", +"Must not be measured by his worth, for then", +"It hath no end.", +"SIWARD", +"Had he his hurts before?", +"ROSS", +"Ay, on the front.", +"SIWARD", +"Why then, God's soldier be he!", +"Had I as many sons as I have hairs,", +"I would not wish them to a fairer death:", +"And so, his knell is knoll'd.", +"MALCOLM", +"He's worth more sorrow,", +"And that I'll spend for him.", +"SIWARD", +"He's worth no more", +"They say he parted well, and paid his score:", +"And so, God be with him! Here comes newer comfort.", +"Re-enter MACDUFF, with MACBETH's head", +"", +"MACDUFF", +"Hail, king! for so thou art: behold, where stands", +"The usurper's cursed head: the time is free:", +"I see thee compass'd with thy kingdom's pearl,", +"That speak my salutation in their minds;", +"Whose voices I desire aloud with mine:", +"Hail, King of Scotland!", +"ALL", +"Hail, King of Scotland!", +"Flourish", +"", +"MALCOLM", +"We shall not spend a large expense of time", +"Before we reckon with your several loves,", +"And make us even with you. My thanes and kinsmen,", +"Henceforth be earls, the first that ever Scotland", +"In such an honour named. What's more to do,", +"Which would be planted newly with the time,", +"As calling home our exiled friends abroad", +"That fled the snares of watchful tyranny;", +"Producing forth the cruel ministers", +"Of this dead butcher and his fiend-like queen,", +"Who, as 'tis thought, by self and violent hands", +"Took off her life; this, and what needful else", +"That calls upon us, by the grace of Grace,", +"We will perform in measure, time and place:", +"So, thanks to all at once and to each one,", +"Whom we invite to see us crown'd at Scone.", +"Flourish. Exeunt"]; + diff --git a/test/main.js b/test/main.js new file mode 100755 index 0000000..6601520 --- /dev/null +++ b/test/main.js @@ -0,0 +1,25 @@ +require.config({ + paths: { + 'codemirror': '../lib/codemirror', + 'mergely': '../lib/mergely', + 'jsunit': 'lib/jsunit/jsunit' + } +}); + +var OPTIONS = {debug:false}; + +require([ + 'codemirror', + 'mergely', + 'jsunit', + //test modules + 'tests', +], function( + mergely, + codemirror, + JsUnit, + tests +){ + tests(); + JsUnit.start(); +}); diff --git a/test/tests.js b/test/tests.js new file mode 100755 index 0000000..f912a66 --- /dev/null +++ b/test/tests.js @@ -0,0 +1,1094 @@ +Object.equals = function( x, y ) { + if ( x === y ) return true; + if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false; + if ( x.constructor !== y.constructor ) return false; + for ( var p in x ) { + if ( ! x.hasOwnProperty( p ) ) continue; + if ( ! y.hasOwnProperty( p ) ) return false; + if ( x[ p ] === y[ p ] ) continue; + if ( typeof( x[ p ] ) !== "object" ) return false; + if ( ! Object.equals( x[ p ], y[ p ] ) ) return false; + } + for ( p in y ) { + if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false; + } + return true; +} + +$(document).ready(function(){ + var DMERGELY = $('\ +
\ +
\ +
'); + + var init = function() { + $('body').append(DMERGELY); + console.log($('#test-mergely').length); + var mergely = new Mgly.mergely(); + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + return mergely; + }; + + (function (JsUnit) { + JsUnit.module('Tests'); + + + // Summary: + // Add one line to the rhs where the lhs is empty. + // Description: + // This tests inserting one line in the rhs only. The visual markup change for the + // lhs usually starts at the end of the change which, when drawing the border, is + // the bottom, but in this case, there are no lines, so there is no bottom. In this + // case, the border must be drawn on the top. The margin markup must also take this + // into account, and visually, it starts from the top of the line, rather than the + // bottom. The rhs markup starts and ends on the same line. + // Example: + // one + JsUnit.test('case-1-rhs-add-one-line', function() { + mergely = init(); + mergely.lhs(''); + mergely.rhs('one'); + + JsUnit.okay(mergely.get('lhs') == '', 'Expected ""'); + JsUnit.okay(mergely.get('rhs') == 'one', 'Expected "one"'); + var diff = '0a1\n'+ + '> one\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start']; + var notok_classes = ['d', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['d', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + for (var clazz in ['mergely', 'a', 'rhs', 'start', 'bg', 'end']) { + JsUnit.okay($.inArray(clazz, classes), 'Expected lhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // Summary: + // Add one line to the rhs at the end of text. + // Description: + // This tests inserting one line in the rhs only at the end of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts and ends on the same line. This is normal insertion. + // Example: + // one one + // two + JsUnit.test('case-2-rhs-add-one-line-after', function() { + mergely = init(); + mergely.lhs('one'); + mergely.rhs('one\ntwo'); + JsUnit.okay(mergely.get('lhs') == 'one', 'Expected "one"'); + JsUnit.okay(mergely.get('rhs') == 'one\ntwo', 'Expected "one two"'); + var diff = '1a2\n'+ + '> two\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start', 'end']; + var notok_classes = ['d', 'c', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(1); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['d', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 1, 'Expected rhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-to'] == 1, 'Expected rhs change to be finish at 1'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Add one line to the rhs before the start of text. + // description: + // This tests inserting one line in the rhs only before the start of text. The + // The visual markup change for the lhs starts at the end of the lhs change + // (normal case), and the rhs markup starts and ends on the same line. This is + // normal insertion. + // example: + // one 1 + // one + JsUnit.test('case-3-rhs-add-one-line-before', function() { + mergely = init(); + mergely.lhs('one'); + mergely.rhs('1\none'); + JsUnit.okay(mergely.get('lhs') == 'one', 'Expected "one"'); + JsUnit.okay(mergely.get('rhs') == '1\none', 'Expected "1 one"'); + var diff = '0a1\n'+ + '> 1\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start']; + var notok_classes = ['d', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['d', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Add one line to the rhs in the middle of text. + // description: + // This tests inserting one line in the rhs only in the middle of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts and ends on the same line. This is normal insertion. + // example: + // one one + // three two + // three + JsUnit.test('case-4-rhs-add-one-line-inbetween', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\nthree'); + mergely.rhs('one\ntwo\nthree'); + JsUnit.okay(mergely.get('lhs') == 'one\nthree', 'Expected "one three"'); + JsUnit.okay(mergely.get('rhs') == 'one\ntwo\nthree', 'Expected "one two three"'); + var diff = '1a2\n'+ + '> two\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start', 'end']; + var notok_classes = ['d', 'c', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(1); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['d', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 1, 'Expected rhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-to'] == 1, 'Expected rhs change to be finish at 1'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Add three lines to the rhs in the middle of text. + // description: + // This tests inserting three lines in the rhs only in the middle of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts on line one of the first change, the next line of the change has 'bg' markup + // and the final line has 'end' markup + // example: + // one one + // five two + // three + // four + // five + JsUnit.test('case-5-rhs-add-three-lines-inbetween', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\nfive'); + mergely.rhs('one\ntwo\nthree\nfour\nfive'); + JsUnit.okay(mergely.get('lhs') == 'one\nfive', 'Expected "one five"'); + JsUnit.okay(mergely.get('rhs') == 'one\ntwo\nthree\nfour\nfive', 'Expected "one two three four five"'); + var diff = '1a2,4\n'+ + '> two\n' + + '> three\n' + + '> four\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start', 'end']; + var notok_classes = ['d', 'c', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(1); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start']; + var notok_classes = ['d', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(2); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'bg']; + var notok_classes = ['d', 'c', 'start', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(3); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'end']; + var notok_classes = ['d', 'c', 'start', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 1, 'Expected rhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-to'] == 3, 'Expected rhs change to be finish at 3'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 3 - valign), 'Expected rhs end to be 3 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove one line from the lhs where the rhs is empty. + // description: + // This tests removing one line from the lhs only. The visual markup change for the + // rhs usually starts at the end of the change which, when drawing the border, is + // the bottom, but in this case, there are no lines, so there is no bottom. In this + // case, the border must be drawn on the top. The margin markup must also take this + // into account, and visually, it starts from the top of the line, rather than the + // bottom. The rhs markup starts and ends on the same line. + // example: + // one + JsUnit.test('case-6-lhs-remove-one-line', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one'); + mergely.rhs(''); + JsUnit.okay(mergely.get('rhs') == '', 'Expected ""'); + JsUnit.okay(mergely.get('lhs') == 'one', 'Expected "one"'); + var diff = '1d0\n'+ + '< one\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start', 'end', 'bg']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start']; + var notok_classes = ['a', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + for (var clazz in ['mergely', 'a', 'rhs', 'start', 'bg', 'end']) { + JsUnit.okay($.inArray(clazz, classes), 'Expected lhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be the different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['lhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected lhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove one line from the lhs at the end of text. + // description: + // This tests inserting one line in the rhs only at the end of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts and ends on the same line. This is normal insertion. + // example: + // one one + // two + JsUnit.test('case-7-lhs-remove-one-line-at-end', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\ntwo'); + mergely.rhs('one'); + JsUnit.okay(mergely.get('lhs') == 'one\ntwo', 'Expected "one two"'); + JsUnit.okay(mergely.get('rhs') == 'one', 'Expected "one"'); + var diff = '2d1\n'+ + '< two\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(1); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start', 'end']; + var notok_classes = ['a', 'c', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 1, 'Expected lhs change to be start from 1'); + JsUnit.okay(change['lhs-line-to'] == 1, 'Expected lhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove one line from the lhs before the start of text. + // description: + // This tests removing one line from the lhs before the start of text. The + // The visual markup change for the lhs starts at the end of the lhs change + // (normal case), and the rhs markup starts and ends on the same line. This is + // normal insertion. + // example: + // 1 one + // one + JsUnit.test('case-8-lhs-remove-one-line-before', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('1\none'); + mergely.rhs('one'); + JsUnit.okay(mergely.get('lhs') == '1\none', 'Expected "1 one"'); + JsUnit.okay(mergely.get('rhs') == 'one', 'Expected "one"'); + var diff = '1d0\n'+ + '< 1\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start', 'end', 'bg']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['lhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected lhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove one line from the lhs in the middle of text. + // description: + // This tests removing one line from the lhs in the middle of text. The visual markup + // change for the rhs start at the end of the rhs change (normal case), and the lhs + // markup starts and ends on the same line. This is normal delete. + // example: + // one one + // two three + // three + JsUnit.test('case-9-rhs-remove-one-line-inbetween', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\ntwo\nthree'); + mergely.rhs('one\nthree'); + JsUnit.okay(mergely.get('lhs') == 'one\ntwo\nthree', 'Expected "one two three"'); + JsUnit.okay(mergely.get('rhs') == 'one\nthree', 'Expected "one three"'); + var diff = '2d1\n'+ + '< two\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(1); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start', 'bg', 'end']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 1, 'Expected lhs change to be start from 1'); + JsUnit.okay(change['lhs-line-to'] == 1, 'Expected lhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['lhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected lhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove three lines from the lhs in the middle of text. + // description: + // This tests the removal of three lines from the lhs in the middle of text. + // + // This tests inserting three lines in the rhs only in the middle of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts on line one of the first change, the next line of the change has 'bg' markup + // and the final line has 'end' markup + // example: + // one one + // two five + // three + // four + // five + JsUnit.test('case-10-lhs-remove-three-lines-inbetween', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\ntwo\nthree\nfour\nfive'); + mergely.rhs('one\nfive'); + JsUnit.okay(mergely.get('lhs') == 'one\ntwo\nthree\nfour\nfive', 'Expected "one two three four five"'); + JsUnit.okay(mergely.get('rhs') == 'one\nfive', 'Expected "one five"'); + var diff = '2,4d1\n'+ + '< two\n' + + '< three\n' + + '< four\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(1); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start']; + var notok_classes = ['a', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(2); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'bg']; + var notok_classes = ['a', 'c', 'start', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(3); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'end']; + var notok_classes = ['a', 'c', 'bg', 'start']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 1, 'Expected lhs change to be start from 1'); + JsUnit.okay(change['lhs-line-to'] == 3, 'Expected lhs change to be finish at 3'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['lhs-y-start'] + extents['em-height'] * 3 - valign), 'Expected rhs end to be 3 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + JsUnit.test('case-11-diff-file-100k-same', function() { + var data = macbeth.join('\n'); + console.log('size', data.length); + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-100k-50-changes', function() { + var data = macbeth.join('\n'); + + // array swap 50 lines + var mcopy = []; + for (var i = 0; i < macbeth.length; ++i) { + mcopy[i] = macbeth[i]; + } + for (var i = 0; i < 50; ++i) { + var x1 = Math.floor((Math.random()*macbeth.length)); + var x2 = Math.floor((Math.random()*macbeth.length)); + var t = mcopy[x1]; + mcopy[x1] = mcopy[x2]; + mcopy[x2] = t; + } + var data2 = mcopy.join('\n'); + console.log('size', data.length); + + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-1000k-same', function() { + var data = ''; + for (var i = 0; i < 10; ++i) { + data += macbeth.join('\n'); + } + console.log('size', data.length); + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-1000k-500-changes', function() { + var data = ''; + for (var i = 0; i < 10; ++i) { + data += macbeth.join('\n'); + } + var mcopy = []; + for (var i = 0; i < macbeth.length; ++i) { + mcopy[i] = macbeth[i]; + } + // array swap 50 lines + for (var i = 0; i < 500; ++i) { + var x1 = Math.floor((Math.random()*macbeth.length)); + var x2 = Math.floor((Math.random()*macbeth.length)); + var t = mcopy[x1]; + mcopy[x1] = mcopy[x2]; + mcopy[x2] = t; + } + var data2 = mcopy.join('\n'); + console.log('size', data.length); + + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-10MB-500-changes', function() { + var data = ''; + for (var i = 0; i < 100; ++i) { + data += macbeth.join('\n'); + } + var mcopy = []; + for (var i = 0; i < macbeth.length; ++i) { + mcopy[i] = macbeth[i]; + } + // array swap 50 lines + for (var i = 0; i < 500; ++i) { + var x1 = Math.floor((Math.random()*macbeth.length)); + var x2 = Math.floor((Math.random()*macbeth.length)); + var t = mcopy[x1]; + mcopy[x1] = mcopy[x2]; + mcopy[x2] = t; + } + var data2 = mcopy.join('\n'); + console.log('size', data.length); + + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-20MB-500-changes', function() { + var data = ''; + for (var i = 0; i < 200; ++i) { + data += macbeth.join('\n'); + } + var mcopy = []; + for (var i = 0; i < macbeth.length; ++i) { + mcopy[i] = macbeth[i]; + } + // array swap 50 lines + for (var i = 0; i < 500; ++i) { + var x1 = Math.floor((Math.random()*macbeth.length)); + var x2 = Math.floor((Math.random()*macbeth.length)); + var t = mcopy[x1]; + mcopy[x1] = mcopy[x2]; + mcopy[x2] = t; + } + var data2 = mcopy.join('\n'); + console.log('size', data.length); + + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.start(); + }(JsUnit)); +}); diff --git a/test/tests2.js b/test/tests2.js new file mode 100755 index 0000000..88e255e --- /dev/null +++ b/test/tests2.js @@ -0,0 +1,1094 @@ +Object.equals = function( x, y ) { + if ( x === y ) return true; + if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false; + if ( x.constructor !== y.constructor ) return false; + for ( var p in x ) { + if ( ! x.hasOwnProperty( p ) ) continue; + if ( ! y.hasOwnProperty( p ) ) return false; + if ( x[ p ] === y[ p ] ) continue; + if ( typeof( x[ p ] ) !== "object" ) return false; + if ( ! Object.equals( x[ p ], y[ p ] ) ) return false; + } + for ( p in y ) { + if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false; + } + return true; +} + +$(document).ready(function(){ + var DMERGELY = $('\ +
\ +
\ +
'); + + var init = function() { + $('body').append(DMERGELY); + console.log($('#test-mergely').length); + var mergely = new Mgly.mergely(); + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + return mergely; + }; + + (function (JsUnit) { + JsUnit.module('Tests'); + + + // Summary: + // Add one line to the rhs where the lhs is empty. + // Description: + // This tests inserting one line in the rhs only. The visual markup change for the + // lhs usually starts at the end of the change which, when drawing the border, is + // the bottom, but in this case, there are no lines, so there is no bottom. In this + // case, the border must be drawn on the top. The margin markup must also take this + // into account, and visually, it starts from the top of the line, rather than the + // bottom. The rhs markup starts and ends on the same line. + // Example: + // one + JsUnit.test('case-1-rhs-add-one-line', function() { + mergely = init(); + mergely.lhs(''); + mergely.rhs('one'); + + JsUnit.okay(mergely.get('lhs') == '', 'Expected ""'); + JsUnit.okay(mergely.get('rhs') == 'one', 'Expected "one"'); + var diff = '0a1\n'+ + '> one\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start']; + var notok_classes = ['d', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['d', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + for (var clazz in ['mergely', 'a', 'rhs', 'start', 'bg', 'end']) { + JsUnit.okay($.inArray(clazz, classes), 'Expected lhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // Summary: + // Add one line to the rhs at the end of text. + // Description: + // This tests inserting one line in the rhs only at the end of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts and ends on the same line. This is normal insertion. + // Example: + // one one + // two + JsUnit.test('case-2-rhs-add-one-line-after', function() { + mergely = init(); + mergely.lhs('one'); + mergely.rhs('one\ntwo'); + JsUnit.okay(mergely.get('lhs') == 'one', 'Expected "one"'); + JsUnit.okay(mergely.get('rhs') == 'one\ntwo', 'Expected "one two"'); + var diff = '1a2\n'+ + '> two\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start', 'end']; + var notok_classes = ['d', 'c', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(1); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['d', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 1, 'Expected rhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-to'] == 1, 'Expected rhs change to be finish at 1'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Add one line to the rhs before the start of text. + // description: + // This tests inserting one line in the rhs only before the start of text. The + // The visual markup change for the lhs starts at the end of the lhs change + // (normal case), and the rhs markup starts and ends on the same line. This is + // normal insertion. + // example: + // one 1 + // one + JsUnit.test('case-3-rhs-add-one-line-before', function() { + mergely = init(); + mergely.lhs('one'); + mergely.rhs('1\none'); + JsUnit.okay(mergely.get('lhs') == 'one', 'Expected "one"'); + JsUnit.okay(mergely.get('rhs') == '1\none', 'Expected "1 one"'); + var diff = '0a1\n'+ + '> 1\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start']; + var notok_classes = ['d', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['d', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Add one line to the rhs in the middle of text. + // description: + // This tests inserting one line in the rhs only in the middle of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts and ends on the same line. This is normal insertion. + // example: + // one one + // three two + // three + JsUnit.test('case-4-rhs-add-one-line-inbetween', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\nthree'); + mergely.rhs('one\ntwo\nthree'); + JsUnit.okay(mergely.get('lhs') == 'one\nthree', 'Expected "one three"'); + JsUnit.okay(mergely.get('rhs') == 'one\ntwo\nthree', 'Expected "one two three"'); + var diff = '1a2\n'+ + '> two\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start', 'end']; + var notok_classes = ['d', 'c', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(1); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['d', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 1, 'Expected rhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-to'] == 1, 'Expected rhs change to be finish at 1'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Add three lines to the rhs in the middle of text. + // description: + // This tests inserting three lines in the rhs only in the middle of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts on line one of the first change, the next line of the change has 'bg' markup + // and the final line has 'end' markup + // example: + // one one + // five two + // three + // four + // five + JsUnit.test('case-5-rhs-add-three-lines-inbetween', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\nfive'); + mergely.rhs('one\ntwo\nthree\nfour\nfive'); + JsUnit.okay(mergely.get('lhs') == 'one\nfive', 'Expected "one five"'); + JsUnit.okay(mergely.get('rhs') == 'one\ntwo\nthree\nfour\nfive', 'Expected "one two three four five"'); + var diff = '1a2,4\n'+ + '> two\n' + + '> three\n' + + '> four\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'lhs', 'start', 'end']; + var notok_classes = ['d', 'c', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(1); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'start']; + var notok_classes = ['d', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(2); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'bg']; + var notok_classes = ['d', 'c', 'start', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(3); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'a', 'rhs', 'end']; + var notok_classes = ['d', 'c', 'start', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 1, 'Expected rhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-to'] == 3, 'Expected rhs change to be finish at 3'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] < change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['lhs-y-end'] == change['lhs-y-start'] - valign, 'Expected lhs start/end to be the same'); + JsUnit.okay(change['rhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 3 - valign), 'Expected rhs end to be 3 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove one line from the lhs where the rhs is empty. + // description: + // This tests removing one line from the lhs only. The visual markup change for the + // rhs usually starts at the end of the change which, when drawing the border, is + // the bottom, but in this case, there are no lines, so there is no bottom. In this + // case, the border must be drawn on the top. The margin markup must also take this + // into account, and visually, it starts from the top of the line, rather than the + // bottom. The rhs markup starts and ends on the same line. + // example: + // one + JsUnit.test('case-6-lhs-remove-one-line', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one'); + mergely.rhs(''); + JsUnit.okay(mergely.get('rhs') == '', 'Expected ""'); + JsUnit.okay(mergely.get('lhs') == 'one', 'Expected "one"'); + var diff = '1d0\n'+ + '< one\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start', 'end', 'bg']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start']; + var notok_classes = ['a', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + for (var clazz in ['mergely', 'a', 'rhs', 'start', 'bg', 'end']) { + JsUnit.okay($.inArray(clazz, classes), 'Expected lhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be the different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['lhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected lhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove one line from the lhs at the end of text. + // description: + // This tests inserting one line in the rhs only at the end of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts and ends on the same line. This is normal insertion. + // example: + // one one + // two + JsUnit.test('case-7-lhs-remove-one-line-at-end', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\ntwo'); + mergely.rhs('one'); + JsUnit.okay(mergely.get('lhs') == 'one\ntwo', 'Expected "one two"'); + JsUnit.okay(mergely.get('rhs') == 'one', 'Expected "one"'); + var diff = '2d1\n'+ + '< two\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(1); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start', 'end']; + var notok_classes = ['a', 'c', 'bg']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 1, 'Expected lhs change to be start from 1'); + JsUnit.okay(change['lhs-line-to'] == 1, 'Expected lhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['rhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected rhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove one line from the lhs before the start of text. + // description: + // This tests removing one line from the lhs before the start of text. The + // The visual markup change for the lhs starts at the end of the lhs change + // (normal case), and the rhs markup starts and ends on the same line. This is + // normal insertion. + // example: + // 1 one + // one + JsUnit.test('case-8-lhs-remove-one-line-before', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('1\none'); + mergely.rhs('one'); + JsUnit.okay(mergely.get('lhs') == '1\none', 'Expected "1 one"'); + JsUnit.okay(mergely.get('rhs') == 'one', 'Expected "one"'); + var diff = '1d0\n'+ + '< 1\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(0); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start', 'end', 'bg']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 0, 'Expected lhs change to be start from 0'); + JsUnit.okay(change['lhs-line-to'] == 0, 'Expected lhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['lhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected lhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove one line from the lhs in the middle of text. + // description: + // This tests removing one line from the lhs in the middle of text. The visual markup + // change for the rhs start at the end of the rhs change (normal case), and the lhs + // markup starts and ends on the same line. This is normal delete. + // example: + // one one + // two three + // three + JsUnit.test('case-9-rhs-remove-one-line-inbetween', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\ntwo\nthree'); + mergely.rhs('one\nthree'); + JsUnit.okay(mergely.get('lhs') == 'one\ntwo\nthree', 'Expected "one two three"'); + JsUnit.okay(mergely.get('rhs') == 'one\nthree', 'Expected "one three"'); + var diff = '2d1\n'+ + '< two\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(1); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start', 'bg', 'end']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 1, 'Expected lhs change to be start from 1'); + JsUnit.okay(change['lhs-line-to'] == 1, 'Expected lhs change to be finish at 1'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['lhs-y-start'] + extents['em-height'] * 1 - valign), 'Expected lhs end to be 1 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + // summary: + // Remove three lines from the lhs in the middle of text. + // description: + // This tests the removal of three lines from the lhs in the middle of text. + // + // This tests inserting three lines in the rhs only in the middle of text. The visual markup + // change for the lhs starts at the end of the lhs change (normal case), and the rhs + // markup starts on line one of the first change, the next line of the change has 'bg' markup + // and the final line has 'end' markup + // example: + // one one + // two five + // three + // four + // five + JsUnit.test('case-10-lhs-remove-three-lines-inbetween', function() { + $('body').append($('
')); + var mergely = new Mgly.mergely; + mergely.init($('#mergely'), { + height: function(h) { return 400; } + }); + mergely.lhs('one\ntwo\nthree\nfour\nfive'); + mergely.rhs('one\nfive'); + JsUnit.okay(mergely.get('lhs') == 'one\ntwo\nthree\nfour\nfive', 'Expected "one two three four five"'); + JsUnit.okay(mergely.get('rhs') == 'one\nfive', 'Expected "one five"'); + var diff = '2,4d1\n'+ + '< two\n' + + '< three\n' + + '< four\n'; + JsUnit.okay(mergely.diff() == diff, 'Unexpected change diff'); + console.log('diff', mergely.diff()); + + var d = new Mgly.diff(mergely.get('lhs'), mergely.get('rhs')); + var changes = mergely._parse_diff('#mergely-lhs', '#mergely-rhs', d.normal_form()); + console.log('changes', changes); + changes = mergely._calculate_offsets('mergely-lhs', 'mergely-rhs', changes); + mergely._markup_changes('mergely-lhs', 'mergely-rhs', changes); + console.log('changes', changes); + JsUnit.okay(changes.length == 1, 'Expected 1 change'); + + // test lhs classes + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(1); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'start']; + var notok_classes = ['a', 'c', 'bg', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(2); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'bg']; + var notok_classes = ['a', 'c', 'start', 'end']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + var lhs_info = mergely.editor['mergely-lhs'].lineInfo(3); + var classes = lhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'lhs', 'end']; + var notok_classes = ['a', 'c', 'bg', 'start']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected lhs row to have class, "' + clazz + '", classes: ' + lhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect lhs row to have class, "' + clazz + '"'); + } + + // test rhs classes + var rhs_info = mergely.editor['mergely-rhs'].lineInfo(0); + var classes = rhs_info.bgClass.split(' '); + var ok_classes = ['mergely', 'd', 'rhs', 'start', 'bg', 'end']; + var notok_classes = ['a', 'c']; + for (var i in ok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, classes) >= 0, 'Expected rhs row to have class, "' + clazz + '", classes: ' + rhs_info.bgClass); + } + for (var i in notok_classes) { + var clazz = ok_classes[i]; + JsUnit.okay($.inArray(clazz, notok_classes) < 0, 'Did not expect rhs row to have class, "' + clazz + '"'); + } + + var extents = mergely._get_extents(); + console.log('extents', extents); + var valign = 2.0;//vertical, esthetic alignment + var change = changes[0]; + // diff + JsUnit.okay(change['lhs-line-from'] == 1, 'Expected lhs change to be start from 1'); + JsUnit.okay(change['lhs-line-to'] == 3, 'Expected lhs change to be finish at 3'); + JsUnit.okay(change['rhs-line-from'] == 0, 'Expected rhs change to be finish at 0'); + JsUnit.okay(change['rhs-line-to'] == 0, 'Expected rhs change to be finish at 0'); + // markup + JsUnit.okay(change['lhs-y-start'] == change['rhs-y-start'], 'Expected lhs/rhs start to be the same'); + JsUnit.okay(change['lhs-y-end'] != change['rhs-y-end'], 'Expected lhs/rhs end to be different'); + JsUnit.okay(change['lhs-y-start'] > 0.0, 'Expected lhs start to be more than 0'); + JsUnit.okay(change['lhs-y-end'] > 0.0, 'Expected lhs end to be more than 0'); + JsUnit.okay(change['rhs-y-start'] > 0.0, 'Expected rhs start to be more than 0'); + JsUnit.okay(change['rhs-y-end'] > 0.0, 'Expected rhs end to be more than 0'); + JsUnit.okay(change['rhs-y-end'] == change['rhs-y-start'] - valign, 'Expected rhs start/end to be the same'); + JsUnit.okay(change['lhs-y-end'] == (change['lhs-y-start'] + extents['em-height'] * 3 - valign), 'Expected rhs end to be 3 line more'); + + if (window[this.name] != true) { + mergely.unbind(); + $('#test-mergely').remove(); + } + window[this.name] = true; + }); + + JsUnit.test('case-11-diff-file-100k-same', function() { + var data = macbeth.join('\n'); + console.log('size', data.length); + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-100k-50-changes', function() { + var data = macbeth.join('\n'); + + // array swap 50 lines + var mcopy = []; + for (var i = 0; i < macbeth.length; ++i) { + mcopy[i] = macbeth[i]; + } + for (var i = 0; i < 50; ++i) { + var x1 = Math.floor((Math.random()*macbeth.length)); + var x2 = Math.floor((Math.random()*macbeth.length)); + var t = mcopy[x1]; + mcopy[x1] = mcopy[x2]; + mcopy[x2] = t; + } + var data2 = mcopy.join('\n'); + console.log('size', data.length); + + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-1000k-same', function() { + var data = ''; + for (var i = 0; i < 10; ++i) { + data += macbeth.join('\n'); + } + console.log('size', data.length); + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-1000k-500-changes', function() { + var data = ''; + for (var i = 0; i < 10; ++i) { + data += macbeth.join('\n'); + } + var mcopy = []; + for (var i = 0; i < macbeth.length; ++i) { + mcopy[i] = macbeth[i]; + } + // array swap 50 lines + for (var i = 0; i < 500; ++i) { + var x1 = Math.floor((Math.random()*macbeth.length)); + var x2 = Math.floor((Math.random()*macbeth.length)); + var t = mcopy[x1]; + mcopy[x1] = mcopy[x2]; + mcopy[x2] = t; + } + var data2 = mcopy.join('\n'); + console.log('size', data.length); + + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-10MB-500-changes', function() { + var data = ''; + for (var i = 0; i < 100; ++i) { + data += macbeth.join('\n'); + } + var mcopy = []; + for (var i = 0; i < macbeth.length; ++i) { + mcopy[i] = macbeth[i]; + } + // array swap 50 lines + for (var i = 0; i < 500; ++i) { + var x1 = Math.floor((Math.random()*macbeth.length)); + var x2 = Math.floor((Math.random()*macbeth.length)); + var t = mcopy[x1]; + mcopy[x1] = mcopy[x2]; + mcopy[x2] = t; + } + var data2 = mcopy.join('\n'); + console.log('size', data.length); + + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.test('case-11-diff-file-100MB-500-changes', function() { + var data = ''; + for (var i = 0; i < 1000; ++i) { + data += macbeth.join('\n'); + } + var mcopy = []; + for (var i = 0; i < macbeth.length; ++i) { + mcopy[i] = macbeth[i]; + } + // array swap 50 lines + for (var i = 0; i < 500; ++i) { + var x1 = Math.floor((Math.random()*macbeth.length)); + var x2 = Math.floor((Math.random()*macbeth.length)); + var t = mcopy[x1]; + mcopy[x1] = mcopy[x2]; + mcopy[x2] = t; + } + var data2 = mcopy.join('\n'); + console.log('size', data.length); + + var t0 = new Date().getTime(); + var diff = new Mgly.diff(data, data); + var t1 = new Date().getTime(); + console.log('diff', diff, 'time: ' + (t1 - t0)); + }); + + JsUnit.start(); + }(JsUnit)); +});