[Pkg-mozext-commits] [sieve-extension] 01/12: Imported Upstream version 0.2.3d

Michael Fladischer fladi-guest at moszumanska.debian.org
Wed Feb 12 08:20:49 UTC 2014


This is an automated email from the git hooks/post-receive script.

fladi-guest pushed a commit to branch master
in repository sieve-extension.

commit 7e30272f97873030658424c46282c0b2e08f0b6e
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Mon Jan 20 08:52:54 2014 +0100

    Imported Upstream version 0.2.3d
---
 bootstrap.js                                       |   16 +-
 .../content/components/SieveAccountManager.js      |   16 +-
 .../content/components/SieveProtocolHandler.js     |   32 +-
 chrome/chromeFiles/content/editor/Sieve.css        |    6 +-
 .../content/editor/SieveFilterEditor.html          |  143 +-
 .../content/editor/SieveFilterEditor.js            |   56 +-
 .../content/editor/SieveFilterExplorer.js          |   20 +-
 chrome/chromeFiles/content/editor/SieveStatus.js   |   26 +-
 chrome/chromeFiles/content/editor/SieveStatus.xul  |    2 +-
 .../content/filterList/SieveFilterList.js          |  219 +
 .../content/filterList/SieveFilterList.xul         |   50 +
 .../content/libs/CodeMirror/CONTRIBUTING.md        |   70 +
 chrome/chromeFiles/content/libs/CodeMirror/LICENSE |    2 +-
 .../chromeFiles/content/libs/CodeMirror/README.md  |    5 +-
 .../libs/CodeMirror/addon/edit/matchbrackets.js    |   74 +
 .../{lib/util => addon/search}/searchcursor.js     |   58 +-
 .../content/libs/CodeMirror/lib/codemirror.css     |  288 +-
 .../content/libs/CodeMirror/lib/codemirror.js      | 7073 +++++++++++++-------
 .../libs/CodeMirror/{ => mode/sieve}/LICENSE       |    6 +-
 .../content/libs/CodeMirror/mode/sieve/sieve.js    |  162 +-
 .../content/libs/CodeMirror/package.json           |    2 +-
 .../content/libs/jQuery/jquery-1.8.3.min.js        |    2 +
 .../libs/libManageSieve/SieveAbstractClient.js     |    2 +-
 .../RFC5228/widgets/simplicity/SieveActionUI.js    |   29 +
 .../RFC5228/widgets/simplicity/SieveBlocksUI.js    |  435 ++
 .../RFC5228/widgets/simplicity/SieveTestsUI.js     |   86 +
 .../content/libs/libSieveDOM/SieveGui.html         |    2 +-
 .../content/libs/libSieveDOM/SieveSimpleGui.html   |  184 +
 .../chromeFiles/content/libs/libSieveDOM/jquery.js |    4 +
 .../libs/libSieveDOM/toolkit/SieveParser.js        |    2 +-
 .../libs/libSieveDOM/toolkit/style/_style.css      |  182 -
 .../libSieveDOM/toolkit/style/simplicity/style.css |  216 +
 .../toolkit/style/stringlist/_style.css            |   73 +
 .../style/{ => stringlist}/listitem.add.png        |  Bin
 .../style/{ => stringlist}/listitem.delete.png     |  Bin
 .../style/{ => stringlist}/listitem.drop.png       |  Bin
 .../libSieveDOM/toolkit/style/stringlist/style.css |   71 +
 .../libs/libSieveDOM/toolkit/style/style.css       |   78 +-
 .../content/modules/overlays/SieveOverlay.jsm      |   13 +-
 .../modules/overlays/SieveOverlayManager.jsm       |   19 +-
 chrome/chromeFiles/content/modules/sieve/Sieve.js  |   35 +-
 .../content/modules/sieve/SieveAccounts.js         |  146 +-
 .../content/modules/sieve/SieveAutoConfig.js       |    6 +-
 .../content/modules/sieve/SieveRequest.js          |   35 +-
 .../content/modules/sieve/SieveResponseCodes.js    |    2 +-
 .../content/modules/sieve/SieveResponseParser.js   |  140 +-
 .../content/modules/sieve/SieveSession.js          |   23 +-
 .../content/options/SieveAccountOptions.xul        |    3 +-
 chrome/chromeFiles/locale/de-DE/locale.dtd         |    2 +-
 chrome/chromeFiles/locale/en-US/locale.dtd         |    2 +-
 chrome/chromeFiles/locale/es-ES/locale.dtd         |    2 +-
 chrome/chromeFiles/locale/fr-FR/locale.dtd         |    2 +-
 chrome/chromeFiles/locale/ru-RU/locale.dtd         |    2 +-
 install.rdf                                        |   18 +-
 54 files changed, 6970 insertions(+), 3172 deletions(-)

diff --git a/bootstrap.js b/bootstrap.js
index f24f226..59e10a2 100644
--- a/bootstrap.js
+++ b/bootstrap.js
@@ -1,3 +1,15 @@
+/*
+ * The contents of this file are licenced. You may obtain a copy of 
+ * the license at https://github.com/thsmi/sieve/ or request it via 
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ * 
+ * The initial author of the code is:
+ *   Thomas Schmid <schmid-thomas at gmx.net>
+ *      
+ */
+
 // Enable Strict Mode
 "use strict"; 
 
@@ -29,8 +41,8 @@ function startup(data, reason)
   
   SieveOverlayManager.addOverlay(
       SieveMailWindowOverlay,"chrome://messenger/content/messenger.xul");
-  /*SieveOverlayManager.addOverlay(
-      SieveFilterListOverlay,"chrome://messenger/content/FilterListDialog.xul");*/
+  SieveOverlayManager.addOverlay(
+      SieveFilterListOverlay,"chrome://messenger/content/FilterListDialog.xul");
 
   SieveOverlayManager.addOverlay(
       SieveToolbarOverlay, "chrome://global/content/customizeToolbar.xul");
diff --git a/chrome/chromeFiles/content/components/SieveAccountManager.js b/chrome/chromeFiles/content/components/SieveAccountManager.js
index f80ce89..f83f28d 100644
--- a/chrome/chromeFiles/content/components/SieveAccountManager.js
+++ b/chrome/chromeFiles/content/components/SieveAccountManager.js
@@ -1,3 +1,15 @@
+/*
+ * The contents of this file are licenced. You may obtain a copy of 
+ * the license at https://github.com/thsmi/sieve/ or request it via 
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ * 
+ * The initial author of the code is:
+ *   Thomas Schmid <schmid-thomas at gmx.net>
+ *      
+ */
+ 
 // Enable Strict Mode
 "use strict"
 
@@ -77,7 +89,7 @@ const SieveAccountManagerComponent = {
         SieveAccountManagerFactory);
     
     
-    var catMgr = Components.classes["@mozilla.org/categorymanager;1"]
+    var catMgr = Cc["@mozilla.org/categorymanager;1"]
                    .getService(Ci.nsICategoryManager);
                 
     catMgr.addCategoryEntry(
@@ -94,7 +106,7 @@ const SieveAccountManagerComponent = {
         SieveAccountManagerExtension.prototype.classID, 
         SieveAccountManagerFactory);
      
-    var catMgr = Components.classes["@mozilla.org/categorymanager;1"]
+    var catMgr = Cc["@mozilla.org/categorymanager;1"]
                    .getService(Ci.nsICategoryManager);
     catMgr.deleteCategoryEntry(
         "mailnews-accountmanager-extensions",
diff --git a/chrome/chromeFiles/content/components/SieveProtocolHandler.js b/chrome/chromeFiles/content/components/SieveProtocolHandler.js
index 3607fca..3d1dc28 100644
--- a/chrome/chromeFiles/content/components/SieveProtocolHandler.js
+++ b/chrome/chromeFiles/content/components/SieveProtocolHandler.js
@@ -1,7 +1,9 @@
 /* 
- * The contents of this file is licenced. You may obtain a copy of
- * the license at http://sieve.mozdev.org or request it via email 
- * from the author. Do not remove or change this comment. 
+ * The contents of this file are licenced. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via 
+ * email from the author. 
+ * 
+ * Do not remove or change this comment. 
  * 
  * The initial author of the code is:
  *   Thomas Schmid <schmid-thomas at gmx.net>
@@ -14,14 +16,11 @@
 
 var EXPORTED_SYMBOLS = [ "SieveProtocolHandlerComponent"];
 
-if (typeof(Cc) == 'undefined')
-  { var Cc = Components.classes; }
-
-if (typeof(Ci) == 'undefined')
-  { var Ci = Components.interfaces; }  
+const Cc = Components.classes; 
+const Ci = Components.interfaces;
+const Cr = Components.results;
 
-if (typeof(Cr) == 'undefined')
-  { var Cr = Components.results; }
+const protocolScheme = "x-sieve";
 
 /**
  * Implements an Protocol handler component for sieve. This is needed inorder
@@ -32,12 +31,13 @@ function SieveProtocolHandler() {};
 
 SieveProtocolHandler.prototype = 
 {
-  scheme : "x-sieve",    
-  defaultPort : 4190,
   
-  classID : Components.ID("{65f30660-14eb-11df-8351-0002a5d5c51b}"),
-  contactID : "@mozilla.org/network/protocol;1?name="+SieveProtocolHandler.prototype.scheme,
-  classDescription: SieveProtocolHandler.prototype.scheme+" protocol handler",  
+  classID : Components.ID("{65f30660-14eb-11da-8351-0002a5d5c51b}"),
+  classDescription: protocolScheme+" protocol handler",  
+  contactID : "@mozilla.org/network/protocol;1?name="+protocolScheme,
+  
+  scheme : protocolScheme,
+  defaultPort : 4190,  
   
   protocolFlags :
     Ci.nsIProtocolHandler.URI_NORELATIVE |
@@ -49,7 +49,7 @@ SieveProtocolHandler.prototype =
   
   allowPort : function(port, scheme)
   {
-    if (scheme==this.scheme)
+    if (scheme == this.scheme)
       return true;
     else
       return false;    
diff --git a/chrome/chromeFiles/content/editor/Sieve.css b/chrome/chromeFiles/content/editor/Sieve.css
index a1898aa..f624578 100644
--- a/chrome/chromeFiles/content/editor/Sieve.css
+++ b/chrome/chromeFiles/content/editor/Sieve.css
@@ -94,7 +94,7 @@
 	border: 1px solid black; 
 	padding: 5px; 
 	margin-top : 5px;
-	-moz-border-radius: 5px;	
+	border-radius: 5px;	
 }
 
 /*#sivExplorerError description
@@ -110,7 +110,7 @@
   border: 1px solid ThreeDShadow;
   padding: 20px;
    
-  -moz-border-radius: 5px;  
+  border-radius: 5px;  
 }
 
 #sivAutoConfigInfo 
@@ -152,7 +152,7 @@
 	border: 1px solid black; 
 	padding: 5px; 
 	margin-top : 5px;
-	-moz-border-radius: 5px;	
+	border-radius: 5px;	
 }
 
 #StatusWarning description
diff --git a/chrome/chromeFiles/content/editor/SieveFilterEditor.html b/chrome/chromeFiles/content/editor/SieveFilterEditor.html
index 4e84307..e5235c1 100644
--- a/chrome/chromeFiles/content/editor/SieveFilterEditor.html
+++ b/chrome/chromeFiles/content/editor/SieveFilterEditor.html
@@ -1,78 +1,99 @@
 <!doctype html>
 <html>
   <head>
-    <title>test</title>
+    <title>Sieve Editor</title>
+	
+    <meta charset="utf-8">	
+	
     <link rel="stylesheet" href="./../libs/CodeMirror/lib/codemirror.css">
+	  <link rel="stylesheet" href="./../libs/CodeMirror/theme/eclipse.css">	
+	
     <script src="./../libs/CodeMirror/lib/codemirror.js"></script>
-	<script src="./../libs/CodeMirror/lib/util/searchcursor.js"></script>
-	<link rel="stylesheet" href="./../libs/CodeMirror/theme/eclipse.css">	
+    <script src="./../libs/CodeMirror/addon/search/searchcursor.js"></script>
+    <script src="./../libs/CodeMirror/addon/edit/matchbrackets.js"></script>
+	
     <script src="./../libs/CodeMirror/mode/sieve/sieve.js"></script>
+	
     <style>
-	    .CodeMirror-fullscreen {
-	      display: block;
-          position: absolute;
-          top: 0; left: 0;
-          width: 100%;
-          z-index: 9999;
-		  background-image: url(chrome://sieve/content/images/splitter.png)  ; 
-		  background-repeat: repeat-y;
-		  background-color:white;
-		  background-position: 650px top;
-		  background-attachment: fixed;
+      .CodeMirror-fullscreen {
+        display: block;
+        position: absolute;
+        top: 0; left: 0;
+        width: 100%;
+        z-index: 9999;
 		  
-		  font-family: -moz-fixed;
-font-size: 12px;
-line-height: normal;
-
-		}
-		.CodeMirror-gutter {
-          border: none;
-          border-right: 1px solid #A9B7C9;
-          -moz-transition: border-width .3s ease-in;	
-      background: #f8f8f8;
-	  min-width:40px;
-	}
+  	    background-image: url(chrome://sieve/content/images/splitter.png)  ; 
+  	    background-repeat: repeat-y;
+  	    background-color:white;
+  	    background-position: 650px top;
+        background-attachment: fixed;
+		  
+		    font-family: -moz-fixed;
+        font-size: 12px;
+        line-height: normal;
+		  }
+		
+		  .CodeMirror-gutter {
+        border: none;
+        border-right: 1px solid #A9B7C9;
+        -moz-transition: border-width .3s ease-in;	
+        background: #f8f8f8;
+	      min-width:40px;
+	    }
 
-	   body { margin:0px;}
-	   .activeline {background: #e8f2ff !important;}
+	    body { margin:0px;}
+	   
+	    .activeline {background: #e8f2ff !important;}	   
 	 </style>
   </head>
+  
   <body>
+    <form>
     <textarea id="code" name="code"></textarea>
-    <script>	
-    function winHeight() {
-      return window.innerHeight || (document.documentElement || document.body).clientHeight;
-    }
-	
-    function setFullScreen(cm) {
-      var wrap = cm.getWrapperElement(), scroll = cm.getScrollerElement();
-     
-      wrap.className += " CodeMirror-fullscreen";
-      scroll.style.height = winHeight() + "px";
-      document.documentElement.style.overflow = "hidden";
-	  
-      cm.refresh();
-    }
-	
-    CodeMirror.connect(window, "resize", function() {
-      var showing = document.body.getElementsByClassName("CodeMirror-fullscreen")[0];
-      if (!showing) return;
-      showing.CodeMirror.getScrollerElement().style.height = winHeight() + "px";
-    });
-		
+    </form> 
+    
+    <script>
+ 
+      function winHeight() {
+        return window.innerHeight || (document.documentElement || document.body).clientHeight;
+      }
+  
+      function setFullScreen(cm) {
+        var wrap = cm.getWrapperElement();
+        wrap.className += " CodeMirror-fullscreen";
+        wrap.style.height = winHeight() + "px";
+        document.documentElement.style.overflow = "hidden";
+        cm.refresh();
+      }
+    
+      CodeMirror.on(window, "resize", function() {
+        document.body.getElementsByClassName("CodeMirror-fullscreen")[0]
+        .CodeMirror.getWrapperElement().style.height = winHeight() + "px";
+      });
+    
       var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
-	    lineNumbers: true,
-		theme: "eclipse",
-	   onCursorActivity: function() {
-         editor.setLineClass(hlLine, null, null);
-          hlLine = editor.setLineClass(editor.getCursor().line, null, "activeline");
-        }	
-	  });
-	  
-	  var hlLine = editor.setLineClass(0, "activeline");	  	  
-	  setFullScreen(editor)
-
-	  
+        lineNumbers: true,
+        theme: "eclipse",
+        matchBrackets: true
+      });
+  
+      setFullScreen(editor,true);
+    
+      var hlLine = editor.addLineClass(0, "background", "activeline");
+    
+      // This function is called externaly upon reloading and updating scripts  
+      function onActiveLineChange() 
+      {
+        var cur = editor.getLineHandle(editor.getCursor().line);
+        
+        if (cur != hlLine) {
+          editor.removeLineClass(hlLine, "background", "activeline");
+          hlLine = editor.addLineClass(cur, "background", "activeline");
+        }
+      }
+      
+      editor.on("cursorActivity", onActiveLineChange);    
+    
     </script>
   </body>
 </html>
diff --git a/chrome/chromeFiles/content/editor/SieveFilterEditor.js b/chrome/chromeFiles/content/editor/SieveFilterEditor.js
index df1bb9c..2523a47 100644
--- a/chrome/chromeFiles/content/editor/SieveFilterEditor.js
+++ b/chrome/chromeFiles/content/editor/SieveFilterEditor.js
@@ -261,12 +261,14 @@ SieveFilterEditor.prototype.onScriptLoaded
 
   var editor = document.getElementById("sivEditor2").contentWindow.editor;
   
-  editor.setValue(script);
   editor.setCursor({line:0,ch:0});
+  editor.setValue(script);
   editor.clearHistory();
-  
+
+    
   // Ugly workaround...
-  editor.getOption("onCursorActivity")();  
+  document.getElementById("sivEditor2").contentWindow.onActiveLineChange();
+
 
   if (gEditorStatus.checksum.server == null) {
     gEditorStatus.checksum.server = this._calcChecksum(this.getScript());
@@ -282,7 +284,7 @@ SieveFilterEditor.prototype.observe
 {
   if (aTopic == "quit-application-requested")
   {
-    // we are asychnonous, so need to trigger the evet if we are done...
+    // we are asychnonous, so need to trigger the event if we are done...
     var callback = function () {
       var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                            .createInstance(Ci.nsISupportsPRBool);
@@ -458,8 +460,8 @@ function onWindowPersist()
 function onWindowLoad()
 {
   document.getElementById("sivEditor2")
-    .contentWindow.editor.setOption("onChange",onInput);
-    
+    .contentWindow.editor.on("change",onInput);
+  
   // hack to prevent links to be opened in the default browser window...
   document.getElementById("ifSideBar").
     addEventListener(
@@ -570,8 +572,6 @@ function onViewSource(visible,aNoUpdate)
 
     document.getElementById("sivEditor2").contentWindow.editor.setValue(script);
     
-    
-    
     onInput();
     return;
   }
@@ -692,7 +692,7 @@ function onSideBarGo(uri)
       "DOMContentLoaded", function(event) { onSideBarLoading(false); }, false);*/
   if (document.getElementById("ifSideBar").addEventListener)
     document.getElementById("ifSideBar").addEventListener(
-      "DOMContentLoaded", function(event) {	onSideBarLoading(false); }, false);
+      "DOMContentLoaded", function(event) { onSideBarLoading(false); }, false);
   
   document.getElementById("ifSideBar").setAttribute('src', uri);
 }
@@ -825,34 +825,34 @@ function onImport()
 function onExport()
 {
   var filePicker = Cc["@mozilla.org/filepicker;1"]
-			.createInstance(Ci.nsIFilePicker);
+      .createInstance(Ci.nsIFilePicker);
 
-	filePicker.defaultExtension = ".siv";
-	filePicker.defaultString = gSFE.getScriptName() + ".siv";
+  filePicker.defaultExtension = ".siv";
+  filePicker.defaultString = gSFE.getScriptName() + ".siv";
 
-	filePicker.appendFilter("Sieve Scripts (*.siv)", "*.siv");
-	filePicker.appendFilter("Text Files (*.txt)", "*.txt");
-	filePicker.appendFilter("All Files (*.*)", "*.*");
-	filePicker.init(window, "Export Sieve Script", filePicker.modeSave);
+  filePicker.appendFilter("Sieve Scripts (*.siv)", "*.siv");
+  filePicker.appendFilter("Text Files (*.txt)", "*.txt");
+  filePicker.appendFilter("All Files (*.*)", "*.*");
+  filePicker.init(window, "Export Sieve Script", filePicker.modeSave);
 
-	var result = filePicker.show();
+  var result = filePicker.show();
 
-	if ((result != filePicker.returnOK) && (result != filePicker.returnReplace))
-		return;
+  if ((result != filePicker.returnOK) && (result != filePicker.returnReplace))
+    return;
 
-	var file = filePicker.file;
+  var file = filePicker.file;
 
-	if (file.exists() == false)
-		file.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+  if (file.exists() == false)
+    file.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
 
-	var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
-			.createInstance(Ci.nsIFileOutputStream);
+  var outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
+      .createInstance(Ci.nsIFileOutputStream);
 
-	outputStream.init(file, 0x04 | 0x08 | 0x20, parseInt("0644", 8), null);
+  outputStream.init(file, 0x04 | 0x08 | 0x20, parseInt("0644", 8), null);
 
-	var data = gSFE.getScript();
-	outputStream.write(data, data.length);
-	outputStream.close();
+  var data = gSFE.getScript();
+  outputStream.write(data, data.length);
+  outputStream.close();
 }
 
 function onErrorBar(visible,aSilent)
diff --git a/chrome/chromeFiles/content/editor/SieveFilterExplorer.js b/chrome/chromeFiles/content/editor/SieveFilterExplorer.js
index 788d6cf..202de29 100644
--- a/chrome/chromeFiles/content/editor/SieveFilterExplorer.js
+++ b/chrome/chromeFiles/content/editor/SieveFilterExplorer.js
@@ -62,7 +62,9 @@ SieveFilterExplorer.prototype.onListScriptResponse
     tree.view.selection.select(0);
       
   this.onStatusChange(0);
-  //TODO force repainting treeview...
+  
+  // force repainting treeview to speedup the ui...
+  tree.treeBoxObject.invalidate();
 }
 
 SieveFilterExplorer.prototype.onSetActiveResponse
@@ -106,7 +108,6 @@ SieveFilterExplorer.prototype._renameScript
   // ... So we try hard and double check our cached scriptnames for possible...
   // ... conflicts inoder to prevent possible dataloss.
   
-  // TODO throw an error instead of retuning null...
   var tree = document.getElementById('treeImapRules');  
     for(var i = 0; i < this._view.rules.length; i++)    
       if (this._view.rules[i].script == newName)
@@ -168,10 +169,6 @@ var gSFE = new SieveFilterExplorer();
 function onWindowLoad()
 {
 
-//	var actList = document.getElementById("conImapAcct");
-//	var actpopup = document.createElement("menupopup");
-//	actList.appendChild(actpopup);
-
   // now create a logger session...
   if (gLogger == null)
     gLogger = Cc["@mozilla.org/consoleservice;1"]
@@ -561,7 +558,16 @@ function onActivateClick()
 function onTreeDblClick(ev)
 {
   var tree = document.getElementById('treeImapRules');
-  // TODO test if tree is visible
+  
+  // test if tree element is visible 
+  var style = window.getComputedStyle(tree, "");
+  
+  if (style.display == 'none')
+    return;
+  
+  if (style.visibility == 'hidden')
+    return false  
+
   var row = {}, column = {}, part = {};
   
   tree.treeBoxObject.getCellAt(ev.clientX, ev.clientY, row, column, part);
diff --git a/chrome/chromeFiles/content/editor/SieveStatus.js b/chrome/chromeFiles/content/editor/SieveStatus.js
index de58a6b..c99992a 100644
--- a/chrome/chromeFiles/content/editor/SieveStatus.js
+++ b/chrome/chromeFiles/content/editor/SieveStatus.js
@@ -98,11 +98,23 @@ function onBadCertOverride(targetSite,permanent)
   {
     var overrideService = Cc["@mozilla.org/security/certoverride;1"]
                             .getService(Ci.nsICertOverrideService);
-
-    var recentCertsSvc = Cc["@mozilla.org/security/recentbadcerts;1"]
-                             .getService(Ci.nsIRecentBadCertsService);
-                             
-    var status = recentCertsSvc.getRecentBadCert(targetSite);    
+ 
+    var status = null;
+    
+    if (Cc["@mozilla.org/security/recentbadcerts;1"])
+    {
+      status = Cc["@mozilla.org/security/recentbadcerts;1"]
+                   .getService(Ci.nsIRecentBadCertsService)
+                   .getRecentBadCert(targetSite); 
+    }
+    else
+    {
+      status = Cc["@mozilla.org/security/x509certdb;1"]
+                   .getService(Ci.nsIX509CertDB)
+                   .getRecentBadCerts(false)
+                   .getRecentBadCert(targetSite);
+    }
+    
     if (!status)
       throw "No certificate stored for taget Site..."
 
@@ -126,7 +138,7 @@ function onBadCertOverride(targetSite,permanent)
   catch (ex)
   {
     onStatus(2,"error.brokencert");
-    gLogger.logStringMessage("onBadCertOverride:"+ex); 
+    Cu.reportError(ex); 
   }
  
 }
@@ -144,7 +156,7 @@ function onDetach()
 }
 
 /**
- * The callback is invoced inf the user wants to reconnect 
+ * The callback is invoced when the user wants to reconnect 
  * 
  * @param {} account
  * @param {} callback
diff --git a/chrome/chromeFiles/content/editor/SieveStatus.xul b/chrome/chromeFiles/content/editor/SieveStatus.xul
index 7d3e697..a21e469 100644
--- a/chrome/chromeFiles/content/editor/SieveStatus.xul
+++ b/chrome/chromeFiles/content/editor/SieveStatus.xul
@@ -196,7 +196,7 @@
         <image pack="center" src="chrome://sieve/content/images/syntax-error.png"/>
         <vbox>
           <description style="font-weight:bold">&status.error;</description>
-          <description id="StatusErrorMsg" pack="center" ></description>
+          <description id="StatusErrorMsg" pack="center"> </description>
           <hbox align="right">
             <button label="&status.error.reconnect;" oncommand="onReconnectClick();" />
           </hbox>                    
diff --git a/chrome/chromeFiles/content/filterList/SieveFilterList.js b/chrome/chromeFiles/content/filterList/SieveFilterList.js
new file mode 100644
index 0000000..b8534db
--- /dev/null
+++ b/chrome/chromeFiles/content/filterList/SieveFilterList.js
@@ -0,0 +1,219 @@
+/*
+ * The content of this file is licenced. You may obtain a copy of the license
+ * at http://sieve.mozdev.org or request it via email from the author. 
+ *
+ * Do not remove or change this comment.
+ * 
+ * The initial author of the code is:
+ *   Thomas Schmid <schmid-thomas at gmx.net>
+ *      
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("chrome://sieve/content/modules/overlays/SieveOverlayManager.jsm");
+
+SieveOverlayManager.require("/sieve/SieveConnectionManager.js",this,window);
+SieveOverlayManager.require("/sieve/SieveAccounts.js",this,window);
+
+
+function errorhandler(msg, url, line)
+{
+  alert(msg);
+  Cu.reportError(msg);
+}
+  
+window.onerror = errorhandler;
+  
+  
+// TODO möglichkeit bauen einen FilterList Dialog an die GUI zu binden bzw. 
+// davon zu befreien. Dadurch wird garantiert dass immer nur die aktuelle
+// Session angezeigt wird.
+
+
+function SieveFilterListDialog()
+{
+  SieveAbstractClient.call(this);
+  this._script = "Thunderbird Mailfilters"
+}
+
+SieveFilterListDialog.prototype.__proto__ = SieveAbstractClient.prototype;
+
+SieveFilterListDialog.prototype.onListScriptResponse
+    = function(response)
+{
+  var scripts = response.getScripts();
+  
+  for (var i=0; i<scripts.length; i++)
+  {
+    if (!scripts[i].active)
+      continue;
+      
+    this._script = scripts[i].script;
+      
+    this.getScript(this._script);
+    return;
+  }
+  
+  try {
+   
+     var capabilities = SieveConnections
+      .getChannel(this._sid,this._cid).extensions;
+        
+  document.getElementById("sivContent")
+    .contentWindow.setSieveScript("",capabilities);
+ 
+  this.onStatusChange(0);
+  }
+  catch (ex) {
+    alert(ex);
+  }
+  
+  
+}
+
+SieveFilterListDialog.prototype.onGetScriptResponse
+    = function(response)
+{
+  try {
+
+     var capabilities = SieveConnections
+      .getChannel(this._sid,this._cid).extensions;
+            
+  document.getElementById("sivContent")
+    .contentWindow.setSieveScript(response.getScriptBody(),capabilities);
+  }
+  catch (ex) {
+    alert(ex);
+  }
+    
+  this.onStatusChange(0); 
+}
+
+
+      
+SieveFilterListDialog.prototype.onChannelClosed
+    = function()
+{
+  // a channel is usually closed when a child window is closed. 
+  // it might be a good idea to check if the script was changed.
+}
+  
+  
+SieveFilterListDialog.prototype.onChannelReady
+    = function(cid)
+{
+  // We observe only our channel...
+  if (cid != this._cid)
+    return;
+      
+  this.listScript();
+  
+  // Step 1: List script
+  
+  // get active if any
+}
+  
+SieveFilterListDialog.prototype.onStatusChange
+    = function(state,message)
+{             
+  // Script ready
+  if (state == 0)
+  {
+    document.getElementById("sivStatus").setAttribute('hidden','true');    
+    document.getElementById('sivContent').removeAttribute('hidden');
+    return;
+  }
+  
+  // The rest has to be redirected to the status window...
+  //document.getElementById('sivExplorerTree').setAttribute('collapsed','true');    
+  document.getElementById("sivStatus").contentWindow.onStatus(state,message)
+  document.getElementById("sivStatus").removeAttribute('hidden');
+  document.getElementById('sivContent').setAttribute('hidden','true');  
+}
+
+
+var gSFLD = new SieveFilterListDialog();
+
+
+/*function onCanChangeAccount(key)
+{
+  if (!gSFLD)
+    return true;
+  
+  if (gSFLD._key != key)
+    return true;
+    
+  return false;  
+}*/
+  
+function onLoad()
+{
+  var key = window.frameElement.getAttribute("key");                         
+                  
+  var account = SieveAccountManager.getAccountByName(key);
+    
+  document.getElementById("sivStatus").contentWindow
+    .onAttach(account,function() { onLoad() });
+    
+  // the content is heavy weight javascript. So load it lazily
+  var iframe = document.getElementById("sivContent")
+  
+  if (iframe.hasAttribute("src"))
+    iframe.contentWindow.location.reload();
+  else
+    iframe.setAttribute("src","chrome://sieve/content/libs/libSieveDOM/SieveSimpleGui.html");
+        
+  if ((!account.isEnabled()) || account.isFirstRun())
+  {
+    account.setFirstRun();
+    gSFLD.onStatusChange(8);
+    
+    return;
+  }
+  
+  gSFLD.onStatusChange(3,"progress.connecting");  
+
+  gSFLD.connect(account);
+   
+}
+
+window.onunload = function ()
+{
+  if (gSFLD)
+    gSFLD.disconnect();
+}
+
+
+
+/*
+ * StartUp...
+ * 
+ * Step 1
+ * 0. Connect
+ * 1. Get Scripts
+ * 
+ * 2. Script Active?
+ *   2a. No create a new Filter named "Thunderbird"
+ *   2b. Make Filter Active
+ *   
+ * 3. Get Active Script
+ * 
+ * 4. pass script to iframe
+ * 
+ * 
+ * On Change:
+ * 1. is script empty?
+ *  1a delete
+ *  1b stop;
+ *  
+ * 2. put script 
+ *   
+ * 
+ * On Close
+ *   Disconnect...
+ *  
+ */
+
diff --git a/chrome/chromeFiles/content/filterList/SieveFilterList.xul b/chrome/chromeFiles/content/filterList/SieveFilterList.xul
new file mode 100644
index 0000000..f93751e
--- /dev/null
+++ b/chrome/chromeFiles/content/filterList/SieveFilterList.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+ <!--
+   The content of this file is licenced. You may obtain a copy of
+   the license at http://sieve.mozdev.org or request it via email 
+   from the author. Do not remove or change this comment. 
+  
+   The initial author of the code is:
+     Thomas Schmid <schmid-thomas at gmx.net>
+-->
+
+<!DOCTYPE window SYSTEM "chrome://sieve/locale/locale.dtd">
+
+<?xml-stylesheet href="chrome://sieve/content/editor/Sieve.css" type="text/css"?>
+
+<!--
+  As other extension may override thunderbird's filter dialog, we we use this
+  warpper to isolate all sieve related code. As side effect we do not need to care
+  about namespace pollution etc...
+  
+  An extra plus is the onload and onclose event,  
+  
+  
+  all code into a separate compartment.
+  It's just a wrapper to isolate all sieve related function from thunderbird's
+  default filter dialog.  
+-->
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 
+      onclose="return onClose();"
+      onload="return onLoad();">
+
+ <!-- <script type="application/javascript" 
+          src="chrome://sieve/content/libs/libManageSieve/SieveAccounts.js"/>
+  <script type="application/javascript" 
+          src="chrome://sieve/content/libs/libManageSieve/SieveRequest.js"/>
+  <script type="application/javascript" 
+          src="chrome://sieve/content/libs/libManageSieve/SieveResponseCodes.js"/>-->
+          
+  <script type="application/javascript" 
+          src="chrome://sieve/content/libs/libManageSieve/SieveAbstractClient.js"/>
+                    
+  <script type="application/javascript"
+          src="chrome://sieve/content/filterList/SieveFilterList.js"/>           
+
+  <iframe src="chrome://sieve/content/editor/SieveStatus.xul" 
+          id="sivStatus" flex="1" />
+  <iframe id="sivContent" flex="1" hidden="true" />
+
+</window>
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/CONTRIBUTING.md b/chrome/chromeFiles/content/libs/CodeMirror/CONTRIBUTING.md
new file mode 100644
index 0000000..afc1837
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/CodeMirror/CONTRIBUTING.md
@@ -0,0 +1,70 @@
+# How to contribute
+
+- [Getting help](#getting-help-)
+- [Submitting bug reports](#submitting-bug-reports-)
+- [Contributing code](#contributing-code-)
+
+## Getting help [^](#how-to-contribute)
+
+Community discussion, questions, and informal bug reporting is done on the
+[CodeMirror Google group](http://groups.google.com/group/codemirror).
+
+## Submitting bug reports [^](#how-to-contribute)
+
+The preferred way to report bugs is to use the
+[GitHub issue tracker](http://github.com/marijnh/CodeMirror/issues). Before
+reporting a bug, read these pointers.
+
+**Note:** The issue tracker is for *bugs*, not requests for help. Questions
+should be asked on the
+[CodeMirror Google group](http://groups.google.com/group/codemirror) instead.
+
+### Reporting bugs effectively
+
+- CodeMirror is maintained by volunteers. They don't owe you anything, so be
+  polite. Reports with an indignant or belligerent tone tend to be moved to the
+  bottom of the pile.
+
+- Include information about **the browser in which the problem occurred**. Even
+  if you tested several browsers, and the problem occurred in all of them,
+  mention this fact in the bug report. Also include browser version numbers and
+  the operating system that you're on.
+
+- Mention which release of CodeMirror you're using. Preferably, try also with
+  the current development snapshot, to ensure the problem has not already been
+  fixed.
+
+- Mention very precisely what went wrong. "X is broken" is not a good bug
+  report. What did you expect to happen? What happened instead? Describe the
+  exact steps a maintainer has to take to make the problem occur. We can not
+  fix something that we can not observe.
+
+- If the problem can not be reproduced in any of the demos included in the
+  CodeMirror distribution, please provide an HTML document that demonstrates
+  the problem. The best way to do this is to go to
+  [jsbin.com](http://jsbin.com/ihunin/edit), enter it there, press save, and
+  include the resulting link in your bug report.
+
+## Contributing code [^](#how-to-contribute)
+
+- Make sure you have a [GitHub Account](https://github.com/signup/free)
+- Fork [CodeMirror](https://github.com/marijnh/CodeMirror/)
+  ([how to fork a repo](https://help.github.com/articles/fork-a-repo))
+- Make your changes
+- If your changes are easy to test or likely to regress, add tests.
+  Tests for the core go into `test/test.js`, some modes have their own
+  test suite under `mode/XXX/test.js`. Feel free to add new test
+  suites to modes that don't have one yet (be sure to link the new
+  tests into `test/index.html`).
+- Make sure all tests pass. Visit `test/index.html` in your browser to
+  run them.
+- Submit a pull request
+([how to create a pull request](https://help.github.com/articles/fork-a-repo))
+
+### Coding standards
+
+- 2 spaces per indentation level, no tabs.
+- Include semicolons after statements.
+- Note that the linter (`test/lint/lint.js`) which is run after each
+  commit complains about unused variables and functions. Prefix their
+  names with an underscore to muffle it.
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/LICENSE b/chrome/chromeFiles/content/libs/CodeMirror/LICENSE
index 3916e96..482d55e 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/LICENSE
+++ b/chrome/chromeFiles/content/libs/CodeMirror/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2012 by Marijn Haverbeke <marijnh at gmail.com>
+Copyright (C) 2013 by Marijn Haverbeke <marijnh at gmail.com>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/README.md b/chrome/chromeFiles/content/libs/CodeMirror/README.md
index 8ed9871..976584e 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/README.md
+++ b/chrome/chromeFiles/content/libs/CodeMirror/README.md
@@ -4,5 +4,6 @@ CodeMirror is a JavaScript component that provides a code editor in
 the browser. When a mode is available for the language you are coding
 in, it will color your code, and optionally help with indentation.
 
-The project page is http://codemirror.net
-The manual is at http://codemirror.net/doc/manual.html
+The project page is http://codemirror.net  
+The manual is at http://codemirror.net/doc/manual.html  
+The contributing guidelines are in [CONTRIBUTING.md](https://github.com/marijnh/CodeMirror/blob/master/CONTRIBUTING.md)
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/matchbrackets.js b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/matchbrackets.js
new file mode 100644
index 0000000..f4925b7
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/matchbrackets.js
@@ -0,0 +1,74 @@
+(function() {
+  var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
+    (document.documentMode == null || document.documentMode < 8);
+
+  var Pos = CodeMirror.Pos;
+  // Disable brace matching in long lines, since it'll cause hugely slow updates  
+  var maxLineLen = 1000;
+
+  var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
+  function findMatchingBracket(cm) {
+    var cur = cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
+    var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
+    if (!match) return null;
+    var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
+    var style = cm.getTokenAt(Pos(cur.line, pos + 1)).type;
+
+    var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
+    function scan(line, lineNo, start) {
+      if (!line.text) return;
+      var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
+      if (start != null) pos = start + d;
+      for (; pos != end; pos += d) {
+        var ch = line.text.charAt(pos);
+        if (re.test(ch) && cm.getTokenAt(Pos(lineNo, pos + 1)).type == style) {
+          var match = matching[ch];
+          if (match.charAt(1) == ">" == forward) stack.push(ch);
+          else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
+          else if (!stack.length) return {pos: pos, match: true};
+        }
+      }
+    }
+    for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) {
+      if (i == cur.line) found = scan(line, i, pos);
+      else found = scan(cm.getLineHandle(i), i);
+      if (found) break;
+    }
+    return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos), match: found && found.match};
+  }
+
+  function matchBrackets(cm, autoclear) {
+    var found = findMatchingBracket(cm);
+    if (!found || cm.getLine(found.from.line).length > maxLineLen ||
+       found.to && cm.getLine(found.to.line).length > maxLineLen)
+      return;
+
+    var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
+    var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style});
+    var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style});
+    // Kludge to work around the IE bug from issue #1193, where text
+    // input stops going to the textare whever this fires.
+    if (ie_lt8 && cm.state.focused) cm.display.input.focus();
+    var clear = function() {
+      cm.operation(function() { one.clear(); two && two.clear(); });
+    };
+    if (autoclear) setTimeout(clear, 800);
+    else return clear;
+  }
+
+  var currentlyHighlighted = null;
+  function doMatchBrackets(cm) {
+    cm.operation(function() {
+      if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
+      if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
+    });
+  }
+
+  CodeMirror.defineOption("matchBrackets", false, function(cm, val) {
+    if (val) cm.on("cursorActivity", doMatchBrackets);
+    else cm.off("cursorActivity", doMatchBrackets);
+  });
+
+  CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
+  CodeMirror.defineExtension("findMatchingBracket", function(){return findMatchingBracket(this);});
+})();
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/lib/util/searchcursor.js b/chrome/chromeFiles/content/libs/CodeMirror/addon/search/searchcursor.js
similarity index 69%
rename from chrome/chromeFiles/content/libs/CodeMirror/lib/util/searchcursor.js
rename to chrome/chromeFiles/content/libs/CodeMirror/addon/search/searchcursor.js
index 970af89..e6554ce 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/lib/util/searchcursor.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/addon/search/searchcursor.js
@@ -1,9 +1,11 @@
 (function(){
+  var Pos = CodeMirror.Pos;
+
   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};
+    pos = pos ? cm.clipPos(pos) : Pos(0, 0);
     this.pos = {from: pos, to: pos};
 
     // The matches method is filled in based on the type of query.
@@ -17,22 +19,22 @@
           query.lastIndex = 0;
           var line = cm.getLine(pos.line).slice(0, pos.ch), match = query.exec(line), start = 0;
           while (match) {
-            start += match.index;
-            line = line.slice(match.index);
+            start += match.index + 1;
+            line = line.slice(start);
             query.lastIndex = 0;
             var newmatch = query.exec(line);
             if (newmatch) match = newmatch;
             else break;
-            start++;
           }
+          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},
+        if (match && match[0])
+          return {from: Pos(pos.line, start),
+                  to: Pos(pos.line, start + match[0].length),
                   match: match};
       };
     } else { // String query
@@ -40,15 +42,21 @@
       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
+      if (target.length == 1) {
+        if (!query.length) {
+          // Empty string would match anything and never progress, so
+          // we define it to match nothing instead.
+          this.matches = function() {};
+        } else {
+          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: Pos(pos.line, match),
+                      to: Pos(pos.line, 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));
@@ -66,10 +74,11 @@
             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};
+            var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
             return {from: reverse ? end : start, to: reverse ? start : end};
           }
         };
+      }
     }
   }
 
@@ -80,7 +89,7 @@
     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};
+        var pos = Pos(line, 0);
         self.pos = {from: pos, to: pos};
         self.atOccurrence = false;
         return false;
@@ -88,17 +97,18 @@
 
       for (;;) {
         if (this.pos = this.matches(reverse, pos)) {
+          if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.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};
+          pos = Pos(pos.line-1, 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};
+          pos = Pos(pos.line + 1, 0);
         }
       }
     },
@@ -107,9 +117,11 @@
     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);
+      if (!this.atOccurrence) return;
+      var lines = CodeMirror.splitLines(newText);
+      this.cm.replaceRange(lines, this.pos.from, this.pos.to);
+      this.pos.to = Pos(this.pos.from.line + lines.length - 1,
+                        lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
     }
   };
 
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.css b/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.css
index 05ad0ed..8de0b19 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.css
+++ b/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.css
@@ -1,173 +1,245 @@
+/* BASICS */
+
 .CodeMirror {
-  line-height: 1em;
+  /* Set height, width, borders, and global font properties here */
   font-family: monospace;
+  height: 300px;
+}
+.CodeMirror-scroll {
+  /* Set scrolling behaviour here */
+  overflow: auto;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+  padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+  padding: 0 4px; /* Horizontal padding of content */
+}
 
-  /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */
+.CodeMirror-scrollbar-filler {
+  background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+  border-right: 1px solid #ddd;
+  background-color: #f7f7f7;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+  padding: 0 3px 0 5px;
+  min-width: 20px;
+  text-align: right;
+  color: #999;
+}
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+  border-left: 1px solid black;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+  border-left: 1px solid silver;
+}
+.CodeMirror.cm-keymap-fat-cursor div.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 */
+.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor:not(#nonsense_id) {
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable {color: black;}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-property {color: black;}
+.cm-s-default .cm-operator {color: black;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-error {color: #f00;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-emstrong {font-style: italic; font-weight: bold;}
+.cm-link {text-decoration: underline;}
+
+.cm-invalidchar {color: #f00;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+   the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+  line-height: 1;
   position: relative;
-  /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */
   overflow: hidden;
 }
 
 .CodeMirror-scroll {
-  overflow: auto;
-  height: 300px;
-  /* This is needed to prevent an IE[67] bug where the scrolled content
-     is visible outside of the scrolling box. */
+  /* 30px is the magic margin used to hide the element's real scrollbars */
+  /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */
+  margin-bottom: -30px; margin-right: -30px;
+  padding-bottom: 30px; padding-right: 30px;
+  height: 100%;
+  outline: none; /* Prevent dragging from highlighting the element */
+  position: relative;
+}
+.CodeMirror-sizer {
   position: relative;
-  outline: none;
 }
 
-/* Vertical scrollbar */
-.CodeMirror-scrollbar {
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+   before actuall scrolling happens, thus preventing shaking and
+   flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {
   position: absolute;
+  z-index: 6;
+  display: none;
+}
+.CodeMirror-vscrollbar {
   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-hscrollbar {
+  bottom: 0; left: 0;
+  overflow-y: hidden;
+  overflow-x: scroll;
 }
-.CodeMirror-scrollbar.cm-sb-ie7 {
-  min-width: 18px;
+.CodeMirror-scrollbar-filler {
+  right: 0; bottom: 0;
+  z-index: 6;
 }
 
-.CodeMirror-gutter {
+.CodeMirror-gutters {
   position: absolute; left: 0; top: 0;
-  z-index: 10;
-  background-color: #f7f7f7;
-  border-right: 1px solid #eee;
-  min-width: 2em;
   height: 100%;
+  padding-bottom: 30px;
+  z-index: 3;
 }
-.CodeMirror-gutter-text {
-  color: #aaa;
-  text-align: right;
-  padding: .4em .2em .4em .4em;
-  white-space: pre !important;
+.CodeMirror-gutter {
+  height: 100%;
+  display: inline-block;
+  /* Hack to make IE7 behave */
+  *zoom:1;
+  *display:inline;
+}
+.CodeMirror-gutter-elt {
+  position: absolute;
   cursor: default;
+  z-index: 4;
 }
+
 .CodeMirror-lines {
-  padding: .4em;
-  white-space: pre;
   cursor: text;
 }
-
 .CodeMirror pre {
-  -moz-border-radius: 0;
-  -webkit-border-radius: 0;
-  -o-border-radius: 0;
-  border-radius: 0;
-  border-width: 0; margin: 0; padding: 0; background: transparent;
+  /* Reset some styles that the rest of the page might have set */
+  -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0;
+  border-width: 0;
+  background: transparent;
   font-family: inherit;
   font-size: inherit;
-  padding: 0; margin: 0;
+  margin: 0;
   white-space: pre;
   word-wrap: normal;
   line-height: inherit;
   color: inherit;
+  z-index: 2;
+  position: relative;
+  overflow: visible;
 }
-
 .CodeMirror-wrap pre {
   word-wrap: break-word;
   white-space: pre-wrap;
   word-break: normal;
 }
+.CodeMirror-linebackground {
+  position: absolute;
+  left: 0; right: 0; top: 0; bottom: 0;
+  z-index: 0;
+}
+
+.CodeMirror-linewidget {
+  position: relative;
+  z-index: 2;
+  overflow: auto;
+}
+
+.CodeMirror-widget {
+  display: inline-block;
+}
+
 .CodeMirror-wrap .CodeMirror-scroll {
   overflow-x: hidden;
 }
 
-.CodeMirror textarea {
-  outline: none !important;
+.CodeMirror-measure {
+  position: absolute;
+  width: 100%; height: 0px;
+  overflow: hidden;
+  visibility: hidden;
 }
+.CodeMirror-measure pre { position: static; }
 
-.CodeMirror pre.CodeMirror-cursor {
-  z-index: 10;
+.CodeMirror div.CodeMirror-cursor {
   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 {
+.CodeMirror-focused div.CodeMirror-cursor {
   visibility: visible;
 }
 
-div.CodeMirror-selected { background: #d9d9d9; }
-.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
 
-.CodeMirror-searching {
+.cm-searching {
   background: #ffa;
   background: rgba(255, 255, 0, .4);
 }
 
-/* Default theme */
-
-.cm-s-default span.cm-keyword {color: #708;}
-.cm-s-default span.cm-atom {color: #219;}
-.cm-s-default span.cm-number {color: #164;}
-.cm-s-default span.cm-def {color: #00f;}
-.cm-s-default span.cm-variable {color: black;}
-.cm-s-default span.cm-variable-2 {color: #05a;}
-.cm-s-default span.cm-variable-3 {color: #085;}
-.cm-s-default span.cm-property {color: black;}
-.cm-s-default span.cm-operator {color: black;}
-.cm-s-default span.cm-comment {color: #a50;}
-.cm-s-default span.cm-string {color: #a11;}
-.cm-s-default span.cm-string-2 {color: #f50;}
-.cm-s-default span.cm-meta {color: #555;}
-.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: #997;}
-.cm-s-default span.cm-tag {color: #170;}
-.cm-s-default span.cm-attribute {color: #00c;}
-.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;}
-
-span.cm-header, span.cm-strong {font-weight: bold;}
-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;}
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
 
 @media print {
-
   /* Hide the cursor when printing */
-  .CodeMirror pre.CodeMirror-cursor {
+  .CodeMirror div.CodeMirror-cursor {
     visibility: hidden;
   }
-
 }
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.js b/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.js
index 5f9724c..87339d0 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.js
@@ -1,2033 +1,3012 @@
-// CodeMirror version 2.34
-
-// 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 version 3.1
+//
 // CodeMirror is the only global var we claim
 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) {
+
+  // BROWSER SNIFFING
+
+  // Crude, but necessary to handle a number of hard-to-feature-detect
+  // bugs and behavior differences.
+  var gecko = /gecko\/\d/i.test(navigator.userAgent);
+  var ie = /MSIE \d/.test(navigator.userAgent);
+  var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
+  var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
+  var webkit = /WebKit\//.test(navigator.userAgent);
+  var qtwebkit = webkit && /Qt\/\d+\.\d+/.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 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
+  var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
+  var phantom = /PhantomJS/.test(navigator.userAgent);
+
+  var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
+  // This is woefully incomplete. Suggestions for alternative methods welcome.
+  var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
+  var mac = ios || /Mac/.test(navigator.platform);
+  var windows = /windows/i.test(navigator.platform);
+
+  var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
+  if (opera_version) opera_version = Number(opera_version[1]);
+  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
+  var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
+  var captureMiddleClick = gecko || (ie && !ie_lt9);
+
+  // Optimize some code when these features are not used
+  var sawReadOnlySpans = false, sawCollapsedSpans = false;
+
+  // CONSTRUCTOR
+
+  function CodeMirror(place, options) {
+    if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
+    
+    this.options = options = options || {};
     // Determine effective options based on given values and defaults.
-    var options = {}, defaults = CodeMirror.defaults;
-    for (var opt in defaults)
-      if (defaults.hasOwnProperty(opt))
-        options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
+    for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
+      options[opt] = defaults[opt];
+    setGuttersForLineNumbers(options);
+
+    var docStart = typeof options.value == "string" ? 0 : options.value.first;
+    var display = this.display = makeDisplay(place, docStart);
+    display.wrapper.CodeMirror = this;
+    updateGutters(this);
+    if (options.autofocus && !mobile) focusInput(this);
+
+    this.state = {keyMaps: [],
+                  overlays: [],
+                  modeGen: 0,
+                  overwrite: false, focused: false,
+                  suppressEdits: false, pasteIncoming: false,
+                  draggingText: false,
+                  highlight: new Delayed()};
+
+    themeChanged(this);
+    if (options.lineWrapping)
+      this.display.wrapper.className += " CodeMirror-wrap";
+
+    var doc = options.value;
+    if (typeof doc == "string") doc = new Doc(options.value, options.mode);
+    operation(this, attachDoc)(this, doc);
+
+    // Override magic textarea content restore that IE sometimes does
+    // on our hidden textarea on reload
+    if (ie) setTimeout(bind(resetInput, this, true), 20);
+
+    registerEventHandlers(this);
+    // IE throws unspecified error in certain cases, when
+    // trying to access activeElement before onload
+    var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
+    if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
+    else onBlur(this);
+
+    operation(this, function() {
+      for (var opt in optionHandlers)
+        if (optionHandlers.propertyIsEnumerable(opt))
+          optionHandlers[opt](this, options[opt], Init);
+      for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
+    })();
+  }
 
-    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");
+  // DISPLAY CONSTRUCTOR
+
+  function makeDisplay(place, docStart) {
+    var d = {};
+    var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
+    if (webkit) input.style.width = "1000px";
+    else 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");
+    d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
+    // The actual fake scrollbars.
+    d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
+    d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
+    d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
     // DIVs containing the selection and the actual code
-    var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1");
+    d.lineDiv = elt("div");
+    d.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");
+    d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
+    // Secondary cursor, shown when on a 'jump' in bi-directional text
+    d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
     // 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");
+    d.measure = elt("div", null, "CodeMirror-measure");
+    // Wraps everything that needs to exist inside the vertically-padded coordinate system
+    d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
+                         null, "position: relative; outline: none");
     // Moved around its parent to cover visible view
-    var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative");
+    d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
     // Set to the height of the text, causes scrolling
-    var sizer = elt("div", [mover], null, "position: relative");
+    d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
+    // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
+    d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px");
+    // Will contain the gutters, if any
+    d.gutters = elt("div", null, "CodeMirror-gutters");
+    d.lineGutter = null;
+    // Helper element to properly size the gutter backgrounds
+    var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%");
     // Provides scrolling
-    var scroller = elt("div", [sizer], "CodeMirror-scroll");
-    scroller.setAttribute("tabIndex", "-1");
+    d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll");
+    d.scroller.setAttribute("tabIndex", "-1");
     // The element in which the editor lives.
-    var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""));
-    if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
+    d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
+                            d.scrollbarFiller, d.scroller], "CodeMirror");
+    // Work around IE7 z-index bug
+    if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
+    if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
 
-    themeChanged(); keyMapChanged();
     // Needed to hide big blue blinking cursor on Mobile Safari
     if (ios) input.style.width = "0px";
-    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";
+    if (!webkit) d.scroller.draggable = true;
     // Needed to handle Tab key in KHTML
-    if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
-
-    // 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"; }
+    if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
     // 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,
-    // 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
-    // selecting bottom-to-top.
-    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, lastScrollTop = 0, draggingText,
-        overwrite = false, suppressEdits = false;
-    // Variables used by startOperation/endOperation to track what
-    // happened during the operation.
-    var updateInput, userSelChange, changes, textChanged, selectionChanged,
-        gutterDirty, callbacks;
+    else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
+
     // 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 bracket has been
-    // marked.
-    var bracketHighlighted;
+    d.viewOffset = d.lastSizeC = 0;
+    d.showingFrom = d.showingTo = docStart;
+
+    // Used to only resize the line number gutter when necessary (when
+    // the amount of lines crosses a boundary that makes its width change)
+    d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
+    // See readInput and resetInput
+    d.prevInput = "";
+    // Set to true when a non-horizontal-scrolling widget is added. As
+    // an optimization, widget aligning is skipped when d is false.
+    d.alignWidgets = false;
+    // Flag that indicates whether we currently expect input to appear
+    // (after some event like 'keypress' or 'input') and are polling
+    // intensively.
+    d.pollingFast = false;
+    // Self-resetting timeout for the poller
+    d.poll = new Delayed();
+    // True when a drag from the editor is active
+    d.draggingText = false;
+
+    d.cachedCharWidth = d.cachedTextHeight = null;
+    d.measureLineCache = [];
+    d.measureLineCachePos = 0;
+
+    // Tracks when resetInput has punted to just putting a short
+    // string instead of the (large) selection.
+    d.inaccurateSelection = false;
+
     // Tracks the maximum line length so that the horizontal scrollbar
     // can be kept static when scrolling.
-    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;})();
-    var history = new History();
-
-    // Register our event handlers.
-    connect(scroller, "mousedown", operation(onMouseDown));
-    connect(scroller, "dblclick", operation(onDoubleClick));
-    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", 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));
-    connect(input, "keypress", operation(onKeyPress));
-    connect(input, "focus", onFocus);
-    connect(input, "blur", onBlur);
+    d.maxLine = null;
+    d.maxLineLength = 0;
+    d.maxLineChanged = false;
+
+    // Used for measuring wheel scrolling granularity
+    d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
+    
+    return d;
+  }
 
-    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, "cut", operation(function(){
-      if (!options.readOnly) replaceSelection("");
-    }));
+  // STATE UPDATES
 
-    // Needed to handle Tab key in KHTML
-    if (khtml) connect(sizer, "mouseup", function() {
-        if (document.activeElement == input) input.blur();
-        focusInput();
+  // Used to get the editor into a consistent state again when options change.
+
+  function loadMode(cm) {
+    cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
+    cm.doc.iter(function(line) {
+      if (line.stateAfter) line.stateAfter = null;
+      if (line.styles) line.styles = null;
     });
+    cm.doc.frontier = cm.doc.first;
+    startWorker(cm, 100);
+    cm.state.modeGen++;
+    if (cm.curOp) regChange(cm);
+  }
 
-    // IE throws unspecified error in certain cases, when
-    // trying to access activeElement before onload
-    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;}
-    // The instance object that we'll return. Mostly calls out to
-    // local functions in the CodeMirror function. Some do some extra
-    // range checking and/or clipping. operation is used to wrap the
-    // call so that changes it makes are tracked, and the display is
-    // updated afterwards.
-    var instance = wrapper.CodeMirror = {
-      getValue: getValue,
-      setValue: operation(setValue),
-      getSelection: getSelection,
-      replaceSelection: operation(replaceSelection),
-      focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
-      setOption: function(option, value) {
-        var oldVal = options[option];
-        options[option] = value;
-        if (option == "mode" || option == "indentUnit") loadMode();
-        else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
-        else if (option == "readOnly" && !value) {resetInput(true);}
-        else if (option == "theme") themeChanged();
-        else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
-        else if (option == "tabSize") updateDisplay(true);
-        else if (option == "keyMap") keyMapChanged();
-        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) {
-        if (typeof dir != "string") {
-          if (dir == null) dir = options.smartIndent ? "smart" : "prev";
-          else dir = dir ? "add" : "subtract";
-        }
-        if (isLine(n)) indentLine(n, dir);
-      }),
-      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), options.tabSize, pos.ch);
-      }),
-      getStateAfter: function(line) {
-        line = clipLine(line == null ? doc.size - 1: line);
-        return getStateBefore(line + 1);
-      },
-      cursorCoords: function(start, mode) {
-        if (start == null) start = sel.inverted;
-        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);
-      },
-      coordsChar: function(coords) {
-        var off = eltOffset(lineSpace);
-        return coordsChar(coords.x - off.left, coords.y - off.top);
-      },
-      markText: operation(markText),
-      setBookmark: setBookmark,
-      findMarksAt: findMarksAt,
-      setMarker: operation(addGutterMarker),
-      clearMarker: operation(removeGutterMarker),
-      setLineClass: operation(setLineClass),
-      hideLine: operation(function(h) {return setLineHidden(h, true);}),
-      showLine: operation(function(h) {return setLineHidden(h, false);}),
-      onDeleteLine: function(line, f) {
-        if (typeof line == "number") {
-          if (!isLine(line)) return null;
-          line = getLine(line);
-        }
-        (line.handlers || (line.handlers = [])).push(f);
-        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";
-        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(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)
-            left = hspace - node.offsetWidth;
-        }
-        node.style.top = (top + paddingTop()) + "px";
-        node.style.left = node.style.right = "";
-        if (horiz == "right") {
-          left = sizer.clientWidth - node.offsetWidth;
-          node.style.right = "0px";
-        } else {
-          if (horiz == "left") left = 0;
-          else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2;
-          node.style.left = (left + paddingLeft()) + "px";
-        }
-        if (scroll)
-          scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
-      },
+  function wrappingChanged(cm) {
+    if (cm.options.lineWrapping) {
+      cm.display.wrapper.className += " CodeMirror-wrap";
+      cm.display.sizer.style.minWidth = "";
+    } else {
+      cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
+      computeMaxLength(cm);
+    }
+    estimateLineHeights(cm);
+    regChange(cm);
+    clearCaches(cm);
+    setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100);
+  }
 
-      lineCount: function() {return doc.size;},
-      clipPos: clipPos,
-      getCursor: function(start) {
-        if (start == null) start = sel.inverted;
-        return copyPos(start ? sel.from : sel.to);
-      },
-      somethingSelected: function() {return !posEq(sel.from, sel.to);},
-      setCursor: operation(function(line, ch, user) {
-        if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
-        else setCursor(line, ch, user);
-      }),
-      setSelection: operation(function(from, to, user) {
-        (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
-      }),
-      getLine: function(line) {if (isLine(line)) return getLine(line).text;},
-      getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
-      setLine: operation(function(line, text) {
-        if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
-      }),
-      removeLine: operation(function(line) {
-        if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
-      }),
-      replaceRange: operation(replaceRange),
-      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() {
-        if(overwrite){
-          overwrite = false;
-          cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
-        } else {
-          overwrite = true;
-          cursor.className += " CodeMirror-overwrite";
-        }
-      },
+  function estimateHeight(cm) {
+    var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
+    var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
+    return function(line) {
+      if (lineIsHidden(cm.doc, line))
+        return 0;
+      else if (wrapping)
+        return (Math.ceil(line.text.length / perLine) || 1) * th;
+      else
+        return th;
+    };
+  }
 
-      posFromIndex: function(off) {
-        var lineNo = 0, ch;
-        doc.iter(0, doc.size, function(line) {
-          var sz = line.text.length + 1;
-          if (sz > off) { ch = off; return true; }
-          off -= sz;
-          ++lineNo;
-        });
-        return clipPos({line: lineNo, ch: ch});
-      },
-      indexFromPos: function (coords) {
-        if (coords.line < 0 || coords.ch < 0) return 0;
-        var index = coords.ch;
-        doc.iter(0, coords.line, function (line) {
-          index += line.text.length + 1;
-        });
-        return index;
-      },
-      scrollTo: function(x, 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};
-      },
-      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();
-      },
+  function estimateLineHeights(cm) {
+    var doc = cm.doc, est = estimateHeight(cm);
+    doc.iter(function(line) {
+      var estHeight = est(line);
+      if (estHeight != line.height) updateLineHeight(line, estHeight);
+    });
+  }
 
-      operation: function(f){return operation(f)();},
-      compoundChange: function(f){return compoundChange(f);},
-      refresh: function(){
-        updateDisplay(true, null, lastScrollTop);
-        if (scrollbar.scrollHeight > lastScrollTop)
-          scrollbar.scrollTop = lastScrollTop;
-      },
-      getInputField: function(){return input;},
-      getWrapperElement: function(){return wrapper;},
-      getScrollerElement: function(){return scroller;},
-      getGutterElement: function(){return gutter;}
-    };
+  function keyMapChanged(cm) {
+    var style = keyMap[cm.options.keyMap].style;
+    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
+      (style ? " cm-keymap-" + style : "");
+  }
 
-    function getLine(n) { return getLineAt(doc, n); }
-    function updateLineHeight(line, height) {
-      gutterDirty = true;
-      var diff = height - line.height;
-      for (var n = line; n; n = n.parent) n.height += diff;
-    }
+  function themeChanged(cm) {
+    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
+      cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
+    clearCaches(cm);
+  }
+
+  function guttersChanged(cm) {
+    updateGutters(cm);
+    regChange(cm);
+  }
 
-    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 updateGutters(cm) {
+    var gutters = cm.display.gutters, specs = cm.options.gutters;
+    removeChildren(gutters);
+    for (var i = 0; i < specs.length; ++i) {
+      var gutterClass = specs[i];
+      var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
+      if (gutterClass == "CodeMirror-linenumbers") {
+        cm.display.lineGutter = gElt;
+        gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
+      }
     }
+    gutters.style.display = i ? "" : "none";
+  }
 
-    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 lineLength(doc, line) {
+    if (line.height == 0) return 0;
+    var len = line.text.length, merged, cur = line;
+    while (merged = collapsedSpanAtStart(cur)) {
+      var found = merged.find();
+      cur = getLine(doc, found.from.line);
+      len += found.from.ch - found.to.ch;
     }
-    function getValue(lineSep) {
-      var text = [];
-      doc.iter(0, doc.size, function(line) { text.push(line.text); });
-      return text.join(lineSep || "\n");
+    cur = line;
+    while (merged = collapsedSpanAtEnd(cur)) {
+      var found = merged.find();
+      len -= cur.text.length - found.from.ch;
+      cur = getLine(doc, found.to.line);
+      len += cur.text.length - found.to.ch;
     }
+    return len;
+  }
 
-    function onScrollBar(e) {
-      if (scrollbar.scrollTop != lastScrollTop) {
-        lastScrollTop = scroller.scrollTop = scrollbar.scrollTop;
-        updateDisplay([]);
+  function computeMaxLength(cm) {
+    var d = cm.display, doc = cm.doc;
+    d.maxLine = getLine(doc, doc.first);
+    d.maxLineLength = lineLength(doc, d.maxLine);
+    d.maxLineChanged = true;
+    doc.iter(function(line) {
+      var len = lineLength(doc, line);
+      if (len > d.maxLineLength) {
+        d.maxLineLength = len;
+        d.maxLine = line;
       }
-    }
+    });
+  }
 
-    function onScrollMain(e) {
-      if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px")
-        gutter.style.left = scroller.scrollLeft + "px";
-      if (scroller.scrollTop != lastScrollTop) {
-        lastScrollTop = scroller.scrollTop;
-        if (scrollbar.scrollTop != lastScrollTop)
-          scrollbar.scrollTop = lastScrollTop;
-        updateDisplay([]);
+  // Make sure the gutters options contains the element
+  // "CodeMirror-linenumbers" when the lineNumbers option is true.
+  function setGuttersForLineNumbers(options) {
+    var found = false;
+    for (var i = 0; i < options.gutters.length; ++i) {
+      if (options.gutters[i] == "CodeMirror-linenumbers") {
+        if (options.lineNumbers) found = true;
+        else options.gutters.splice(i--, 1);
       }
-      if (options.onScroll) options.onScroll(instance);
     }
+    if (!found && options.lineNumbers)
+      options.gutters.push("CodeMirror-linenumbers");
+  }
 
-    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 == sizer && n != mover) return;
+  // SCROLLBARS
+
+  // Re-synchronize the fake scrollbars with the actual size of the
+  // content. Optionally force a scrollTop.
+  function updateScrollbars(d /* display */, docHeight) {
+    var totalHeight = docHeight + 2 * paddingTop(d);
+    d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
+    var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
+    var needsH = d.scroller.scrollWidth > d.scroller.clientWidth;
+    var needsV = scrollHeight > d.scroller.clientHeight;
+    if (needsV) {
+      d.scrollbarV.style.display = "block";
+      d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
+      d.scrollbarV.firstChild.style.height = 
+        (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
+    } else d.scrollbarV.style.display = "";
+    if (needsH) {
+      d.scrollbarH.style.display = "block";
+      d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
+      d.scrollbarH.firstChild.style.width =
+        (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
+    } else d.scrollbarH.style.display = "";
+    if (needsH && needsV) {
+      d.scrollbarFiller.style.display = "block";
+      d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
+    } else d.scrollbarFiller.style.display = "";
+
+    if (mac_geLion && scrollbarWidth(d.measure) === 0)
+      d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
+  }
 
-      // See if this is a click in the gutter
-      for (var n = e_target(e); n != wrapper; n = n.parentNode)
-        if (n.parentNode == gutterText) {
-          if (options.onGutterClick)
-            options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
-          return e_preventDefault(e);
-        }
+  function visibleLines(display, doc, viewPort) {
+    var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
+    if (typeof viewPort == "number") top = viewPort;
+    else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
+    top = Math.floor(top - paddingTop(display));
+    var bottom = Math.ceil(top + height);
+    return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
+  }
 
-      var start = posFromMouse(e);
+  // LINE NUMBERS
 
-      switch (e_button(e)) {
-      case 3:
-        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
-      // (posFromMouse returning non-null), we have to adjust the
-      // selection.
-      if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
-
-      if (!focused) onFocus();
-
-      var now = +new Date, type = "single";
-      if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
-        type = "triple";
-        e_preventDefault(e);
-        setTimeout(focusInput, 20);
-        selectLine(start.line);
-      } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
-        type = "double";
-        lastDoubleClick = {time: now, pos: start};
-        e_preventDefault(e);
-        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();
+  function alignHorizontally(cm) {
+    var display = cm.display;
+    if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
+    var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
+    var gutterW = display.gutters.offsetWidth, l = comp + "px";
+    for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
+      for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
+    }
+    if (cm.options.fixedGutter)
+      display.gutters.style.left = (comp + gutterW) + "px";
+  }
+
+  function maybeUpdateLineNumberWidth(cm) {
+    if (!cm.options.lineNumbers) return false;
+    var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
+    if (last.length != display.lineNumChars) {
+      var test = display.measure.appendChild(elt("div", [elt("div", last)],
+                                                 "CodeMirror-linenumber CodeMirror-gutter-elt"));
+      var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
+      display.lineGutter.style.width = "";
+      display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
+      display.lineNumWidth = display.lineNumInnerWidth + padding;
+      display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
+      display.lineGutter.style.width = display.lineNumWidth + "px";
+      return true;
+    }
+    return false;
+  }
+
+  function lineNumberFor(options, i) {
+    return String(options.lineNumberFormatter(i + options.firstLineNumber));
+  }
+  function compensateForHScroll(display) {
+    return getRect(display.scroller).left - getRect(display.sizer).left;
+  }
+
+  // DISPLAY DRAWING
+
+  function updateDisplay(cm, changes, viewPort) {
+    var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo;
+    var updated = updateDisplayInner(cm, changes, viewPort);
+    if (updated) {
+      signalLater(cm, "update", cm);
+      if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
+        signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
+    }
+    updateSelection(cm);
+    updateScrollbars(cm.display, cm.doc.height);
+
+    return updated;
+  }
+
+  // Uses a set of changes plus the current scroll position to
+  // determine which DOM updates have to be made, and makes the
+  // updates.
+  function updateDisplayInner(cm, changes, viewPort) {
+    var display = cm.display, doc = cm.doc;
+    if (!display.wrapper.clientWidth) {
+      display.showingFrom = display.showingTo = doc.first;
+      display.viewOffset = 0;
+      return;
+    }
+
+    // Compute the new visible window
+    // If scrollTop is specified, use that to determine which lines
+    // to render instead of the current scrollbar position.
+    var visible = visibleLines(display, doc, viewPort);
+    // Bail out if the visible area is already rendered and nothing changed.
+    if (changes.length == 0 &&
+        visible.from > display.showingFrom && visible.to < display.showingTo)
+      return;
+
+    if (maybeUpdateLineNumberWidth(cm))
+      changes = [{from: doc.first, to: doc.first + doc.size}];
+    var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
+    display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
+
+    // Used to determine which lines need their line numbers updated
+    var positionsChangedFrom = Infinity;
+    if (cm.options.lineNumbers)
+      for (var i = 0; i < changes.length; ++i)
+        if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }
+
+    var end = doc.first + doc.size;
+    var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
+    var to = Math.min(end, visible.to + cm.options.viewportMargin);
+    if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
+    if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
+    if (sawCollapsedSpans) {
+      from = lineNo(visualLine(doc, getLine(doc, from)));
+      while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
+    }
+
+    // Create a range of theoretically intact lines, and punch holes
+    // in that using the change info.
+    var intact = [{from: Math.max(display.showingFrom, doc.first),
+                   to: Math.min(display.showingTo, end)}];
+    if (intact[0].from >= intact[0].to) intact = [];
+    else intact = computeIntact(intact, changes);
+    // When merged lines are present, we might have to reduce the
+    // intact ranges because changes in continued fragments of the
+    // intact lines do require the lines to be redrawn.
+    if (sawCollapsedSpans)
+      for (var i = 0; i < intact.length; ++i) {
+        var range = intact[i], merged;
+        while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
+          var newTo = merged.find().from.line;
+          if (newTo > range.from) range.to = newTo;
+          else { intact.splice(i--, 1); break; }
         }
       }
-      var last = start, going;
-      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) 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;
+
+    // Clip off the parts that won't be visible
+    var intactLines = 0;
+    for (var i = 0; i < intact.length; ++i) {
+      var range = intact[i];
+      if (range.from < from) range.from = from;
+      if (range.to > to) range.to = to;
+      if (range.from >= range.to) intact.splice(i--, 1);
+      else intactLines += range.to - range.from;
+    }
+    if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
+      updateViewOffset(cm);
+      return;
+    }
+    intact.sort(function(a, b) {return a.from - b.from;});
+
+    var focused = document.activeElement;
+    if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
+    patchDisplay(cm, from, to, intact, positionsChangedFrom);
+    display.lineDiv.style.display = "";
+    if (document.activeElement != focused && focused.offsetHeight) focused.focus();
+
+    var different = from != display.showingFrom || to != display.showingTo ||
+      display.lastSizeC != display.wrapper.clientHeight;
+    // This is just a bogus formula that detects when the editor is
+    // resized or the font size changes.
+    if (different) display.lastSizeC = display.wrapper.clientHeight;
+    display.showingFrom = from; display.showingTo = to;
+    startWorker(cm, 100);
+
+    var prevBottom = display.lineDiv.offsetTop;
+    for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
+      if (ie_lt8) {
+        var bot = node.offsetTop + node.offsetHeight;
+        height = bot - prevBottom;
+        prevBottom = bot;
+      } else {
+        var box = getRect(node);
+        height = box.bottom - box.top;
       }
-      e_preventDefault(e);
-      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}));
-        }
+      var diff = node.lineObj.height - height;
+      if (height < 2) height = textHeight(display);
+      if (diff > .001 || diff < -.001) {
+        updateLineHeight(node.lineObj, height);
+        var widgets = node.lineObj.widgets;
+        if (widgets) for (var i = 0; i < widgets.length; ++i)
+          widgets[i].height = widgets[i].node.offsetHeight;
       }
+    }
+    updateViewOffset(cm);
+
+    if (visibleLines(display, doc, viewPort).to > to)
+      updateDisplayInner(cm, [], viewPort);
+    return true;
+  }
+
+  function updateViewOffset(cm) {
+    var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
+    // Position the mover div to align with the current virtual scroll position
+    cm.display.mover.style.top = off + "px";
+  }
 
-      function extend(e) {
-        var cur = posFromMouse(e, true);
-        if (cur && !posEq(cur, last)) {
-          if (!focused) onFocus();
-          last = cur;
-          doSelect(cur);
-          updateInput = false;
-          var visible = visibleLines();
-          if (cur.line >= visible.to || cur.line < visible.from)
-            going = setTimeout(operation(function(){extend(e);}), 150);
+  function computeIntact(intact, changes) {
+    for (var i = 0, l = changes.length || 0; i < l; ++i) {
+      var change = changes[i], intact2 = [], diff = change.diff || 0;
+      for (var j = 0, l2 = intact.length; j < l2; ++j) {
+        var range = intact[j];
+        if (change.to <= range.from && change.diff) {
+          intact2.push({from: range.from + diff, to: range.to + diff});
+        } else if (change.to <= range.from || change.from >= range.to) {
+          intact2.push(range);
+        } else {
+          if (change.from > range.from)
+            intact2.push({from: range.from, to: change.from});
+          if (change.to < range.to)
+            intact2.push({from: change.to + diff, to: range.to + diff});
         }
       }
+      intact = intact2;
+    }
+    return intact;
+  }
 
-      function done(e) {
-        clearTimeout(going);
-        var cur = posFromMouse(e);
-        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);
-      e_preventDefault(e);
+  function getDimensions(cm) {
+    var d = cm.display, left = {}, width = {};
+    for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
+      left[cm.options.gutters[i]] = n.offsetLeft;
+      width[cm.options.gutters[i]] = n.offsetWidth;
     }
-    function onDrop(e) {
-      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) {
-        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() {
-                var end = replaceRange(text.join(""), pos, pos);
-                setSelectionUser(pos, end);
-              })();
+    return {fixedPos: compensateForHScroll(d),
+            gutterTotalWidth: d.gutters.offsetWidth,
+            gutterLeft: left,
+            gutterWidth: width,
+            wrapperWidth: d.wrapper.clientWidth};
+  }
+
+  function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
+    var dims = getDimensions(cm);
+    var display = cm.display, lineNumbers = cm.options.lineNumbers;
+    if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
+      removeChildren(display.lineDiv);
+    var container = display.lineDiv, cur = container.firstChild;
+
+    function rm(node) {
+      var next = node.nextSibling;
+      if (webkit && mac && cm.display.currentWheelTarget == node) {
+        node.style.display = "none";
+        node.lineObj = null;
+      } else {
+        node.parentNode.removeChild(node);
+      }
+      return next;
+    }
+
+    var nextIntact = intact.shift(), lineN = from;
+    cm.doc.iter(from, to, function(line) {
+      if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
+      if (lineIsHidden(cm.doc, line)) {
+        if (line.height != 0) updateLineHeight(line, 0);
+        if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i)
+          if (line.widgets[i].showIfHidden) {
+            var prev = cur.previousSibling;
+            if (/pre/i.test(prev.nodeName)) {
+              var wrap = elt("div", null, null, "position: relative");
+              prev.parentNode.replaceChild(wrap, prev);
+              wrap.appendChild(prev);
+              prev = wrap;
             }
-          };
-          reader.readAsText(file);
-        };
-        for (var i = 0; i < n; ++i) loadFile(files[i], i);
+            var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget"));
+            positionLineWidget(line.widgets[i], wnode, prev, dims);
+          }
+      } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
+        // This line is intact. Skip to the actual node. Update its
+        // line number if needed.
+        while (cur.lineObj != line) cur = rm(cur);
+        if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
+          setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
+        cur = cur.nextSibling;
       } 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) {
-            compoundChange(function() {
-              var curFrom = sel.from, curTo = sel.to;
-              setSelectionUser(pos, pos);
-              if (draggingText) replaceRange("", curFrom, curTo);
-              replaceSelection(text);
-              focusInput();
-            });
+        // For lines with widgets, make an attempt to find and reuse
+        // the existing element, so that widgets aren't needlessly
+        // removed and re-inserted into the dom
+        if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
+          if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
+        // This line needs to be generated.
+        var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
+        if (lineNode != reuse) {
+          container.insertBefore(lineNode, cur);
+        } else {
+          while (cur != reuse) cur = rm(cur);
+          cur = cur.nextSibling;
+        }
+
+        lineNode.lineObj = line;
+      }
+      ++lineN;
+    });
+    while (cur) cur = rm(cur);
+  }
+
+  function buildLineElement(cm, line, lineNo, dims, reuse) {
+    var lineElement = lineContent(cm, line);
+    var markers = line.gutterMarkers, display = cm.display, wrap;
+
+    if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets)
+      return lineElement;
+
+    // Lines with gutter elements, widgets or a background class need
+    // to be wrapped again, and have the extra elements added to the
+    // wrapper div
+    
+    if (reuse) {
+      reuse.alignable = null;
+      var isOk = true, widgetsSeen = 0;
+      for (var n = reuse.firstChild, next; n; n = next) {
+        next = n.nextSibling;
+        if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
+          reuse.removeChild(n);
+        } else {
+          for (var i = 0, first = true; i < line.widgets.length; ++i) {
+            var widget = line.widgets[i], isFirst = false;
+            if (!widget.above) { isFirst = first; first = false; }
+            if (widget.node == n.firstChild) {
+              positionLineWidget(widget, n, reuse, dims);
+              ++widgetsSeen;
+              if (isFirst) reuse.insertBefore(lineElement, n);
+              break;
+            }
           }
+          if (i == line.widgets.length) { isOk = false; break; }
         }
-        catch(e){}
+      }
+      if (isOk && widgetsSeen == line.widgets.length) {
+        wrap = reuse;
+        reuse.className = line.wrapClass || "";
       }
     }
-    function onDragStart(e) {
-      var txt = getSelection();
-      e.dataTransfer.setData("Text", txt);
-
-      // Use dummy image instead of default browsers image.
-      if (e.dataTransfer.setDragImage)
-        e.dataTransfer.setDragImage(elt('img'), 0, 0);
+    if (!wrap) {
+      wrap = elt("div", null, line.wrapClass, "position: relative");
+      wrap.appendChild(lineElement);
     }
+    // Kludge to make sure the styled element lies behind the selection (by z-index)
+    if (line.bgClass)
+      wrap.insertBefore(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground"), wrap.firstChild);
+    if (cm.options.lineNumbers || markers) {
+      var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
+                                             (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
+                                         wrap.firstChild);
+      if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
+      if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
+        wrap.lineNumber = gutterWrap.appendChild(
+          elt("div", lineNumberFor(cm.options, lineNo),
+              "CodeMirror-linenumber CodeMirror-gutter-elt",
+              "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
+              + display.lineNumInnerWidth + "px"));
+      if (markers)
+        for (var k = 0; k < cm.options.gutters.length; ++k) {
+          var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
+          if (found)
+            gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
+                                       dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
+        }
+    }
+    if (ie_lt8) wrap.style.zIndex = 2;
+    if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
+      var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
+      positionLineWidget(widget, node, wrap, dims);
+      if (widget.above)
+        wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
+      else
+        wrap.appendChild(node);
+      signalLater(widget, "redraw");
+    }
+    return wrap;
+  }
 
-    function doHandleBinding(bound, dropShift) {
-      if (typeof bound == "string") {
-        bound = commands[bound];
-        if (!bound) return false;
+  function positionLineWidget(widget, node, wrap, dims) {
+    if (widget.noHScroll) {
+      (wrap.alignable || (wrap.alignable = [])).push(node);
+      var width = dims.wrapperWidth;
+      node.style.left = dims.fixedPos + "px";
+      if (!widget.coverGutter) {
+        width -= dims.gutterTotalWidth;
+        node.style.paddingLeft = dims.gutterTotalWidth + "px";
       }
-      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;
-      }
-      return true;
+      node.style.width = width + "px";
+    }
+    if (widget.coverGutter) {
+      node.style.zIndex = 5;
+      node.style.position = "relative";
+      if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
     }
-    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);
+  }
+
+  // SELECTION / CURSOR
+
+  function updateSelection(cm) {
+    var display = cm.display;
+    var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
+    if (collapsed || cm.options.showCursorWhenSelecting)
+      updateSelectionCursor(cm);
+    else
+      display.cursor.style.display = display.otherCursor.style.display = "none";
+    if (!collapsed)
+      updateSelectionRange(cm);
+    else
+      display.selectionDiv.style.display = "none";
+
+    // Move the hidden textarea near the cursor to prevent scrolling artifacts
+    var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
+    var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
+    display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
+                                                      headPos.top + lineOff.top - wrapOff.top)) + "px";
+    display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
+                                                       headPos.left + lineOff.left - wrapOff.left)) + "px";
+  }
+
+  // No selection, plain cursor
+  function updateSelectionCursor(cm) {
+    var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
+    display.cursor.style.left = pos.left + "px";
+    display.cursor.style.top = pos.top + "px";
+    display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
+    display.cursor.style.display = "";
+
+    if (pos.other) {
+      display.otherCursor.style.display = "";
+      display.otherCursor.style.left = pos.other.left + "px";
+      display.otherCursor.style.top = pos.other.top + "px";
+      display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
+    } else { display.otherCursor.style.display = "none"; }
+  }
+
+  // Highlight selection
+  function updateSelectionRange(cm) {
+    var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
+    var fragment = document.createDocumentFragment();
+    var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
+
+    function add(left, top, width, bottom) {
+      if (top < 0) top = 0;
+      fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
+                               "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
+                               "px; height: " + (bottom - top) + "px"));
+    }
+
+    function drawForLine(line, fromArg, toArg, retTop) {
+      var lineObj = getLine(doc, line);
+      var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity;
+      function coords(ch) {
+        return charCoords(cm, Pos(line, ch), "div", lineObj);
+      }
+
+      iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
+        var leftPos = coords(dir == "rtl" ? to - 1 : from);
+        var rightPos = coords(dir == "rtl" ? from : to - 1);
+        var left = leftPos.left, right = rightPos.right;
+        if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
+          add(left, leftPos.top, null, leftPos.bottom);
+          left = pl;
+          if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
         }
-      }, 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) { 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 (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"))
-          replaceSelection("");
-      }
-    }
-    function onKeyPress(e) {
-      if (pollingFast) readInput();
-      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(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) {
-        if (mode.electricChars.indexOf(ch) > -1)
-          setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
-      }
-      if (handleCharBinding(e, ch)) return;
-      fastPoll();
-    }
-    function onKeyUp(e) {
-      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
-      if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
-    }
-
-    function onFocus() {
-      if (options.readOnly == "nocursor") return;
-      if (!focused) {
-        if (options.onFocus) options.onFocus(instance);
-        focused = true;
-        if (scroller.className.search(/\bCodeMirror-focused\b/) == -1)
-          scroller.className += " CodeMirror-focused";
-      }
-      slowPoll();
-      restartBlink();
-    }
-    function onBlur() {
-      if (focused) {
-        if (options.onBlur) options.onBlur(instance);
-        focused = false;
-        if (bracketHighlighted)
-          operation(function(){
-            if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
-          })();
-        scroller.className = scroller.className.replace(" CodeMirror-focused", "");
-      }
-      clearInterval(blinker);
-      setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
-    }
-
-    // Replace the range from from to to by the strings in newText.
-    // 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 (toArg == null && to == lineLen) right = clientWidth;
+        if (fromArg == null && from == 0) left = pl;
+        rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal);
+        if (left < pl + 1) left = pl;
+        add(left, rightPos.top, right - left, rightPos.bottom);
       });
-      if (history) {
-        history.addChange(from.line, newText.length, old);
-        while (history.done.length > options.undoDepth) history.done.shift();
-      }
-      var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText);
-      updateLinesNoUndo(from, to, lines, selFrom, selTo);
-    }
-    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(newHL(line.text, line.markedSpans)); });
-        out.push({start: change.start, added: change.old.length, old: replaced});
-        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);}
-    function redo() {unredoHelper(history.undone, history.done);}
-
-    function updateLinesNoUndo(from, to, lines, selFrom, selTo) {
-      if (suppressEdits) return;
-      var recomputeMaxLength = false, maxLineLength = maxLine.text.length;
-      if (!options.lineWrapping)
-        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 || lines.length > 1) gutterDirty = true;
-
-      var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
-      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;
-        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 (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);
+      return rVal;
+    }
+
+    if (sel.from.line == sel.to.line) {
+      drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
+    } else {
+      var fromObj = getLine(doc, sel.from.line);
+      var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine;
+      while (merged = collapsedSpanAtEnd(cur)) {
+        var found = merged.find();
+        path.push(found.from.ch, found.to.line, found.to.ch);
+        if (found.to.line == sel.to.line) {
+          path.push(sel.to.ch);
+          singleLine = true;
+          break;
         }
-      } 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.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);
+        cur = getLine(doc, found.to.line);
       }
-      if (options.lineWrapping) {
-        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);
-        });
+
+      // This is a single, merged line
+      if (singleLine) {
+        for (var i = 0; i < path.length; i += 3)
+          drawForLine(path[i], path[i+1], path[i+2]);
       } else {
-        doc.iter(from.line, from.line + lines.length, function(line) {
-          var l = line.text;
-          if (!line.hidden && l.length > maxLineLength) {
-            maxLine = line; maxLineLength = l.length; maxLineChanged = true;
-            recomputeMaxLength = false;
-          }
-        });
-        if (recomputeMaxLength) updateMaxLine = true;
-      }
-
-      // 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});
-      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(clipPos(selFrom), clipPos(selTo),
-                   updateLine(sel.from.line), updateLine(sel.to.line));
-    }
-
-    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);
+        var middleTop, middleBot, toObj = getLine(doc, sel.to.line);
+        if (sel.from.ch)
+          // Draw the first line of selection.
+          middleTop = drawForLine(sel.from.line, sel.from.ch, null, false);
+        else
+          // Simply include it in the middle block.
+          middleTop = heightAtLine(cm, fromObj) - display.viewOffset;
+
+        if (!sel.to.ch)
+          middleBot = heightAtLine(cm, toObj) - display.viewOffset;
+        else
+          middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true);
+
+        if (middleTop < middleBot) add(pl, middleTop, null, middleBot);
+      }
+    }
+
+    removeChildrenAndAdd(display.selectionDiv, fragment);
+    display.selectionDiv.style.display = "";
+  }
+
+  // Cursor-blinking
+  function restartBlink(cm) {
+    var display = cm.display;
+    clearInterval(display.blinker);
+    var on = true;
+    display.cursor.style.visibility = display.otherCursor.style.visibility = "";
+    display.blinker = setInterval(function() {
+      if (!display.cursor.offsetHeight) return;
+      display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
+    }, cm.options.cursorBlinkRate);
+  }
+
+  // HIGHLIGHT WORKER
+
+  function startWorker(cm, time) {
+    if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
+      cm.state.highlight.set(time, bind(highlightWorker, cm));
+  }
+
+  function highlightWorker(cm) {
+    var doc = cm.doc;
+    if (doc.frontier < doc.first) doc.frontier = doc.first;
+    if (doc.frontier >= cm.display.showingTo) return;
+    var end = +new Date + cm.options.workTime;
+    var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
+    var changed = [], prevChange;
+    doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
+      if (doc.frontier >= cm.display.showingFrom) { // Visible
+        var oldStyles = line.styles;
+        line.styles = highlightLine(cm, line, state);
+        var ischange = !oldStyles || oldStyles.length != line.styles.length;
+        for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
+        if (ischange) {
+          if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
+          else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
         }
+        line.stateAfter = copyState(doc.mode, state);
       } else {
-        sizer.style.minHeight = "";
+        processLine(cm, line, state);
+        line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
+      }
+      ++doc.frontier;
+      if (+new Date > end) {
+        startWorker(cm, cm.options.workDelay);
+        return true;
+      }
+    });
+    if (changed.length)
+      operation(cm, function() {
+        for (var i = 0; i < changed.length; ++i)
+          regChange(this, changed[i].start, changed[i].end);
+      })();
+  }
+
+  // Finds the line to start with when starting a parse. Tries to
+  // find a line with a stateAfter, so that it can start with a
+  // valid state. If that fails, it returns the line with the
+  // smallest indentation, which tends to need the least context to
+  // parse correctly.
+  function findStartLine(cm, n) {
+    var minindent, minline, doc = cm.doc;
+    for (var search = n, lim = n - 100; search > lim; --search) {
+      if (search <= doc.first) return doc.first;
+      var line = getLine(doc, search - 1);
+      if (line.stateAfter) return search;
+      var indented = countColumn(line.text, null, cm.options.tabSize);
+      if (minline == null || minindent > indented) {
+        minline = search - 1;
+        minindent = indented;
       }
-      // Position the mover div to align with the current virtual scroll position
-      mover.style.top = displayOffset * textHeight() + "px";
     }
+    return minline;
+  }
 
-    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) {
-      from = clipPos(from);
-      if (!to) to = from; else to = clipPos(to);
-      code = splitLines(code);
-      function adjustPos(pos) {
-        if (posLess(pos, from)) return pos;
-        if (!posLess(to, pos)) return end;
-        var line = pos.line + code.length - (to.line - from.line) - 1;
-        var ch = pos.ch;
-        if (pos.line == to.line)
-          ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0));
-        return {line: line, ch: ch};
-      }
-      var end;
-      replaceRange1(code, from, to, function(end1) {
-        end = end1;
-        return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
-      });
-      return end;
+  function getStateBefore(cm, n) {
+    var doc = cm.doc, display = cm.display;
+      if (!doc.mode.startState) return true;
+    var pos = findStartLine(cm, n), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
+    if (!state) state = startState(doc.mode);
+    else state = copyState(doc.mode, state);
+    doc.iter(pos, n, function(line) {
+      processLine(cm, line, state);
+      var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
+      line.stateAfter = save ? copyState(doc.mode, state) : null;
+      ++pos;
+    });
+    return state;
+  }
+
+  // POSITION MEASUREMENT
+  
+  function paddingTop(display) {return display.lineSpace.offsetTop;}
+  function paddingLeft(display) {
+    var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
+    return e.offsetLeft;
+  }
+
+  function measureChar(cm, line, ch, data) {
+    var dir = -1;
+    data = data || measureLine(cm, line);
+    
+    for (var pos = ch;; pos += dir) {
+      var r = data[pos];
+      if (r) break;
+      if (dir < 0 && pos == 0) dir = 1;
     }
-    function replaceSelection(code, collapse) {
-      replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
-        if (collapse == "end") return {from: end, to: end};
-        else if (collapse == "start") return {from: sel.from, to: sel.from};
-        else return {from: sel.from, to: end};
-      });
+    return {left: pos < ch ? r.right : r.left,
+            right: pos > ch ? r.left : r.right,
+            top: r.top, bottom: r.bottom};
+  }
+
+  function measureLine(cm, line) {
+    // First look in the cache
+    var display = cm.display, cache = cm.display.measureLineCache;
+    for (var i = 0; i < cache.length; ++i) {
+      var memo = cache[i];
+      if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
+          display.scroller.clientWidth == memo.width &&
+          memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
+        return memo.measure;
     }
-    function replaceRange1(code, from, to, computeSel) {
-      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);
+    
+    var measure = measureLineInner(cm, line);
+    // Store result in the cache
+    var memo = {text: line.text, width: display.scroller.clientWidth,
+                markedSpans: line.markedSpans, measure: measure,
+                classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
+    if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo;
+    else cache.push(memo);
+    return measure;
+  }
+
+  function measureLineInner(cm, line) {
+    var display = cm.display, measure = emptyArray(line.text.length);
+    var pre = lineContent(cm, line, measure);
+
+    // IE does not cache element positions of inline elements between
+    // calls to getBoundingClientRect. This makes the loop below,
+    // which gathers the positions of all the characters on the line,
+    // do an amount of layout work quadratic to the number of
+    // characters. When line wrapping is off, we try to improve things
+    // by first subdividing the line into a bunch of inline blocks, so
+    // that IE can reuse most of the layout information from caches
+    // for those blocks. This does interfere with line wrapping, so it
+    // doesn't work when wrapping is on, but in that case the
+    // situation is slightly better, since IE does cache line-wrapping
+    // information and only recomputes per-line.
+    if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
+      var fragment = document.createDocumentFragment();
+      var chunk = 10, n = pre.childNodes.length;
+      for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
+        var wrap = elt("div", null, null, "display: inline-block");
+        for (var j = 0; j < chunk && n; ++j) {
+          wrap.appendChild(pre.firstChild);
+          --n;
+        }
+        fragment.appendChild(wrap);
+      }
+      pre.appendChild(fragment);
     }
 
-    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(lineSep || "\n");
+    removeChildrenAndAdd(display.measure, pre);
+
+    var outer = getRect(display.lineDiv);
+    var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
+    // Work around an IE7/8 bug where it will sometimes have randomly
+    // replaced our pre with a clone at this point.
+    if (ie_lt9 && display.measure.first != pre)
+      removeChildrenAndAdd(display.measure, pre);
+
+    for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
+      var size = getRect(cur);
+      var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot);
+      for (var j = 0; j < vranges.length; j += 2) {
+        var rtop = vranges[j], rbot = vranges[j+1];
+        if (rtop > bot || rbot < top) continue;
+        if (rtop <= top && rbot >= bot ||
+            top <= rtop && bot >= rbot ||
+            Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
+          vranges[j] = Math.min(top, rtop);
+          vranges[j+1] = Math.max(bot, rbot);
+          break;
+        }
+      }
+      if (j == vranges.length) vranges.push(top, bot);
+      var right = size.right;
+      if (cur.measureRight) right = getRect(cur.measureRight).left;
+      data[i] = {left: size.left - outer.left, right: right - outer.left, top: j};
     }
-    function getSelection(lineSep) {
-      return getRange(sel.from, sel.to, lineSep);
+    for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
+      var vr = cur.top;
+      cur.top = vranges[vr]; cur.bottom = vranges[vr+1];
+    }
+    if (!cm.options.lineWrapping) {
+      var last = pre.lastChild;
+      if (last.nodeType == 3) last = pre.appendChild(elt("span", "\u200b"));
+      data.width = getRect(last).right - outer.left;
     }
 
-    function slowPoll() {
-      if (pollingFast) return;
-      poll.set(options.pollInterval, function() {
-        readInput();
-        if (focused) slowPoll();
-      });
+    return data;
+  }
+
+  function clearCaches(cm) {
+    cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
+    cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
+    cm.display.maxLineChanged = true;
+    cm.display.lineNumChars = null;
+  }
+
+  // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
+  function intoCoordSystem(cm, lineObj, rect, context) {
+    if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
+      var size = widgetHeight(lineObj.widgets[i]);
+      rect.top += size; rect.bottom += size;
     }
-    function fastPoll() {
-      var missed = false;
-      pollingFast = true;
-      function p() {
-        var changed = readInput();
-        if (!changed && !missed) {missed = true; poll.set(60, p);}
-        else {pollingFast = false; slowPoll();}
-      }
-      poll.set(20, p);
-    }
-
-    // Previnput is a hack to work with IME. If we reset the textarea
-    // on every change, that breaks IME. So we look for changes
-    // compared to the previous content instead. (Modern browsers have
-    // events that indicate IME taking place, but these are not widely
-    // supported or compatible enough yet to rely on.)
-    var prevInput = "";
-    function readInput() {
-      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))
-        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");
-      if (text.length > 1000) { input.value = prevInput = ""; }
-      else prevInput = text;
-      if (!nestedOperation) endOperation();
-      return true;
+    if (context == "line") return rect;
+    if (!context) context = "local";
+    var yOff = heightAtLine(cm, lineObj);
+    if (context != "local") yOff -= cm.display.viewOffset;
+    if (context == "page") {
+      var lOff = getRect(cm.display.lineSpace);
+      yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop);
+      var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft);
+      rect.left += xOff; rect.right += xOff;
     }
-    function resetInput(user) {
-      if (!posEq(sel.from, sel.to)) {
-        prevInput = "";
-        input.value = getSelection();
-        if (focused) selectInput(input);
-      } else if (user) prevInput = input.value = "";
-    }
-
-    function focusInput() {
-      if (options.readOnly != "nocursor") input.focus();
-    }
-
-    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 {x: x, y: cursor.y, yBot: cursor.yBot};
-    }
-    function scrollIntoView(x1, y1, x2, y2) {
-      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 = 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;
-      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;
+    rect.top += yOff; rect.bottom += yOff;
+    return rect;
+  }
+
+  function charCoords(cm, pos, context, lineObj) {
+    if (!lineObj) lineObj = getLine(cm.doc, pos.line);
+    return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context);
+  }
+
+  function cursorCoords(cm, pos, context, lineObj, measurement) {
+    lineObj = lineObj || getLine(cm.doc, pos.line);
+    if (!measurement) measurement = measureLine(cm, lineObj);
+    function get(ch, right) {
+      var m = measureChar(cm, lineObj, ch, measurement);
+      if (right) m.left = m.right; else m.right = m.left;
+      return intoCoordSystem(cm, lineObj, m, context);
+    }
+    var order = getOrder(lineObj), ch = pos.ch;
+    if (!order) return get(ch);
+    var main, other, linedir = order[0].level;
+    for (var i = 0; i < order.length; ++i) {
+      var part = order[i], rtl = part.level % 2, nb, here;
+      if (part.from < ch && part.to > ch) return get(ch, rtl);
+      var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
+      if (left == ch) {
+        // IE returns bogus offsets and widths for edges where the
+        // direction flips, but only for the side with the lower
+        // level. So we try to use the side with the higher level.
+        if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true);
+        else here = get(rtl && part.from != part.to ? ch - 1 : ch);
+        if (rtl == linedir) main = here; else other = here;
+      } else if (right == ch) {
+        var nb = i < order.length - 1 && order[i+1];
+        if (!rtl && nb && nb.from == nb.to) continue;
+        if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from);
+        else here = get(rtl ? ch : ch - 1, true);
+        if (rtl == linedir) main = here; else other = here;
       }
-      return result;
     }
+    if (linedir && !ch) other = get(order[0].to - 1);
+    if (!main) return other;
+    if (other) main.other = other;
+    return main;
+  }
+
+  function PosMaybeOutside(line, ch, outside) {
+    var pos = new Pos(line, ch);
+    if (outside) pos.outside = true;
+    return pos;
+  }
+
+  // Coords must be lineSpace-local
+  function coordsChar(cm, x, y) {
+    var doc = cm.doc;
+    y += cm.display.viewOffset;
+    if (y < 0) return PosMaybeOutside(doc.first, 0, true);
+    var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
+    if (lineNo > last)
+      return PosMaybeOutside(doc.first + doc.size - 1, getLine(doc, last).text.length, true);
+    if (x < 0) x = 0;
+
+    for (;;) {
+      var lineObj = getLine(doc, lineNo);
+      var found = coordsCharInner(cm, lineObj, lineNo, x, y);
+      var merged = collapsedSpanAtEnd(lineObj);
+      var mergedPos = merged && merged.find();
+      if (merged && found.ch >= mergedPos.from.ch)
+        lineNo = mergedPos.to.line;
+      else
+        return found;
+    }
+  }
 
-    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)};
+  function coordsCharInner(cm, lineObj, lineNo, x, y) {
+    var innerOff = y - heightAtLine(cm, lineObj);
+    var wrongLine = false, cWidth = cm.display.wrapper.clientWidth;
+    var measurement = measureLine(cm, lineObj);
+
+    function getX(ch) {
+      var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
+                            lineObj, measurement);
+      wrongLine = true;
+      if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth);
+      else if (innerOff < sp.top) return sp.left + cWidth;
+      else wrongLine = false;
+      return sp.left;
     }
-    // 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, scrollTop) {
-      if (!scroller.clientWidth) {
-        showingFrom = showingTo = displayOffset = 0;
-        return;
+
+    var bidi = getOrder(lineObj), dist = lineObj.text.length;
+    var from = lineLeft(lineObj), to = lineRight(lineObj);
+    var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
+
+    if (x > toX) return PosMaybeOutside(lineNo, to, toOutside);
+    // Do a binary search between these bounds.
+    for (;;) {
+      if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
+        var after = x - fromX < toX - x, ch = after ? from : to;
+        while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
+        var pos = PosMaybeOutside(lineNo, ch, after ? fromOutside : toOutside);
+        pos.after = after;
+        return pos;
       }
-      // Compute the new visible window
-      // 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) {
-        updateVerticalScroll(scrollTop);
-        return;
+      var step = Math.ceil(dist / 2), middle = from + step;
+      if (bidi) {
+        middle = from;
+        for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
       }
-      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);
+      var middleX = getX(middle);
+      if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist -= step;}
+      else {from = middle; fromX = middleX; fromOutside = wrongLine; dist = step;}
+    }
+  }
 
-      // Create a range of theoretically intact lines, and punch holes
-      // in that using the change info.
-      var intact = changes === true ? [] :
-        computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
-      // Clip off the parts that won't be visible
-      var intactLines = 0;
-      for (var i = 0; i < intact.length; ++i) {
-        var range = intact[i];
-        if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
-        if (range.to > to) range.to = to;
-        if (range.from >= range.to) intact.splice(i--, 1);
-        else intactLines += range.to - range.from;
-      }
-      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;
-      lineDiv.style.display = "none";
-      patchDisplay(from, to, intact);
-      lineDiv.style.display = gutter.style.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);
-      startWorker(100);
-
-      // Since this is all rather error prone, it is honoured with the
-      // only assertion in the whole file.
-      if (lineDiv.childNodes.length != showingTo - showingFrom)
-        throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
-                        " nodes=" + lineDiv.childNodes.length);
-
-      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) {
-              updateLineHeight(line, height);
-              gutterDirty = heightChanged = true;
-            }
-          }
-          curNode = curNode.nextSibling;
-        });
-        return heightChanged;
+  var measureText;
+  function textHeight(display) {
+    if (display.cachedTextHeight != null) return display.cachedTextHeight;
+    if (measureText == null) {
+      measureText = elt("pre");
+      // Measure a bunch of lines, for browsers that compute
+      // fractional heights.
+      for (var i = 0; i < 49; ++i) {
+        measureText.appendChild(document.createTextNode("x"));
+        measureText.appendChild(elt("br"));
       }
+      measureText.appendChild(document.createTextNode("x"));
+    }
+    removeChildrenAndAdd(display.measure, measureText);
+    var height = measureText.offsetHeight / 50;
+    if (height > 3) display.cachedTextHeight = height;
+    removeChildren(display.measure);
+    return height || 1;
+  }
+
+  function charWidth(display) {
+    if (display.cachedCharWidth != null) return display.cachedCharWidth;
+    var anchor = elt("span", "x");
+    var pre = elt("pre", [anchor]);
+    removeChildrenAndAdd(display.measure, pre);
+    var width = anchor.offsetWidth;
+    if (width > 2) display.cachedCharWidth = width;
+    return width || 10;
+  }
+
+  // OPERATIONS
+
+  // Operations are used to wrap changes in such a way that each
+  // change won't have to update the cursor and display (which would
+  // be awkward, slow, and error-prone), but instead updates are
+  // batched and then all combined and executed at once.
+
+  var nextOpId = 0;
+  function startOperation(cm) {
+    cm.curOp = {
+      // An array of ranges of lines that have to be updated. See
+      // updateDisplay.
+      changes: [],
+      updateInput: null,
+      userSelChange: null,
+      textChanged: null,
+      selectionChanged: false,
+      updateMaxLine: false,
+      updateScrollPos: false,
+      id: ++nextOpId
+    };
+    if (!delayedCallbackDepth++) delayedCallbacks = [];
+  }
+
+  function endOperation(cm) {
+    var op = cm.curOp, doc = cm.doc, display = cm.display;
+    cm.curOp = null;
+
+    if (op.updateMaxLine) computeMaxLength(cm);
+    if (display.maxLineChanged && !cm.options.lineWrapping) {
+      var width = measureLine(cm, display.maxLine).width;
+      display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
+      display.maxLineChanged = false;
+      var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
+      if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
+        setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
+    }
+    var newScrollPos, updated;
+    if (op.updateScrollPos) {
+      newScrollPos = op.updateScrollPos;
+    } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
+      var coords = cursorCoords(cm, doc.sel.head);
+      newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
+    }
+    if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null)
+      updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop);
+    if (!updated && op.selectionChanged) updateSelection(cm);
+    if (op.updateScrollPos) {
+      display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
+      display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
+      alignHorizontally(cm);
+    } else if (newScrollPos) {
+      scrollCursorIntoView(cm);
+    }
+    if (op.selectionChanged) restartBlink(cm);
+
+    if (cm.state.focused && op.updateInput)
+      resetInput(cm, op.userSelChange);
+
+    var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
+    if (hidden) for (var i = 0; i < hidden.length; ++i)
+      if (!hidden[i].lines.length) signal(hidden[i], "hide");
+    if (unhidden) for (var i = 0; i < unhidden.length; ++i)
+      if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
+
+    var delayed;
+    if (!--delayedCallbackDepth) {
+      delayed = delayedCallbacks;
+      delayedCallbacks = null;
+    }
+    if (op.textChanged)
+      signal(cm, "change", cm, op.textChanged);
+    if (op.selectionChanged) signal(cm, "cursorActivity", cm);
+    if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
+  }
+
+  // Wraps a function in an operation. Returns the wrapped function.
+  function operation(cm1, f) {
+    return function() {
+      var cm = cm1 || this, withOp = !cm.curOp;
+      if (withOp) startOperation(cm);
+      try { var result = f.apply(cm, arguments); }
+      finally { if (withOp) endOperation(cm); }
+      return result;
+    };
+  }
+  function docOperation(f) {
+    return function() {
+      var withOp = this.cm && !this.cm.curOp, result;
+      if (withOp) startOperation(this.cm);
+      try { result = f.apply(this, arguments); }
+      finally { if (withOp) endOperation(this.cm); }
+      return result;
+    };
+  }
+  function runInOp(cm, f) {
+    var withOp = !cm.curOp, result;
+    if (withOp) startOperation(cm);
+    try { result = f(); }
+    finally { if (withOp) endOperation(cm); }
+    return result;
+  }
+
+  function regChange(cm, from, to, lendiff) {
+    if (from == null) from = cm.doc.first;
+    if (to == null) to = cm.doc.first + cm.doc.size;
+    cm.curOp.changes.push({from: from, to: to, diff: lendiff});
+  }
+
+  // INPUT HANDLING
+
+  function slowPoll(cm) {
+    if (cm.display.pollingFast) return;
+    cm.display.poll.set(cm.options.pollInterval, function() {
+      readInput(cm);
+      if (cm.state.focused) slowPoll(cm);
+    });
+  }
+
+  function fastPoll(cm) {
+    var missed = false;
+    cm.display.pollingFast = true;
+    function p() {
+      var changed = readInput(cm);
+      if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
+      else {cm.display.pollingFast = false; slowPoll(cm);}
+    }
+    cm.display.poll.set(20, p);
+  }
+
+  // prevInput is a hack to work with IME. If we reset the textarea
+  // on every change, that breaks IME. So we look for changes
+  // compared to the previous content instead. (Modern browsers have
+  // events that indicate IME taking place, but these are not widely
+  // supported or compatible enough yet to rely on.)
+  function readInput(cm) {
+    var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
+    if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false;
+    var text = input.value;
+    if (text == prevInput && posEq(sel.from, sel.to)) return false;
+    // IE enjoys randomly deselecting our input's text when
+    // re-focusing. If the selection is gone but the cursor is at the
+    // start of the input, that's probably what happened.
+    if (ie && text && input.selectionStart === 0) {
+      resetInput(cm, true);
+      return false;
+    }
+    var withOp = !cm.curOp;
+    if (withOp) startOperation(cm);
+    sel.shift = false;
+    var same = 0, l = Math.min(prevInput.length, text.length);
+    while (same < l && prevInput[same] == text[same]) ++same;
+    var from = sel.from, to = sel.to;
+    if (same < prevInput.length)
+      from = Pos(from.line, from.ch - (prevInput.length - same));
+    else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
+      to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
+    var updateInput = cm.curOp.updateInput;
+    makeChange(cm.doc, {from: from, to: to, text: splitLines(text.slice(same)),
+                        origin: cm.state.pasteIncoming ? "paste" : "+input"}, "end");
+               
+    cm.curOp.updateInput = updateInput;
+    if (text.length > 1000) input.value = cm.display.prevInput = "";
+    else cm.display.prevInput = text;
+    if (withOp) endOperation(cm);
+    cm.state.pasteIncoming = false;
+    return true;
+  }
+
+  function resetInput(cm, user) {
+    var minimal, selected, doc = cm.doc;
+    if (!posEq(doc.sel.from, doc.sel.to)) {
+      cm.display.prevInput = "";
+      minimal = hasCopyEvent &&
+        (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
+      if (minimal) cm.display.input.value = "-";
+      else cm.display.input.value = selected || cm.getSelection();
+      if (cm.state.focused) selectInput(cm.display.input);
+    } else if (user) cm.display.prevInput = cm.display.input.value = "";
+    cm.display.inaccurateSelection = minimal;
+  }
+
+  function focusInput(cm) {
+    if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
+      cm.display.input.focus();
+  }
+
+  function isReadOnly(cm) {
+    return cm.options.readOnly || cm.doc.cantEdit;
+  }
+
+  // EVENT HANDLERS
+
+  function registerEventHandlers(cm) {
+    var d = cm.display;
+    on(d.scroller, "mousedown", operation(cm, onMouseDown));
+    on(d.scroller, "dblclick", operation(cm, e_preventDefault));
+    on(d.lineSpace, "selectstart", function(e) {
+      if (!eventInWidget(d, e)) e_preventDefault(e);
+    });
+    // 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 (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
+
+    on(d.scroller, "scroll", function() {
+      setScrollTop(cm, d.scroller.scrollTop);
+      setScrollLeft(cm, d.scroller.scrollLeft, true);
+      signal(cm, "scroll", cm);
+    });
+    on(d.scrollbarV, "scroll", function() {
+      setScrollTop(cm, d.scrollbarV.scrollTop);
+    });
+    on(d.scrollbarH, "scroll", function() {
+      setScrollLeft(cm, d.scrollbarH.scrollLeft);
+    });
+
+    on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
+    on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
+
+    function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
+    on(d.scrollbarH, "mousedown", reFocus);
+    on(d.scrollbarV, "mousedown", reFocus);
+    // Prevent wrapper from ever scrolling
+    on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
+
+    function onResize() {
+      // Might be a text scaling operation, clear size caches.
+      d.cachedCharWidth = d.cachedTextHeight = null;
+      clearCaches(cm);
+      runInOp(cm, bind(regChange, cm));
+    }
+    on(window, "resize", onResize);
+    // Above handler holds on to the editor and its data structures.
+    // Here we poll to unregister it when the editor is no longer in
+    // the document, so that it can be garbage-collected.
+    function unregister() {
+      for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
+      if (p) setTimeout(unregister, 5000);
+      else off(window, "resize", onResize);
+    }    
+    setTimeout(unregister, 5000);
+
+    on(d.input, "keyup", operation(cm, function(e) {
+      if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+      if (e.keyCode == 16) cm.doc.sel.shift = false;
+    }));
+    on(d.input, "input", bind(fastPoll, cm));
+    on(d.input, "keydown", operation(cm, onKeyDown));
+    on(d.input, "keypress", operation(cm, onKeyPress));
+    on(d.input, "focus", bind(onFocus, cm));
+    on(d.input, "blur", bind(onBlur, cm));
+
+    function drag_(e) {
+      if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
+      e_stop(e);
+    }
+    if (cm.options.dragDrop) {
+      on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
+      on(d.scroller, "dragenter", drag_);
+      on(d.scroller, "dragover", drag_);
+      on(d.scroller, "drop", operation(cm, onDrop));
+    }
+    on(d.scroller, "paste", function(e){
+      if (eventInWidget(d, e)) return;
+      focusInput(cm); 
+      fastPoll(cm);
+    });
+    on(d.input, "paste", function() {
+      cm.state.pasteIncoming = true;
+      fastPoll(cm);
+    });
+
+    function prepareCopy() {
+      if (d.inaccurateSelection) {
+        d.prevInput = "";
+        d.inaccurateSelection = false;
+        d.input.value = cm.getSelection();
+        selectInput(d.input);
+      }
+    }
+    on(d.input, "cut", prepareCopy);
+    on(d.input, "copy", prepareCopy);
+
+    // Needed to handle Tab key in KHTML
+    if (khtml) on(d.sizer, "mouseup", function() {
+        if (document.activeElement == d.input) d.input.blur();
+        focusInput(cm);
+    });
+  }
+
+  function eventInWidget(display, e) {
+    for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
+      if (!n) return true;
+      if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) ||
+          n.parentNode == display.sizer && n != display.mover) return true;
+    }
+  }
+
+  function posFromMouse(cm, e, liberal) {
+    var display = cm.display;
+    if (!liberal) {
+      var target = e_target(e);
+      if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
+          target == display.scrollbarV || target == display.scrollbarV.firstChild ||
+          target == display.scrollbarFiller) return null;
+    }
+    var x, y, space = getRect(display.lineSpace);
+    // Fails unpredictably on IE[67] when mouse is dragged around quickly.
+    try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
+    return coordsChar(cm, x - space.left, y - space.top);
+  }
+
+  var lastClick, lastDoubleClick;
+  function onMouseDown(e) {
+    var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
+    sel.shift = e.shiftKey;
+
+    if (eventInWidget(display, e)) {
+      if (!webkit) {
+        display.scroller.draggable = false;
+        setTimeout(function(){display.scroller.draggable = true;}, 100);
+      }
+      return;
+    }
+    if (clickInGutter(cm, e)) return;
+    var start = posFromMouse(cm, e);
+
+    switch (e_button(e)) {
+    case 3:
+      if (captureMiddleClick) onContextMenu.call(cm, cm, e);
+      return;
+    case 2:
+      if (start) extendSelection(cm.doc, start);
+      setTimeout(bind(focusInput, cm), 20);
+      e_preventDefault(e);
+      return;
+    }
+    // For button 1, if it was clicked inside the editor
+    // (posFromMouse returning non-null), we have to adjust the
+    // selection.
+    if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
+
+    if (!cm.state.focused) onFocus(cm);
+
+    var now = +new Date, type = "single";
+    if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
+      type = "triple";
+      e_preventDefault(e);
+      setTimeout(bind(focusInput, cm), 20);
+      selectLine(cm, start.line);
+    } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
+      type = "double";
+      lastDoubleClick = {time: now, pos: start};
+      e_preventDefault(e);
+      var word = findWordAt(getLine(doc, start.line).text, start);
+      extendSelection(cm.doc, word.from, word.to);
+    } else { lastClick = {time: now, pos: start}; }
+
+    var last = start;
+    if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
+        !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
+      var dragEnd = operation(cm, function(e2) {
+        if (webkit) display.scroller.draggable = false;
+        cm.state.draggingText = false;
+        off(document, "mouseup", dragEnd);
+        off(display.scroller, "drop", dragEnd);
+        if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
+          e_preventDefault(e2);
+          extendSelection(cm.doc, start);
+          focusInput(cm);
+        }
+      });
+      // Let the drag handler handle this.
+      if (webkit) display.scroller.draggable = true;
+      cm.state.draggingText = dragEnd;
+      // IE's approach to draggable
+      if (display.scroller.dragDrop) display.scroller.dragDrop();
+      on(document, "mouseup", dragEnd);
+      on(display.scroller, "drop", dragEnd);
+      return;
+    }
+    e_preventDefault(e);
+    if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
+
+    var startstart = sel.from, startend = sel.to;
+
+    function doSelect(cur) {
+      if (type == "single") {
+        extendSelection(cm.doc, clipPos(doc, start), cur);
+        return;
+      }
+
+      startstart = clipPos(doc, startstart);
+      startend = clipPos(doc, startend);
+      if (type == "double") {
+        var word = findWordAt(getLine(doc, cur.line).text, cur);
+        if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
+        else extendSelection(cm.doc, startstart, word.to);
+      } else if (type == "triple") {
+        if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
+        else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
+      }
+    }
+
+    var editorSize = getRect(display.wrapper);
+    // Used to ensure timeout re-tries don't fire when another extend
+    // happened in the meantime (clearTimeout isn't reliable -- at
+    // least on Chrome, the timeouts still happen even when cleared,
+    // if the clear happens after their scheduled firing time).
+    var counter = 0;
+
+    function extend(e) {
+      var curCount = ++counter;
+      var cur = posFromMouse(cm, e, true);
+      if (!cur) return;
+      if (!posEq(cur, last)) {
+        if (!cm.state.focused) onFocus(cm);
+        last = cur;
+        doSelect(cur);
+        var visible = visibleLines(display, doc);
+        if (cur.line >= visible.to || cur.line < visible.from)
+          setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
+      } else {
+        var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
+        if (outside) setTimeout(operation(cm, function() {
+          if (counter != curCount) return;
+          display.scroller.scrollTop += outside;
+          extend(e);
+        }), 50);
+      }
+    }
+
+    function done(e) {
+      counter = Infinity;
+      var cur = posFromMouse(cm, e);
+      if (cur) doSelect(cur);
+      e_preventDefault(e);
+      focusInput(cm);
+      off(document, "mousemove", move);
+      off(document, "mouseup", up);
+    }
+
+    var move = operation(cm, function(e) {
+      if (!ie && !e_button(e)) done(e);
+      else extend(e);
+    });
+    var up = operation(cm, done);
+    on(document, "mousemove", move);
+    on(document, "mouseup", up);
+  }
+
+  function onDrop(e) {
+    var cm = this;
+    if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
+      return;
+    e_preventDefault(e);
+    var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
+    if (!pos || isReadOnly(cm)) return;
+    if (files && files.length && window.FileReader && window.File) {
+      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(cm.doc, pos);
+            replaceRange(cm.doc, text.join(""), pos, "around", "paste");
+          }
+        };
+        reader.readAsText(file);
+      };
+      for (var i = 0; i < n; ++i) loadFile(files[i], i);
+    } else {
+      // Don't do a replace if the drop happened inside of the selected text.
+      if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
+        cm.state.draggingText(e);
+        // Ensure the editor is re-focused
+        setTimeout(bind(focusInput, cm), 20);
+        return;
+      }
+      try {
+        var text = e.dataTransfer.getData("Text");
+        if (text) {
+          var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
+          setSelection(cm.doc, pos, pos);
+          if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
+          cm.replaceSelection(text, null, "paste");
+          focusInput(cm);
+          onFocus(cm);
+        }
+      }
+      catch(e){}
+    }
+  }
+
+  function clickInGutter(cm, e) {
+    var display = cm.display;
+    try { var mX = e.clientX, mY = e.clientY; }
+    catch(e) { return false; }
+
+    if (mX >= Math.floor(getRect(display.gutters).right)) return false;
+    e_preventDefault(e);
+    if (!hasHandler(cm, "gutterClick")) return true;
+
+    var lineBox = getRect(display.lineDiv);
+    if (mY > lineBox.bottom) return true;
+    mY -= lineBox.top - display.viewOffset;
+
+    for (var i = 0; i < cm.options.gutters.length; ++i) {
+      var g = display.gutters.childNodes[i];
+      if (g && getRect(g).right >= mX) {
+        var line = lineAtHeight(cm.doc, mY);
+        var gutter = cm.options.gutters[i];
+        signalLater(cm, "gutterClick", cm, line, gutter, e);
+        break;
+      }
+    }
+    return true;
+  }
+
+  function onDragStart(cm, e) {
+    if (eventInWidget(cm.display, e)) return;
+    
+    var txt = cm.getSelection();
+    e.dataTransfer.setData("Text", txt);
+
+    // Use dummy image instead of default browsers image.
+    // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
+    if (e.dataTransfer.setDragImage && !safari) {
+      var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
+      if (opera) {
+        img.width = img.height = 1;
+        cm.display.wrapper.appendChild(img);
+        // Force a relayout, or Opera won't use our image for some obscure reason
+        img._top = img.offsetTop;
+      }
+      e.dataTransfer.setDragImage(img, 0, 0);
+      if (opera) img.parentNode.removeChild(img);
+    }
+  }
+
+  function setScrollTop(cm, val) {
+    if (Math.abs(cm.doc.scrollTop - val) < 2) return;
+    cm.doc.scrollTop = val;
+    if (!gecko) updateDisplay(cm, [], val);
+    if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
+    if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
+    if (gecko) updateDisplay(cm, []);
+  }
+  function setScrollLeft(cm, val, isScroller) {
+    if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
+    val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
+    cm.doc.scrollLeft = val;
+    alignHorizontally(cm);
+    if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
+    if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
+  }
+
+  // Since the delta values reported on mouse wheel events are
+  // unstandardized between browsers and even browser versions, and
+  // generally horribly unpredictable, this code starts by measuring
+  // the scroll effect that the first few mouse wheel events have,
+  // and, from that, detects the way it can convert deltas to pixel
+  // offsets afterwards.
+  //
+  // The reason we want to know the amount a wheel event will scroll
+  // is that it gives us a chance to update the display before the
+  // actual scrolling happens, reducing flickering.
+
+  var wheelSamples = 0, wheelPixelsPerUnit = null;
+  // Fill in a browser-detected starting value on browsers where we
+  // know one. These don't have to be accurate -- the result of them
+  // being wrong would just be a slight flicker on the first wheel
+  // scroll (if it is large enough).
+  if (ie) wheelPixelsPerUnit = -.53;
+  else if (gecko) wheelPixelsPerUnit = 15;
+  else if (chrome) wheelPixelsPerUnit = -.7;
+  else if (safari) wheelPixelsPerUnit = -1/3;
+
+  function onScrollWheel(cm, e) {
+    var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
+    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
+    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
+    else if (dy == null) dy = e.wheelDelta;
+
+    // Webkit browsers on OS X abort momentum scrolls when the target
+    // of the scroll event is removed from the scrollable element.
+    // This hack (see related code in patchDisplay) makes sure the
+    // element is kept around.
+    if (dy && mac && webkit) {
+      for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
+        if (cur.lineObj) {
+          cm.display.currentWheelTarget = cur;
+          break;
+        }
+      }
+    }
+
+    var display = cm.display, scroll = display.scroller;
+    // On some browsers, horizontal scrolling will cause redraws to
+    // happen before the gutter has been realigned, causing it to
+    // wriggle around in a most unseemly way. When we have an
+    // estimated pixels/delta value, we just handle horizontal
+    // scrolling entirely here. It'll be slightly off from native, but
+    // better than glitching out.
+    if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
+      if (dy)
+        setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
+      setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
+      e_preventDefault(e);
+      display.wheelStartX = null; // Abort measurement, if in progress
+      return;
+    }
+
+    if (dy && wheelPixelsPerUnit != null) {
+      var pixels = dy * wheelPixelsPerUnit;
+      var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
+      if (pixels < 0) top = Math.max(0, top + pixels - 50);
+      else bot = Math.min(cm.doc.height, bot + pixels + 50);
+      updateDisplay(cm, [], {top: top, bottom: bot});
+    }
+
+    if (wheelSamples < 20) {
+      if (display.wheelStartX == null) {
+        display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
+        display.wheelDX = dx; display.wheelDY = dy;
+        setTimeout(function() {
+          if (display.wheelStartX == null) return;
+          var movedX = scroll.scrollLeft - display.wheelStartX;
+          var movedY = scroll.scrollTop - display.wheelStartY;
+          var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
+            (movedX && display.wheelDX && movedX / display.wheelDX);
+          display.wheelStartX = display.wheelStartY = null;
+          if (!sample) return;
+          wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
+          ++wheelSamples;
+        }, 200);
+      } else {
+        display.wheelDX += dx; display.wheelDY += dy;
+      }
+    }
+  }
+
+  function doHandleBinding(cm, bound, dropShift) {
+    if (typeof bound == "string") {
+      bound = commands[bound];
+      if (!bound) return false;
+    }
+    // Ensure previous input has been read, so that the handler sees a
+    // consistent view of the document
+    if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
+    var doc = cm.doc, prevShift = doc.sel.shift, done = false;
+    try {
+      if (isReadOnly(cm)) cm.state.suppressEdits = true;
+      if (dropShift) doc.sel.shift = false;
+      done = bound(cm) != Pass;
+    } finally {
+      doc.sel.shift = prevShift;
+      cm.state.suppressEdits = false;
+    }
+    return done;
+  }
+
+  function allKeyMaps(cm) {
+    var maps = cm.state.keyMaps.slice(0);
+    maps.push(cm.options.keyMap);
+    if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys);
+    return maps;
+  }
+
+  var maybeTransition;
+  function handleKeyBinding(cm, e) {
+    // Handle auto keymap transitions
+    var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
+    clearTimeout(maybeTransition);
+    if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
+      if (getKeyMap(cm.options.keyMap) == startMap)
+        cm.options.keyMap = (next.call ? next.call(null, cm) : next);
+    }, 50);
+
+    var name = keyName(e, true), handled = false;
+    if (!name) return false;
+    var keymaps = allKeyMaps(cm);
+
+    if (e.shiftKey) {
+      // First try to resolve full name (including 'Shift-'). Failing
+      // that, see if there is a cursor-motion command (starting with
+      // 'go') bound to the keyname without 'Shift-'.
+      handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
+             || lookupKey(name, keymaps, function(b) {
+                  if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b);
+                });
+    } else {
+      handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
+    }
+    if (handled == "stop") handled = false;
+
+    if (handled) {
+      e_preventDefault(e);
+      restartBlink(cm);
+      if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
+    }
+    return handled;
+  }
+
+  function handleCharBinding(cm, e, ch) {
+    var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
+                            function(b) { return doHandleBinding(cm, b, true); });
+    if (handled) {
+      e_preventDefault(e);
+      restartBlink(cm);
+    }
+    return handled;
+  }
+
+  var lastStoppedKey = null;
+  function onKeyDown(e) {
+    var cm = this;
+    if (!cm.state.focused) onFocus(cm);
+    if (ie && e.keyCode == 27) { e.returnValue = false; }
+    if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+    var code = e.keyCode;
+    // IE does strange things with escape.
+    cm.doc.sel.shift = code == 16 || e.shiftKey;
+    // First give onKeyEvent option a chance to handle this.
+    var handled = handleKeyBinding(cm, e);
+    if (opera) {
+      lastStoppedKey = handled ? code : null;
+      // Opera has no cut event... we try to at least catch the key combo
+      if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
+        cm.replaceSelection("");
+    }
+  }
+
+  function onKeyPress(e) {
+    var cm = this;
+    if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+    var keyCode = e.keyCode, charCode = e.charCode;
+    if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
+    if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
+    var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
+    if (this.options.electricChars && this.doc.mode.electricChars &&
+        this.options.smartIndent && !isReadOnly(this) &&
+        this.doc.mode.electricChars.indexOf(ch) > -1)
+      setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
+    if (handleCharBinding(cm, e, ch)) return;
+    fastPoll(cm);
+  }
+
+  function onFocus(cm) {
+    if (cm.options.readOnly == "nocursor") return;
+    if (!cm.state.focused) {
+      signal(cm, "focus", cm);
+      cm.state.focused = true;
+      if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
+        cm.display.wrapper.className += " CodeMirror-focused";
+      resetInput(cm, true);
+    }
+    slowPoll(cm);
+    restartBlink(cm);
+  }
+  function onBlur(cm) {
+    if (cm.state.focused) {
+      signal(cm, "blur", cm);
+      cm.state.focused = false;
+      cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
+    }
+    clearInterval(cm.display.blinker);
+    setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
+  }
+
+  var detectingSelectAll;
+  function onContextMenu(cm, e) {
+    var display = cm.display, sel = cm.doc.sel;
+    if (eventInWidget(display, e)) return;
+
+    var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
+    if (!pos || opera) return; // Opera is difficult.
+    if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
+      operation(cm, setSelection)(cm.doc, pos, pos);
+
+    var oldCSS = display.input.style.cssText;
+    display.inputDiv.style.position = "absolute";
+    display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
+      "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
+      "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
+    focusInput(cm);
+    resetInput(cm, true);
+    // Adds "Select all" to context menu in FF
+    if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
+
+    function rehide() {
+      display.inputDiv.style.position = "relative";
+      display.input.style.cssText = oldCSS;
+      if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
+      slowPoll(cm);
+
+      // Try to detect the user choosing select-all 
+      if (display.input.selectionStart != null && (!ie || ie_lt9)) {
+        clearTimeout(detectingSelectAll);
+        var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0;
+        display.prevInput = " ";
+        display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
+        var poll = function(){
+          if (display.prevInput == " " && display.input.selectionStart == 0)
+            operation(cm, commands.selectAll)(cm);
+          else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
+          else resetInput(cm);
+        };
+        detectingSelectAll = setTimeout(poll, 200);
+      }
+    }
+
+    if (captureMiddleClick) {
+      e_stop(e);
+      var mouseup = function() {
+        off(window, "mouseup", mouseup);
+        setTimeout(rehide, 20);
+      };
+      on(window, "mouseup", mouseup);
+    } else {
+      setTimeout(rehide, 50);
+    }
+  }
+
+  // UPDATING
+
+  function changeEnd(change) {
+    return Pos(change.from.line + change.text.length - 1,
+               lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
+  }
+
+  // Make sure a position will be valid after the given change.
+  function clipPostChange(doc, change, pos) {
+    if (!posLess(change.from, pos)) return clipPos(doc, pos);
+    var diff = (change.text.length - 1) - (change.to.line - change.from.line);
+    if (pos.line > change.to.line + diff) {
+      var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
+      if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
+      return clipToLen(pos, getLine(doc, preLine).text.length);
+    }
+    if (pos.line == change.to.line + diff)
+      return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
+                       getLine(doc, change.to.line).text.length - change.to.ch);
+    var inside = pos.line - change.from.line;
+    return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
+  }
+
+  // Hint can be null|"end"|"start"|"around"|{anchor,head}
+  function computeSelAfterChange(doc, change, hint) {
+    if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
+      return {anchor: clipPostChange(doc, change, hint.anchor),
+              head: clipPostChange(doc, change, hint.head)};
+
+    if (hint == "start") return {anchor: change.from, head: change.from};
+    
+    var end = changeEnd(change);
+    if (hint == "around") return {anchor: change.from, head: end};
+    if (hint == "end") return {anchor: end, head: end};
+
+    // hint is null, leave the selection alone as much as possible
+    var adjustPos = function(pos) {
+      if (posLess(pos, change.from)) return pos;
+      if (!posLess(change.to, pos)) return end;
+
+      var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
+      if (pos.line == change.to.line) ch += end.ch - change.to.ch;
+      return Pos(line, ch);
+    };
+    return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
+  }
+
+  function filterChange(doc, change) {
+    var obj = {
+      canceled: false,
+      from: change.from,
+      to: change.to,
+      text: change.text,
+      origin: change.origin,
+      update: function(from, to, text, origin) {
+        if (from) this.from = clipPos(doc, from);
+        if (to) this.to = clipPos(doc, to);
+        if (text) this.text = text;
+        if (origin !== undefined) this.origin = origin;
+      },
+      cancel: function() { this.canceled = true; }
+    };
+    signal(doc, "beforeChange", doc, obj);
+    if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
+
+    if (obj.canceled) return null;
+    return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
+  }
+
+  // Replace the range from from to to by the strings in replacement.
+  // change is a {from, to, text [, origin]} object
+  function makeChange(doc, change, selUpdate, ignoreReadOnly) {
+    if (doc.cm) {
+      if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
+      if (doc.cm.state.suppressEdits) return;
+    }
+
+    if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
+      change = filterChange(doc, change);
+      if (!change) return;
+    }
+
+    // Possibly split or suppress the update based on the presence
+    // of read-only spans in its range.
+    var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
+    if (split) {
+      for (var i = split.length - 1; i >= 1; --i)
+        makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
+      if (split.length)
+        makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
+    } else {
+      makeChangeNoReadonly(doc, change, selUpdate);
+    }
+  }
+
+  function makeChangeNoReadonly(doc, change, selUpdate) {
+    var selAfter = computeSelAfterChange(doc, change, selUpdate);
+    addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
+
+    makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
+    var rebased = [];
+
+    linkedDocs(doc, function(doc, sharedHist) {
+      if (!sharedHist && indexOf(rebased, doc.history) == -1) {
+        rebaseHist(doc.history, change);
+        rebased.push(doc.history);
+      }
+      makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
+    });
+  }
+
+  function makeChangeFromHistory(doc, type) {
+    var hist = doc.history;
+    var event = (type == "undo" ? hist.done : hist.undone).pop();
+    if (!event) return;
+    hist.dirtyCounter += type == "undo" ? -1 : 1;
+
+    var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
+                anchorAfter: event.anchorBefore, headAfter: event.headBefore};
+    (type == "undo" ? hist.undone : hist.done).push(anti);
+
+    for (var i = event.changes.length - 1; i >= 0; --i) {
+      var change = event.changes[i];
+      change.origin = type;
+      anti.changes.push(historyChangeFromChange(doc, change));
+
+      var after = i ? computeSelAfterChange(doc, change, null)
+                    : {anchor: event.anchorBefore, head: event.headBefore};
+      makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
+      var rebased = [];
+
+      linkedDocs(doc, function(doc, sharedHist) {
+        if (!sharedHist && indexOf(rebased, doc.history) == -1) {
+          rebaseHist(doc.history, change);
+          rebased.push(doc.history);
+        }
+        makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
+      });
+    }
+  }
+
+  function shiftDoc(doc, distance) {
+    function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
+    doc.first += distance;
+    if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
+    doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
+    doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
+  }
+
+  function makeChangeSingleDoc(doc, change, selAfter, spans) {
+    if (doc.cm && !doc.cm.curOp)
+      return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
+
+    if (change.to.line < doc.first) {
+      shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
+      return;
+    }
+    if (change.from.line > doc.lastLine()) return;
+
+    // Clip the change to the size of this doc
+    if (change.from.line < doc.first) {
+      var shift = change.text.length - 1 - (doc.first - change.from.line);
+      shiftDoc(doc, shift);
+      change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
+                text: [lst(change.text)], origin: change.origin};
+    }
+    var last = doc.lastLine();
+    if (change.to.line > last) {
+      change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
+                text: [change.text[0]], origin: change.origin};
+    }
+
+    if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
+    if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
+    else updateDoc(doc, change, spans, selAfter);
+  }
+
+  function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
+    var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
+
+    var recomputeMaxLength = false, checkWidthStart = from.line;
+    if (!cm.options.lineWrapping) {
+      checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
+      doc.iter(checkWidthStart, to.line + 1, function(line) {
+        if (line == display.maxLine) {
+          recomputeMaxLength = true;
+          return true;
+        }
+      });
+    }
+
+    updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
+
+    if (!cm.options.lineWrapping) {
+      doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
+        var len = lineLength(doc, line);
+        if (len > display.maxLineLength) {
+          display.maxLine = line;
+          display.maxLineLength = len;
+          display.maxLineChanged = true;
+          recomputeMaxLength = false;
+        }
+      });
+      if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
+    }
+
+    // Adjust frontier, schedule worker
+    doc.frontier = Math.min(doc.frontier, from.line);
+    startWorker(cm, 400);
+
+    var lendiff = change.text.length - (to.line - from.line) - 1;
+    // Remember that these lines changed, for updating the display
+    regChange(cm, from.line, to.line + 1, lendiff);
+    if (hasHandler(cm, "change")) {
+      var changeObj = {from: from, to: to, text: change.text, origin: change.origin};
+      if (cm.curOp.textChanged) {
+        for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
+        cur.next = changeObj;
+      } else cm.curOp.textChanged = changeObj;
+    }
+  }
+
+  function replaceRange(doc, code, from, to, origin) {
+    if (!to) to = from;
+    if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
+    if (typeof code == "string") code = splitLines(code);
+    makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
+  }
+
+  // POSITION OBJECT
+
+  function Pos(line, ch) {
+    if (!(this instanceof Pos)) return new Pos(line, ch);
+    this.line = line; this.ch = ch;
+  }
+  CodeMirror.Pos = Pos;
+
+  function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
+  function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
+  function copyPos(x) {return Pos(x.line, x.ch);}
+
+  // SELECTION
+
+  function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
+  function clipPos(doc, pos) {
+    if (pos.line < doc.first) return Pos(doc.first, 0);
+    var last = doc.first + doc.size - 1;
+    if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
+    return clipToLen(pos, getLine(doc, pos.line).text.length);
+  }
+  function clipToLen(pos, linelen) {
+    var ch = pos.ch;
+    if (ch == null || ch > linelen) return Pos(pos.line, linelen);
+    else if (ch < 0) return Pos(pos.line, 0);
+    else return pos;
+  }
+  function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
+
+  // If shift is held, this will move the selection anchor. Otherwise,
+  // it'll set the whole selection.
+  function extendSelection(doc, pos, other, bias) {
+    if (doc.sel.shift || doc.sel.extend) {
+      var anchor = doc.sel.anchor;
+      if (other) {
+        var posBefore = posLess(pos, anchor);
+        if (posBefore != posLess(other, anchor)) {
+          anchor = pos;
+          pos = other;
+        } else if (posBefore != posLess(pos, other)) {
+          pos = other;
+        }
+      }
+      setSelection(doc, anchor, pos, bias);
+    } else {
+      setSelection(doc, pos, other || pos, bias);
+    }
+    if (doc.cm) doc.cm.curOp.userSelChange = true;
+  }
+
+  function filterSelectionChange(doc, anchor, head) {
+    var obj = {anchor: anchor, head: head};
+    signal(doc, "beforeSelectionChange", doc, obj);
+    if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
+    obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
+    return obj;
+  }
+
+  // Update the selection. Last two args are only used by
+  // updateDoc, since they have to be expressed in the line
+  // numbers before the update.
+  function setSelection(doc, anchor, head, bias, checkAtomic) {
+    if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
+      var filtered = filterSelectionChange(doc, anchor, head);
+      head = filtered.head;
+      anchor = filtered.anchor;
+    }
+
+    var sel = doc.sel;
+    sel.goalColumn = null;
+    // Skip over atomic spans.
+    if (checkAtomic || !posEq(anchor, sel.anchor))
+      anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
+    if (checkAtomic || !posEq(head, sel.head))
+      head = skipAtomic(doc, head, bias, checkAtomic != "push");
+
+    if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
+
+    sel.anchor = anchor; sel.head = head;
+    var inv = posLess(head, anchor);
+    sel.from = inv ? head : anchor;
+    sel.to = inv ? anchor : head;
+
+    if (doc.cm)
+      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
+
+    signalLater(doc, "cursorActivity", doc);
+  }
+
+  function reCheckSelection(cm) {
+    setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
+  }
+
+  function skipAtomic(doc, pos, bias, mayClear) {
+    var flipped = false, curPos = pos;
+    var dir = bias || 1;
+    doc.cantEdit = false;
+    search: for (;;) {
+      var line = getLine(doc, curPos.line), toClear;
+      if (line.markedSpans) {
+        for (var i = 0; i < line.markedSpans.length; ++i) {
+          var sp = line.markedSpans[i], m = sp.marker;
+          if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
+              (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
+            if (mayClear && m.clearOnEnter) {
+              (toClear || (toClear = [])).push(m);
+              continue;
+            } else if (!m.atomic) continue;
+            var newPos = m.find()[dir < 0 ? "from" : "to"];
+            if (posEq(newPos, curPos)) {
+              newPos.ch += dir;
+              if (newPos.ch < 0) {
+                if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
+                else newPos = null;
+              } else if (newPos.ch > line.text.length) {
+                if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
+                else newPos = null;
+              }
+              if (!newPos) {
+                if (flipped) {
+                  // Driven in a corner -- no valid cursor position found at all
+                  // -- try again *with* clearing, if we didn't already
+                  if (!mayClear) return skipAtomic(doc, pos, bias, true);
+                  // Otherwise, turn off editing until further notice, and return the start of the doc
+                  doc.cantEdit = true;
+                  return Pos(doc.first, 0);
+                }
+                flipped = true; newPos = pos; dir = -dir;
+              }
+            }
+            curPos = newPos;
+            continue search;
+          }
+        }
+        if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear();
+      }
+      return curPos;
+    }
+  }
+
+  // SCROLLING
+
+  function scrollCursorIntoView(cm) {
+    var coords = scrollPosIntoView(cm, cm.doc.sel.head);
+    if (!cm.state.focused) return;
+    var display = cm.display, box = getRect(display.sizer), doScroll = null;
+    if (coords.top + box.top < 0) doScroll = true;
+    else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
+    if (doScroll != null && !phantom) {
+      var hidden = display.cursor.style.display == "none";
+      if (hidden) {
+        display.cursor.style.display = "";
+        display.cursor.style.left = coords.left + "px";
+        display.cursor.style.top = (coords.top - display.viewOffset) + "px";
+      }
+      display.cursor.scrollIntoView(doScroll);
+      if (hidden) display.cursor.style.display = "none";
+    }
+  }
+
+  function scrollPosIntoView(cm, pos) {
+    for (;;) {
+      var changed = false, coords = cursorCoords(cm, pos);
+      var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
+      var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
+      if (scrollPos.scrollTop != null) {
+        setScrollTop(cm, scrollPos.scrollTop);
+        if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
+      }
+      if (scrollPos.scrollLeft != null) {
+        setScrollLeft(cm, scrollPos.scrollLeft);
+        if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
+      }
+      if (!changed) return coords;
+    }
+  }
+
+  function scrollIntoView(cm, x1, y1, x2, y2) {
+    var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
+    if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
+    if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
+  }
+
+  function calculateScrollPos(cm, x1, y1, x2, y2) {
+    var display = cm.display, pt = paddingTop(display);
+    y1 += pt; y2 += pt;
+    var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
+    var docBottom = cm.doc.height + 2 * pt;
+    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 = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
+    x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
+    var gutterw = display.gutters.offsetWidth;
+    var atLeft = x1 < gutterw + 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;
+    }
+    return result;
+  }
+
+  // API UTILITIES
+
+  function indentLine(cm, n, how, aggressive) {
+    var doc = cm.doc;
+    if (!how) how = "add";
+    if (how == "smart") {
+      if (!cm.doc.mode.indent) how = "prev";
+      else var state = getStateBefore(cm, n);
+    }
+
+    var tabSize = cm.options.tabSize;
+    var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
+    var curSpaceString = line.text.match(/^\s*/)[0], indentation;
+    if (how == "smart") {
+      indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
+      if (indentation == Pass) {
+        if (!aggressive) return;
+        how = "prev";
+      }
+    }
+    if (how == "prev") {
+      if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
+      else indentation = 0;
+    } else if (how == "add") {
+      indentation = curSpace + cm.options.indentUnit;
+    } else if (how == "subtract") {
+      indentation = curSpace - cm.options.indentUnit;
+    }
+    indentation = Math.max(0, indentation);
+
+    var indentString = "", pos = 0;
+    if (cm.options.indentWithTabs)
+      for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
+    if (pos < indentation) indentString += spaceStr(indentation - pos);
+
+    if (indentString != curSpaceString)
+      replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+    line.stateAfter = null;
+  }
+
+  function changeLine(cm, handle, op) {
+    var no = handle, line = handle, doc = cm.doc;
+    if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
+    else no = lineNo(handle);
+    if (no == null) return null;
+    if (op(line, no)) regChange(cm, no, no + 1);
+    else return null;
+    return line;
+  }
+
+  function findPosH(doc, pos, dir, unit, visually) {
+    var line = pos.line, ch = pos.ch;
+    var lineObj = getLine(doc, line);
+    var possible = true;
+    function findNextLine() {
+      var l = line + dir;
+      if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
+      line = l;
+      return lineObj = getLine(doc, l);
+    }
+    function moveOnce(boundToLine) {
+      var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
+      if (next == null) {
+        if (!boundToLine && findNextLine()) {
+          if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
+          else ch = dir < 0 ? lineObj.text.length : 0;
+        } else return (possible = false);
+      } else ch = next;
+      return true;
+    }
+
+    if (unit == "char") moveOnce();
+    else if (unit == "column") moveOnce(true);
+    else if (unit == "word") {
+      var sawWord = false;
+      for (;;) {
+        if (dir < 0) if (!moveOnce()) break;
+        if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
+        else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
+        if (dir > 0) if (!moveOnce()) break;
+      }
+    }
+    var result = skipAtomic(doc, Pos(line, ch), dir, true);
+    if (!possible) result.hitSide = true;
+    return result;
+  }
+
+  function findPosV(cm, pos, dir, unit) {
+    var doc = cm.doc, x = pos.left, y;
+    if (unit == "page") {
+      var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
+      y = pos.top + dir * pageSize;
+    } else if (unit == "line") {
+      y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
+    }
+    for (;;) {
+      var target = coordsChar(cm, x, y);
+      if (!target.outside) break;
+      if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
+      y += dir * 5;
+    }
+    return target;
+  }
+
+  function findWordAt(line, pos) {
+    var start = pos.ch, end = pos.ch;
+    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: Pos(pos.line, start), to: Pos(pos.line, end)};
+  }
+
+  function selectLine(cm, line) {
+    extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
+  }
 
-      if (options.lineWrapping) checkHeights();
+  // PROTOTYPE
 
-      gutter.style.display = gutterDisplay;
-      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;
-    }
+  // The publicly visible API. Note that operation(null, f) means
+  // 'wrap f in an operation, performed on its `this` parameter'
 
-    function computeIntact(intact, changes) {
-      for (var i = 0, l = changes.length || 0; i < l; ++i) {
-        var change = changes[i], intact2 = [], diff = change.diff || 0;
-        for (var j = 0, l2 = intact.length; j < l2; ++j) {
-          var range = intact[j];
-          if (change.to <= range.from && change.diff)
-            intact2.push({from: range.from + diff, to: range.to + diff,
-                          domStart: range.domStart});
-          else if (change.to <= range.from || change.from >= range.to)
-            intact2.push(range);
-          else {
-            if (change.from > range.from)
-              intact2.push({from: range.from, to: change.from, domStart: range.domStart});
-            if (change.to < range.to)
-              intact2.push({from: change.to + diff, to: range.to + diff,
-                            domStart: range.domStart + (change.to - range.from)});
-          }
-        }
-        intact = intact2;
-      }
-      return intact;
-    }
+  CodeMirror.prototype = {
+    focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
 
-    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) removeChildren(lineDiv);
-      else {
-        var domPos = 0, curNode = lineDiv.firstChild, n;
-        for (var i = 0; i < intact.length; ++i) {
-          var cur = intact[i];
-          while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
-          for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
-        }
-        while (curNode) curNode = killNode(curNode);
-      }
-      // This pass fills in the lines that actually changed.
-      var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
-      doc.iter(from, to, function(line) {
-        if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
-        if (!nextIntact || nextIntact.from > j) {
-          if (line.hidden) var lineElement = elt("pre");
-          else {
-            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.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");
-            }
-          }
-          lineDiv.insertBefore(lineElement, curNode);
-        } else {
-          curNode = curNode.nextSibling;
-        }
-        ++j;
-      });
-    }
+    setOption: function(option, value) {
+      var options = this.options, old = options[option];
+      if (options[option] == value && option != "mode") return;
+      options[option] = value;
+      if (optionHandlers.hasOwnProperty(option))
+        operation(this, optionHandlers[option])(this, value, old);
+    },
 
-    function updateGutter() {
-      if (!options.gutter && !options.lineNumbers) return;
-      var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
-      gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
-      var fragment = document.createDocumentFragment(), i = showingFrom, normalNode;
-      doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
-        if (line.hidden) {
-          fragment.appendChild(elt("pre"));
-        } else {
-          var marker = line.gutterMarker;
-          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";
-          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";
-      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);
-      var fromPos = localCoords(sel.from, true);
-      var toPos = collapsed ? fromPos : localCoords(sel.to, true);
-      var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
-      var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
-      inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
-      inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
-      if (collapsed) {
-        cursor.style.top = headPos.y + "px";
-        cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
-        cursor.style.display = "";
-        selectionDiv.style.display = "none";
-      } else {
-        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 ? 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, clientHeight) - middleStart;
-        if (middleHeight > 0.2 * th)
-          add(0, middleStart, 0, middleHeight);
-        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 = "";
-      }
-    }
-
-    function setShift(val) {
-      if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
-      else shiftSelecting = null;
-    }
-    function setSelectionUser(from, to) {
-      var sh = shiftSelecting && clipPos(shiftSelecting);
-      if (sh) {
-        if (posLess(sh, from)) from = sh;
-        else if (posLess(to, sh)) to = sh;
-      }
-      setSelection(from, to);
-      userSelChange = true;
-    }
-    // Update the selection. Last two args are only used by
-    // updateLines, since they have to be expressed in the line
-    // numbers before the update.
-    function setSelection(from, to, oldFrom, oldTo) {
-      goalColumn = null;
-      if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
-      if (posEq(sel.from, from) && posEq(sel.to, to)) return;
-      if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
-
-      // Skip over hidden lines.
-      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));
-        }
-      }
+    getOption: function(option) {return this.options[option];},
+    getDoc: function() {return this.doc;},
 
-      sel.from = from; sel.to = to;
-      selectionChanged = true;
-    }
-    function skipHidden(pos, oldLine, oldCh) {
-      function getNonHidden(dir) {
-        var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
-        while (lNo != end) {
-          var line = getLine(lNo);
-          if (!line.hidden) {
-            var ch = pos.ch;
-            if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length;
-            return {line: lNo, ch: ch};
-          }
-          lNo += dir;
+    addKeyMap: function(map) {
+      this.state.keyMaps.push(map);
+    },
+    removeKeyMap: function(map) {
+      var maps = this.state.keyMaps;
+      for (var i = 0; i < maps.length; ++i)
+        if ((typeof map == "string" ? maps[i].name : maps[i]) == map) {
+          maps.splice(i, 1);
+          return true;
         }
-      }
-      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);
-    }
-    function setCursor(line, ch, user) {
-      var pos = clipPos({line: line, ch: ch || 0});
-      (user ? setSelectionUser : setSelection)(pos, pos);
-    }
-
-    function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
-    function clipPos(pos) {
-      if (pos.line < 0) return {line: 0, ch: 0};
-      if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
-      var ch = pos.ch, linelen = getLine(pos.line).text.length;
-      if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
-      else if (ch < 0) return {line: pos.line, ch: 0};
-      else return pos;
-    }
-
-    function findPosH(dir, unit) {
-      var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
-      var lineObj = getLine(line);
-      function findNextLine() {
-        for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
-          var lo = getLine(l);
-          if (!lo.hidden) { line = l; lineObj = lo; return true; }
+    },
+
+    addOverlay: operation(null, function(spec, options) {
+      var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
+      if (mode.startState) throw new Error("Overlays may not be stateful.");
+      this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
+      this.state.modeGen++;
+      regChange(this);
+    }),
+    removeOverlay: operation(null, function(spec) {
+      var overlays = this.state.overlays;
+      for (var i = 0; i < overlays.length; ++i) {
+        if (overlays[i].modeSpec == spec) {
+          overlays.splice(i, 1);
+          this.state.modeGen++;
+          regChange(this);
+          return;
         }
       }
-      function moveOnce(boundToLine) {
-        if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
-          if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
-          else return false;
-        } else ch += dir;
-        return true;
-      }
-      if (unit == "char") moveOnce();
-      else if (unit == "column") moveOnce(true);
-      else if (unit == "word") {
-        var sawWord = false;
-        for (;;) {
-          if (dir < 0) if (!moveOnce()) break;
-          if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
-          else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
-          if (dir > 0) if (!moveOnce()) break;
-        }
+    }),
+
+    indentLine: operation(null, function(n, dir, aggressive) {
+      if (typeof dir != "string") {
+        if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
+        else dir = dir ? "add" : "subtract";
       }
-      return {line: line, ch: ch};
-    }
-    function moveH(dir, unit) {
-      var pos = dir < 0 ? sel.from : sel.to;
-      if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
-      setCursor(pos.line, pos.ch, true);
-    }
-    function deleteH(dir, unit) {
-      if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
-      else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
-      else replaceRange("", sel.from, findPosH(dir, unit));
-      userSelChange = true;
-    }
-    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") {
-        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 findWordAt(pos) {
-      var line = getLine(pos.line).text;
-      var start = pos.ch, end = pos.ch;
-      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}, clipPos({line: line + 1, ch: 0}));
-    }
-    function indentSelected(mode) {
-      if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
+      if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
+    }),
+    indentSelection: operation(null, function(how) {
+      var sel = this.doc.sel;
+      if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
       var e = sel.to.line - (sel.to.ch ? 0 : 1);
-      for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
-    }
-
-    function indentLine(n, how) {
-      if (!how) how = "add";
-      if (how == "smart") {
-        if (!mode.indent) how = "prev";
-        else var state = getStateBefore(n);
-      }
-
-      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 == "add") indentation = curSpace + options.indentUnit;
-      else if (how == "subtract") indentation = curSpace - options.indentUnit;
-      indentation = Math.max(0, indentation);
-      var diff = indentation - curSpace;
-
-      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);
-
-      if (indentString != curSpaceString)
-        replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
-    }
-
-    function loadMode() {
-      mode = CodeMirror.getMode(options, options.mode);
-      doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
-      frontier = 0;
-      startWorker(100);
-    }
-    function gutterChanged() {
-      var visible = options.gutter || options.lineNumbers;
-      gutter.style.display = visible ? "" : "none";
-      if (visible) gutterDirty = true;
-      else lineDiv.parentNode.style.marginLeft = 0;
-    }
-    function wrappingChanged(from, to) {
-      if (options.lineWrapping) {
-        wrapper.className += " CodeMirror-wrap";
-        var perLine = scroller.clientWidth / charWidth() - 3;
-        doc.iter(0, doc.size, function(line) {
-          if (line.hidden) return;
-          var guess = Math.ceil(line.text.length / perLine) || 1;
-          if (guess != 1) updateLineHeight(line, guess);
-        });
-        lineSpace.style.minWidth = widthForcer.style.left = "";
-      } else {
-        wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
-        computeMaxLength();
-        doc.iter(0, doc.size, function(line) {
-          if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
-        });
-      }
-      changes.push({from: 0, to: doc.size});
-    }
-    function themeChanged() {
-      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 : "");
-    }
+      for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
+    }),
 
-    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; i < this.lines.length; ++i) {
-        var line = this.lines[i];
-        var span = getMarkedSpanFor(line.markedSpans, this, true);
-        if (span.from != null || span.to != null) {
-          var lineN = lineNo(line);
-          min = Math.min(min, lineN); max = Math.max(max, lineN);
-        }
-      }
-      if (min != Infinity)
-        changes.push({from: min, to: max + 1});
-      this.lines.length = 0;
-    });
-    TextMarker.prototype.find = function() {
-      var from, 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};
-        }
+    // Fetch the parser token for a given character. Useful for hacks
+    // that want to inspect the mode state (say, for completion).
+    getTokenAt: function(pos) {
+      var doc = this.doc;
+      pos = clipPos(doc, pos);
+      var state = getStateBefore(this, pos.line), mode = this.doc.mode;
+      var line = getLine(doc, pos.line);
+      var stream = new StringStream(line.text, this.options.tabSize);
+      while (stream.pos < pos.ch && !stream.eol()) {
+        stream.start = stream.pos;
+        var style = mode.token(stream, state);
       }
-      if (this.type == "bookmark") return from;
-      return from && {from: from, to: to};
-    };
+      return {start: stream.start,
+              end: stream.pos,
+              string: stream.current(),
+              className: style || null, // Deprecated, use 'type' instead
+              type: style || null,
+              state: state};
+    },
 
-    function markText(from, to, className, options) {
-      from = clipPos(from); to = clipPos(to);
-      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 = [])).push(span);
-        marker.lines.push(line);
-        ++curLine;
-      });
-      changes.push({from: from.line, to: to.line + 1});
-      return marker;
-    }
+    getStateAfter: function(line) {
+      var doc = this.doc;
+      line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
+      return getStateBefore(this, line + 1);
+    },
 
-    function setBookmark(pos) {
-      pos = clipPos(pos);
-      var marker = new TextMarker("bookmark"), line = getLine(pos.line);
-      var span = {from: pos.ch, to: pos.ch, marker: marker};
-      (line.markedSpans || (line.markedSpans = [])).push(span);
-      marker.lines.push(line);
-      return marker;
-    }
+    cursorCoords: function(start, mode) {
+      var pos, sel = this.doc.sel;
+      if (start == null) pos = sel.head;
+      else if (typeof start == "object") pos = clipPos(this.doc, start);
+      else pos = start ? sel.from : sel.to;
+      return cursorCoords(this, pos, mode || "page");
+    },
 
-    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;
-    }
+    charCoords: function(pos, mode) {
+      return charCoords(this, clipPos(this.doc, pos), mode || "page");
+    },
 
-    function addGutterMarker(line, text, className) {
-      if (typeof line == "number") line = getLine(clipLine(line));
-      line.gutterMarker = {text: text, style: className};
-      gutterDirty = true;
-      return line;
-    }
-    function removeGutterMarker(line) {
-      if (typeof line == "number") line = getLine(clipLine(line));
-      line.gutterMarker = null;
-      gutterDirty = true;
-    }
-
-    function changeLine(handle, op) {
-      var no = handle, line = handle;
-      if (typeof handle == "number") line = getLine(clipLine(handle));
-      else no = lineNo(handle);
-      if (no == null) return null;
-      if (op(line, no)) changes.push({from: no, to: no + 1});
-      else return null;
-      return line;
-    }
-    function setLineClass(handle, className, bgClassName) {
-      return changeLine(handle, function(line) {
-        if (line.className != className || line.bgClassName != bgClassName) {
-          line.className = className;
-          line.bgClassName = bgClassName;
-          return true;
+    coordsChar: function(coords) {
+      var off = getRect(this.display.lineSpace);
+      var scrollY = window.pageYOffset || (document.documentElement || document.body).scrollTop;
+      var scrollX = window.pageXOffset || (document.documentElement || document.body).scrollLeft;
+      return coordsChar(this, coords.left - off.left - scrollX, coords.top - off.top - scrollY);
+    },
+
+    defaultTextHeight: function() { return textHeight(this.display); },
+
+    setGutterMarker: operation(null, function(line, gutterID, value) {
+      return changeLine(this, line, function(line) {
+        var markers = line.gutterMarkers || (line.gutterMarkers = {});
+        markers[gutterID] = value;
+        if (!value && isEmpty(markers)) line.gutterMarkers = null;
+        return true;
+      });
+    }),
+
+    clearGutter: operation(null, function(gutterID) {
+      var cm = this, doc = cm.doc, i = doc.first;
+      doc.iter(function(line) {
+        if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
+          line.gutterMarkers[gutterID] = null;
+          regChange(cm, i, i + 1);
+          if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
         }
+        ++i;
       });
-    }
-    function setLineHidden(handle, hidden) {
-      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);
+    }),
+
+    addLineClass: operation(null, function(handle, where, cls) {
+      return changeLine(this, handle, function(line) {
+        var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
+        if (!line[prop]) line[prop] = cls;
+        else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false;
+        else line[prop] += " " + cls;
+        return true;
+      });
+    }),
+
+    removeLineClass: operation(null, function(handle, where, cls) {
+      return changeLine(this, handle, function(line) {
+        var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
+        var cur = line[prop];
+        if (!cur) return false;
+        else if (cls == null) line[prop] = null;
+        else {
+          var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), "");
+          if (upd == cur) return false;
+          line[prop] = upd || null;
         }
+        return true;
       });
-    }
+    }),
 
-    function lineInfo(line) {
+    addLineWidget: operation(null, function(handle, node, options) {
+      return addLineWidget(this, handle, node, options);
+    }),
+
+    removeLineWidget: function(widget) { widget.clear(); },
+
+    lineInfo: function(line) {
       if (typeof line == "number") {
-        if (!isLine(line)) return null;
+        if (!isLine(this.doc, line)) return null;
         var n = line;
-        line = getLine(line);
+        line = getLine(this.doc, line);
         if (!line) return null;
       } 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, bgClass: line.bgClassName};
-    }
-
-    function measureLine(line, ch) {
-      if (ch == 0) return {top: 0, left: 0};
-      var wbr = options.lineWrapping && ch < line.text.length &&
-                spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1));
-      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 = elt("span", "x");
-        anchor.parentNode.insertBefore(backup, anchor.nextSibling);
-        top = backup.offsetTop;
-      }
-      return {top: top, left: left};
-    }
-    function localCoords(pos, inLineWrap) {
-      var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
-      if (pos.ch == 0) x = 0;
-      else {
-        var sp = measureLine(getLine(pos.line), pos.ch);
-        x = sp.left;
-        if (options.lineWrapping) y += Math.max(0, sp.top);
-      }
-      return {x: x, y: y, yBot: y + lh};
-    }
-    // Coords must be lineSpace-local
-    function coordsChar(x, y) {
-      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;
+      return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
+              textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
+              widgets: line.widgets};
+    },
+
+    getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
+
+    addWidget: function(pos, node, scroll, vert, horiz) {
+      var display = this.display;
+      pos = cursorCoords(this, clipPos(this.doc, pos));
+      var top = pos.bottom, left = pos.left;
+      node.style.position = "absolute";
+      display.sizer.appendChild(node);
+      if (vert == "over") {
+        top = pos.top;
+      } else if (vert == "above" || vert == "near") {
+        var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
+        hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
+        // Default to positioning above (if specified and possible); otherwise default to positioning below
+        if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
+          top = pos.top - node.offsetHeight;
+        else if (pos.bottom + node.offsetHeight <= vspace)
+          top = pos.bottom;
+        if (left + node.offsetWidth > hspace)
+          left = hspace - node.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 + innerOff * scroller.clientWidth * .9) / cw));
-      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 {line: lineNo, ch: 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) {
-          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 (wrongLine) toX += 1000; }
-        else {from = middle; fromX = middleX;}
+      node.style.top = (top + paddingTop(display)) + "px";
+      node.style.left = node.style.right = "";
+      if (horiz == "right") {
+        left = display.sizer.clientWidth - node.offsetWidth;
+        node.style.right = "0px";
+      } else {
+        if (horiz == "left") left = 0;
+        else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
+        node.style.left = left + "px";
       }
-    }
-    function pageCoords(pos) {
-      var local = localCoords(pos, true), off = eltOffset(lineSpace);
-      return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
-    }
+      if (scroll)
+        scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
+    },
 
-    var cachedHeight, cachedHeightFor, measurePre;
-    function textHeight() {
-      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;
-      removeChildrenAndAdd(measure, measurePre.cloneNode(true));
-      cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
-      removeChildren(measure);
-      return cachedHeight;
-    }
-    var cachedWidth, cachedWidthFor = 0;
-    function charWidth() {
-      if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
-      cachedWidthFor = scroller.clientWidth;
-      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;}
-
-    function posFromMouse(e, liberal) {
-      var offW = eltOffset(scroller, true), x, y;
-      // Fails unpredictably on IE[67] when mouse is dragged around quickly.
-      try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
-      // This is a mess of a heuristic to try and determine whether a
-      // scroll-bar was clicked or not, and to return null if one was
-      // (and !liberal).
-      if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
-        return null;
-      var offL = eltOffset(lineSpace, true);
-      return coordsChar(x - offL.left, y - offL.top);
-    }
-    var detectingSelectAll;
-    function onContextMenu(e) {
-      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);
-
-      var oldCSS = input.style.cssText;
-      inputDiv.style.position = "absolute";
-      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);";
-      focusInput();
-      resetInput(true);
-      // Adds "Select all" to context menu in FF
-      if (posEq(sel.from, sel.to)) input.value = prevInput = " ";
-
-      function rehide() {
-        inputDiv.style.position = "relative";
-        input.style.cssText = oldCSS;
-        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);
-        }
+    triggerOnKeyDown: operation(null, onKeyDown),
+
+    execCommand: function(cmd) {return commands[cmd](this);},
+
+    findPosH: function(from, amount, unit, visually) {
+      var dir = 1;
+      if (amount < 0) { dir = -1; amount = -amount; }
+      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
+        cur = findPosH(this.doc, cur, dir, unit, visually);
+        if (cur.hitSide) break;
       }
+      return cur;
+    },
+
+    moveH: operation(null, function(dir, unit) {
+      var sel = this.doc.sel, pos;
+      if (sel.shift || sel.extend || posEq(sel.from, sel.to))
+        pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
+      else
+        pos = dir < 0 ? sel.from : sel.to;
+      extendSelection(this.doc, pos, pos, dir);
+    }),
+
+    deleteH: operation(null, function(dir, unit) {
+      var sel = this.doc.sel;
+      if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
+      else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
+      this.curOp.userSelChange = true;
+    }),
+
+    findPosV: function(from, amount, unit, goalColumn) {
+      var dir = 1, x = goalColumn;
+      if (amount < 0) { dir = -1; amount = -amount; }
+      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
+        var coords = cursorCoords(this, cur, "div");
+        if (x == null) x = coords.left;
+        else coords.left = x;
+        cur = findPosV(this, coords, dir, unit);
+        if (cur.hitSide) break;
+      }
+      return cur;
+    },
+
+    moveV: operation(null, function(dir, unit) {
+      var sel = this.doc.sel;
+      var pos = cursorCoords(this, sel.head, "div");
+      if (sel.goalColumn != null) pos.left = sel.goalColumn;
+      var target = findPosV(this, pos, dir, unit);
+
+      if (unit == "page")
+        this.display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top;
+      extendSelection(this.doc, target, target, dir);
+      sel.goalColumn = pos.left;
+    }),
+
+    toggleOverwrite: function() {
+      if (this.state.overwrite = !this.state.overwrite)
+        this.display.cursor.className += " CodeMirror-overwrite";
+      else
+        this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
+    },
+
+    scrollTo: operation(null, function(x, y) {
+      this.curOp.updateScrollPos = {scrollLeft: x, scrollTop: y};
+    }),
+    getScrollInfo: function() {
+      var scroller = this.display.scroller, co = scrollerCutOff;
+      return {left: scroller.scrollLeft, top: scroller.scrollTop,
+              height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
+              clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
+    },
 
-      if (gecko) {
-        e_stop(e);
-        var mouseup = connect(window, "mouseup", function() {
-          mouseup();
-          setTimeout(rehide, 20);
-        }, true);
+    scrollIntoView: function(pos) {
+      if (typeof pos == "number") pos = Pos(pos, 0);
+      if (!pos || pos.line != null) {
+        pos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
+        scrollPosIntoView(this, pos);
       } else {
-        setTimeout(rehide, 50);
-      }
-    }
-
-    // Cursor-blinking
-    function restartBlink() {
-      clearInterval(blinker);
-      var on = true;
-      cursor.style.visibility = "";
-      blinker = setInterval(function() {
-        cursor.style.visibility = (on = !on) ? "" : "hidden";
-      }, options.cursorBlinkRate);
-    }
-
-    var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
-    function matchBrackets(autoclear) {
-      var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
-      var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
-      if (!match) return;
-      var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
-      for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
-        if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
-
-      var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
-      function scan(line, from, to) {
-        if (!line.text) return;
-        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] != 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];
-              if (match.charAt(1) == ">" == forward) stack.push(cur);
-              else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
-              else if (!stack.length) return {pos: pos, match: true};
-            }
-          }
-        }
+        scrollIntoView(this, pos.left, pos.top, pos.right, pos.bottom);
       }
-      for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
-        var line = getLine(i), first = i == head.line;
-        var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
-        if (found) break;
-      }
-      if (!found) found = {pos: null, match: false};
-      var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
-      var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
-          two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
-      var clear = operation(function(){one.clear(); two && two.clear();});
-      if (autoclear) setTimeout(clear, 800);
-      else bracketHighlighted = clear;
-    }
-
-    // Finds the line to start with when starting a parse. Tries to
-    // find a line with a stateAfter, so that it can start with a
-    // valid state. If that fails, it returns the line with the
-    // smallest indentation, which tends to need the least context to
-    // parse correctly.
-    function findStartLine(n) {
-      var minindent, minline;
-      for (var search = n, lim = n - 40; search > lim; --search) {
-        if (search == 0) return 0;
-        var line = getLine(search-1);
-        if (line.stateAfter) return search;
-        var indented = line.indentation(options.tabSize);
-        if (minline == null || minindent > indented) {
-          minline = search - 1;
-          minindent = indented;
-        }
+    },
+
+    setSize: function(width, height) {
+      function interpret(val) {
+        return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
       }
-      return minline;
-    }
-    function getStateBefore(n) {
-      var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter;
-      if (!state) state = startState(mode);
-      else state = 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;
-      });
-      return state;
-    }
-    function highlightWorker() {
-      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);
-        } 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 (frontier < showingTo)
-        highlight.set(time, highlightWorker);
-    }
-
-    // Operations are used to wrap changes in such a way that each
-    // change won't have to update the cursor and display (which would
-    // be awkward, slow, and error-prone), but instead updates are
-    // batched and then all combined and executed at once.
-    function startOperation() {
-      updateInput = userSelChange = textChanged = null;
-      changes = []; selectionChanged = false; callbacks = [];
-    }
-    function endOperation() {
-      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 (newScrollPos) scrollCursorIntoView();
-      if (selectionChanged) restartBlink();
-
-      if (focused && (updateInput === true || (updateInput !== false && selectionChanged)))
-        resetInput(userSelChange);
-
-      if (selectionChanged && options.matchBrackets)
-        setTimeout(operation(function() {
-          if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
-          if (posEq(sel.from, sel.to)) matchBrackets(false);
-        }), 20);
-      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);
-      for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
-      if (updated && options.onUpdate) options.onUpdate(instance);
-    }
-    var nestedOperation = 0;
-    function operation(f) {
-      return function() {
-        if (!nestedOperation++) startOperation();
-        try {var result = f.apply(this, arguments);}
-        finally {if (!--nestedOperation) endOperation();}
-        return result;
-      };
-    }
+      if (width != null) this.display.wrapper.style.width = interpret(width);
+      if (height != null) this.display.wrapper.style.height = interpret(height);
+      this.refresh();
+    },
 
-    function compoundChange(f) {
-      history.startCompound();
-      try { return f(); } finally { history.endCompound(); }
-    }
+    on: function(type, f) {on(this, type, f);},
+    off: function(type, f) {off(this, type, f);},
+
+    operation: function(f){return runInOp(this, f);},
+
+    refresh: operation(null, function() {
+      clearCaches(this);
+      this.curOp.updateScrollPos = {scrollTop: this.doc.scrollTop, scrollLeft: this.doc.scrollLeft};
+      regChange(this);
+    }),
+
+    swapDoc: operation(null, function(doc) {
+      var old = this.doc;
+      old.cm = null;
+      attachDoc(this, doc);
+      clearCaches(this);
+      this.curOp.updateScrollPos = {scrollTop: doc.scrollTop, scrollLeft: doc.scrollLeft};
+      return old;
+    }),
+
+    getInputField: function(){return this.display.input;},
+    getWrapperElement: function(){return this.display.wrapper;},
+    getScrollerElement: function(){return this.display.scroller;},
+    getGutterElement: function(){return this.display.gutters;}
+  };
+
+  // OPTION DEFAULTS
 
-    for (var ext in extensions)
-      if (extensions.propertyIsEnumerable(ext) &&
-          !instance.propertyIsEnumerable(ext))
-        instance[ext] = extensions[ext];
-    return instance;
-  } // (end of function CodeMirror)
+  var optionHandlers = CodeMirror.optionHandlers = {};
 
   // The default configuration options.
-  CodeMirror.defaults = {
-    value: "",
-    mode: null,
-    theme: "default",
-    indentUnit: 2,
-    indentWithTabs: false,
-    smartIndent: true,
-    tabSize: 4,
-    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,
-    onUpdate: null,
-    onFocus: null, onBlur: null, onScroll: null,
-    matchBrackets: false,
-    cursorBlinkRate: 530,
-    workTime: 100,
-    workDelay: 200,
-    pollInterval: 100,
-    undoDepth: 40,
-    tabindex: null,
-    autofocus: null,
-    lineNumberFormatter: function(integer) { return integer; }
-  };
+  var defaults = CodeMirror.defaults = {};
 
-  var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
-  var mac = ios || /Mac/.test(navigator.platform);
-  var win = /Win/.test(navigator.platform);
+  function option(name, deflt, handle, notOnInit) {
+    CodeMirror.defaults[name] = deflt;
+    if (handle) optionHandlers[name] =
+      notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
+  }
+
+  var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
+
+  // These two are, on init, called from the constructor because they
+  // have to be initialized before the editor can start at all.
+  option("value", "", function(cm, val) {
+    cm.setValue(val);
+  }, true);
+  option("mode", null, function(cm, val) {
+    cm.doc.modeOption = val;
+    loadMode(cm);
+  }, true);
+
+  option("indentUnit", 2, loadMode, true);
+  option("indentWithTabs", false);
+  option("smartIndent", true);
+  option("tabSize", 4, function(cm) {
+    loadMode(cm);
+    clearCaches(cm);
+    regChange(cm);
+  }, true);
+  option("electricChars", true);
+  option("rtlMoveVisually", !windows);
+
+  option("theme", "default", function(cm) {
+    themeChanged(cm);
+    guttersChanged(cm);
+  }, true);
+  option("keyMap", "default", keyMapChanged);
+  option("extraKeys", null);
+
+  option("onKeyEvent", null);
+  option("onDragEvent", null);
+
+  option("lineWrapping", false, wrappingChanged, true);
+  option("gutters", [], function(cm) {
+    setGuttersForLineNumbers(cm.options);
+    guttersChanged(cm);
+  }, true);
+  option("fixedGutter", true, function(cm, val) {
+    cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
+    cm.refresh();
+  }, true);
+  option("lineNumbers", false, function(cm) {
+    setGuttersForLineNumbers(cm.options);
+    guttersChanged(cm);
+  }, true);
+  option("firstLineNumber", 1, guttersChanged, true);
+  option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
+  option("showCursorWhenSelecting", false, updateSelection, true);
+  
+  option("readOnly", false, function(cm, val) {
+    if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
+    else if (!val) resetInput(cm, true);
+  });
+  option("dragDrop", true);
+
+  option("cursorBlinkRate", 530);
+  option("cursorHeight", 1);
+  option("workTime", 100);
+  option("workDelay", 100);
+  option("flattenSpans", true);
+  option("pollInterval", 100);
+  option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
+  option("viewportMargin", 10, function(cm){cm.refresh();}, true);
+
+  option("tabindex", null, function(cm, val) {
+    cm.display.input.tabIndex = val || "";
+  });
+  option("autofocus", null);
+
+  // MODE DEFINITION AND QUERYING
 
   // Known modes, by name and by MIME
   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) {
@@ -2036,9 +3015,11 @@ window.CodeMirror = (function() {
     }
     modes[name] = mode;
   };
+
   CodeMirror.defineMIME = function(mime, spec) {
     mimeModes[mime] = spec;
   };
+
   CodeMirror.resolveMode = function(spec) {
     if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
       spec = mimeModes[spec];
@@ -2047,62 +3028,111 @@ window.CodeMirror = (function() {
     if (typeof spec == "string") return {name: spec};
     else return spec || {name: "null"};
   };
+
   CodeMirror.getMode = function(options, spec) {
-    var spec = CodeMirror.resolveMode(spec);
+    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];
+      for (var prop in exts) {
+        if (!exts.hasOwnProperty(prop)) continue;
+        if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
+        modeObj[prop] = exts[prop];
+      }
     }
     modeObj.name = spec.name;
     return modeObj;
   };
-  CodeMirror.listModes = function() {
-    var list = [];
-    for (var m in modes)
-      if (modes.propertyIsEnumerable(m)) list.push(m);
-    return list;
-  };
-  CodeMirror.listMIMEs = function() {
-    var list = [];
-    for (var m in mimeModes)
-      if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
-    return list;
-  };
 
-  var extensions = CodeMirror.extensions = {};
-  CodeMirror.defineExtension = function(name, func) {
-    extensions[name] = func;
-  };
+  CodeMirror.defineMode("null", function() {
+    return {token: function(stream) {stream.skipToEnd();}};
+  });
+  CodeMirror.defineMIME("text/plain", "null");
 
   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];
+    copyObj(properties, exts);
+  };
+
+  // EXTENSIONS
+
+  CodeMirror.defineExtension = function(name, func) {
+    CodeMirror.prototype[name] = func;
+  };
+
+  CodeMirror.defineOption = option;
+
+  var initHooks = [];
+  CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
+
+  // MODE STATE HANDLING
+
+  // Utility functions for working with state. Exported because modes
+  // sometimes need to do this.
+  function copyState(mode, state) {
+    if (state === true) return state;
+    if (mode.copyState) return mode.copyState(state);
+    var nstate = {};
+    for (var n in state) {
+      var val = state[n];
+      if (val instanceof Array) val = val.concat([]);
+      nstate[n] = val;
+    }
+    return nstate;
+  }
+  CodeMirror.copyState = copyState;
+
+  function startState(mode, a1, a2) {
+    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};
   };
 
+  // STANDARD COMMANDS
+
   var commands = CodeMirror.commands = {
-    selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
+    selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},
     killLine: function(cm) {
       var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
-      if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
-      else cm.replaceRange("", from, sel ? to : {line: from.line});
+      if (!sel && cm.getLine(from.line).length == from.ch)
+        cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete");
+      else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete");
+    },
+    deleteLine: function(cm) {
+      var l = cm.getCursor().line;
+      cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
     },
-    deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
     undo: function(cm) {cm.undo();},
     redo: function(cm) {cm.redo();},
-    goDocStart: function(cm) {cm.setCursor(0, 0, true);},
-    goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
-    goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
+    goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
+    goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
+    goLineStart: function(cm) {
+      cm.extendSelection(lineStart(cm, cm.getCursor().line));
+    },
     goLineStartSmart: function(cm) {
-      var cur = cm.getCursor();
-      var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
-      cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
+      var cur = cm.getCursor(), start = lineStart(cm, cur.line);
+      var line = cm.getLineHandle(start.line);
+      var order = getOrder(line);
+      if (!order || order[0].level == 0) {
+        var firstNonWS = Math.max(0, line.text.search(/\S/));
+        var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
+        cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));
+      } else cm.extendSelection(start);
+    },
+    goLineEnd: function(cm) {
+      cm.extendSelection(lineEnd(cm, cm.getCursor().line));
     },
-    goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
     goLineUp: function(cm) {cm.moveV(-1, "line");},
     goLineDown: function(cm) {cm.moveV(1, "line");},
     goPageUp: function(cm) {cm.moveV(-1, "page");},
@@ -2113,36 +3143,40 @@ window.CodeMirror = (function() {
     goColumnRight: function(cm) {cm.moveH(1, "column");},
     goWordLeft: function(cm) {cm.moveH(-1, "word");},
     goWordRight: function(cm) {cm.moveH(1, "word");},
-    delCharLeft: function(cm) {cm.deleteH(-1, "char");},
-    delCharRight: function(cm) {cm.deleteH(1, "char");},
-    delWordLeft: function(cm) {cm.deleteH(-1, "word");},
-    delWordRight: function(cm) {cm.deleteH(1, "word");},
+    delCharBefore: function(cm) {cm.deleteH(-1, "char");},
+    delCharAfter: function(cm) {cm.deleteH(1, "char");},
+    delWordBefore: function(cm) {cm.deleteH(-1, "word");},
+    delWordAfter: function(cm) {cm.deleteH(1, "word");},
     indentAuto: function(cm) {cm.indentSelection("smart");},
     indentMore: function(cm) {cm.indentSelection("add");},
     indentLess: function(cm) {cm.indentSelection("subtract");},
-    insertTab: function(cm) {cm.replaceSelection("\t", "end");},
+    insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");},
     defaultTab: function(cm) {
       if (cm.somethingSelected()) cm.indentSelection("add");
-      else cm.replaceSelection("\t", "end");
+      else cm.replaceSelection("\t", "end", "+input");
     },
     transposeChars: function(cm) {
       var cur = cm.getCursor(), line = cm.getLine(cur.line);
       if (cur.ch > 0 && cur.ch < line.length - 1)
         cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
-                        {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
+                        Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
     },
     newlineAndIndent: function(cm) {
-      cm.replaceSelection("\n", "end");
-      cm.indentLine(cm.getCursor().line);
+      operation(cm, function() {
+        cm.replaceSelection("\n", "end", "+input");
+        cm.indentLine(cm.getCursor().line, null, true);
+      })();
     },
     toggleOverwrite: function(cm) {cm.toggleOverwrite();}
   };
 
+  // STANDARD KEYMAPS
+
   var keyMap = CodeMirror.keyMap = {};
   keyMap.basic = {
     "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
     "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
-    "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
+    "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
     "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
   };
   // Note that the save and find-related commands aren't defined by
@@ -2151,7 +3185,7 @@ window.CodeMirror = (function() {
     "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
     "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
     "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-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "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"
@@ -2159,8 +3193,8 @@ window.CodeMirror = (function() {
   keyMap.macDefault = {
     "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
     "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
-    "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",
+    "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore",
+    "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "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"]
@@ -2169,43 +3203,59 @@ window.CodeMirror = (function() {
   keyMap.emacsy = {
     "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
     "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
-    "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
-    "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
+    "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
+    "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
   };
 
+  // KEYMAP DISPATCH
+
   function getKeyMap(val) {
     if (typeof val == "string") return keyMap[val];
     else return val;
   }
-  function lookupKey(name, extraMap, map, handle, stop) {
+
+  function lookupKey(name, maps, handle) {
     function lookup(map) {
       map = getKeyMap(map);
       var found = map[name];
-      if (found === false) {
-        if (stop) stop();
-        return true;
-      }
+      if (found === false) return "stop";
       if (found != null && handle(found)) return true;
-      if (map.nofallthrough) {
-        if (stop) stop();
-        return true;
-      }
+      if (map.nofallthrough) return "stop";
+
       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;
+        var done = lookup(fallthrough[i]);
+        if (done) return done;
       }
       return false;
     }
-    if (extraMap && lookup(extraMap)) return true;
-    return lookup(map);
+
+    for (var i = 0; i < maps.length; ++i) {
+      var done = lookup(maps[i]);
+      if (done) return done;
+    }
   }
   function isModifierKey(event) {
-    var name = keyNames[e_prop(event, "keyCode")];
+    var name = keyNames[event.keyCode];
     return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
   }
+  function keyName(event, noShift) {
+    var name = keyNames[event.keyCode];
+    if (name == null || event.altGraphKey) return false;
+    if (event.altKey) name = "Alt-" + name;
+    if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
+    if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
+    if (!noShift && event.shiftKey) name = "Shift-" + name;
+    return name;
+  }
+  CodeMirror.lookupKey = lookupKey;
+  CodeMirror.isModifierKey = isModifierKey;
+  CodeMirror.keyName = keyName;
+
+  // FROMTEXTAREA
 
   CodeMirror.fromTextArea = function(textarea, options) {
     if (!options) options = {};
@@ -2222,78 +3272,44 @@ window.CodeMirror = (function() {
         textarea.getAttribute("autofocus") != null && hasFocus == document.body;
     }
 
-    function save() {textarea.value = instance.getValue();}
+    function save() {textarea.value = cm.getValue();}
     if (textarea.form) {
       // Deplorable hack to make the submit method do the right thing.
-      var rmSubmit = connect(textarea.form, "submit", save, true);
-      if (typeof textarea.form.submit == "function") {
-        var realSubmit = textarea.form.submit;
-        textarea.form.submit = function wrappedSubmit() {
+      on(textarea.form, "submit", save);
+      var form = textarea.form, realSubmit = form.submit;
+      try {
+        var wrappedSubmit = form.submit = function() {
           save();
-          textarea.form.submit = realSubmit;
-          textarea.form.submit();
-          textarea.form.submit = wrappedSubmit;
+          form.submit = realSubmit;
+          form.submit();
+          form.submit = wrappedSubmit;
         };
-      }
+      } catch(e) {}
     }
 
     textarea.style.display = "none";
-    var instance = CodeMirror(function(node) {
+    var cm = CodeMirror(function(node) {
       textarea.parentNode.insertBefore(node, textarea.nextSibling);
     }, options);
-    instance.save = save;
-    instance.getTextArea = function() { return textarea; };
-    instance.toTextArea = function() {
+    cm.save = save;
+    cm.getTextArea = function() { return textarea; };
+    cm.toTextArea = function() {
       save();
-      textarea.parentNode.removeChild(instance.getWrapperElement());
+      textarea.parentNode.removeChild(cm.getWrapperElement());
       textarea.style.display = "";
       if (textarea.form) {
-        rmSubmit();
+        off(textarea.form, "submit", save);
         if (typeof textarea.form.submit == "function")
           textarea.form.submit = realSubmit;
       }
     };
-    return instance;
+    return cm;
   };
 
-  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);
+  // STRING STREAM
 
-  // Utility functions for working with state. Exported because modes
-  // sometimes need to do this.
-  function copyState(mode, state) {
-    if (state === true) return state;
-    if (mode.copyState) return mode.copyState(state);
-    var nstate = {};
-    for (var n in state) {
-      var val = state[n];
-      if (val instanceof Array) val = val.concat([]);
-      nstate[n] = val;
-    }
-    return nstate;
-  }
-  CodeMirror.copyState = copyState;
-  function startState(mode, a1, a2) {
-    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};
-  };
+  // Fed to the mode parsers, provides helper functions to make
+  // parsers more succinct.
 
   // The character stream used by a mode's parser.
   function StringStream(string, tabSize) {
@@ -2301,6 +3317,7 @@ window.CodeMirror = (function() {
     this.string = string;
     this.tabSize = tabSize || 8;
   }
+
   StringStream.prototype = {
     eol: function() {return this.pos >= this.string.length;},
     sol: function() {return this.pos == 0;},
@@ -2346,30 +3363,215 @@ window.CodeMirror = (function() {
         if (match && consume !== false) this.pos += match[0].length;
         return match;
       }
-    },
-    current: function(){return this.string.slice(this.start, this.pos);}
+    },
+    current: function(){return this.string.slice(this.start, this.pos);}
+  };
+  CodeMirror.StringStream = StringStream;
+
+  // TEXTMARKERS
+
+  function TextMarker(doc, type) {
+    this.lines = [];
+    this.type = type;
+    this.doc = doc;
+  }
+  CodeMirror.TextMarker = TextMarker;
+
+  TextMarker.prototype.clear = function() {
+    if (this.explicitlyCleared) return;
+    var cm = this.doc.cm, withOp = cm && !cm.curOp;
+    if (withOp) startOperation(cm);
+    var min = null, max = null;
+    for (var i = 0; i < this.lines.length; ++i) {
+      var line = this.lines[i];
+      var span = getMarkedSpanFor(line.markedSpans, this);
+      if (span.to != null) max = lineNo(line);
+      line.markedSpans = removeMarkedSpan(line.markedSpans, span);
+      if (span.from != null)
+        min = lineNo(line);
+      else if (this.collapsed && !lineIsHidden(this.doc, line) && cm)
+        updateLineHeight(line, textHeight(cm.display));
+    }
+    if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
+      var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual);
+      if (len > cm.display.maxLineLength) {
+        cm.display.maxLine = visual;
+        cm.display.maxLineLength = len;
+        cm.display.maxLineChanged = true;
+      }
+    }
+
+    if (min != null && cm) regChange(cm, min, max + 1);
+    this.lines.length = 0;
+    this.explicitlyCleared = true;
+    if (this.collapsed && this.doc.cantEdit) {
+      this.doc.cantEdit = false;
+      if (cm) reCheckSelection(cm);
+    }
+    if (withOp) endOperation(cm);
+    signalLater(this, "clear");
+  };
+
+  TextMarker.prototype.find = function() {
+    var from, 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 = Pos(found, span.from);
+        if (span.to != null) to = Pos(found, span.to);
+      }
+    }
+    if (this.type == "bookmark") return from;
+    return from && {from: from, to: to};
+  };
+
+  TextMarker.prototype.getOptions = function(copyWidget) {
+    var repl = this.replacedWith;
+    return {className: this.className,
+            inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight,
+            atomic: this.atomic,
+            collapsed: this.collapsed,
+            clearOnEnter: this.clearOnEnter,
+            replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl,
+            readOnly: this.readOnly,
+            startStyle: this.startStyle, endStyle: this.endStyle};
+  };
+
+  TextMarker.prototype.attachLine = function(line) {
+    if (!this.lines.length && this.doc.cm) {
+      var op = this.doc.cm.curOp;
+      if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
+        (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
+    }
+    this.lines.push(line);
+  };
+  TextMarker.prototype.detachLine = function(line) {
+    this.lines.splice(indexOf(this.lines, line), 1);
+    if (!this.lines.length && this.doc.cm) {
+      var op = this.doc.cm.curOp;
+      (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
+    }
+  };
+
+  function markText(doc, from, to, options, type) {
+    if (options && options.shared) return markTextShared(doc, from, to, options, type);
+    if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
+
+    var marker = new TextMarker(doc, type);
+    if (type == "range" && !posLess(from, to)) return marker;
+    if (options) copyObj(options, marker);
+    if (marker.replacedWith) {
+      marker.collapsed = true;
+      marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
+    }
+    if (marker.collapsed) sawCollapsedSpans = true;
+
+    var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
+    doc.iter(curLine, to.line + 1, function(line) {
+      if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
+        updateMaxLine = true;
+      var span = {from: null, to: null, marker: marker};
+      size += line.text.length;
+      if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
+      if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
+      if (marker.collapsed) {
+        if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
+        if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
+        else updateLineHeight(line, 0);
+      }
+      addMarkedSpan(line, span);
+      ++curLine;
+    });
+    if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
+      if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
+    });
+
+    if (marker.readOnly) {
+      sawReadOnlySpans = true;
+      if (doc.history.done.length || doc.history.undone.length)
+        doc.clearHistory();
+    }
+    if (marker.collapsed) {
+      if (collapsedAtStart != collapsedAtEnd)
+        throw new Error("Inserting collapsed marker overlapping an existing one");
+      marker.size = size;
+      marker.atomic = true;
+    }
+    if (cm) {
+      if (updateMaxLine) cm.curOp.updateMaxLine = true;
+      if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed)
+        regChange(cm, from.line, to.line + 1);
+      if (marker.atomic) reCheckSelection(cm);
+    }
+    return marker;
+  }
+
+  // SHARED TEXTMARKERS
+
+  function SharedTextMarker(markers, primary) {
+    this.markers = markers;
+    this.primary = primary;
+    for (var i = 0, me = this; i < markers.length; ++i) {
+      markers[i].parent = this;
+      on(markers[i], "clear", function(){me.clear();});
+    }
+  }
+  CodeMirror.SharedTextMarker = SharedTextMarker;
+
+  SharedTextMarker.prototype.clear = function() {
+    if (this.explicitlyCleared) return;
+    this.explicitlyCleared = true;
+    for (var i = 0; i < this.markers.length; ++i)
+      this.markers[i].clear();
+    signalLater(this, "clear");
+  };
+  SharedTextMarker.prototype.find = function() {
+    return this.primary.find();
+  };
+  SharedTextMarker.prototype.getOptions = function(copyWidget) {
+    var inner = this.primary.getOptions(copyWidget);
+    inner.shared = true;
+    return inner;
   };
-  CodeMirror.StringStream = StringStream;
 
-  function MarkedSpan(from, to, marker) {
-    this.from = from; this.to = to; this.marker = marker;
+  function markTextShared(doc, from, to, options, type) {
+    options = copyObj(options);
+    options.shared = false;
+    var markers = [markText(doc, from, to, options, type)], primary = markers[0];
+    linkedDocs(doc, function(doc) {
+      markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
+      for (var i = 0; i < doc.linked.length; ++i)
+        if (doc.linked[i].isParent) return;
+      primary = lst(markers);
+    });
+    return new SharedTextMarker(markers, primary);
   }
 
-  function getMarkedSpanFor(spans, marker, del) {
+  // TEXTMARKER SPANS
+
+  function getMarkedSpanFor(spans, marker) {
     if (spans) for (var i = 0; i < spans.length; ++i) {
       var span = spans[i];
-      if (span.marker == marker) {
-        if (del) spans.splice(i, 1);
-        return span;
-      }
+      if (span.marker == marker) return span;
     }
   }
+  function removeMarkedSpan(spans, span) {
+    for (var r, i = 0; i < spans.length; ++i)
+      if (spans[i] != span) (r || (r = [])).push(spans[i]);
+    return r;
+  }
+  function addMarkedSpan(line, span) {
+    line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
+    span.marker.attachLine(line);
+  }
 
-  function markedSpansBefore(old, startCh, endCh) {
+  function markedSpansBefore(old, startCh, isInsert) {
     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) {
+      if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) {
         var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
         (nw || (nw = [])).push({from: span.from,
                                 to: endsAfter ? null : span.to,
@@ -2379,11 +3581,11 @@ window.CodeMirror = (function() {
     return nw;
   }
 
-  function markedSpansAfter(old, endCh) {
+  function markedSpansAfter(old, endCh, isInsert) {
     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) {
+      if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {
         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,
@@ -2393,14 +3595,18 @@ window.CodeMirror = (function() {
     return nw;
   }
 
-  function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) {
-    if (!oldFirst && !oldLast) return newText;
+  function stretchSpansOverChange(doc, change) {
+    var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
+    var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
+    if (!oldFirst && !oldLast) return null;
+
+    var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to);
     // Get the spans that 'stick out' on both sides
-    var first = markedSpansBefore(oldFirst, startCh);
-    var last = markedSpansAfter(oldLast, endCh);
+    var first = markedSpansBefore(oldFirst, startCh, isInsert);
+    var last = markedSpansAfter(oldLast, endCh, isInsert);
 
     // Next, merge those two ends
-    var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0);
+    var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
     if (first) {
       // Fix up .to properties of first
       for (var i = 0; i < first.length; ++i) {
@@ -2430,249 +3636,537 @@ window.CodeMirror = (function() {
       }
     }
 
-    var newMarkers = [newHL(newText[0], first)];
+    var newMarkers = [first];
     if (!sameLine) {
       // Fill gap with whole-line-spans
-      var gap = newText.length - 2, gapMarkers;
+      var gap = change.text.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));
+        newMarkers.push(gapMarkers);
+      newMarkers.push(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) { return typeof val == "string" ? null : val.markedSpans; }
-  function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; }
+  function mergeOldSpans(doc, change) {
+    var old = getOldSpans(doc, change);
+    var stretched = stretchSpansOverChange(doc, change);
+    if (!old) return stretched;
+    if (!stretched) return old;
+
+    for (var i = 0; i < old.length; ++i) {
+      var oldCur = old[i], stretchCur = stretched[i];
+      if (oldCur && stretchCur) {
+        spans: for (var j = 0; j < stretchCur.length; ++j) {
+          var span = stretchCur[j];
+          for (var k = 0; k < oldCur.length; ++k)
+            if (oldCur[k].marker == span.marker) continue spans;
+          oldCur.push(span);
+        }
+      } else if (stretchCur) {
+        old[i] = stretchCur;
+      }
+    }
+    return old;
+  }
+
+  function removeReadOnlyRanges(doc, from, to) {
+    var markers = null;
+    doc.iter(from.line, to.line + 1, function(line) {
+      if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
+        var mark = line.markedSpans[i].marker;
+        if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
+          (markers || (markers = [])).push(mark);
+      }
+    });
+    if (!markers) return null;
+    var parts = [{from: from, to: to}];
+    for (var i = 0; i < markers.length; ++i) {
+      var mk = markers[i], m = mk.find();
+      for (var j = 0; j < parts.length; ++j) {
+        var p = parts[j];
+        if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue;
+        var newParts = [j, 1];
+        if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from))
+          newParts.push({from: p.from, to: m.from});
+        if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to))
+          newParts.push({from: m.to, to: p.to});
+        parts.splice.apply(parts, newParts);
+        j += newParts.length - 1;
+      }
+    }
+    return parts;
+  }
+
+  function collapsedSpanAt(line, ch) {
+    var sps = sawCollapsedSpans && line.markedSpans, found;
+    if (sps) for (var sp, i = 0; i < sps.length; ++i) {
+      sp = sps[i];
+      if (!sp.marker.collapsed) continue;
+      if ((sp.from == null || sp.from < ch) &&
+          (sp.to == null || sp.to > ch) &&
+          (!found || found.width < sp.marker.width))
+        found = sp.marker;
+    }
+    return found;
+  }
+  function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
+  function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
+
+  function visualLine(doc, line) {
+    var merged;
+    while (merged = collapsedSpanAtStart(line))
+      line = getLine(doc, merged.find().from.line);
+    return line;
+  }
+
+  function lineIsHidden(doc, line) {
+    var sps = sawCollapsedSpans && line.markedSpans;
+    if (sps) for (var sp, i = 0; i < sps.length; ++i) {
+      sp = sps[i];
+      if (!sp.marker.collapsed) continue;
+      if (sp.from == null) return true;
+      if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
+        return true;
+    }
+  }
+  function lineIsHiddenInner(doc, line, span) {
+    if (span.to == null) {
+      var end = span.marker.find().to, endLine = getLine(doc, end.line);
+      return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
+    }
+    if (span.marker.inclusiveRight && span.to == line.text.length)
+      return true;
+    for (var sp, i = 0; i < line.markedSpans.length; ++i) {
+      sp = line.markedSpans[i];
+      if (sp.marker.collapsed && sp.from == span.to &&
+          (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
+          lineIsHiddenInner(doc, line, sp)) return true;
+    }
+  }
 
   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);
-    }
+    for (var i = 0; i < spans.length; ++i)
+      spans[i].marker.detachLine(line);
     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);
+      spans[i].marker.attachLine(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 WIDGETS
+
+  var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
+    for (var opt in options) if (options.hasOwnProperty(opt))
+      this[opt] = options[opt];
+    this.cm = cm;
+    this.node = node;
+  };
+  function widgetOperation(f) {
+    return function() {
+      var withOp = !this.cm.curOp;
+      if (withOp) startOperation(this.cm);
+      try {var result = f.apply(this, arguments);}
+      finally {if (withOp) endOperation(this.cm);}
+      return result;
+    };
+  }
+  LineWidget.prototype.clear = widgetOperation(function() {
+    var ws = this.line.widgets, no = lineNo(this.line);
+    if (no == null || !ws) return;
+    for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
+    if (!ws.length) this.line.widgets = null;
+    updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
+    regChange(this.cm, no, no + 1);
+  });
+  LineWidget.prototype.changed = widgetOperation(function() {
+    var oldH = this.height;
+    this.height = null;
+    var diff = widgetHeight(this) - oldH;
+    if (!diff) return;
+    updateLineHeight(this.line, this.line.height + diff);
+    var no = lineNo(this.line);
+    regChange(this.cm, no, no + 1);
+  });
+
+  function widgetHeight(widget) {
+    if (widget.height != null) return widget.height;
+    if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)
+      removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
+    return widget.height = widget.node.offsetHeight;
+  }
+
+  function addLineWidget(cm, handle, node, options) {
+    var widget = new LineWidget(cm, node, options);
+    if (widget.noHScroll) cm.display.alignWidgets = true;
+    changeLine(cm, handle, function(line) {
+      (line.widgets || (line.widgets = [])).push(widget);
+      widget.line = line;
+      if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
+        var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop;
+        updateLineHeight(line, line.height + widgetHeight(widget));
+        if (aboveVisible)
+          cm.curOp.updateScrollPos = {scrollTop: cm.doc.scrollTop + widget.height,
+                                      scrollLeft: cm.doc.scrollLeft};
+      }
+      return true;
+    });
+    return widget;
+  }
+
+  // LINE DATA STRUCTURE
 
   // Line objects. These hold state related to a line, including
   // highlighting info (the styles array).
-  function Line(text, markedSpans) {
-    this.text = text;
-    this.height = 1;
-    attachMarkedSpans(this, markedSpans);
-  }
-  Line.prototype = {
-    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 || (this.styles = []);
-      var pos = st.length = 0;
-      if (this.text == "" && mode.blankLine) mode.blankLine(state);
-      while (!stream.eol()) {
-        var style = mode.token(stream, state), substr = stream.current();
-        stream.start = stream.pos;
-        if (pos && st[pos-1] == style) {
-          st[pos-2] += substr;
-        } else if (substr) {
-          st[pos++] = substr; st[pos++] = style;
+  function makeLine(text, markedSpans, estimateHeight) {
+    var line = {text: text};
+    attachMarkedSpans(line, markedSpans);
+    line.height = estimateHeight ? estimateHeight(line) : 1;
+    return line;
+  }
+
+  function updateLine(line, text, markedSpans, estimateHeight) {
+    line.text = text;
+    if (line.stateAfter) line.stateAfter = null;
+    if (line.styles) line.styles = null;
+    if (line.order != null) line.order = null;
+    detachMarkedSpans(line);
+    attachMarkedSpans(line, markedSpans);
+    var estHeight = estimateHeight ? estimateHeight(line) : 1;
+    if (estHeight != line.height) updateLineHeight(line, estHeight);
+    signalLater(line, "change");
+  }
+
+  function cleanUpLine(line) {
+    line.parent = null;
+    detachMarkedSpans(line);
+  }
+
+  // Run the given mode's parser over a line, update the styles
+  // array, which contains alternating fragments of text and CSS
+  // classes.
+  function runMode(cm, text, mode, state, f) {
+    var flattenSpans = cm.options.flattenSpans;
+    var curText = "", curStyle = null;
+    var stream = new StringStream(text, cm.options.tabSize);
+    if (text == "" && mode.blankLine) mode.blankLine(state);
+    while (!stream.eol()) {
+      var style = mode.token(stream, state);
+      if (stream.pos > 5000) {
+        flattenSpans = false;
+        // Webkit seems to refuse to render text nodes longer than 57444 characters
+        stream.pos = Math.min(text.length, stream.start + 50000);
+        style = null;
+      }
+      var substr = stream.current();
+      stream.start = stream.pos;
+      if (!flattenSpans || curStyle != style) {
+        if (curText) f(curText, curStyle);
+        curText = substr; curStyle = style;
+      } else curText = curText + substr;
+    }
+    if (curText) f(curText, curStyle);
+  }
+
+  function highlightLine(cm, line, state) {
+    // A styles array always starts with a number identifying the
+    // mode/overlays that it is based on (for easy invalidation).
+    var st = [cm.state.modeGen];
+    // Compute the base array of styles
+    runMode(cm, line.text, cm.doc.mode, state, function(txt, style) {st.push(txt, style);});
+
+    // Run overlays, adjust style array.
+    for (var o = 0; o < cm.state.overlays.length; ++o) {
+      var overlay = cm.state.overlays[o], i = 1;
+      runMode(cm, line.text, overlay.mode, true, function(txt, style) {
+        var start = i, len = txt.length;
+        // Ensure there's a token end at the current position, and that i points at it
+        while (len) {
+          var cur = st[i], len_ = cur.length;
+          if (len_ <= len) {
+            len -= len_;
+          } else {
+            st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len));
+            len = 0;
+          }
+          i += 2;
         }
-        // Give up when line is ridiculously long
-        if (stream.pos > 5000) {
-          st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
-          break;
+        if (!style) return;
+        if (overlay.opaque) {
+          st.splice(start, i - start, txt, style);
+          i = start + 2;
+        } else {
+          for (; start < i; start += 2) {
+            var cur = st[start+1];
+            st[start+1] = cur ? cur + " " + style : style;
+          }
         }
+      });
+    }
+
+    return st;
+  }
+
+  function getLineStyles(cm, line) {
+    if (!line.styles || line.styles[0] != cm.state.modeGen)
+      line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
+    return line.styles;
+  }
+
+  // Lightweight form of highlight -- proceed over this line and
+  // update state, but don't save a style array.
+  function processLine(cm, line, state) {
+    var mode = cm.doc.mode;
+    var stream = new StringStream(line.text, cm.options.tabSize);
+    if (line.text == "" && mode.blankLine) mode.blankLine(state);
+    while (!stream.eol() && stream.pos <= 5000) {
+      mode.token(stream, state);
+      stream.start = stream.pos;
+    }
+  }
+
+  var styleToClassCache = {};
+  function styleToClass(style) {
+    if (!style) return null;
+    return styleToClassCache[style] ||
+      (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
+  }
+
+  function lineContent(cm, realLine, measure) {
+    var merged, line = realLine, lineBefore, sawBefore, simple = true;
+    while (merged = collapsedSpanAtStart(line)) {
+      simple = false;
+      line = getLine(cm.doc, merged.find().from.line);
+      if (!lineBefore) lineBefore = line;
+    }
+
+    var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure,
+                   measure: null, addedOne: false, cm: cm};
+    if (line.textClass) builder.pre.className = line.textClass;
+
+    do {
+      builder.measure = line == realLine && measure;
+      builder.pos = 0;
+      builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
+      if (measure && sawBefore && line != realLine && !builder.addedOne) {
+        measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
+        builder.addedOne = true;
       }
-    },
-    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;
+      var next = insertLineContent(line, builder, getLineStyles(cm, line));
+      sawBefore = line == lineBefore;
+      if (next) {
+        line = getLine(cm.doc, next.to.line);
+        simple = false;
       }
-    },
-    // 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, 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);
+    } while (next);
+
+    if (measure && !builder.addedOne)
+      measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
+    if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
+      builder.pre.appendChild(document.createTextNode("\u00a0"));
+
+    var order;
+    // Work around problem with the reported dimensions of single-char
+    // direction spans on IE (issue #1129). See also the comment in
+    // cursorCoords.
+    if (measure && ie && (order = getOrder(line))) {
+      var l = order.length - 1;
+      if (order[l].from == order[l].to) --l;
+      var last = order[l], prev = order[l - 1];
+      if (last.from + 1 == last.to && prev && last.level < prev.level) {
+        var span = measure[builder.pos - 1];
+        if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure),
+                                               span.nextSibling);
       }
-      return {start: stream.start,
-              end: stream.pos,
-              string: stream.current(),
-              className: style || null,
-              state: state};
-    },
-    indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
-    // Produces an HTML fragment for the line, taking selection,
-    // marking, and highlighting into account.
-    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 (!specials.test(text)) {
-          col += text.length;
-          var content = document.createTextNode(text);
+    }
+
+    return builder.pre;
+  }
+
+  var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
+  function buildToken(builder, text, style, startStyle, endStyle) {
+    if (!text) return;
+    if (!tokenSpecialChars.test(text)) {
+      builder.col += text.length;
+      var content = document.createTextNode(text);
+    } else {
+      var content = document.createDocumentFragment(), pos = 0;
+      while (true) {
+        tokenSpecialChars.lastIndex = pos;
+        var m = tokenSpecialChars.exec(text);
+        var skipped = m ? m.index - pos : text.length - pos;
+        if (skipped) {
+          content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
+          builder.col += skipped;
+        }
+        if (!m) break;
+        pos += skipped + 1;
+        if (m[0] == "\t") {
+          var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
+          content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
+          builder.col += tabWidth;
         } 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;
-            }
-          }
+          var token = elt("span", "\u2022", "cm-invalidchar");
+          token.title = "\\u" + m[0].charCodeAt(0).toString(16);
+          content.appendChild(token);
+          builder.col += 1;
         }
-        if (style) html.appendChild(elt("span", [content], style));
-        else html.appendChild(content);
-      }
-      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) {
-            if (wrapAt > outPos) {
-              span_(html, text.slice(0, wrapAt - outPos), style);
-              // See comment at the definition of spanAffectsWrapping
-              if (compensateForWrapping) html.appendChild(elt("wbr"));
-            }
-            html.appendChild(anchor);
-            var cut = wrapAt - outPos;
-            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(){};
-          }
-        };
       }
+    }
+    if (style || startStyle || endStyle || builder.measure) {
+      var fullStyle = style || "";
+      if (startStyle) fullStyle += startStyle;
+      if (endStyle) fullStyle += endStyle;
+      return builder.pre.appendChild(elt("span", [content], fullStyle));
+    }
+    builder.pre.appendChild(content);
+  }
 
-      var st = this.styles, allText = this.text, marked = this.markedSpans;
-      var len = allText.length;
-      function styleToClass(style) {
-        if (!style) return null;
-        return "cm-" + style.replace(/ +/g, " cm-");
-      }
-      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(pre, str, styleToClass(style));
-        }
-      } 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;
-        var advanceMarks = function() {
-          var m;
-          while (markpos < marked.length &&
-                 ((m = marked[markpos]).from == pos || m.from == null)) {
-            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;
-            if (to == null) to = Infinity;
-            if (to == pos) marks.splice(i--, 1);
-            else nextChange = Math.min(to, nextChange);
+  function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
+    for (var i = 0; i < text.length; ++i) {
+      var ch = text.charAt(i), start = i == 0;
+      if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
+        ch = text.slice(i, i + 2);
+        ++i;
+      } else if (i && builder.cm.options.lineWrapping &&
+                 spanAffectsWrapping.test(text.slice(i - 1, i + 1))) {
+        builder.pre.appendChild(elt("wbr"));
+      }
+      builder.measure[builder.pos] =
+        buildToken(builder, ch, style,
+                   start && startStyle, i == text.length - 1 && endStyle);
+      builder.pos += ch.length;
+    }
+    if (text.length) builder.addedOne = true;
+  }
+
+  function buildCollapsedSpan(builder, size, widget) {
+    if (widget) {
+      if (!builder.display) widget = widget.cloneNode(true);
+      builder.pre.appendChild(widget);
+      if (builder.measure && size) {
+        builder.measure[builder.pos] = widget;
+        builder.addedOne = true;
+      }
+    }
+    builder.pos += size;
+  }
+
+  // Outputs a number of spans to make up a line, taking highlighting
+  // and marked text into account.
+  function insertLineContent(line, builder, styles) {
+    var spans = line.markedSpans;
+    if (!spans) {
+      for (var i = 1; i < styles.length; i+=2)
+        builder.addToken(builder, styles[i], styleToClass(styles[i+1]));
+      return;
+    }
+
+    var allText = line.text, len = allText.length;
+    var pos = 0, i = 1, text = "", style;
+    var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed;
+    for (;;) {
+      if (nextChange == pos) { // Update current marker set
+        spanStyle = spanEndStyle = spanStartStyle = "";
+        collapsed = null; nextChange = Infinity;
+        var foundBookmark = null;
+        for (var j = 0; j < spans.length; ++j) {
+          var sp = spans[j], m = sp.marker;
+          if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
+            if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
+            if (m.className) spanStyle += " " + m.className;
+            if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
+            if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
+            if (m.collapsed && (!collapsed || collapsed.marker.width < m.width))
+              collapsed = sp;
+          } else if (sp.from > pos && nextChange > sp.from) {
+            nextChange = sp.from;
           }
-        };
-        var m = 0;
-        while (pos < len) {
-          if (nextChange == pos) advanceMarks();
-          var upto = Math.min(len, nextChange);
-          while (true) {
-            if (text) {
-              var end = pos + text.length;
-              var appliedStyle = style;
-              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;
-            }
-            text = st[i++]; style = styleToClass(st[i++]);
+          if (m.type == "bookmark" && sp.from == pos && m.replacedWith)
+            foundBookmark = m.replacedWith;
+        }
+        if (collapsed && (collapsed.from || 0) == pos) {
+          buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
+                             collapsed.from != null && collapsed.marker.replacedWith);
+          if (collapsed.to == null) return collapsed.marker.find();
+        }
+        if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark);
+      }
+      if (pos >= len) break;
+
+      var upto = Math.min(len, nextChange);
+      while (true) {
+        if (text) {
+          var end = pos + text.length;
+          if (!collapsed) {
+            var tokenText = end > upto ? text.slice(0, upto - pos) : text;
+            builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
+                             spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "");
           }
+          if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
+          pos = end;
+          spanStartStyle = "";
         }
+        text = styles[i++]; style = styleToClass(styles[i++]);
       }
-      return pre;
-    },
-    cleanUp: function() {
-      this.parent = null;
-      detachMarkedSpans(this);
     }
-  };
+  }
+
+  // DOCUMENT DATA STRUCTURE
+
+  function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {
+    function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
+
+    var from = change.from, to = change.to, text = change.text;
+    var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
+    var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
+
+    // First adjust the line structure
+    if (from.ch == 0 && to.ch == 0 && lastText == "") {
+      // This is a whole-line replace. Treated specially to make
+      // sure line objects move the way they are supposed to.
+      for (var i = 0, e = text.length - 1, added = []; i < e; ++i)
+        added.push(makeLine(text[i], spansFor(i), estimateHeight));
+      updateLine(lastLine, lastLine.text, lastSpans, estimateHeight);
+      if (nlines) doc.remove(from.line, nlines);
+      if (added.length) doc.insert(from.line, added);
+    } else if (firstLine == lastLine) {
+      if (text.length == 1) {
+        updateLine(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch),
+                   lastSpans, estimateHeight);
+      } else {
+        for (var added = [], i = 1, e = text.length - 1; i < e; ++i)
+          added.push(makeLine(text[i], spansFor(i), estimateHeight));
+        added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
+        updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0), estimateHeight);
+        doc.insert(from.line + 1, added);
+      }
+    } else if (text.length == 1) {
+      updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch),
+                 spansFor(0), estimateHeight);
+      doc.remove(from.line + 1, nlines);
+    } else {
+      updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0), estimateHeight);
+      updateLine(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans, estimateHeight);
+      for (var i = 1, e = text.length - 1, added = []; i < e; ++i)
+        added.push(makeLine(text[i], spansFor(i), estimateHeight));
+      if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
+      doc.insert(from.line + 1, added);
+    }
+
+    signalLater(doc, "change", doc, change);
+    setSelection(doc, selAfter.anchor, selAfter.head, null, true);
+  }
 
-  // Data structure that holds the sequence of lines.
   function LeafChunk(lines) {
     this.lines = lines;
     this.parent = null;
@@ -2682,22 +4176,22 @@ window.CodeMirror = (function() {
     }
     this.height = height;
   }
+
   LeafChunk.prototype = {
     chunkSize: function() { return this.lines.length; },
-    remove: function(at, n, callbacks) {
+    removeInner: function(at, n) {
       for (var i = at, e = at + n; i < e; ++i) {
         var line = this.lines[i];
         this.height -= line.height;
-        line.cleanUp();
-        if (line.handlers)
-          for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
+        cleanUpLine(line);
+        signalLater(line, "delete");
       }
       this.lines.splice(at, n);
     },
     collapse: function(lines) {
       lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
     },
-    insertHeight: function(at, lines, height) {
+    insertInner: function(at, lines, height) {
       this.height += height;
       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;
@@ -2707,6 +4201,7 @@ window.CodeMirror = (function() {
         if (op(this.lines[at])) return true;
     }
   };
+
   function BranchChunk(children) {
     this.children = children;
     var size = 0, height = 0;
@@ -2719,15 +4214,16 @@ window.CodeMirror = (function() {
     this.height = height;
     this.parent = null;
   }
+
   BranchChunk.prototype = {
     chunkSize: function() { return this.size; },
-    remove: function(at, n, callbacks) {
+    removeInner: function(at, n) {
       this.size -= n;
       for (var i = 0; i < this.children.length; ++i) {
         var child = this.children[i], sz = child.chunkSize();
         if (at < sz) {
           var rm = Math.min(n, sz - at), oldHeight = child.height;
-          child.remove(at, rm, callbacks);
+          child.removeInner(at, rm);
           this.height -= oldHeight - child.height;
           if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
           if ((n -= rm) == 0) break;
@@ -2744,18 +4240,13 @@ window.CodeMirror = (function() {
     collapse: function(lines) {
       for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
     },
-    insert: function(at, lines) {
-      var height = 0;
-      for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
-      this.insertHeight(at, lines, height);
-    },
-    insertHeight: function(at, lines, height) {
+    insertInner: function(at, lines, height) {
       this.size += lines.length;
       this.height += height;
       for (var i = 0, e = this.children.length; i < e; ++i) {
         var child = this.children[i], sz = child.chunkSize();
         if (at <= sz) {
-          child.insertHeight(at, lines, height);
+          child.insertInner(at, lines, height);
           if (child.lines && child.lines.length > 50) {
             while (child.lines.length > 50) {
               var spilled = child.lines.splice(child.lines.length - 25, 25);
@@ -2771,42 +4262,302 @@ window.CodeMirror = (function() {
         at -= sz;
       }
     },
-    maybeSpill: function() {
-      if (this.children.length <= 10) return;
-      var me = this;
-      do {
-        var spilled = me.children.splice(me.children.length - 5, 5);
-        var sibling = new BranchChunk(spilled);
-        if (!me.parent) { // Become the parent node
-          var copy = new BranchChunk(me.children);
-          copy.parent = me;
-          me.children = [copy, sibling];
-          me = copy;
-        } else {
-          me.size -= sibling.size;
-          me.height -= sibling.height;
-          var myIndex = indexOf(me.parent.children, me);
-          me.parent.children.splice(myIndex + 1, 0, sibling);
-        }
-        sibling.parent = me.parent;
-      } while (me.children.length > 10);
-      me.parent.maybeSpill();
+    maybeSpill: function() {
+      if (this.children.length <= 10) return;
+      var me = this;
+      do {
+        var spilled = me.children.splice(me.children.length - 5, 5);
+        var sibling = new BranchChunk(spilled);
+        if (!me.parent) { // Become the parent node
+          var copy = new BranchChunk(me.children);
+          copy.parent = me;
+          me.children = [copy, sibling];
+          me = copy;
+        } else {
+          me.size -= sibling.size;
+          me.height -= sibling.height;
+          var myIndex = indexOf(me.parent.children, me);
+          me.parent.children.splice(myIndex + 1, 0, sibling);
+        }
+        sibling.parent = me.parent;
+      } while (me.children.length > 10);
+      me.parent.maybeSpill();
+    },
+    iterN: function(at, n, op) {
+      for (var i = 0, e = this.children.length; i < e; ++i) {
+        var child = this.children[i], sz = child.chunkSize();
+        if (at < sz) {
+          var used = Math.min(n, sz - at);
+          if (child.iterN(at, used, op)) return true;
+          if ((n -= used) == 0) break;
+          at = 0;
+        } else at -= sz;
+      }
+    }
+  };
+
+  var nextDocId = 0;
+  var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
+    if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
+    if (firstLine == null) firstLine = 0;
+    
+    BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]);
+    this.first = firstLine;
+    this.scrollTop = this.scrollLeft = 0;
+    this.cantEdit = false;
+    this.history = makeHistory();
+    this.frontier = firstLine;
+    var start = Pos(firstLine, 0);
+    this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
+    this.id = ++nextDocId;
+    this.modeOption = mode;
+
+    if (typeof text == "string") text = splitLines(text);
+    updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start});
+  };
+
+  Doc.prototype = createObj(BranchChunk.prototype, {
+    iter: function(from, to, op) {
+      if (op) this.iterN(from - this.first, to - from, op);
+      else this.iterN(this.first, this.first + this.size, from);
+    },
+
+    insert: function(at, lines) {
+      var height = 0;
+      for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
+      this.insertInner(at - this.first, lines, height);
+    },
+    remove: function(at, n) { this.removeInner(at - this.first, n); },
+
+    getValue: function(lineSep) {
+      var lines = getLines(this, this.first, this.first + this.size);
+      if (lineSep === false) return lines;
+      return lines.join(lineSep || "\n");
+    },
+    setValue: function(code) {
+      var top = Pos(this.first, 0), last = this.first + this.size - 1;
+      makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
+                        text: splitLines(code), origin: "setValue"},
+                 {head: top, anchor: top}, true);
+    },
+    replaceRange: function(code, from, to, origin) {
+      from = clipPos(this, from);
+      to = to ? clipPos(this, to) : from;
+      replaceRange(this, code, from, to, origin);
+    },
+    getRange: function(from, to, lineSep) {
+      var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
+      if (lineSep === false) return lines;
+      return lines.join(lineSep || "\n");
+    },
+
+    getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
+    setLine: function(line, text) {
+      if (isLine(this, line))
+        replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
+    },
+    removeLine: function(line) {
+      if (isLine(this, line))
+        replaceRange(this, "", Pos(line, 0), clipPos(this, Pos(line + 1, 0)));
+    },
+
+    getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
+    getLineNumber: function(line) {return lineNo(line);},
+
+    lineCount: function() {return this.size;},
+    firstLine: function() {return this.first;},
+    lastLine: function() {return this.first + this.size - 1;},
+
+    clipPos: function(pos) {return clipPos(this, pos);},
+
+    getCursor: function(start) {
+      var sel = this.sel, pos;
+      if (start == null || start == "head") pos = sel.head;
+      else if (start == "anchor") pos = sel.anchor;
+      else if (start == "end" || start === false) pos = sel.to;
+      else pos = sel.from;
+      return copyPos(pos);
+    },
+    somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);},
+
+    setCursor: docOperation(function(line, ch, extend) {
+      var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line);
+      if (extend) extendSelection(this, pos);
+      else setSelection(this, pos, pos);
+    }),
+    setSelection: docOperation(function(anchor, head) {
+      setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor));
+    }),
+    extendSelection: docOperation(function(from, to) {
+      extendSelection(this, clipPos(this, from), to && clipPos(this, to));
+    }),
+
+    getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},
+    replaceSelection: function(code, collapse, origin) {
+      makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around");
+    },
+    undo: docOperation(function() {makeChangeFromHistory(this, "undo");}),
+    redo: docOperation(function() {makeChangeFromHistory(this, "redo");}),
+
+    setExtending: function(val) {this.sel.extend = val;},
+
+    historySize: function() {
+      var hist = this.history;
+      return {undo: hist.done.length, redo: hist.undone.length};
+    },
+    clearHistory: function() {this.history = makeHistory();},
+
+    markClean: function() {
+      this.history.dirtyCounter = 0;
+      this.history.lastOp = this.history.lastOrigin = null;
+    },
+    isClean: function () {return this.history.dirtyCounter == 0;},
+      
+    getHistory: function() {
+      return {done: copyHistoryArray(this.history.done),
+              undone: copyHistoryArray(this.history.undone)};
+    },
+    setHistory: function(histData) {
+      var hist = this.history = makeHistory();
+      hist.done = histData.done.slice(0);
+      hist.undone = histData.undone.slice(0);
+    },
+
+    markText: function(from, to, options) {
+      return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
+    },
+    setBookmark: function(pos, options) {
+      var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
+                      insertLeft: options && options.insertLeft};
+      pos = clipPos(this, pos);
+      return markText(this, pos, pos, realOpts, "bookmark");
+    },
+    findMarksAt: function(pos) {
+      pos = clipPos(this, pos);
+      var markers = [], spans = getLine(this, 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.parent || span.marker);
+      }
+      return markers;
+    },
+    getAllMarks: function() {
+      var markers = [];
+      this.iter(function(line) {
+        var sps = line.markedSpans;
+        if (sps) for (var i = 0; i < sps.length; ++i)
+          if (sps[i].from != null) markers.push(sps[i].marker);
+      });
+      return markers;
+    },
+
+    posFromIndex: function(off) {
+      var ch, lineNo = this.first;
+      this.iter(function(line) {
+        var sz = line.text.length + 1;
+        if (sz > off) { ch = off; return true; }
+        off -= sz;
+        ++lineNo;
+      });
+      return clipPos(this, Pos(lineNo, ch));
+    },
+    indexFromPos: function (coords) {
+      coords = clipPos(this, coords);
+      var index = coords.ch;
+      if (coords.line < this.first || coords.ch < 0) return 0;
+      this.iter(this.first, coords.line, function (line) {
+        index += line.text.length + 1;
+      });
+      return index;
+    },
+
+    copy: function(copyHistory) {
+      var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
+      doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
+      doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor,
+                 shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn};
+      if (copyHistory) {
+        doc.history.undoDepth = this.history.undoDepth;
+        doc.setHistory(this.getHistory());
+      }
+      return doc;
+    },
+
+    linkedDoc: function(options) {
+      if (!options) options = {};
+      var from = this.first, to = this.first + this.size;
+      if (options.from != null && options.from > from) from = options.from;
+      if (options.to != null && options.to < to) to = options.to;
+      var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
+      if (options.sharedHist) copy.history = this.history;
+      (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
+      copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
+      return copy;
     },
-    iter: function(from, to, op) { this.iterN(from, to - from, op); },
-    iterN: function(at, n, op) {
-      for (var i = 0, e = this.children.length; i < e; ++i) {
-        var child = this.children[i], sz = child.chunkSize();
-        if (at < sz) {
-          var used = Math.min(n, sz - at);
-          if (child.iterN(at, used, op)) return true;
-          if ((n -= used) == 0) break;
-          at = 0;
-        } else at -= sz;
+    unlinkDoc: function(other) {
+      if (other instanceof CodeMirror) other = other.doc;
+      if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
+        var link = this.linked[i];
+        if (link.doc != other) continue;
+        this.linked.splice(i, 1);
+        other.unlinkDoc(this);
+        break;
+      }
+      // If the histories were shared, split them again
+      if (other.history == this.history) {
+        var splitIds = [other.id];
+        linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
+        other.history = makeHistory();
+        other.history.done = copyHistoryArray(this.history.done, splitIds);
+        other.history.undone = copyHistoryArray(this.history.undone, splitIds);
+      }
+    },
+    iterLinkedDocs: function(f) {linkedDocs(this, f);},
+
+    getMode: function() {return this.mode;},
+    getEditor: function() {return this.cm;}
+  });
+
+  Doc.prototype.eachLine = Doc.prototype.iter;
+
+  // The Doc methods that should be available on CodeMirror instances
+  var dontDelegate = "iter insert remove copy getEditor".split(" ");
+  for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
+    CodeMirror.prototype[prop] = (function(method) {
+      return function() {return method.apply(this.doc, arguments);};
+    })(Doc.prototype[prop]);
+
+  function linkedDocs(doc, f, sharedHistOnly) {
+    function propagate(doc, skip, sharedHist) {
+      if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
+        var rel = doc.linked[i];
+        if (rel.doc == skip) continue;
+        var shared = sharedHist && rel.sharedHist;
+        if (sharedHistOnly && !shared) continue;
+        f(rel.doc, shared);
+        propagate(rel.doc, doc, shared);
       }
     }
-  };
+    propagate(doc, null, true);
+  }
+
+  function attachDoc(cm, doc) {
+    if (doc.cm) throw new Error("This document is already in use.");
+    cm.doc = doc;
+    doc.cm = cm;
+    estimateLineHeights(cm);
+    loadMode(cm);
+    if (!cm.options.lineWrapping) computeMaxLength(cm);
+    cm.options.mode = doc.modeOption;
+    regChange(cm);
+  }
+
+  // LINE UTILITIES
 
-  function getLineAt(chunk, n) {
+  function getLine(chunk, n) {
+    n -= chunk.first;
     while (!chunk.lines) {
       for (var i = 0;; ++i) {
         var child = chunk.children[i], sz = child.chunkSize();
@@ -2816,19 +4567,43 @@ window.CodeMirror = (function() {
     }
     return chunk.lines[n];
   }
+
+  function getBetween(doc, start, end) {
+    var out = [], n = start.line;
+    doc.iter(start.line, end.line + 1, function(line) {
+      var text = line.text;
+      if (n == end.line) text = text.slice(0, end.ch);
+      if (n == start.line) text = text.slice(start.ch);
+      out.push(text);
+      ++n;
+    });
+    return out;
+  }
+  function getLines(doc, from, to) {
+    var out = [];
+    doc.iter(from, to, function(line) { out.push(line.text); });
+    return out;
+  }
+
+  function updateLineHeight(line, height) {
+    var diff = height - line.height;
+    for (var n = line; n; n = n.parent) n.height += diff;
+  }
+
   function lineNo(line) {
     if (line.parent == null) return null;
     var cur = line.parent, no = indexOf(cur.lines, line);
     for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
-      for (var i = 0, e = chunk.children.length; ; ++i) {
+      for (var i = 0;; ++i) {
         if (chunk.children[i] == cur) break;
         no += chunk.children[i].chunkSize();
       }
     }
-    return no;
+    return no + cur.first;
   }
+
   function lineAtHeight(chunk, h) {
-    var n = 0;
+    var n = chunk.first;
     outer: do {
       for (var i = 0, e = chunk.children.length; i < e; ++i) {
         var child = chunk.children[i], ch = child.height;
@@ -2845,58 +4620,196 @@ window.CodeMirror = (function() {
     }
     return n + i;
   }
-  function heightAtLine(chunk, n) {
-    var h = 0;
-    outer: do {
-      for (var i = 0, e = chunk.children.length; i < e; ++i) {
-        var child = chunk.children[i], sz = child.chunkSize();
-        if (n < sz) { chunk = child; continue outer; }
-        n -= sz;
-        h += child.height;
+
+  function heightAtLine(cm, lineObj) {
+    lineObj = visualLine(cm.doc, lineObj);
+
+    var h = 0, chunk = lineObj.parent;
+    for (var i = 0; i < chunk.lines.length; ++i) {
+      var line = chunk.lines[i];
+      if (line == lineObj) break;
+      else h += line.height;
+    }
+    for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
+      for (var i = 0; i < p.children.length; ++i) {
+        var cur = p.children[i];
+        if (cur == chunk) break;
+        else h += cur.height;
       }
-      return h;
-    } while (!chunk.lines);
-    for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
+    }
     return h;
   }
 
-  // The history object 'chunks' changes that are made close together
-  // and at almost the same time into bigger undoable units.
-  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 = lst(this.done), last = cur && lst(cur);
-      var dtime = time - this.time;
-
-      if (this.compound && cur && !this.closed) {
-        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;
+  function getOrder(line) {
+    var order = line.order;
+    if (order == null) order = line.order = bidiOrdering(line.text);
+    return order;
+  }
+
+  // HISTORY
+
+  function makeHistory() {
+    return {
+      // Arrays of history events. Doing something adds an event to
+      // done and clears undo. Undoing moves events from done to
+      // undone, redoing moves them in the other direction.
+      done: [], undone: [], undoDepth: Infinity,
+      // Used to track when changes can be merged into a single undo
+      // event
+      lastTime: 0, lastOp: null, lastOrigin: null,
+      // Used by the isClean() method
+      dirtyCounter: 0
+    };
+  }
+
+  function attachLocalSpans(doc, change, from, to) {
+    var existing = change["spans_" + doc.id], n = 0;
+    doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
+      if (line.markedSpans)
+        (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
+      ++n;
+    });
+  }
+
+  function historyChangeFromChange(doc, change) {
+    var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
+    attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
+    linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
+    return histChange;
+  }
+
+  function addToHistory(doc, change, selAfter, opId) {
+    var hist = doc.history;
+    hist.undone.length = 0;
+    var time = +new Date, cur = lst(hist.done);
+
+    if (cur &&
+        (hist.lastOp == opId ||
+         hist.lastOrigin == change.origin && change.origin &&
+         ((change.origin.charAt(0) == "+" && hist.lastTime > time - 600) || change.origin.charAt(0) == "*"))) {
+      // Merge this change into the last event
+      var last = lst(cur.changes);
+      if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
+        // Optimized case for simple insertion -- don't want to add
+        // new changesets for every character typed
+        last.to = changeEnd(change);
       } else {
-        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;
+        // Add new sub-event
+        cur.changes.push(historyChangeFromChange(doc, change));
+      }
+      cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head;
+    } else {
+      // Can not be merged, start a new event.
+      cur = {changes: [historyChangeFromChange(doc, change)],
+             anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
+             anchorAfter: selAfter.anchor, headAfter: selAfter.head};
+      hist.done.push(cur);
+      while (hist.done.length > hist.undoDepth)
+        hist.done.shift();
+      if (hist.dirtyCounter < 0)
+        // The user has made a change after undoing past the last clean state. 
+        // We can never get back to a clean state now until markClean() is called.
+        hist.dirtyCounter = NaN;
+      else
+        hist.dirtyCounter++;
     }
-  };
+    hist.lastTime = time;
+    hist.lastOp = opId;
+    hist.lastOrigin = change.origin;
+  }
+
+  function removeClearedSpans(spans) {
+    if (!spans) return null;
+    for (var i = 0, out; 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 getOldSpans(doc, change) {
+    var found = change["spans_" + doc.id];
+    if (!found) return null;
+    for (var i = 0, nw = []; i < change.text.length; ++i)
+      nw.push(removeClearedSpans(found[i]));
+    return nw;
+  }
+
+  // Used both to provide a JSON-safe object in .getHistory, and, when
+  // detaching a document, to split the history in two
+  function copyHistoryArray(events, newGroup) {
+    for (var i = 0, copy = []; i < events.length; ++i) {
+      var event = events[i], changes = event.changes, newChanges = [];
+      copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore,
+                 anchorAfter: event.anchorAfter, headAfter: event.headAfter});
+      for (var j = 0; j < changes.length; ++j) {
+        var change = changes[j], m;
+        newChanges.push({from: change.from, to: change.to, text: change.text});
+        if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
+          if (indexOf(newGroup, Number(m[1])) > -1) {
+            lst(newChanges)[prop] = change[prop];
+            delete change[prop];
+          }
+        }
+      }
+    }
+    return copy;
+  }
+
+  // Rebasing/resetting history to deal with externally-sourced changes
+
+  function rebaseHistSel(pos, from, to, diff) {
+    if (to < pos.line) {
+      pos.line += diff;
+    } else if (from < pos.line) {
+      pos.line = from;
+      pos.ch = 0;
+    }
+  }
+
+  // Tries to rebase an array of history events given a change in the
+  // document. If the change touches the same lines as the event, the
+  // event, and everything 'behind' it, is discarded. If the change is
+  // before the event, the event's positions are updated. Uses a
+  // copy-on-write scheme for the positions, to avoid having to
+  // reallocate them all on every rebase, but also avoid problems with
+  // shared position objects being unsafely updated.
+  function rebaseHistArray(array, from, to, diff) {
+    for (var i = 0; i < array.length; ++i) {
+      var sub = array[i], ok = true;
+      for (var j = 0; j < sub.changes.length; ++j) {
+        var cur = sub.changes[j];
+        if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); }
+        if (to < cur.from.line) {
+          cur.from.line += diff;
+          cur.to.line += diff;
+        } else if (from <= cur.to.line) {
+          ok = false;
+          break;
+        }
+      }
+      if (!sub.copied) {
+        sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore);
+        sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter);
+        sub.copied = true;
+      }
+      if (!ok) {
+        array.splice(0, i + 1);
+        i = 0;
+      } else {
+        rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore);
+        rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter);
+      }
+    }
+  }
+
+  function rebaseHist(hist, change) {
+    var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
+    rebaseHistArray(hist.done, from, to, diff);
+    rebaseHistArray(hist.undone, from, to, diff);
+  }
+
+  // EVENT OPERATORS
 
   function stopMethod() {e_stop(this);}
   // Ensure an event has a stop method.
@@ -2930,60 +4843,80 @@ window.CodeMirror = (function() {
     return b;
   }
 
-  // Allow 3rd-party code to override event properties by adding an override
-  // object to an event object.
-  function e_prop(e, prop) {
-    var overridden = e.override && e.override.hasOwnProperty(prop);
-    return overridden ? e.override[prop] : e[prop];
+  // EVENT HANDLING
+
+  function on(emitter, type, f) {
+    if (emitter.addEventListener)
+      emitter.addEventListener(type, f, false);
+    else if (emitter.attachEvent)
+      emitter.attachEvent("on" + type, f);
+    else {
+      var map = emitter._handlers || (emitter._handlers = {});
+      var arr = map[type] || (map[type] = []);
+      arr.push(f);
+    }
   }
 
-  // Event handler registration. If disconnect is true, it'll return a
-  // function that unregisters the handler.
-  function connect(node, type, handler, disconnect) {
-    if (typeof node.addEventListener == "function") {
-      node.addEventListener(type, handler, false);
-      if (disconnect) return function() {node.removeEventListener(type, handler, false);};
-    } else {
-      var wrapHandler = function(event) {handler(event || window.event);};
-      node.attachEvent("on" + type, wrapHandler);
-      if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
+  function off(emitter, type, f) {
+    if (emitter.removeEventListener)
+      emitter.removeEventListener(type, f, false);
+    else if (emitter.detachEvent)
+      emitter.detachEvent("on" + type, f);
+    else {
+      var arr = emitter._handlers && emitter._handlers[type];
+      if (!arr) return;
+      for (var i = 0; i < arr.length; ++i)
+        if (arr[i] == f) { arr.splice(i, 1); break; }
     }
   }
-  CodeMirror.connect = connect;
 
-  function Delayed() {this.id = null;}
-  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
+  function signal(emitter, type /*, values...*/) {
+    var arr = emitter._handlers && emitter._handlers[type];
+    if (!arr) return;
+    var args = Array.prototype.slice.call(arguments, 2);
+    for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
+  }
 
-  var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
+  var delayedCallbacks, delayedCallbackDepth = 0;
+  function signalLater(emitter, type /*, values...*/) {
+    var arr = emitter._handlers && emitter._handlers[type];
+    if (!arr) return;
+    var args = Array.prototype.slice.call(arguments, 2);
+    if (!delayedCallbacks) {
+      ++delayedCallbackDepth;
+      delayedCallbacks = [];
+      setTimeout(fireDelayed, 0);
+    }
+    function bnd(f) {return function(){f.apply(null, args);};};
+    for (var i = 0; i < arr.length; ++i)
+      delayedCallbacks.push(bnd(arr[i]));
+  }
 
-  // Detect drag-and-drop
-  var dragAndDrop = function() {
-    // 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;
-  }();
+  function fireDelayed() {
+    --delayedCallbackDepth;
+    var delayed = delayedCallbacks;
+    delayedCallbacks = null;
+    for (var i = 0; i < delayed.length; ++i) delayed[i]();
+  }
 
-  // Feature-detect whether newlines in textareas are converted to \r\n
-  var lineSep = function () {
-    var te = elt("textarea");
-    te.value = "foo\nbar";
-    if (te.value.indexOf("\r") > -1) return "\r\n";
-    return "\n";
-  }();
+  function hasHandler(emitter, type) {
+    var arr = emitter._handlers && emitter._handlers[type];
+    return arr && arr.length > 0;
+  }
 
-  // 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 = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
+  CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
+
+  // MISC UTILITIES
+
+  // Number of pixels added to scroller and sizer to hide scrollbar
+  var scrollerCutOff = 30;
+
+  // Returned or thrown by various protocols to signal 'I'm not
+  // handling this'.
+  var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
+
+  function Delayed() {this.id = null;}
+  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
 
   // Counts the column offset in a string, taking tabs into account.
   // Used mostly to find indentation.
@@ -2998,28 +4931,7 @@ window.CodeMirror = (function() {
     }
     return n;
   }
-
-  function eltOffset(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 }; }
-    catch(e) { box = {top: 0, left: 0}; }
-    if (!screen) {
-      // Get the toplevel scroll, working around browser differences.
-      if (window.pageYOffset == null) {
-        var t = document.documentElement || document.body.parentNode;
-        if (t.scrollTop == null) t = document.body;
-        box.top += t.scrollTop; box.left += t.scrollLeft;
-      } else {
-        box.top += window.pageYOffset; box.left += window.pageXOffset;
-      }
-    }
-    return box;
-  }
-
-  function eltText(node) {
-    return node.textContent || node.innerText || node.nodeValue || "";
-  }
+  CodeMirror.countColumn = countColumn;
 
   var spaceStrs = [""];
   function spaceStr(n) {
@@ -3037,10 +4949,51 @@ window.CodeMirror = (function() {
     } else node.select();
   }
 
-  // Operations on {line, ch} objects.
-  function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
-  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};}
+  function indexOf(collection, elt) {
+    if (collection.indexOf) return collection.indexOf(elt);
+    for (var i = 0, e = collection.length; i < e; ++i)
+      if (collection[i] == elt) return i;
+    return -1;
+  }
+
+  function createObj(base, props) {
+    function Obj() {}
+    Obj.prototype = base;
+    var inst = new Obj();
+    if (props) copyObj(props, inst);
+    return inst;
+  }
+
+  function copyObj(obj, target) {
+    if (!target) target = {};
+    for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
+    return target;
+  }
+
+  function emptyArray(size) {
+    for (var a = [], i = 0; i < size; ++i) a.push(undefined);
+    return a;
+  }
+
+  function bind(f) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    return function(){return f.apply(null, args);};
+  }
+
+  var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
+  function isWordChar(ch) {
+    return /\w/.test(ch) || ch > "\x80" &&
+      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
+  }
+
+  function isEmpty(obj) {
+    for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
+    return true;
+  }
+
+  var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/;
+
+  // DOM UTILITIES
 
   function elt(tag, content, className, style) {
     var e = document.createElement(tag);
@@ -3050,13 +5003,18 @@ window.CodeMirror = (function() {
     else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
     return e;
   }
+
   function removeChildren(e) {
-    e.innerHTML = "";
+    // IE will break all parent-child relations in subnodes when setting innerHTML
+    if (!ie) e.innerHTML = "";
+    else while (e.firstChild) e.removeChild(e.firstChild);
     return e;
   }
+
   function removeChildrenAndAdd(parent, e) {
-    removeChildren(parent).appendChild(e);
+    return removeChildren(parent).appendChild(e);
   }
+
   function setTextContent(e, str) {
     if (ie_lt9) {
       e.innerHTML = "";
@@ -3064,24 +5022,54 @@ window.CodeMirror = (function() {
     } else e.textContent = str;
   }
 
-  // Used to position the cursor after an undo/redo by finding the
-  // last edited character.
-  function editEnd(from, to) {
-    if (!to) return 0;
-    if (!from) return to.length;
-    for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
-      if (from.charAt(i) != to.charAt(j)) break;
-    return j + 1;
+  function getRect(node) {
+    return node.getBoundingClientRect();
   }
+  CodeMirror.replaceGetRect = function(f) { getRect = f; };
 
-  function indexOf(collection, elt) {
-    if (collection.indexOf) return collection.indexOf(elt);
-    for (var i = 0, e = collection.length; i < e; ++i)
-      if (collection[i] == elt) return i;
-    return -1;
+  // FEATURE DETECTION
+
+  // Detect drag-and-drop
+  var dragAndDrop = function() {
+    // 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;
+  }();
+
+  // 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 = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
+
+  var knownScrollbarWidth;
+  function scrollbarWidth(measure) {
+    if (knownScrollbarWidth != null) return knownScrollbarWidth;
+    var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
+    removeChildrenAndAdd(measure, test);
+    if (test.offsetWidth)
+      knownScrollbarWidth = test.offsetHeight - test.clientHeight;
+    return knownScrollbarWidth || 0;
   }
-  function isWordChar(ch) {
-    return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
+
+  var zwspSupported;
+  function zeroWidthElement(measure) {
+    if (zwspSupported == null) {
+      var test = elt("span", "\u200b");
+      removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
+      if (measure.firstChild.offsetHeight != 0)
+        zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;
+    }
+    if (zwspSupported) return elt("span", "\u200b");
+    else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
   }
 
   // See if "".split is the broken IE version, if so, provide an
@@ -3115,10 +5103,14 @@ window.CodeMirror = (function() {
     return range.compareEndPoints("StartToEnd", range) != 0;
   };
 
-  CodeMirror.defineMode("null", function() {
-    return {token: function(stream) {stream.skipToEnd();}};
-  });
-  CodeMirror.defineMIME("text/plain", "null");
+  var hasCopyEvent = (function() {
+    var e = elt("div");
+    if ("oncopy" in e) return true;
+    e.setAttribute("oncopy", "return;");
+    return typeof e.oncopy == 'function';
+  })();
+
+  // KEY NAMING
 
   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",
@@ -3137,7 +5129,256 @@ window.CodeMirror = (function() {
     for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
   })();
 
-  CodeMirror.version = "2.34";
+  // BIDI HELPERS
+
+  function iterateBidiSections(order, from, to, f) {
+    if (!order) return f(from, to, "ltr");
+    for (var i = 0; i < order.length; ++i) {
+      var part = order[i];
+      if (part.from < to && part.to > from || from == to && part.to == from)
+        f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
+    }
+  }
+
+  function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
+  function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
+
+  function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
+  function lineRight(line) {
+    var order = getOrder(line);
+    if (!order) return line.text.length;
+    return bidiRight(lst(order));
+  }
+
+  function lineStart(cm, lineN) {
+    var line = getLine(cm.doc, lineN);
+    var visual = visualLine(cm.doc, line);
+    if (visual != line) lineN = lineNo(visual);
+    var order = getOrder(visual);
+    var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
+    return Pos(lineN, ch);
+  }
+  function lineEnd(cm, lineN) {
+    var merged, line;
+    while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN)))
+      lineN = merged.find().to.line;
+    var order = getOrder(line);
+    var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
+    return Pos(lineN, ch);
+  }
+
+  // This is somewhat involved. It is needed in order to move
+  // 'visually' through bi-directional text -- i.e., pressing left
+  // should make the cursor go left, even when in RTL text. The
+  // tricky part is the 'jumps', where RTL and LTR text touch each
+  // other. This often requires the cursor offset to move more than
+  // one unit, in order to visually move one unit.
+  function moveVisually(line, start, dir, byUnit) {
+    var bidi = getOrder(line);
+    if (!bidi) return moveLogically(line, start, dir, byUnit);
+    var moveOneUnit = byUnit ? function(pos, dir) {
+      do pos += dir;
+      while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
+      return pos;
+    } : function(pos, dir) { return pos + dir; };
+    var linedir = bidi[0].level;
+    for (var i = 0; i < bidi.length; ++i) {
+      var part = bidi[i], sticky = part.level % 2 == linedir;
+      if ((part.from < start && part.to > start) ||
+          (sticky && (part.from == start || part.to == start))) break;
+    }
+    var target = moveOneUnit(start, part.level % 2 ? -dir : dir);
+
+    while (target != null) {
+      if (part.level % 2 == linedir) {
+        if (target < part.from || target > part.to) {
+          part = bidi[i += dir];
+          target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1));
+        } else break;
+      } else {
+        if (target == bidiLeft(part)) {
+          part = bidi[--i];
+          target = part && bidiRight(part);
+        } else if (target == bidiRight(part)) {
+          part = bidi[++i];
+          target = part && bidiLeft(part);
+        } else break;
+      }
+    }
+
+    return target < 0 || target > line.text.length ? null : target;
+  }
+
+  function moveLogically(line, start, dir, byUnit) {
+    var target = start + dir;
+    if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir;
+    return target < 0 || target > line.text.length ? null : target;
+  }
+
+  // Bidirectional ordering algorithm
+  // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
+  // that this (partially) implements.
+
+  // One-char codes used for character types:
+  // L (L):   Left-to-Right
+  // R (R):   Right-to-Left
+  // r (AL):  Right-to-Left Arabic
+  // 1 (EN):  European Number
+  // + (ES):  European Number Separator
+  // % (ET):  European Number Terminator
+  // n (AN):  Arabic Number
+  // , (CS):  Common Number Separator
+  // m (NSM): Non-Spacing Mark
+  // b (BN):  Boundary Neutral
+  // s (B):   Paragraph Separator
+  // t (S):   Segment Separator
+  // w (WS):  Whitespace
+  // N (ON):  Other Neutrals
+
+  // Returns null if characters are ordered as they appear
+  // (left-to-right), or an array of sections ({from, to, level}
+  // objects) in the order in which they occur visually.
+  var bidiOrdering = (function() {
+    // Character types for codepoints 0 to 0xff
+    var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
+    // Character types for codepoints 0x600 to 0x6ff
+    var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
+    function charType(code) {
+      if (code <= 0xff) return lowTypes.charAt(code);
+      else if (0x590 <= code && code <= 0x5f4) return "R";
+      else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);
+      else if (0x700 <= code && code <= 0x8ac) return "r";
+      else return "L";
+    }
+
+    var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
+    var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
+    // Browsers seem to always treat the boundaries of block elements as being L.
+    var outerType = "L";
+
+    return function(str) {
+      if (!bidiRE.test(str)) return false;
+      var len = str.length, types = [];
+      for (var i = 0, type; i < len; ++i)
+        types.push(type = charType(str.charCodeAt(i)));
+
+      // W1. Examine each non-spacing mark (NSM) in the level run, and
+      // change the type of the NSM to the type of the previous
+      // character. If the NSM is at the start of the level run, it will
+      // get the type of sor.
+      for (var i = 0, prev = outerType; i < len; ++i) {
+        var type = types[i];
+        if (type == "m") types[i] = prev;
+        else prev = type;
+      }
+
+      // W2. Search backwards from each instance of a European number
+      // until the first strong type (R, L, AL, or sor) is found. If an
+      // AL is found, change the type of the European number to Arabic
+      // number.
+      // W3. Change all ALs to R.
+      for (var i = 0, cur = outerType; i < len; ++i) {
+        var type = types[i];
+        if (type == "1" && cur == "r") types[i] = "n";
+        else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
+      }
+
+      // W4. A single European separator between two European numbers
+      // changes to a European number. A single common separator between
+      // two numbers of the same type changes to that type.
+      for (var i = 1, prev = types[0]; i < len - 1; ++i) {
+        var type = types[i];
+        if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
+        else if (type == "," && prev == types[i+1] &&
+                 (prev == "1" || prev == "n")) types[i] = prev;
+        prev = type;
+      }
+
+      // W5. A sequence of European terminators adjacent to European
+      // numbers changes to all European numbers.
+      // W6. Otherwise, separators and terminators change to Other
+      // Neutral.
+      for (var i = 0; i < len; ++i) {
+        var type = types[i];
+        if (type == ",") types[i] = "N";
+        else if (type == "%") {
+          for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
+          var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N";
+          for (var j = i; j < end; ++j) types[j] = replace;
+          i = end - 1;
+        }
+      }
+
+      // W7. Search backwards from each instance of a European number
+      // until the first strong type (R, L, or sor) is found. If an L is
+      // found, then change the type of the European number to L.
+      for (var i = 0, cur = outerType; i < len; ++i) {
+        var type = types[i];
+        if (cur == "L" && type == "1") types[i] = "L";
+        else if (isStrong.test(type)) cur = type;
+      }
+
+      // N1. A sequence of neutrals takes the direction of the
+      // surrounding strong text if the text on both sides has the same
+      // direction. European and Arabic numbers act as if they were R in
+      // terms of their influence on neutrals. Start-of-level-run (sor)
+      // and end-of-level-run (eor) are used at level run boundaries.
+      // N2. Any remaining neutrals take the embedding direction.
+      for (var i = 0; i < len; ++i) {
+        if (isNeutral.test(types[i])) {
+          for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
+          var before = (i ? types[i-1] : outerType) == "L";
+          var after = (end < len - 1 ? types[end] : outerType) == "L";
+          var replace = before || after ? "L" : "R";
+          for (var j = i; j < end; ++j) types[j] = replace;
+          i = end - 1;
+        }
+      }
+
+      // Here we depart from the documented algorithm, in order to avoid
+      // building up an actual levels array. Since there are only three
+      // levels (0, 1, 2) in an implementation that doesn't take
+      // explicit embedding into account, we can build up the order on
+      // the fly, without following the level-based algorithm.
+      var order = [], m;
+      for (var i = 0; i < len;) {
+        if (countsAsLeft.test(types[i])) {
+          var start = i;
+          for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
+          order.push({from: start, to: i, level: 0});
+        } else {
+          var pos = i, at = order.length;
+          for (++i; i < len && types[i] != "L"; ++i) {}
+          for (var j = pos; j < i;) {
+            if (countsAsNum.test(types[j])) {
+              if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});
+              var nstart = j;
+              for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
+              order.splice(at, 0, {from: nstart, to: j, level: 2});
+              pos = j;
+            } else ++j;
+          }
+          if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});
+        }
+      }
+      if (order[0].level == 1 && (m = str.match(/^\s+/))) {
+        order[0].from = m[0].length;
+        order.unshift({from: 0, to: m[0].length, level: 0});
+      }
+      if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
+        lst(order).to -= m[0].length;
+        order.push({from: len - m[0].length, to: len, level: 0});
+      }
+      if (order[0].level != lst(order).level)
+        order.push({from: len, to: len, level: order[0].level});
+
+      return order;
+    };
+  })();
+
+  // THE END
+
+  CodeMirror.version = "3.1";
 
   return CodeMirror;
 })();
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/LICENSE b/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/LICENSE
similarity index 83%
copy from chrome/chromeFiles/content/libs/CodeMirror/LICENSE
copy to chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/LICENSE
index 3916e96..8a74612 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/LICENSE
+++ b/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2012 by Marijn Haverbeke <marijnh at gmail.com>
+Copyright (C) 2012 Thomas Schmid <schmid-thomas at gmx.net>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -17,7 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
-
-Please note that some subdirectories of the CodeMirror distribution
-include their own LICENSE files, and are released under different
-licences.
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/sieve.js b/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/sieve.js
index 4d153a2..b1bba53 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/sieve.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/sieve.js
@@ -1,86 +1,88 @@
 /*
- * The content of this file is licenced. You may obtain a copy of the 
- * license at http://sieve.mozdev.org or request it via email from the author. 
- *
- * Do not remove or change this comment.
- * 
- * The initial author of the code is:
- *   Thomas Schmid <schmid-thomas at gmx.net>
- *   
- * Contributor(s):
- *   
+ * See LICENSE in this directory for the license under which this code
+ * is released.
  */
 
 CodeMirror.defineMode("sieve", function(config) {
-  
-  function words(aWords) {
-    var obj = {};
-    for (var i = 0; i < aWords.length; ++i)
-      obj[aWords[i]] = true;
+  function words(str) {
+    var obj = {}, words = str.split(" ");
+    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
     return obj;
   }
-  
-  var keywords = words(["if","elsif","else","stop","require"]);
-  var atoms = words(["true","false","not"]);
+
+  var keywords = words("if elsif else stop require");
+  var atoms = words("true false not");
   var indentUnit = config.indentUnit;
-  
+
   function tokenBase(stream, state) {
 
     var ch = stream.next();
+    if (ch == "/" && stream.eat("*")) {
+      state.tokenize = tokenCComment;
+      return tokenCComment(stream, state);
+    }
 
-    switch (ch) {
-      case "/" :
-        if (stream.eat("*") == false)
-          break;
+    if (ch === '#') {
+      stream.skipToEnd();
+      return "comment";
+    }
 
-        state.tokenize = tokenCComment;
-        return tokenCComment(stream, state);
-        
-      case "#" :
-        stream.skipToEnd();
-        return "comment";
-        
-      case "\"" :
-        state.tokenize = tokenString(ch);
-        return state.tokenize(stream, state);      
-      
-      case "(":
-       state._indent.push("("); 
-      case "{" :
-        state._indent.push("{");
-        return null;
-       
-      case ")" :
-        state._indent.pop();
-      case "}" :
-        state._indent.pop();
-        return null;
-           
-      case "," :
-      case ";" :
-        return null;
+    if (ch == "\"") {
+      state.tokenize = tokenString(ch);
+      return state.tokenize(stream, state);
+    }
+    
+    if (ch == "(") {
+      state._indent.push("(");
+      // add virtual angel wings so that editor behaves...
+      // ...more sane incase of broken brackets
+      state._indent.push("{");
+      return null;
+    }
+
+    if (ch === "{") {
+      state._indent.push("{");
+      return null;
+    }
+    
+    if (ch == ")")  {
+      state._indent.pop();
+      state._indent.pop();    
+    }
+
+    if (ch === "}") {
+      state._indent.pop();
+      return null;
+    }
+    
+    if (ch == ",")
+      return null;
       
-      // ":" (ALPHA / "_") *(ALPHA / DIGIT / "_")
-      case ":" :
-        stream.eatWhile(/[a-zA-Z_]/);
-        stream.eatWhile(/[a-zA-Z0-9_]/);
+    if (ch == ";")
+      return null;
       
-        return "operator";  
-        
-    }
     
+    if (/[{}\(\),;]/.test(ch))
+      return null;
+
     // 1*DIGIT "K" / "M" / "G"
     if (/\d/.test(ch)) {
       stream.eatWhile(/[\d]/);
-      stream.eat(/[KkMmGg]/)
+      stream.eat(/[KkMmGg]/);
       return "number";
-    }    
-    
+    }
+
+    // ":" (ALPHA / "_") *(ALPHA / DIGIT / "_")
+    if (ch == ":") {
+      stream.eatWhile(/[a-zA-Z_]/);
+      stream.eatWhile(/[a-zA-Z0-9_]/);
+
+      return "operator";
+    }
+
     stream.eatWhile(/\w/);
-    //stream.eatWhile(/[\w\$_]/);
-    
     var cur = stream.current();
-    
+
     // "text:" *(SP / HTAB) (hash-comment / CRLF)
     // *(multiline-literal / multiline-dotstart)
     // "." CRLF
@@ -89,12 +91,12 @@ CodeMirror.defineMode("sieve", function(config) {
       state.tokenize = tokenMultiLineString;
       return "string";
     }
-    
+
     if (keywords.propertyIsEnumerable(cur))
       return "keyword";
 
     if (atoms.propertyIsEnumerable(cur))
-      return "atom"; 
+      return "atom";
       
     return null;
   }
@@ -105,25 +107,25 @@ CodeMirror.defineMode("sieve", function(config) {
     // the first line is special it may contain a comment
     if (!stream.sol()) {
       stream.eatSpace();
-      
+
       if (stream.peek() == "#") {
         stream.skipToEnd();
-        return "comment";        
+        return "comment";
       }
- 
+
       stream.skipToEnd();
       return "string";
     }
-    
+
     if ((stream.next() == ".")  && (stream.eol()))
     {
       state._multiLineString = false;
       state.tokenize = tokenBase;
     }
-    
-    return "string";   
+
+    return "string";
   }
-  
+
   function tokenCComment(stream, state) {
     var maybeEnd = false, ch;
     while ((ch = stream.next()) != null) {
@@ -149,35 +151,29 @@ CodeMirror.defineMode("sieve", function(config) {
     };
   }
 
-    
   return {
     startState: function(base) {
-
       return {tokenize: tokenBase,
               baseIndent: base || 0,
-              _indent: [],
-              };
+              _indent: []};
     },
 
     token: function(stream, state) {
-            
       if (stream.eatSpace())
         return null;
-        
-      return (state.tokenize || tokenBase)(stream, state) 
-      
+
+      return (state.tokenize || tokenBase)(stream, state);;
     },
 
-    indent: function(state, textAfter)
-    {
+    indent: function(state, _textAfter) {
       var length = state._indent.length;
-      if (textAfter && (textAfter[0] == "}"))
+      if (_textAfter && (_textAfter[0] == "}"))
         length--;
       
       if (length <0)
         length = 0;
       
-      return length * indentUnit;      
+      return length * indentUnit;
     },
 
     electricChars: "}"
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/package.json b/chrome/chromeFiles/content/libs/CodeMirror/package.json
index 81e762b..8b435d1 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/package.json
+++ b/chrome/chromeFiles/content/libs/CodeMirror/package.json
@@ -1,6 +1,6 @@
 {
     "name": "codemirror",
-    "version":"2.34.0",
+    "version":"3.10.00",
     "main": "codemirror.js",
     "description": "In-browser code editing made bearable",
     "licenses": [{"type": "MIT",
diff --git a/chrome/chromeFiles/content/libs/jQuery/jquery-1.8.3.min.js b/chrome/chromeFiles/content/libs/jQuery/jquery-1.8.3.min.js
new file mode 100644
index 0000000..83589da
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/jQuery/jquery-1.8.3.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v1.8.3 jquery.com | jquery.org/license */
+(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){retur [...]
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/libs/libManageSieve/SieveAbstractClient.js b/chrome/chromeFiles/content/libs/libManageSieve/SieveAbstractClient.js
index 61be620..9665569 100644
--- a/chrome/chromeFiles/content/libs/libManageSieve/SieveAbstractClient.js
+++ b/chrome/chromeFiles/content/libs/libManageSieve/SieveAbstractClient.js
@@ -188,7 +188,7 @@ SieveAbstractClient.prototype.disconnect
     sivManager.removeSessionListener(this._sid, this);
     sivManager.closeChannel(this._sid,this._cid);
   }
-  catch (ex) 
+  catch (ex)
   {
     Components.utils.reportError(ex);
   }
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveActionUI.js b/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveActionUI.js
new file mode 100644
index 0000000..2a4d187
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveActionUI.js
@@ -0,0 +1,29 @@
+function SieveActionBox(item)
+{
+  this._item = item;
+}
+
+SieveActionBox.prototype.html
+  = function ()
+{
+  <select>
+    <option>enumerate all actions here</option>
+    <option>enumerate all actions here</option>
+  </select>
+  
+  if selected update details
+  
+  
+}
+
+
+if (!SieveDesigner)
+  throw "Could not register Action Widgets";
+
+  
+SieveDesigner.register(SieveDiscard, SieveDiscardUI);
+SieveDesigner.register(SieveKeep, SieveKeepUI);
+SieveDesigner.register(SieveStop, SieveStopUI);
+
+SieveDesigner.register(SieveFileInto, SieveFileIntoUI);
+SieveDesigner.register(SieveRedirect,SieveRedirectUI);
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveBlocksUI.js b/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveBlocksUI.js
new file mode 100644
index 0000000..2abe71e
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveBlocksUI.js
@@ -0,0 +1,435 @@
+/* 
+ * The contents of this file is licenced. You may obtain a copy of
+ * the license at http://sieve.mozdev.org or request it via email 
+ * from the author. Do not remove or change this comment. 
+ * 
+ * The initial author of the code is:
+ *   Thomas Schmid <schmid-thomas at gmx.net>
+ */
+
+"use strict";
+
+function SieveRichList(elms)
+{
+  this._items = [];
+  this._selectedIndex = 0;
+  
+  // We need to refactor our elements, as sieve does not align perfectly fine
+  // with our user interface, so we do not care about comments, whitespace etc.
+  // and rip the script appart. On save we have to do the opposit...
+    
+  var actions = null;
+  
+  while (elms.length) 
+  {
+   
+    if (elms[0].nodeType() == "action")
+    {
+      if (!actions)
+        actions = [];
+      
+      actions.push(elms[0]); 
+      
+      elms[0].remove(true);
+      
+      continue;
+    }
+    
+    if (elms[0].nodeType() == "condition")
+    {
+      if (actions)
+        this.append(new SieveRichListItem(this,actions));
+        
+      if (elms[0].children().length > 1)
+        throw " Script too complex, elsif or else not supported" 
+        
+      // extract all actions...
+      actions = [];
+      var children = elms[0].children(0).children(); 
+       
+      while (children.length)
+      {       
+        if (children[0].nodeType() == "condition")
+          throw "Script to complex nested if statement not supported"
+        
+        if (children[0].nodeType() == "action")
+          actions.push(children[0]);
+          
+          
+        children[0].remove(true);
+      }
+      
+      // then all tests...
+      var test = elms[0].children(0).test();      
+      
+      this.append(new SieveRichListItem(this, actions, test));
+      
+      test.remove(true);
+      
+      actions = null;
+      
+      continue;
+    }
+
+    // Safe to Skip should be whitespaces and similar stuff...    
+    elms[0].remove(true);    
+  }
+    
+  if (actions) 
+    this.append(new SieveRichListItem(this,actions));
+
+  // as we ripped the elements out of the sieve script we should remove them...    
+  // ... so that our document contains just our actions and conditions plus...
+  // ... the root node.
+  var whitelist = [];
+  for (var i=0; i<this._items.length; i++)
+  {
+    whitelist = whitelist.concat(this._items[i]._actions)    
+    if (this._items[i]._condition)
+      whitelist = whitelist.concat(this._items[i]._condition)
+  }
+   
+  if (whitelist.length)
+    whitelist[0].document().compact(whitelist);
+         
+}
+
+SieveRichList.prototype.item
+  = function (pos)
+{
+  if (typeof(pos) == "undefined")
+    return (this._items[this._items.length-1])
+    
+  return this._items[pos];
+}
+
+SieveRichList.prototype.append
+  = function (item)
+{  
+  this._items.push(item);  
+  return this;
+}
+
+SieveRichList.prototype.selectedIndex
+  = function (idx)
+{
+  if (typeof(idx) == "undefined")
+    return this._selectedIndex;
+    
+  if (this._selectedIndex == idx)
+    return this._selectedIndex;
+  
+  // we can move the only to the next item when the element validates
+  if (!this._items[this._selectedIndex].validate())
+    return this._selectedIndex;
+    
+  // ... disable and deselect it
+  this._items[this._selectedIndex].editable(false);    
+  
+  this._selectedIndex = idx;
+  
+  // ... and move to the new element.
+  this._items[idx].editable(true);
+  
+  return this._selectedIndex;  
+}
+
+SieveRichList.prototype.selectedItem
+  = function (item)
+{ 
+  if (typeof(item) == "undefined")
+    return this._items[this.selectedIndex()]; 
+    
+  var idx = this._items.indexOf(item);
+  
+  // in case we can not find the item, we return null...
+  if (idx == -1)
+    return null;
+  
+  // ... we do the same in case we can not select the item.
+  if (idx != this.selectedIndex(idx))
+    return null;
+    
+  return this._items[idx];  
+}
+
+SieveRichList.prototype.html
+  = function ()
+{
+  var elm = $("<div/>");
+  
+  if (this.selectedItem())
+    this.selectedItem().editable(true);    
+  
+  for (var i=0; i<this._items.length; i++)
+    elm.append(this._items[i].html())
+  
+  elm.addClass("sivRichList");
+  
+  return elm;
+}
+
+// Selected element is always editable...
+// Wenn switching to an other editable element then then call on editable..
+
+/******************************************************************************/
+
+/**
+ * 
+ * @param {} item
+ */
+function SieveRichListItem(parent, actions, condition)
+{    
+  this._isEditable = false;
+  this._parent = parent;
+  
+  this._actions = actions;
+  this._condition = condition;
+}
+
+/*SieveRichListItem.prototype.relax
+  = function () 
+{
+  for (var i=0; i<this._actions.length; i++)
+    this._actions.remove(true);
+    
+  if (this._condition) 
+    for (var i=0; i<this._condition.length; i++)
+      this._condition.remove(true);    
+}*/
+
+SieveRichListItem.prototype.validate
+  = function ()
+{
+  return true;    
+}
+
+SieveRichListItem.prototype.htmlConditional
+   = function (html,type,tests)
+{
+  var elm = $("<div/>");
+  
+  if (html)
+    elm = html;
+    
+  if (!this.editable())
+  {    
+    if (type == 2)
+      elm.append($("<div/>").text("Match any of the following"))
+    else if (type == 1)
+      elm.append($("<div/>").text("Match all of the following"));
+    else
+      elm.append($("<div/>").text("Match all Messages"));
+      
+   // test [0,test,0]
+   // test [[0,test,0],[0,test,0],[0,test,0]]
+    if (tests && tests.length)
+    {
+      for (var i=0; i<tests.length; i++)
+      {
+        if (tests[i][1].nodeType() != "test")
+          throw "Script to compex, nested Tests...";
+          
+        elm.append($("<div/>").text(tests[i][1].toScript()));
+      }
+    }
+      
+    return elm;
+  }
+      
+ // If editable: 
+   elm.append($("<div/>").text("Match"))
+      .append($("<div/>")
+        .append($("<input type='radio'/>"))
+        .append($("<span/>").text("all of the following")) 
+        .append($("<input type='radio'/>"))
+        .append($("<span/>").text("any of the following"))
+        .append($("<input type='radio'/>"))
+        .append($("<span/>").text("all Messages")));
+
+    if (tests && tests.length)
+    {
+      for (var i=0; i<tests.length; i++)
+      {
+        if (tests[i][1].nodeType() != "test")
+          throw "Script to compex, nested Tests...";
+      
+        if (tests[i][1].widget())
+          elm.append(tests[i][1].widget().html(true));
+        else
+          elm.append($("<div/>").text(tests[i][1].toScript()));
+      }
+    }
+     
+   
+   var elm2 = $("<select/>");
+   
+   elm2.append($("<option/>").text("..."))
+   
+   for (var key in SieveLexer.types["test"])
+     if (key != "test/boolean")
+       if (SieveLexer.types["test"][key].onCapable(SieveLexer.capabilities()))
+         elm2.append($("<option/>").text(key));        
+      
+   return elm.append($("<div/>").append(elm2));
+}
+
+SieveRichListItem.prototype.htmlConditions
+    = function ()
+{ 
+  var elm = $("<div/>").addClass("sivCondition")
+  
+  var item = this._condition;
+  
+  if (!item)
+    return this.htmlConditional(elm,0)      
+  
+  if (item.nodeType() == "test")
+    return this.htmlConditional(elm,1,[[null,item,null]]);
+ 
+  if (item.nodeName() == "operator/anyof")
+  {
+    if (item.isAllOf)
+      return this.htmlConditional(elm,1,item.tests);
+    
+    return this.htmlConditional(elm,2,item.tests);
+  }
+  
+  throw "Script too complext, unsupported operator"+item.nodeType();
+}
+
+
+SieveRichListItem.prototype.htmlActions
+  = function  ()
+{
+  
+  var elm = $("<div/>")
+    .append($("<div/>").text("Peform these Actions"))
+    .addClass("sivAction");
+  
+  var actions = this._actions;
+    
+  for (var i=0; i<this._actions.length; i++)
+  {
+    if (actions[i].nodeType() == "whitespace")
+      continue;
+      
+    if (actions[i].nodeType() == "action")
+    {      
+      elm.append($("<div/>").text(actions[i].toScript()))
+      continue;
+    }
+    
+    throw "Script to complex [Ax12], test expected"+actions[i].nodeType();
+  }
+  
+  if (this.editable())
+  {
+    var elm2 = $("<select/>");
+    
+    elm2.append($("<option/>").text("..."))
+    
+    for (var key in SieveLexer.types["action"])
+      if (key != "test/boolean")
+        if (SieveLexer.types["action"][key].onCapable(SieveLexer.capabilities()))
+          elm2.append($("<option/>").text(key));
+          
+    elm.append($("<div/>").append(elm2))     
+  }         
+  
+  return elm;  
+}  
+
+SieveRichListItem.prototype.editable
+    = function (isEditable)
+{
+  if (typeof(isEditable) == "undefined")
+    return this._isEditable;
+
+  if (this._isEditable ==  isEditable)
+    return this;
+    
+  this._isEditable = isEditable;
+    
+  // update the inner HTML
+  this.reflowInner();
+}
+
+SieveRichListItem.prototype.reflowInner
+  = function ()
+{
+  // we can skip if the element is not bound to a DOM
+  if (!this._html)
+    return;
+    
+  // remove old content
+  this._html.children().remove();
+  
+  this._html
+    .append(this.htmlConditions())
+    .append(this.htmlActions());
+  
+  var that = this;
+  
+  if (this.editable())
+  {
+    this._html.attr("sivEditable","true")
+    return;
+  }
+    
+  this._html.removeAttr("sivEditable")
+  
+  this._html.click(function(e) 
+  { 
+    if (that._parent.selectedItem(that) == null)
+      return false;
+        
+    $(this).unbind('click'); 
+    e.preventDefault(); 
+    return true; 
+  } );
+}
+
+
+SieveRichListItem.prototype.html
+  = function ()
+{
+  if (this._html)
+    return this._html;
+    
+  this._html =  $("<div/>");
+  
+  this.reflowInner();      
+  
+  this._html.addClass("sivRichListItem");
+  
+  return this._html;
+}
+
+
+function SieveRootNodeUI(elm)
+{
+  SieveAbstractBoxUI.call(this,elm);  
+  this.richlist = new SieveRichList(elm.children(1).children());
+}
+
+SieveRootNodeUI.prototype.__proto__ = SieveAbstractBoxUI.prototype;
+
+
+SieveRootNodeUI.prototype.createHtml
+    = function (parent)
+{
+  var elm = $(document.createElement("div"))
+              .addClass("sivBlock");
+  
+  var item = null;
+  var blockElms = this.getSieve();  
+     
+  return parent.append(this.richlist.html());
+}
+
+
+if (!SieveDesigner)
+  throw "Could not register Block Widgets";
+
+SieveDesigner.register(SieveRootNode, SieveRootNodeUI);
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveTestsUI.js b/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveTestsUI.js
new file mode 100644
index 0000000..735c019
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/RFC5228/widgets/simplicity/SieveTestsUI.js
@@ -0,0 +1,86 @@
+
+/******************************************************************************/
+
+function SieveTestUI(elm)
+{
+  SieveAbstractBoxUI.call(this,elm);
+}
+
+SieveTestUI.prototype.__proto__ = SieveAbstractBoxUI.prototype;
+
+
+SieveTestUI.prototype.createHtml
+    = function (parent)
+{  
+  
+  return parent.append($("<div/>")
+    .append($("<div/>").text("address"))
+    .append($("<button/>").text("Trash")))
+    .append($("<div/>")
+      .css("display","table")
+      .append($("<div/>")
+        .css({"display":"table-cell","vertical-align":"middle"})
+        .append((new SieveStringListUI(this.getSieve().headerList))
+          .defaults(["To","From","Cc","Bcc","Reply-To"]).html()))
+      .append($("<div/>")
+        .css({"display":"table-cell","vertical-align":"middle"})
+        .append((new SieveMatchType2UI(this.getSieve().matchType)).html()))
+      .append($("<div/>")
+        .css({"display":"table-cell","vertical-align":"middle"})
+        .append((new SieveStringListUI(this.getSieve().keyList)).html())));   
+}
+
+function SieveMatchType2UI(elm)
+{
+  SieveAbstractBoxUI.call(this,elm);
+}
+
+SieveMatchType2UI.prototype.__proto__ = SieveAbstractBoxUI.prototype;
+
+
+SieveMatchType2UI.prototype.createHtml
+    = function (parent)
+{
+  return parent.append($("<select/>")
+    .append($("<option/>").text("is"))
+    .append($("<option/>").text("matches"))
+    .append($("<option/>").text("contains")));
+}
+
+/*<style type="text/css">
+
+div.container {
+  border: 1px solid #000000;
+  display:table;  
+}
+
+div.cell {
+  padding:20px;
+  display:table-cell;
+  vertical-align:middle;
+}
+
+</style>
+
+<div>
+  <div>address</div>
+  <div class="container">
+    <div class="cell">
+      <div>[ TO       |+|X|V]</div>
+      <div>[ From       |+|X||V]</div>
+      <div>[ BCC       |+|X|V]</div> 
+    </div>
+    <div class="cell">
+      <div>[ Contains       |V ]</div>
+    </div>
+    <div class="cell">
+      <div>[ Example.com       |+|X|V]</div>
+      <div>[ Example.org       |+|X|V]</div>
+    </div>
+  </div>
+</div>
+<div>address</div>*/
+if (!SieveDesigner)
+  throw "Could not register Block Widgets";
+
+SieveDesigner.register(SieveAddress, SieveTestUI);
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/SieveGui.html b/chrome/chromeFiles/content/libs/libSieveDOM/SieveGui.html
index 4256c23..61e73e3 100644
--- a/chrome/chromeFiles/content/libs/libSieveDOM/SieveGui.html
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/SieveGui.html
@@ -7,7 +7,7 @@
   <link rel="stylesheet" href="toolkit/style/style.css" type="text/css" />
 
   <!-- Global Imports -->  
-  <script type="application/javascript" src="jquery.js"></script>
+  <script type="application/javascript" src="./../jQuery/jquery-1.8.3.min.js"></script>
   <!--<script type="application/javascript" src="UI/jquery-ui.js"></script>-->
   
   <!-- Basic Sieve Elements -->
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/SieveSimpleGui.html b/chrome/chromeFiles/content/libs/libSieveDOM/SieveSimpleGui.html
new file mode 100644
index 0000000..403f0fa
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/SieveSimpleGui.html
@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Sieve DOM</title>
+  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+  <link rel="stylesheet" href="toolkit/style/simplicity/style.css" type="text/css" />
+</head>
+<body>
+  <!-- Global Imports -->  
+  <script type="application/javascript" src="./../jQuery/jquery-1.8.3.min.js"></script>
+  
+  <!-- Basic Sieve Elements -->
+  <script type="application/javascript" src="toolkit/SieveParser.js"></script>
+  <script type="application/javascript" src="toolkit/SieveLexer.js"></script>
+  <script type="application/javascript" src="toolkit/SieveDesigner.js"></script>
+  
+  <script type="application/javascript" src="toolkit/SieveScriptDOM.js"></script>  
+  <script type="application/javascript" src="toolkit/logic/Elements.js"></script>
+  <script type="application/javascript" src="toolkit/widgets/Boxes.js"></script>
+   
+  <!-- logic related Imports -->
+  <!-- RFC 5228 - Sieve -->
+  <script type="application/javascript" src="RFC5228/logic/SieveWhiteSpaces.js" ></script>
+  <script type="application/javascript" src="RFC5228/logic/SieveStrings.js" ></script>
+  <script type="application/javascript" src="RFC5228/logic/SieveNumbers.js" ></script>
+  <script type="application/javascript" src="RFC5228/logic/SieveBlocks.js"></script>  
+  <script type="application/javascript" src="RFC5228/logic/SieveTests.js"></script>
+  <script type="application/javascript" src="RFC5228/logic/SieveOperators.js"></script>
+  <script type="application/javascript" src="RFC5228/logic/SieveConditions.js"></script>  
+  <script type="application/javascript" src="RFC5228/logic/SieveActions.js"></script>
+  <script type="application/javascript" src="RFC5228/logic/SieveImports.js"></script>
+  
+  <!-- RFC 5429 - Reject -->
+  <script type="application/javascript" src="RFC5429/logic/SieveReject.js"></script>  
+  
+  <!-- RFC 5232 - ImapFlags-->
+  <script type="application/javascript" src="RFC5232/logic/SieveImapFlags.js"></script>
+
+    <!-- UI related Imports -->
+  <!-- RFC 5228 - Sieve -->
+  <script type="application/javascript" src="RFC5228/widgets/simplicity/SieveBlocksUI.js"></script>  
+  <script type="application/javascript" src="RFC5228/widgets/simplicity/SieveTestsUI.js"></script>  
+    <script type="application/javascript" src="RFC5228/widgets/SieveStringsUI.js"></script>  
+    
+<script type="text/javascript">
+//<![CDATA[
+
+$(document).ready(function() {
+  init();
+});
+
+function setSieveScript(script,capabilities)
+{
+  if (capabilities)
+    SieveLexer.capabilities(capabilities);
+    // reset environemnt
+	init();
+	
+	if (!script)
+	  script =$('#txtScript').val();
+	 else
+	  $('#txtScript').val(script);
+	  
+    dom2.script(script);
+	
+	$("#txtOutput")
+	  .val(dom2.script());
+	  
+	$("#divOutput")
+	  .empty()
+	  .append(dom2.html())	
+}
+
+function getSieveScript()
+{
+  return dom2.script();
+}
+
+function require()
+{
+  var requires = {};
+  
+  dom2.root().require(requires);
+  
+  for (var i in requires)
+    alert(i);  
+}
+
+function capabilities()
+{
+  SieveLexer.capabilities({"imap4flags":true,"fileinto":true,"reject":true,"envelope":true});
+}
+
+function compact()
+{
+  alert(dom2.compact());
+}
+function debug(obj)
+{
+  //var logger = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
+
+  var str = "";
+  for (tempVar in obj)
+    str += tempVar+"\n";
+	
+  alert(str);
+  //logger.logStringMessage(str);
+}
+
+
+
+  function init()
+  {
+    // Yes it's a global object
+    dom2 = new SieveDocument(SieveLexer,SieveDesigner);
+  }
+
+  /*function errorhandler(msg, url, line)
+  {
+    showInfoMessage(msg,"");
+  }
+  
+  window.onerror = errorhandler;*/
+  
+  function showInfoMessage(message, content)
+  {
+    $("#infobarsubject > span").text(message);
+	$("#infobarmessage > span").text(content);
+    $("#infobar").toggle();	
+  }
+  
+//]]>
+</script>
+<div id="infobar">
+  <div id="infobarsubject"  > 
+     <span>
+       Message Text
+	 </span>
+	 <button onclick='$("#infobar").toggle();'>Dismiss</button>
+  </div>
+</div>
+
+
+<div>   
+  <div id="divOutput">
+  </div>
+  <button> Add </button>
+</div>
+
+<div style="margin-left:200px;">
+  <div>
+    <div id="boxScript" style="overflow: hidden;width: 100%; ">  
+      <div style="float:left; padding:5px;">
+        <div>Input:</div>
+        <textarea id="txtScript" cols="50" rows="10" wrap="off">
+        </textarea>
+      </div>
+      <div style="float:left; padding:5px;">
+        <div>Result:</div>
+        <textarea id="txtOutput" cols="50" rows="10" readonly="readonly" wrap="off"></textarea>
+      </div>
+    </div>
+  </div >
+  <div id="debug">
+    <button onclick="setSieveScript();">
+	  Parse Sieve Script
+	</button>
+    <button onclick="$('#txtOutput').val(getSieveScript());">
+	  Update Sieve Script
+	</button>
+    <button onclick="require()">
+	  Collect Require
+	</button>	
+    <button onclick="capabilities()">
+	  Set Capabilities
+	</button>		
+    <button onclick="$('#boxScript').toggle()">
+	  Show/Hide
+	</button>		
+	<input id="draggable" />
+  </div>
+</div>  
+</body>
+</html>
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/jquery.js b/chrome/chromeFiles/content/libs/libSieveDOM/jquery.js
new file mode 100644
index 0000000..628ed9b
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/jquery.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.6.4 http://jquery.com/ | http://jquery.org/license */
+(function(a,b){function cu(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cr(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),ci.close();d=ci.createElement( [...]
+t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i [...]
+(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushS [...]
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/SieveParser.js b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/SieveParser.js
index 4d0dd56..304888f 100644
--- a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/SieveParser.js
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/SieveParser.js
@@ -24,7 +24,7 @@ SieveParser.prototype.isChar
   if (!Array.isArray(ch))
     return (this._data.charAt(this._pos+offset) == ch);
   
-  var ch = [].concat(ch)
+  ch = [].concat(ch)
   
   for (var i=0; i<ch.length; i++)
     if (this._data.charAt(this._pos+offset) == ch[i])
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/_style.css b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/_style.css
deleted file mode 100644
index 3bfe236..0000000
--- a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/_style.css
+++ /dev/null
@@ -1,182 +0,0 @@
-.sivDropBox:empty {
-	height: 5px;
-	margin: 2px;
-}    
-
-.sivDropBox[sivDragging="true"] {
-  background-color : red;
-  background-image: -moz-linear-gradient(rgba(255,255,255,.25), rgba(0,0,0,.15));
-  -moz-border-radius: 2px;
-box-shadow: 0 0 5px red;
-}
-
-
-body {
-  font-family:verdana;
-  font-size: 12px;
-  background: url('background.png') #333;
-}
-	
-.SivMailAddress {
-  font-family:courier;
-}
-
-.SivText {
-  font-family:courier;
-}
-
-  .SivElement h1 {
-    font-size:1em;   
-    padding:0px;
-    margin:0px;
-  }
-
-  .SivStringListItem {
-    padding:10px 0px 20px 20px;
-  }
-  
-    #trash 
-    {
-      height: 80px;
-      min-width:80px;
-      background-color:transparent;   
-      background-image:url('trash.png');
-      background-repeat:no-repeat;
-      background-position:center center;   
-    } 
- 
-#trash[sivDragging="true"] 
-{
-  background-image:url('trash-full.png');
-} 
-   
-.SivElementBlock {
-  padding-left: 20px;
-  padding-right: 5px;
-}
-
-.SivElementTest {
-  padding-left: 20px;
-  padding-right: 5px;
-}
-  
-.SivElement {
-  background-color: -moz-dialog;
-  background-image: -moz-linear-gradient(rgba(255,255,255,.25), rgba(0,0,0,.15));
-  color: -moz-dialogText;
-  margin: 1px;
-  -moz-border-radius: 3px;
-
-  margin-left: auto;
-  margin-right: auto;
-  width:600px;
-  border: 1px solid #000066;
- 
-/*  background-color: hsl(219,45%,60%);*/
-/*  background-color: hsl(92,45%,52%);*/
-}
-
-.SivElement[sivIsEditable=true]
-{
-  /*background-color: hsl(92,45%,52%);*/  
-  background-color: white;
-  background-image: none;
-  box-shadow: 0 0 5px gray;
-}
-
-
-/*sivDragBox : hoover
-.SivElement[sivIsEditable=true]
-{
-  background-color: hsl(92,45%,52%);
-}*/
-
-/*.SivElement[sivElmType=editable]
-{
-  background-image:url('edit');
-  background-repeat:no-repeat;
-  background-position:top left;   
-}*/
-
-.SivFocusedElement {
-  background-color: -moz-dialog;
-  background-image: -moz-linear-gradient(rgba(255,255,255,.25), rgba(0,0,0,.15));
-  color: -moz-dialogText;
-  margin: 1px;
-  -moz-border-radius: 2px;
-
-  margin-left: auto;
-  margin-right: auto;
-  width:600px;
-  border: 1px solid #000066;
-
-  background-color: hsl(92,45%,52%);
-}
-
-div.floating-menu {
-  position:fixed;
-  top: 10px;
-  left: 10px;
-  background:#fff4c8;
-  border:1px solid #ffcc00;
-  width:150px;
-  z-index:100;
-  left: 20px;
-}
-
-div .floating-menu .SivElement {
-   width:140px; 
-}
-
-.SivElement:hover {
-  /*background-image: -moz-linear-gradient(rgba(255,255,255,5), rgba(50%,50%,50%,.2), rgba(0,0,0,.15));*/
-  /* selected...
-  background-image: -moz-linear-gradient(rgba(0,0,0,.4), rgba(0,0,0,.1));*/
-
-}
-
-/* Style for all editable elements */
-
-.SivElement[sivIsEditable=true] > div
-{
-  padding: 7px;
-}
-
-.sivEditorHelpIcon {
-  float:right;
-  background:  url('help.png') no-repeat center center;
-  cursor:pointer;
-  padding:10px;
-  margin:5px;
-}
-
-.sivEditorHelpText {
-  display:none;
-  padding-bottom:20px;
-  border-bottom: 1px solid gray;
-  background-image: -moz-linear-gradient(hsl(215,60%,92%), hsl(215,58%,88%));
-  /*background-image: -moz-linear-gradient(bottom, #ffdd66, #ffeb7d);*/
-  cursor:pointer;
-}
-
-.sivControlBox {
-  border-top: 1px solid gray;
-  background-color:lightgray;
-  background-image: -moz-linear-gradient(rgba(255,255,255,.25), rgba(0,0,0,.15));
-  overflow: hidden;
-  vertical-align:middle;
-}
-
-/* Box for error messages */
-.sivControlBox > div {
-  color:red;
-  font-weight: bold;
-}
-
-.sivControlBox > button {
-  float: right;
-}
-
-.sivEditableElement:not([sivIsEditable=true]) > div{
-  background: url('edit.png') no-repeat right center ;
-}
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/simplicity/style.css b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/simplicity/style.css
new file mode 100644
index 0000000..c621230
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/simplicity/style.css
@@ -0,0 +1,216 @@
+ at import url("./../stringlist/style.css");
+
+body {
+  font-family:verdana;
+  font-size: 11px;
+  background-color:lightgray;
+  cursor: default;
+  margin:0px;
+}
+
+.sivConditionText,
+.sivConditionalText,
+div[sivtype]:not([siviseditable]) > .sivSummaryContent
+{
+  border: 1px #DDE4E9 solid;
+  background-color: #F3F6F7;
+  padding-left:3px;
+  
+  cursor: default;
+}
+
+/* Style all interactive Elements */
+.sivEditableElement[sivtype]:not([sivIsEditable=true]) > .sivSummaryContent {
+  background-image: url('edit.png');
+  background-repeat: no-repeat;
+  background-position: right center ;
+}
+
+.sivEditableElement[sivtype]:not([siviseditable]) > .sivSummaryContent:hover
+{
+  border: 1px red #EDEDED solid;
+  background-color: white;
+  box-shadow: 0 0 7px gray;
+}
+
+/* Editmode on */
+.sivEditableElement[sivtype][sivIsEditable=true]
+{
+  border: 1px solid #EDEDED;
+  background-color: white;
+  background-image: none;
+  box-shadow: 0 0 7px gray;
+}
+
+.sivEditableElement[sivtype][sivIsEditable=true] > div
+{
+  padding: 4px;
+}
+
+
+div[sivtype][sivDragging="true"] {
+  border-radius: 2px;
+  box-shadow: 0 0 5px red;
+}
+
+
+       
+.sivSummaryContent em{
+  font-family:courier;
+  font-style:normal;
+}
+
+
+  .sivEditableElement h1 {
+    font-size:1em;   
+    padding:0px;
+    margin:0px;    
+  }
+
+
+
+  div.floating-menu {
+  position:absolute;
+  background:#fff4c8;
+  border:1px solid #ffcc00;
+  width:150px;
+  z-index:100;
+  left: 20px;
+  top: 20px;
+  }
+
+  div .floating-menu .SivElement {
+   width:140px; 
+  }
+
+
+#infobar {
+  background-image: -moz-linear-gradient(bottom, #ffdd66, #ffeb7d);
+
+  border: 1px solid black;
+  border-top: none;
+  position:absolute;
+  top: 0px;
+  
+  width: 800px;
+  left: 50%;
+  margin-left: -400px;
+  border-bottom-right-radius: 5px;
+  border-bottom-left-radius: 5px;
+  
+  display: none;
+}
+
+#infobarsubject
+{
+  line-height:20px;
+  padding: 10px;
+  font-weight:bold;
+}
+
+#infobar button {
+  float:right;
+}
+
+
+  /* Style for all editable elements */
+
+  .sivEditorCloseIcon {
+  float:right;
+  cursor:pointer;
+  height: 10px;
+  width: 9px;
+  margin-right:3px;
+  
+  color: gray;
+  }
+
+  .sivEditorHelpIcon {
+  float:right;
+  cursor:pointer;
+  height: 10px;
+  width: 9px;
+  
+  color: gray;
+  }
+
+  .sivEditorHelpText {
+  display:none;
+  padding-bottom:20px;
+  border-bottom: 1px solid gray;
+  background-image: -moz-linear-gradient(hsl(215,60%,92%), hsl(215,58%,88%));
+  /*background-image: -moz-linear-gradient(bottom, #ffdd66, #ffeb7d);*/
+  cursor:pointer;
+  }
+
+  .sivControlBox {
+  border-top: 1px solid gray;
+  background-color:lightgray;
+  background-image: -moz-linear-gradient(rgba(255,255,255,.25), rgba(0,0,0,.15));
+  overflow: hidden;
+  vertical-align:middle;
+  }
+
+  /* Box for error messages */
+  .sivControlBox > div {
+  color:red;
+  font-weight: bold;
+  }
+
+  .sivControlBox > button {
+  float: right;
+  }
+
+
+
+  #divOutput {  
+    background-color:white;    
+  }
+
+.sivRichList > .sivRichListItem {
+  padding: 5px 10px;
+  margin:1px;
+}
+
+.sivRichList > .sivRichListItem[sivEditable] {
+/*  background-color: -moz-cellhighlight;
+  color: -moz-cellhighlighttext;  */
+/*     background-color: hsla(216,45%,88%,.98);*/
+   background-color: Highlight;
+   /*opacity: 0.6;*/
+   color: HighlightText; 
+   
+/*     background-color: hsla(216,45%,88%,.98);
+     box-shadow: 0px 1px 2px rgb(204,214,234) inset;   */
+   -moz-outline-radius: 3px;   
+   outline: 1px #999 dotted;
+   /*outline-offset: -1px;*/
+   
+     border: 1px solid hsl(213,45%,65%);
+     box-shadow: 0 0 0 1px hsla(0,0%,100%,.5) inset,
+                 0 1px 0 hsla(0,0%,100%,.3) inset;
+     background-image: -moz-linear-gradient(hsl(212,86%,92%), hsl(212,91%,86%));
+     color: black;     
+     
+/*  */
+  
+}
+
+.sivRichList > .sivRichListItem:not(:last-of-type) {
+  border-bottom: 1px solid gray;
+  margin-bottom: 2px;
+}
+
+./*sivRichList {
+  -moz-appearance: textfield;
+}*/
+
+.sivAction > div:not(:first-of-type),
+.sivCondition > div:not(:first-of-type) {
+  margin-left: 10px;
+}
+
+
+
+
+
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/_style.css b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/_style.css
new file mode 100644
index 0000000..66842f9
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/_style.css
@@ -0,0 +1,73 @@
+/* Default style for String list items dropdown */
+
+.SivStringList {
+  margin-left:10px;
+  margin-right:10px;  
+}
+
+.sivStringListItem
+{
+  padding-top:3px;
+  padding-bottom:2px;
+}
+
+/* We nest our text field*/
+.sivStringListItem > span {
+  -moz-appearance: textfield;
+  position:relative;
+  padding: 1px;
+  padding-top:3px;
+}
+
+.sivStringListItem > span > input {
+  -moz-appearance: none !important;
+  border:none;
+  width:100%;
+  min-width: 150px;
+}
+
+/* the containter for our icons */
+.sivStringListItem > span > span {
+ position:absolute;
+ top:0;
+ right:0;
+ margin-top:2px;
+ background-color: red;
+}
+
+.sivStringDrop,
+.sivStringAdd,
+.sivStringRemove {
+  width: 16px;
+  height:16px;
+  vertical-align: middle;
+  background-repeat:no-repeat;
+  background-position:center center; 
+  background-color: -moz-Field;
+}
+
+.SivStringList .sivStringListItem:hover:last-child .sivStringAdd {
+  background-image: url('listitem.add.png');
+  display:inline-block;
+}
+
+.SivStringList .sivStringListItem:hover:not(:only-of-type) .sivStringRemove {
+  background-image: url('listitem.delete.png');
+  display:inline-block;
+}
+
+.SivStringList .sivStringListItem:hover .sivStringDrop {
+  background-image: url('listitem.drop.png');
+  display:inline-block;
+}
+
+.sivStringListItem select {
+  z-index:500; 
+  position: absolute;
+  left: 1px;
+  top: 100%;
+  width: 100%;
+  border: 1px outset black !important;
+  border-left-width: 2px ! important;  
+  overflow:-moz-scrollbars-none !important;
+}
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/listitem.add.png b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/listitem.add.png
similarity index 100%
rename from chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/listitem.add.png
rename to chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/listitem.add.png
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/listitem.delete.png b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/listitem.delete.png
similarity index 100%
rename from chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/listitem.delete.png
rename to chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/listitem.delete.png
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/listitem.drop.png b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/listitem.drop.png
similarity index 100%
rename from chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/listitem.drop.png
rename to chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/listitem.drop.png
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/style.css b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/style.css
new file mode 100644
index 0000000..f4c8934
--- /dev/null
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/stringlist/style.css
@@ -0,0 +1,71 @@
+SivStringList {
+  padding-left: 20px;
+  padding-bottom: 15px;
+}
+
+.sivStringListItem
+{
+	/* Inputbox's borders and margin are 2px, we need to compenate that... */ 
+	margin-top:-2px;
+	/*  ... anyhow in sum the box must maintain it's height, so we need to add extra padding*/
+	padding:1px;
+}
+
+/* We nest our text field into a fake one, just  a look alike */
+.sivStringListItem > span {
+  -moz-appearance: textfield;
+   position:relative;
+	width:100%;
+}
+
+/* the original input has no styling at all */
+.sivStringListItem > span > input {
+  -moz-appearance: none !important;
+  background-color: transparent;
+  border: 0px none;
+}
+
+/* the containter for our icons */
+.sivStringListItem > span > span {
+ position:absolute;
+ top:0;
+ right:0;
+ margin-top: 2px;
+}
+
+.sivStringDrop,
+.sivStringAdd,
+.sivStringRemove {
+  width: 16px;
+  height:16px;
+  vertical-align: middle;
+  background-repeat:no-repeat;
+  background-position:center center; 
+  background-color: -moz-Field;
+}
+
+.SivStringList .sivStringListItem:hover:last-child .sivStringAdd {
+  background-image: url('listitem.add.png');
+  display:inline-block;
+}
+
+.SivStringList .sivStringListItem:hover:not(:only-of-type) .sivStringRemove {
+  background-image: url('listitem.delete.png');
+  display:inline-block;
+}
+
+.SivStringList .sivStringListItem:hover .sivStringDrop {
+  background-image: url('listitem.drop.png');
+  display:inline-block;
+}
+
+.sivStringListItem select {
+  z-index:500; 
+  position: absolute;
+  left: 1px;
+  top: 100%;
+  width: 100%;
+  border: 1px outset black !important;
+  border-left-width: 2px ! important;  
+  overflow:-moz-scrollbars-none !important;
+}
diff --git a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/style.css b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/style.css
index 7b2a23c..4a3936b 100644
--- a/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/style.css
+++ b/chrome/chromeFiles/content/libs/libSieveDOM/toolkit/style/style.css
@@ -1,3 +1,4 @@
+ at import url("./stringlist/style.css");
 
 .sivConditionText,
 .sivConditionalText,
@@ -40,7 +41,7 @@ div[sivtype]:not([siviseditable]) > .sivSummaryContent
 
 
 div[sivtype][sivDragging="true"] {
-  -moz-border-radius: 2px;
+  border-radius: 2px;
   box-shadow: 0 0 5px red;
 }
 
@@ -112,11 +113,7 @@ div[sivtype][sivDragging="true"] {
   .sivControlBox > button {
   float: right;
   }
-
-
-
-
-
+  
 
 /* Blocks */
 
@@ -195,8 +192,6 @@ div[sivtype][sivDragging="true"] {
   box-shadow: 0 0 5px red;
 }
 
-
-
 #sivActions > div[sivtype]
 {
   margin:2px 10px;  
@@ -206,73 +201,6 @@ div[sivtype][sivDragging="true"] {
   border: 1px solid gray;
 }
 
-.SivStringList {
-  padding-left: 20px;
-  padding-bottom: 15px;
-}
-
-.sivStringListItem
-{
-  padding-top:3px;
-  padding-bottom:2px;
-}
-
-/* We nest our text field*/
-.sivStringListItem > span {
-  -moz-appearance: textfield;
-   position:relative;
-}
-
-.sivStringListItem > span > input {
-  -moz-appearance: none !important;
-  border:none;
-  width:200px;
-}
-
-/* the containter for our icons */
-.sivStringListItem > span > span {
- position:absolute;
- top:0;
- right:0;
-}
-
-.sivStringDrop,
-.sivStringAdd,
-.sivStringRemove {
-  width: 16px;
-  height:16px;
-  vertical-align: middle;
-  background-repeat:no-repeat;
-  background-position:center center; 
-  background-color: -moz-Field;
-}
-
-.SivStringList .sivStringListItem:hover:last-child .sivStringAdd {
-  background-image: url('listitem.add.png');
-  display:inline-block;
-}
-
-.SivStringList .sivStringListItem:hover:not(:only-of-type) .sivStringRemove {
-  background-image: url('listitem.delete.png');
-  display:inline-block;
-}
-
-.SivStringList .sivStringListItem:hover .sivStringDrop {
-  background-image: url('listitem.drop.png');
-  display:inline-block;
-}
-
-.sivStringListItem select {
-  z-index:500; 
-  position: absolute;
-  left: 1px;
-  top: 100%;
-  width: 100%;
-  border: 1px outset black !important;
-  border-left-width: 2px ! important;  
-  overflow:-moz-scrollbars-none !important;
-}
-
 .sivMatchType,
 .sivAddressPart,
 .sivComparator
diff --git a/chrome/chromeFiles/content/modules/overlays/SieveOverlay.jsm b/chrome/chromeFiles/content/modules/overlays/SieveOverlay.jsm
index c0ce90d..3d4f3e0 100644
--- a/chrome/chromeFiles/content/modules/overlays/SieveOverlay.jsm
+++ b/chrome/chromeFiles/content/modules/overlays/SieveOverlay.jsm
@@ -1,3 +1,14 @@
+/*
+ * The content of this file is licenced. You may obtain a copy of the license
+ * at http://sieve.mozdev.org or request it via email from the author. 
+ *
+ * Do not remove or change this comment.
+ * 
+ * The initial author of the code is:
+ *   Thomas Schmid <schmid-thomas at gmx.net>
+ *      
+ */
+
 // Enable Strict Mode
 "use strict";  
 
@@ -300,7 +311,7 @@ SieveMailWindowOverlay.prototype.load
   
   
   SieveOverlayUtils.addTabType(SieveTabType,tabmail);
- // TODO add finaly method when all windows are closed, to unload unused components
+  // TODO add finally method when all windows are closed, to unload unused components
   
   this.unloadCallback( 
     function() { SieveOverlayUtils.removeTabType(SieveTabType,tabmail);})
diff --git a/chrome/chromeFiles/content/modules/overlays/SieveOverlayManager.jsm b/chrome/chromeFiles/content/modules/overlays/SieveOverlayManager.jsm
index ef8d2d4..05b354a 100644
--- a/chrome/chromeFiles/content/modules/overlays/SieveOverlayManager.jsm
+++ b/chrome/chromeFiles/content/modules/overlays/SieveOverlayManager.jsm
@@ -1,3 +1,14 @@
+/*
+ * The content of this file is licenced. You may obtain a copy of the license
+ * at http://sieve.mozdev.org or request it via email from the author. 
+ *
+ * Do not remove or change this comment.
+ * 
+ * The initial author of the code is:
+ *   Thomas Schmid <schmid-thomas at gmx.net>
+ *      
+ */
+
 // Enable Strict Mode
 "use strict";  
 
@@ -439,20 +450,20 @@ var SieveOverlayManager =
   },
   
   loadOverlay : function (window)
-  { 
+  {
     var url = window.document.baseURI;
-        
+    
     if (!this._overlayUrls[url])
       return;
     
     SieveOverlayManager.loadWatcher(window);
     
-    for (var i=0; i<this._overlayUrls[url].length; i++)
+    for (var i=0; i < this._overlayUrls[url].length; i++)
     {
       let overlay = new (this._overlayUrls[url][i])();
       this._overlays.push(overlay);
       overlay.load(window);      
-    }    
+    }
   },
   
   load : function()
diff --git a/chrome/chromeFiles/content/modules/sieve/Sieve.js b/chrome/chromeFiles/content/modules/sieve/Sieve.js
index feda916..409d0b4 100644
--- a/chrome/chromeFiles/content/modules/sieve/Sieve.js
+++ b/chrome/chromeFiles/content/modules/sieve/Sieve.js
@@ -1,7 +1,9 @@
 /* 
  * The content of this file is licensed. You may obtain a copy of
- * the license at http://sieve.mozdev.org or request it via email 
- * from the author(s). Do not remove or change this comment. 
+ * the license at https://github.com/thsmi/sieve/ or request it via 
+ * email from the author.
+ * 
+ * Do not remove or change this comment. 
  * 
  * The initial author of the code is:
  *   Thomas Schmid <schmid-thomas at gmx.net>
@@ -220,7 +222,6 @@ Sieve.prototype.isAlive
   return this.socket.isAlive(); 
 }
 
-// if the parameter ignoreCertError is set, cert errors will be ignored
 /**
  * This method secures the connection to the sieve server. By activating 
  * Transport Layer Security all Data exchanged is crypted. 
@@ -294,6 +295,25 @@ Sieve.prototype.addListener
   this.listener = listener;
 }
 
+/**
+ * Adds a request to the send queue. 
+ * 
+ * Normal request runs to completion, so they are blocking the queue
+ * until they are fully processed. If the request failes, the error 
+ * handler is triggered and the request is dequeued.
+ * 
+ * A greedy request in constrast accepts whatever it can get. Upon an 
+ * error greedy request are not dequeued. They fail silently and the next 
+ * requests is processed. This continues until a request succeeds, a non 
+ * greedy request failes or the queue has no more requests. 
+ *  
+ * @param {SieveAbstractRequest} request
+ *   the request object which should be added to the queue
+ *   
+ * @optional @param {bool} greedy
+ *   if true requests fail silently
+ *      
+ */
 Sieve.prototype.addRequest 
     = function(request,greedy)
 {
@@ -373,11 +393,14 @@ Sieve.prototype.connect
     
   // If we know the proxy setting, we can do a shortcut...
   if (proxy)
-  {
+  { 
     this.onProxyAvailable(null,null,proxy[0],null);
     return;
   }
-  
+
+  if (this.debug.level & (1 << 2))
+    this.debug.logger.logStringMessage("Lookup Proxy Configuration for x-sieve://"+this.host+":"+this.port+" ...");
+    
   var ios = Cc["@mozilla.org/network/io-service;1"]
                 .getService(Ci.nsIIOService);
                     
@@ -568,7 +591,7 @@ Sieve.prototype._onStop
 }
 
 Sieve.prototype.onDataAvailable 
-    = function(request, context, inputStream, offset, count)
+    = function(aRequest, context, inputStream, offset, count)
 {
   var binaryInStream = Cc["@mozilla.org/binaryinputstream;1"]
       .createInstance(Ci.nsIBinaryInputStream)
diff --git a/chrome/chromeFiles/content/modules/sieve/SieveAccounts.js b/chrome/chromeFiles/content/modules/sieve/SieveAccounts.js
index 791cde3..7dc2e7f 100644
--- a/chrome/chromeFiles/content/modules/sieve/SieveAccounts.js
+++ b/chrome/chromeFiles/content/modules/sieve/SieveAccounts.js
@@ -1,7 +1,9 @@
 /* 
- * The contents of this file is licenced. You may obtain a copy of
- * the license at http://sieve.mozdev.org or request it via email 
- * from the author. Do not remove or change this comment. 
+ * The contents of this file are licenced. You may obtain a copy of
+ * the license at https://github.com/thsmi/sieve/ or request it via 
+ * email from the author.
+ * 
+ * Do not remove or change this comment. 
  * 
  * The initial author of the code is:
  *   Thomas Schmid <schmid-thomas at gmx.net>
@@ -99,9 +101,6 @@ SieveImapAuth.prototype.getPassword
     return account.password;
     
   // ... otherwise we it is our job...
-  var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"]
-                  .getService(Ci.nsIPromptService);
-    
   var strings = Services.strings
                     .createBundle("chrome://sieve/locale/locale.properties");
 
@@ -109,14 +108,12 @@ SieveImapAuth.prototype.getPassword
   var input = {value:null};
   var check = {value:false}; 
   var result 
-    = prompts.promptPassword(
+    = Services.prompt.promptPassword(
         null,
         strings.GetStringFromName("account.password.title"), 
         strings.GetStringFromName("account.password.description"),
         input, null, check);
-  
-  prompts = null;
-  
+
   if (result)
     return input.value;
 
@@ -127,8 +124,8 @@ SieveImapAuth.prototype.getUsername
     = function ()
 {
   // use the IMAP Key to load the Account...
-  var account = Components.classes['@mozilla.org/messenger/account-manager;1']
-	              .getService(Components.interfaces.nsIMsgAccountManager)
+  var account = Cc['@mozilla.org/messenger/account-manager;1']
+	              .getService(Ci.nsIMsgAccountManager)
 	              .getIncomingServer(this.imapKey);
 	                
   return account.realUsername;
@@ -166,10 +163,7 @@ SieveImapAuth.prototype.getType
  *   the unique URI of the associated sieve account
  */
 function SieveCustomAuth2(host, uri)
-{
-  if (("@mozilla.org/login-manager;1" in Components.classes) == false)
-    throw "SieveCustomAuth2: No login manager component found...";
-  
+{  
   if (uri == null)
     throw "SieveCustomAuth2: URI can't be null"; 
 
@@ -220,8 +214,7 @@ SieveCustomAuth2.prototype.setUsername
   
   
   // we should also update the LoginManager...
-  var loginManager = Components.classes["@mozilla.org/login-manager;1"]
-                        .getService(Components.interfaces.nsILoginManager);
+  var loginManager = Services.logins;
                         
   // ...first look for entries which meet the proposed naming...  
   var logins = 
@@ -287,39 +280,25 @@ SieveCustomAuth2.prototype.getPassword
 {
   var username = this.getUsername();
     
-  var loginManager = Components.classes["@mozilla.org/login-manager;1"]
-                        .getService(Components.interfaces.nsILoginManager);
-  
+
   // First look for entries which meet the proposed naming...  
   var logins = 
-        loginManager.findLogins(
+        Services.logins.findLogins(
             {},"sieve://"+this.host,null,"sieve://"+this.host);
 
   for (var i = 0; i < logins.length; i++)
-  {
-    if (logins[i].username != username)
-      continue;
-    
-    return logins[i].password;    
-  }
+    if (logins[i].username == username)
+      return logins[i].password;
   
   // but as Thunderbird fails to import the passwort and username properly...
   // ...there might be some slightly different entries...      
-  logins = 
-    loginManager.findLogins({},"sieve://"+this.uri,"",null);
+  logins = Services.logins.findLogins({},"sieve://"+this.uri,"",null);
   
   for (var i = 0; i < logins.length; i++)
-  {
-    if (logins[i].username != username)
-      continue;
-    
-    return logins[i].password;    
-  }
+    if (logins[i].username == username)
+      return logins[i].password;
   
-  // we found no password, so let's prompt for it
-  var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                  .getService(Components.interfaces.nsIPromptService);  
-                        
+  // we found no password, so let's prompt for it 
   var input = {value:null};
   var check = {value:false};
      
@@ -327,7 +306,7 @@ SieveCustomAuth2.prototype.getPassword
     .createBundle("chrome://sieve/locale/locale.properties");
     
   var result = 
-    prompts.promptPassword(//window,
+    Services.prompt.promptPassword(//window,
         null,
         strings.GetStringFromName("account.password.title"), 
         strings.GetStringFromName("account.password.description"), 
@@ -345,8 +324,8 @@ SieveCustomAuth2.prototype.getPassword
     // the password might be already added while the password prompt is displayed    
     try
     {      
-      var login = Components.classes["@mozilla.org/login-manager/loginInfo;1"]
-                            .createInstance(Components.interfaces.nsILoginInfo); 
+      var login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+                            .createInstance(Ci.nsILoginInfo); 
 
       login.init("sieve://"+this.host,null,"sieve://"+this.host, 
                  ""+this.getUsername(),""+input.value,"", "");
@@ -488,10 +467,10 @@ SieveSocks4Proxy.prototype.setPort
   port = parseInt(port,10);
   
   if (isNaN(port))
-    throw "Invalid Port Number";
+    throw "Invalid port number";
     
   if ((port < 0) || (port > 65535))
-    throw "Ivalid Port Number";
+    throw "Invalid port number";
 
   Services.prefs.setCharPref(this.prefURI+".port",""+port);
 }
@@ -500,8 +479,8 @@ SieveSocks4Proxy.prototype.getProxyInfo
     = function()
 {
   // generate proxy info
-  var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
-                .getService(Components.interfaces.nsIProtocolProxyService);
+  var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+                .getService(Ci.nsIProtocolProxyService);
   return [pps.newProxyInfo("socks4",this.getHost(),this.getPort(),0,4294967295,null)]
 }
 
@@ -542,8 +521,8 @@ SieveSocks5Proxy.prototype.getProxyInfo
     = function()
 {
   // generate proxy info
-  var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
-                .getService(Components.interfaces.nsIProtocolProxyService);
+  var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+                .getService(Ci.nsIProtocolProxyService);
                                 
   return [ pps.newProxyInfo("socks",this.getHost(),this.getPort(),(this.usesRemoteDNS()?(1<<0):0),4294967295,null)]; 
 }
@@ -652,8 +631,8 @@ SieveImapHost.prototype.getHostname
     = function ()
 {
   // use the IMAP Key to load the Account...
-  var account = Components.classes['@mozilla.org/messenger/account-manager;1']  
-                    .getService(Components.interfaces.nsIMsgAccountManager)
+  var account = Cc['@mozilla.org/messenger/account-manager;1']  
+                    .getService(Ci.nsIMsgAccountManager)
                     .getIncomingServer(this.imapKey);
 
   return account.realHostName;
@@ -896,14 +875,12 @@ SievePromptAuthorization.prototype.getAuthorization
 {
   var check = {value: false}; 
   var input = {value: ""};
-  var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                  .getService(Components.interfaces.nsIPromptService);
   
   var strings = Services.strings
     .createBundle("chrome://sieve/locale/locale.properties");
     
   var result = 
-    prompts.prompt(
+    Services.prompt.prompt(
        null, 
        strings.GetStringFromName("account.authorization.title"), 
        strings.GetStringFromName("account.authorization.description"), 
@@ -1202,7 +1179,10 @@ function SieveAccounts()
 }
 
 /**
- * Returns all SieveAccounts of the currently active Thunderbrid profile.  
+ * Returns a list containing a SieveAccounts configured for every compatible 
+ * nsIMsgIncommingServer in Thunderbird's AccountManager. Compatible accounts 
+ * are POP3 and IMAP.
+ *  
  * @return {SieveAccount[]}
  *   Array containing SieveAccounts
  */
@@ -1214,25 +1194,54 @@ SieveAccounts.prototype.getAccounts
   if (this.accounts)
     return this.accounts
     
-  var accountManager = Components.classes['@mozilla.org/messenger/account-manager;1']
-                           .getService(Components.interfaces.nsIMsgAccountManager);
+  var servers = Cc['@mozilla.org/messenger/account-manager;1']
+                    .getService(Ci.nsIMsgAccountManager)
+                    .allServers;
                           
   this.accounts = new Array();
   
-  for (var i = 0; i < accountManager.allServers.Count(); i++)
+  // The new account manager's interface introduced in TB 20.0a1 uses nsIArray...
+  if (servers instanceof Ci.nsIArray)
+  {    
+    var enumerator = servers.enumerate();
+    
+    while (enumerator.hasMoreElements())
+    {
+      var account = enumerator.getNext().QueryInterface(Ci.nsIMsgIncomingServer);
+            
+      if ((account.type != "imap") && (account.type != "pop3"))
+        continue;
+      
+      this.accounts.push(new SieveAccount(account));
+    }
+  }
+  
+  // ... while the old one relies upon Ci.nsISupportsArray ...
+  if (servers instanceof Ci.nsISupportsArray)
   {
-    var account = accountManager.allServers.GetElementAt(i)
-                    .QueryInterface(Components.interfaces.nsIMsgIncomingServer);
+    for (var i = 0; i < servers.Count(); i++)
+    {
+      var account = servers.GetElementAt(i).QueryInterface(Ci.nsIMsgIncomingServer);
           
-    if ((account.type != "imap") && (account.type != "pop3"))
-      continue;
+      if ((account.type != "imap") && (account.type != "pop3"))
+        continue;
 
-    this.accounts.push(new SieveAccount(account));        
+      this.accounts.push(new SieveAccount(account));        
+    }
   }
       
   return this.accounts;
 }
 
+/**
+ * Loads and returns a SieveAccount for the specified nsIMsgIncommingServer.
+ * 
+ * @param {nsIMsgIncommingServer} server
+ *   the incomming server for which the sieve account should be returend
+ * 
+ * @return {SieveAccount}
+ *   a SieveAccount for the incomming server
+ */
 SieveAccounts.prototype.getAccountByServer
     = function (server)
 {
@@ -1240,17 +1249,18 @@ SieveAccounts.prototype.getAccountByServer
 }
 
 /**
- * Loads a Sieve Account by its associated nsIMsgIncomingServer Account
+ * Loads and returns a Sieve Account by a nsIMsgIncomingServer's unique id
+ * 
  * @param {String} key
- *   The Unique Identifier of the associated nsIMsgIncomingServer Account
+ *   The unique identifier of the associated nsIMsgIncomingServer account
  * @return {SieveAccount}
- *   A corresponding SieveAccount
+ *   a SieveAccount for the unique id 
  */
 SieveAccounts.prototype.getAccountByName
     = function (key)
 {
-  var accountManager = Components.classes['@mozilla.org/messenger/account-manager;1']
-                         .getService(Components.interfaces.nsIMsgAccountManager);
+  var accountManager = Cc['@mozilla.org/messenger/account-manager;1']
+                         .getService(Ci.nsIMsgAccountManager);
 
   return new SieveAccount(accountManager.getIncomingServer(key));                       
 }
diff --git a/chrome/chromeFiles/content/modules/sieve/SieveAutoConfig.js b/chrome/chromeFiles/content/modules/sieve/SieveAutoConfig.js
index 58e8549..a6c91c9 100644
--- a/chrome/chromeFiles/content/modules/sieve/SieveAutoConfig.js
+++ b/chrome/chromeFiles/content/modules/sieve/SieveAutoConfig.js
@@ -34,7 +34,7 @@ SieveAutoConfig.prototype =
   addHost: function(host, port, proxy)
   {
     if (this.activeHosts > 0)
-      throw ("Auto config already running");
+      throw new Error("Auto config already running");
     
     this.hosts.push(new SieveAutoConfigHost(host,port,proxy,this));
   },
@@ -42,7 +42,7 @@ SieveAutoConfig.prototype =
   run: function(listener)
   { 
     if (this.activeHosts > 0)
-      throw ("Auto config already running");
+      throw new Error("Auto config already running");
     
     this.listener = listener;
     this.activeHosts = this.hosts.length;
@@ -65,7 +65,7 @@ SieveAutoConfig.prototype =
     this.activeHosts--;
     
     // the error listener is only invoked, when all tests failed... 
-    if (!this.activeHosts)
+    if (this.activeHosts > 0)
       return;
     
     this.cancel();
diff --git a/chrome/chromeFiles/content/modules/sieve/SieveRequest.js b/chrome/chromeFiles/content/modules/sieve/SieveRequest.js
index b51a3e6..04bc6d6 100644
--- a/chrome/chromeFiles/content/modules/sieve/SieveRequest.js
+++ b/chrome/chromeFiles/content/modules/sieve/SieveRequest.js
@@ -49,10 +49,11 @@ Cu.import("chrome://sieve/content/modules/sieve/SieveResponse.js");
 
 /**
  * Manage Sieve uses for literals UTF-8 as encoding, network sockets are usualy 
- * binary, and javascript is something inbetween. This means we have to convert
+ * binary, and javascript is something in between. This means we have to convert
  * UTF-8 into a binary by our own...
  * 
- * @param {String} string The binary string which should be converted 
+ * @param {String} str The binary string which should be converted
+ * @param @optional {String} charset The charset as string. Optional, defaults to "UTF-8". 
  * @return {String} The converted string in UTF8 
  * 
  * @author Thomas Schmid <schmid-thomas at gmx.net>
@@ -72,6 +73,21 @@ function JSStringToByteArray(str,charset)
   return converter.convertToByteArray(str, {});
 }
 
+
+/**
+ * Escapes a string. All Backslashes are converted to \\  while 
+ * all quotes are esacped as \"
+ *   
+ * @param {string} str
+ *   the string which should be escaped
+ * @return {string}
+ *   the escaped string.
+ */
+function escapeString(str)
+{
+  return str.replace(/\\/g,"\\\\").replace(/"/g,"\\\"");
+}
+
 //****************************************************************************//
 
 /**
@@ -228,7 +244,7 @@ SieveGetScriptRequest.prototype.addGetScriptListener
 SieveGetScriptRequest.prototype.getNextRequest
     = function ()
 {
-  return "GETSCRIPT \""+this.script+"\"\r\n";
+  return "GETSCRIPT \""+escapeString(this.script)+"\"\r\n";
 }
 
 SieveGetScriptRequest.prototype.onOk
@@ -256,7 +272,7 @@ SieveGetScriptRequest.prototype.addResponse
  */
 function SievePutScriptRequest(script, body) 
 {
-  this.script = script;
+  this.script = escapeString(script);
    
   // cleanup linebreaks...
   this.body = body.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g,"\r\n");
@@ -307,7 +323,8 @@ SievePutScriptRequest.prototype.getNextRequest
 //  converter.charset = "utf-8" ;
 //
   //return converter.ConvertFromUnicode(aStr);}
-  
+ 
+
   return "PUTSCRIPT \""+this.script+"\" {"+JSStringToByteArray(this.body).length+"+}\r\n"
         +this.body+"\r\n";
 }
@@ -421,7 +438,7 @@ function SieveSetActiveRequest(script)
   if (script == null)
     this.script = "";
   else
-    this.script = script;
+    this.script = escapeString(script);
 }
 
 // Inherrit prototypes from SieveAbstractRequest...
@@ -504,7 +521,7 @@ SieveCapabilitiesRequest.prototype.addResponse
  */
 function SieveDeleteScriptRequest(script) 
 {
-  this.script = script;
+  this.script = escapeString(script);
 }
 
 // Inherrit prototypes from SieveAbstractRequest...
@@ -599,8 +616,8 @@ SieveNoopRequest.prototype.addResponse
  */
 function SieveRenameScriptRequest(oldScript, newScript) 
 {
-  this.oldScript = oldScript;
-  this.newScript = newScript
+  this.oldScript = escapeString(oldScript);
+  this.newScript = escapeString(newScript);
 }
 
 // Inherrit prototypes from SieveAbstractRequest...
diff --git a/chrome/chromeFiles/content/modules/sieve/SieveResponseCodes.js b/chrome/chromeFiles/content/modules/sieve/SieveResponseCodes.js
index afc28c1..c09692e 100644
--- a/chrome/chromeFiles/content/modules/sieve/SieveResponseCodes.js
+++ b/chrome/chromeFiles/content/modules/sieve/SieveResponseCodes.js
@@ -1,6 +1,6 @@
 /* 
  * The contents of this file is licenced. You may obtain a copy of
- * the license at http://sieve.mozdev.org or request it via email 
+ * the license at https://github.com/thsmi/sieve/ or request it via email 
  * from the author. Do not remove or change this comment. 
  * 
  * The initial author of the code is:
diff --git a/chrome/chromeFiles/content/modules/sieve/SieveResponseParser.js b/chrome/chromeFiles/content/modules/sieve/SieveResponseParser.js
index 5c305d1..dbe3706 100644
--- a/chrome/chromeFiles/content/modules/sieve/SieveResponseParser.js
+++ b/chrome/chromeFiles/content/modules/sieve/SieveResponseParser.js
@@ -1,6 +1,6 @@
 /* 
  * The contents of this file is licenced. You may obtain a copy of
- * the license at http://sieve.mozdev.org or request it via email 
+ * the license at https://github.com/thsmi/sieve/ or request it via email 
  * from the author. Do not remove or change this comment. 
  * 
  * The initial author of the code is:
@@ -13,6 +13,18 @@
 // Expose to javascript modules
 var EXPORTED_SYMBOLS = [ "SieveResponseParser" ];
 
+// It's save to declare constants within a module...
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const CHAR_LF = 10
+const CHAR_CR = 13;
+const CHAR_SPACE = 32;
+const CHAR_QUOTE = 34;
+const CHAR_BACKSLASH = 92;
+const CHAR_LEFT_BRACES = 123;
+const CHAR_RIGHT_BRACES = 125;
+
 /**
  * The manage sieve protocol syntax uses a fixed gramar which is based on atomar tokens. 
  * This class offers an interface to test for and extract these predefined tokens. It supports 
@@ -40,7 +52,7 @@ function SieveResponseParser(data)
 }
 
 /**
- * Extracts the given number of bytes the buffer. 
+ * Extracts the given number of bytes from the buffer. 
  * 
  * @param {int} size
  *   The number of bytes as integer which should be extracted
@@ -65,10 +77,10 @@ SieveResponseParser.prototype.isLineBreak
     return false;
     
   // Test for a linebreak #13#10
-  if (this.data[this.pos] != 13)
+  if (this.data[this.pos] != CHAR_CR)
     return false;
     
-  if (this.data[this.pos+1] != 10)
+  if (this.data[this.pos+1] != CHAR_LF)
     return false;
 
   return true;
@@ -83,7 +95,7 @@ SieveResponseParser.prototype.extractLineBreak
     = function ()
 {
   if (this.isLineBreak() == false)
-    throw "Linebreak expected:\r\n"+this.getData();
+    throw "Linebreak expected but found:\n"+this.getData();
   
   this.pos += 2;
 }
@@ -96,7 +108,7 @@ SieveResponseParser.prototype.extractLineBreak
 SieveResponseParser.prototype.isSpace
     = function ()
 { 
-  if (this.data[this.pos] == 32)
+  if (this.data[this.pos] == CHAR_SPACE)
     return true;
     
   return false;
@@ -111,16 +123,16 @@ SieveResponseParser.prototype.extractSpace
     = function ()
 {
   if (this.isSpace() == false)
-    throw "Space expected in: "+this.getData();
+    throw "Space expected but found:\n"+this.getData();
     
   this.pos++;
 }
 
-//     literal               = "{" number  "+}" CRLF *OCTET
+// literal = "{" number  "+}" CRLF *OCTET
 SieveResponseParser.prototype.isLiteral
     = function ()
 {
-  if (this.data[this.pos] == 123)
+  if (this.data[this.pos] == CHAR_LEFT_BRACES)
     return true;
 
   return false;
@@ -132,7 +144,7 @@ SieveResponseParser.prototype.extractLiteral
     = function ()
 {
   if ( this.isLiteral() == false )
-    throw "Literal Expected in :\r\n"+this.getData();
+    throw "Literal Expected but found\n"+this.getData();
          
   // remove the "{"
   this.pos++;
@@ -140,17 +152,14 @@ SieveResponseParser.prototype.extractLiteral
   // some sieve implementations are broken, this means ....
   // ... we can get "{4+}\r\n1234" or "{4}\r\n1234"
   
-  var nextBracket = this.indexOf(125);
+  var nextBracket = this.indexOf(CHAR_RIGHT_BRACES);
   if (nextBracket == -1)
-    throw "Error unbalanced parathesis \"{\"";
+    throw "Error unbalanced parentheses \"{\" in\n"+parser.getData();
   
   // extract the size, and ignore "+"
   var size = parseInt(this.getData(this.pos, nextBracket).replace(/\+/,""),10);
     
-  this.pos = nextBracket+1;
-
-  if ( this.isLineBreak() == false)
-    throw "Linebreak Expected";        
+  this.pos = nextBracket+1;      
         
   this.extractLineBreak();
 
@@ -161,50 +170,105 @@ SieveResponseParser.prototype.extractLiteral
   return literal;
 }
 
+/**
+ * Searches the buffer for a character.
+ * 
+ * @param {byte} character
+ *   the chararcter which should be found
+ * @optional @param {int} offset
+ *   an absolut offset, from which to start seachring 
+ * @return {int} character
+ *   the characters absolute position within the buffer otherwise -1 if not found
+ */
 SieveResponseParser.prototype.indexOf
-    = function (character)
+    = function (character,offset)
 {     
-  for (var i=this.pos; i<this.data.length; i++)
-  {
+  if (typeof(offset) == "undefined")
+    offset = this.pos;
+
+  for (var i=offset; i<this.data.length; i++)
     if (this.data[i] == character)
       return i;
-  }
   
   return -1;
 }
 
+/**
+ * Test if the buffer starts with a quote character (#34)
+ * @return {Boolean}
+ *   true if buffer starts with a quote character, otherwise false
+ */
 SieveResponseParser.prototype.isQuoted
     = function ()
 { 
-  if (this.data[this.pos] == 34)
+  if (this.data[this.pos] == CHAR_QUOTE)
     return true;
 
   return false;
 }
 
+/**
+ * Extracts a quoted string form the buffer. It is aware of escape sequences.
+ * 
+ * If it does not start with a valid string an exception is thrown.
+ * 
+ * @return {string}
+ *   the quoted string extracted, it is garanteed to be free of escape sequences
+ */
 SieveResponseParser.prototype.extractQuoted
     = function ()
 {
   if (this.isQuoted() == false)
-    throw "Quoted expected";
+    throw "Quoted string expected but found \n"+this.getData();
+ 
+  // now search for the end. But we need to be aware of escape sequences.
+  var nextQuote = this.pos+1;
+ 
+  while (this.data[nextQuote] != CHAR_QUOTE)
+  {
+    
+    // Quoted stings can not contain linebreaks...
+    if (this.data[nextQuote] == CHAR_LF)
+      throw "Linebreak (LF) in Quoted String detected";
 
-  // remove the Quote "
-  this.pos++;
+    if (this.data[nextQuote] == CHAR_CR)
+      throw "Linebreak (CR) in Quoted String detected";
+      
+    // is it an escape sequence?
+    if (this.data[nextQuote] == CHAR_BACKSLASH)
+    {
+      // Yes, it's a backslash so get the next char...
+      nextQuote++;
+
+      // ... only \\ and \" are valid escape sequences
+      if ((this.data[nextQuote] != CHAR_BACKSLASH) && (this.data[nextQuote] != CHAR_QUOTE))
+        throw "Invalid Escape Sequence";    
+    }
+    
+    // move to the next character
+    nextQuote++
+    
+    if (this.nextQuote >= this.data.length)
+      throw "Unterminated Quoted string"; 
+  }
   
-  // save the position of the next "
-  var nextQuote = this.indexOf(34);
-
-  if (nextQuote == -1)
-    throw "Error unbalanced quotes";
-
-  var quoted = this.getData(this.pos,nextQuote);
-
+  var quoted = this.getData(this.pos+1,nextQuote);
+ 
   this.pos = nextQuote+1;
-
+   
+  // Cleanup escape sequences
+  quoted = quoted.replace('\\"','"',"g")
+  quoted = quoted.replace("\\\\","\\","g");  
+  
   return quoted;
 }
 
-
+/**
+ * Tests if the a quoted or literal string starts at the current position.
+ * 
+ * @return {Boolean}
+ *   true if a strings starts, otherwise false 
+ */
 SieveResponseParser.prototype.isString
     = function ()
 {
@@ -225,7 +289,7 @@ SieveResponseParser.prototype.extractString
   if ( this.isLiteral() )
     return this.extractLiteral();
         
-  throw "Message String expected";        
+  throw "String expected but found\n"+this.getData();        
 }
 
 /**
@@ -335,8 +399,8 @@ SieveResponseParser.prototype.getData
   if (arguments.length < 1)
     startIndex = this.pos;
     
-  var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
-                    .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+  var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                    .createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "UTF-8" ;
   
   var byteArray = this.data.slice(startIndex,endIndex);
@@ -359,4 +423,4 @@ SieveResponseParser.prototype.isEmpty
     return true;
     
   return false;
-}
\ No newline at end of file
+}
diff --git a/chrome/chromeFiles/content/modules/sieve/SieveSession.js b/chrome/chromeFiles/content/modules/sieve/SieveSession.js
index e7fd2e0..e60bd87 100644
--- a/chrome/chromeFiles/content/modules/sieve/SieveSession.js
+++ b/chrome/chromeFiles/content/modules/sieve/SieveSession.js
@@ -1,3 +1,15 @@
+/*
+ * The contents of this file are licenced. You may obtain a copy of 
+ * the license at https://github.com/thsmi/sieve/ or request it via 
+ * email from the author.
+ *
+ * Do not remove or change this comment.
+ * 
+ * The initial author of the code is:
+ *   Thomas Schmid <schmid-thomas at gmx.net>
+ *      
+ */
+
 // Enable Strict Mode
 "use strict";
 
@@ -27,15 +39,14 @@ Cu.import("chrome://sieve/content/modules/sieve/SieveResponseCodes.js");
  * "physical" link to the server. All channels share the session's link.
  * 
  * @param {SieveAccount} account
- *   an reference to a sieve account. this is needed to obtain login informations.
+ *   a reference to a sieve account. this is needed to obtain login informations.
  * @param @optional {Object} sid
- *   a unique Identifier for this Session. Only neede to make debugging easyer.
+ *   a unique Identifier for this Session. Only needed to make debugging easier.
  *   
  */
 function SieveSession(accountId,sid)
 {
  
-  
   this.idx = 0;
 
   // Load Account by ID
@@ -486,7 +497,7 @@ SieveSession.prototype =
     if (!this.sieve)
     {
       this.state = 0;
-      return
+      return true
     }
     
     // ... we always try to exit with an Logout request...      
@@ -497,7 +508,7 @@ SieveSession.prototype =
       /*request.addErrorListener(levent);*/
       this.sieve.addRequest(request);
       
-      return;
+      return false;
     }
     
     // ... but this obviously is not always usefull
@@ -509,7 +520,7 @@ SieveSession.prototype =
     // update state: we are disconnected
     this.state = 0;
       
-    return;
+    return true;
   },
 
   isConnecting : function()
diff --git a/chrome/chromeFiles/content/options/SieveAccountOptions.xul b/chrome/chromeFiles/content/options/SieveAccountOptions.xul
index 33e62af..91c4fbc 100644
--- a/chrome/chromeFiles/content/options/SieveAccountOptions.xul
+++ b/chrome/chromeFiles/content/options/SieveAccountOptions.xul
@@ -23,7 +23,8 @@
         ondialogaccept="return onDialogAccept();"
         persist="screenX screenY"
         style="width: 48em; height: 27em;"
-        title="&options.title;">
+        title="&options.title;"
+        id="SieveAccountOptions">
     
   <script type="application/javascript" src="chrome://sieve/content/options/SieveAccountOptions.js"/>
   
diff --git a/chrome/chromeFiles/locale/de-DE/locale.dtd b/chrome/chromeFiles/locale/de-DE/locale.dtd
index 3133af1..0d09b92 100644
--- a/chrome/chromeFiles/locale/de-DE/locale.dtd
+++ b/chrome/chromeFiles/locale/de-DE/locale.dtd
@@ -249,7 +249,7 @@ und konfiguriern Sie das Sieve Konto manuell.">
 <!ENTITY edit.toolbar.print "Drucken" >
 <!ENTITY edit.toolbar.search "Suchen">
 
-<!ENTITY edit.sidebar.uri "http://sieve.mozdev.org/reference/en/index.html">
+<!ENTITY edit.sidebar.uri "http://thsmi.github.com/sieve-reference/en/index.html">
 <!ENTITY edit.sidebar.title "Sieve Sprachreferenz">
 
 <!ENTITY edit.searchreplace.title "Suchen und ersetzen">
diff --git a/chrome/chromeFiles/locale/en-US/locale.dtd b/chrome/chromeFiles/locale/en-US/locale.dtd
index 9724927..0dcbc37 100644
--- a/chrome/chromeFiles/locale/en-US/locale.dtd
+++ b/chrome/chromeFiles/locale/en-US/locale.dtd
@@ -242,7 +242,7 @@ to do a manual configuration.">
 <!ENTITY edit.toolbar.print "Print">
 <!ENTITY edit.toolbar.search "Search">
 
-<!ENTITY edit.sidebar.uri "http://sieve.mozdev.org/reference/en/index.html">
+<!ENTITY edit.sidebar.uri "http://thsmi.github.com/sieve-reference/en/index.html">
 <!ENTITY edit.sidebar.title "Sieve Language Reference">
 
 <!-- Search and Replace Side bar -->
diff --git a/chrome/chromeFiles/locale/es-ES/locale.dtd b/chrome/chromeFiles/locale/es-ES/locale.dtd
index 7249226..74291f6 100644
--- a/chrome/chromeFiles/locale/es-ES/locale.dtd
+++ b/chrome/chromeFiles/locale/es-ES/locale.dtd
@@ -244,7 +244,7 @@ hacer la configuración manualmente.">
 <!ENTITY edit.toolbar.print "Imprimir">
 <!ENTITY edit.toolbar.search "Buscar">
 
-<!ENTITY edit.sidebar.uri "http://sieve.mozdev.org/reference/en/index.html">
+<!ENTITY edit.sidebar.uri "http://thsmi.github.com/sieve-reference/en/index.html">
 <!ENTITY edit.sidebar.title "Referencia de Lenguaje Sieve">
 
 <!ENTITY edit.searchreplace.title "Buscar y Reemplazar">
diff --git a/chrome/chromeFiles/locale/fr-FR/locale.dtd b/chrome/chromeFiles/locale/fr-FR/locale.dtd
index 7c6287d..6d3ea37 100644
--- a/chrome/chromeFiles/locale/fr-FR/locale.dtd
+++ b/chrome/chromeFiles/locale/fr-FR/locale.dtd
@@ -245,7 +245,7 @@ essayez une configuration manuelle.">
 <!ENTITY edit.toolbar.print "Imprimer">
 <!ENTITY edit.toolbar.search "Rechercher">
 
-<!ENTITY edit.sidebar.uri "http://sieve.mozdev.org/reference/en/index.html">
+<!ENTITY edit.sidebar.uri "http://thsmi.github.com/sieve-reference/en/index.html">
 <!ENTITY edit.sidebar.title "Base de référence du langage Sieve">
 
 <!ENTITY edit.searchreplace.title "Rechercher et Remplacer">
diff --git a/chrome/chromeFiles/locale/ru-RU/locale.dtd b/chrome/chromeFiles/locale/ru-RU/locale.dtd
index 85a5a1a..493df4d 100644
--- a/chrome/chromeFiles/locale/ru-RU/locale.dtd
+++ b/chrome/chromeFiles/locale/ru-RU/locale.dtd
@@ -244,7 +244,7 @@
 <!ENTITY edit.toolbar.print "Печать" >
 <!ENTITY edit.toolbar.search "Поиск">
 
-<!ENTITY edit.sidebar.uri "http://sieve.mozdev.org/reference/en/index.html">
+<!ENTITY edit.sidebar.uri "http://thsmi.github.com/sieve-reference/en/index.html">
 <!ENTITY edit.sidebar.title "Справочник по языку Sieve">
 
 <!ENTITY edit.searchreplace.title "Найти и заменить">
diff --git a/install.rdf b/install.rdf
index 2e7c11e..be4dd47 100644
--- a/install.rdf
+++ b/install.rdf
@@ -1,19 +1,29 @@
-<?xml version="1.0"?>
+<?xml version="1.0"?>
+<!--
+ 
+ The contents of this file is licenced. You may obtain a copy of
+ the license at https://github.com/thsmi/sieve/ or request it via email 
+ from the author. Do not remove or change this comment. 
+  
+ The initial author of the code is:
+   Thomas Schmid <schmid-thomas at gmx.net>
+
+-->
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>sieve at mozdev.org</em:id>
-    <em:version>0.2.2</em:version>
+    <em:version>0.2.3d</em:version>
     <em:type>2</em:type>
 
     <em:targetApplication>
       <!-- Thunderbird -->
       <Description>
         <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
-        <em:minVersion>10.0a1</em:minVersion>
-      	<em:maxVersion>18.0a1</em:maxVersion>  
+        <em:minVersion>10</em:minVersion>
+      	<em:maxVersion>30.0</em:maxVersion>  
       </Description>
     </em:targetApplication>
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/sieve-extension.git



More information about the Pkg-mozext-commits mailing list