[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_5_0BETA1-215-g5d00fdf

Michal Čihař nijel at users.sourceforge.net
Fri Jan 27 11:10:17 CET 2012


The branch, master has been updated
       via  5d00fdf3da118fbbffc78b4eeda5e9cd48bf2bf1 (commit)
       via  952a181f1fcf1fbcc0ff301111be4ce335f282d7 (commit)
       via  bda9be54eb9d3d7c6fe052903bcfa945c7f35cb8 (commit)
       via  ad29ef2de567d2c2a4f2ba155e93ee4a926b8a9a (commit)
       via  9b9d3e5cbc59def734181a4bd40a48205651d83b (commit)
      from  8e30c72834a64dfa9f984466abe0071208dc6bd9 (commit)


- Log -----------------------------------------------------------------
-----------------------------------------------------------------------

Summary of changes:
 js/codemirror/lib/codemirror.js         | 1525 ++++++++++++++++++++++---------
 js/functions.js                         |   16 +-
 themes/original/css/theme_right.css.php |   17 +-
 themes/pmahomme/css/theme_right.css.php |   15 +
 4 files changed, 1125 insertions(+), 448 deletions(-)

diff --git a/js/codemirror/lib/codemirror.js b/js/codemirror/lib/codemirror.js
index 27bec72..8e9a34e 100644
--- a/js/codemirror/lib/codemirror.js
+++ b/js/codemirror/lib/codemirror.js
@@ -1,3 +1,5 @@
+// CodeMirror v2.18
+
 // 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.
@@ -16,18 +18,19 @@ var CodeMirror = (function() {
     var targetDocument = options["document"];
     // The element in which the editor lives.
     var wrapper = targetDocument.createElement("div");
-    wrapper.className = "CodeMirror";
+    wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
     // This mess creates the base DOM structure for the editor.
     wrapper.innerHTML =
       '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
-        '<textarea style="position: absolute; width: 2px;" wrap="off"></textarea></div>' +
+        '<textarea style="position: absolute; width: 10000px;" wrap="off" ' +
+          'autocorrect="off" autocapitalize="off"></textarea></div>' +
       '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
         '<div style="position: relative">' + // Set to the height of the text, causes scrolling
-          '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
           '<div style="position: relative">' + // Moved around its parent to cover visible view
             '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
             // Provides positioning relative to (visible) text origin
             '<div class="CodeMirror-lines"><div style="position: relative">' +
+              '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden"></div>' +
               '<pre class="CodeMirror-cursor"> </pre>' + // Absolutely positioned blinky cursor
               '<div></div>' + // This DIV contains the actual code
             '</div></div></div></div></div>';
@@ -35,21 +38,29 @@ var CodeMirror = (function() {
     // I've never seen more elegant code in my life.
     var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
         scroller = wrapper.lastChild, code = scroller.firstChild,
-        measure = code.firstChild, mover = measure.nextSibling,
-        gutter = mover.firstChild, gutterText = gutter.firstChild,
-        lineSpace = gutter.nextSibling.firstChild,
-        cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
+        mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
+        lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
+        cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
+    if (!webkit) lineSpace.draggable = true;
     if (options.tabindex != null) input.tabindex = options.tabindex;
     if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
 
+    // Check for problem with IE innerHTML not working when we have a
+    // P (or similar) parent node.
+    try { stringWidth("x"); }
+    catch (e) {
+      if (e.message.match(/unknown runtime/i))
+        e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
+      throw e;
+    }
+
     // 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. lines an array of Line objects
-    // (see Line constructor), work an array of lines that should be
-    // parsed, and history the undo history (instance of History
-    // constructor).
-    var mode, lines = [new Line("")], work, history = new History(), focused;
+    // mode holds a mode API object. doc is the tree of Line objects,
+    // work an array of lines that should be parsed, and history the
+    // undo history (instance of History constructor).
+    var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
     loadMode();
     // The selection. These are always maintained to point at valid
     // positions. Inverted is used to remember that the user is
@@ -59,12 +70,12 @@ var CodeMirror = (function() {
     // whether the user is holding shift. reducedSelection is a hack
     // to get around the fact that we can't create inverted
     // selections. See below.
-    var shiftSelecting, reducedSelection, lastDoubleClick;
+    var shiftSelecting, reducedSelection, lastClick, lastDoubleClick, draggingText;
     // Variables used by startOperation/endOperation to track what
     // happened during the operation.
-    var updateInput, changes, textChanged, selectionChanged, leaveInputAlone;
+    var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
     // Current visible range (may be bigger than the view window).
-    var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
+    var displayOffset = 0, showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
     // editing will hold an object describing the things we put in the
     // textarea, to help figure out whether something changed.
     // bracketHighlighted is used to remember that a backet has been
@@ -76,17 +87,33 @@ var CodeMirror = (function() {
 
     // Initialize the content.
     operation(function(){setValue(options.value || ""); updateInput = false;})();
+    var history = new History();
+
+    var slowPollInterval = 2000;
+    // Gecko and Opera Linux do not reliably fire any event when starting an IME compose
+    var alwaysPollForIME = (!win && !mac) && (gecko || window.opera);
+    if (options.pollForIME && alwaysPollForIME) slowPollInterval = 50;
+    function keyMightStartIME(keyCode) {
+      return (win && ((gecko && keyCode == 229) || (window.opera && keyCode == 197))) || (mac && gecko);
+    }
 
     // Register our event handlers.
     connect(scroller, "mousedown", operation(onMouseDown));
+    connect(scroller, "dblclick", operation(onDoubleClick));
+    connect(lineSpace, "dragstart", onDragStart);
+    connect(lineSpace, "selectstart", e_preventDefault);
     // Gecko browsers fire contextmenu *after* opening the menu, at
     // which point we can't mess with it anymore. Context menu is
     // handled in onMouseDown for Gecko.
     if (!gecko) connect(scroller, "contextmenu", onContextMenu);
-    connect(code, "dblclick", operation(onDblClick));
-    connect(scroller, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);});
+    connect(scroller, "scroll", function() {
+      updateDisplay([]);
+      if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
+      if (options.onScroll) options.onScroll(instance);
+    });
     connect(window, "resize", function() {updateDisplay(true);});
     connect(input, "keyup", operation(onKeyUp));
+    connect(input, "input", function() {fastPoll(curKeyId);});
     connect(input, "keydown", operation(onKeyDown));
     connect(input, "keypress", operation(onKeyPress));
     connect(input, "focus", onFocus);
@@ -98,44 +125,51 @@ var CodeMirror = (function() {
     connect(scroller, "paste", function(){focusInput(); fastPoll();});
     connect(input, "paste", function(){fastPoll();});
     connect(input, "cut", function(){fastPoll();});
-    
-    // IE throws unspecified error in certain cases, when 
+
+    // IE throws unspecified error in certain cases, when
     // trying to access activeElement before onload
     var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
     if (hasFocus) setTimeout(onFocus, 20);
     else onBlur();
 
-    function isLine(l) {return l >= 0 && l < lines.length;}
+    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 = {
+    var instance = wrapper.CodeMirror = {
       getValue: getValue,
       setValue: operation(setValue),
       getSelection: getSelection,
       replaceSelection: operation(replaceSelection),
       focus: function(){focusInput(); onFocus(); fastPoll();},
       setOption: function(option, value) {
+        var oldVal = options[option];
         options[option] = value;
-        if (option == "lineNumbers" || option == "gutter") gutterChanged();
-        else if (option == "mode" || option == "indentUnit") loadMode();
+        if (option == "mode" || option == "indentUnit") loadMode();
         else if (option == "readOnly" && value == "nocursor") input.blur();
         else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
+        else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
+        else if (option == "pollForIME" && alwaysPollForIME) slowPollInterval = value ? 50 : 2000;
+        if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
+          operation(gutterChanged)();
       },
       getOption: function(option) {return options[option];},
       undo: operation(undo),
       redo: operation(redo),
-      indentLine: operation(function(n) {if (isLine(n)) indentLine(n, "smart");}),
+      indentLine: operation(function(n, dir) {
+        if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
+      }),
       historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
+      clearHistory: function() {history = new History();},
       matchBrackets: operation(function(){matchBrackets(true);}),
-      getTokenAt: function(pos) {
+      getTokenAt: operation(function(pos) {
         pos = clipPos(pos);
-        return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
-      },
+        return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
+      }),
       getStateAfter: function(line) {
-        line = clipLine(line == null ? lines.length - 1: line);
+        line = clipLine(line == null ? doc.size - 1: line);
         return getStateBefore(line + 1);
       },
       cursorCoords: function(start){
@@ -145,24 +179,25 @@ var CodeMirror = (function() {
       charCoords: function(pos){return pageCoords(clipPos(pos));},
       coordsChar: function(coords) {
         var off = eltOffset(lineSpace);
-        var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight())));
-        return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)});
+        return coordsChar(coords.x - off.left, coords.y - off.top);
       },
       getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
-      markText: operation(function(a, b, c){return operation(markText(a, b, c));}),
-      setMarker: addGutterMarker,
-      clearMarker: removeGutterMarker,
+      markText: operation(markText),
+      setBookmark: setBookmark,
+      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);}),
       lineInfo: lineInfo,
-      addWidget: function(pos, node, scroll, where) {
+      addWidget: function(pos, node, scroll, vert, horiz) {
         pos = localCoords(clipPos(pos));
         var top = pos.yBot, left = pos.x;
         node.style.position = "absolute";
         code.appendChild(node);
-        node.style.left = left + "px";
-        if (where == "over") top = pos.y;
-        else if (where == "near") {
-          var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()),
+        if (vert == "over") top = pos.y;
+        else if (vert == "near") {
+          var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
               hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
           if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
             top = pos.y - node.offsetHeight;
@@ -170,12 +205,20 @@ var CodeMirror = (function() {
             left = hspace - node.offsetWidth;
         }
         node.style.top = (top + paddingTop()) + "px";
-        node.style.left = (left + paddingLeft()) + "px";
+        node.style.left = node.style.right = "";
+        if (horiz == "right") {
+          left = code.clientWidth - node.offsetWidth;
+          node.style.right = "0px";
+        } else {
+          if (horiz == "left") left = 0;
+          else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
+          node.style.left = (left + paddingLeft()) + "px";
+        }
         if (scroll)
           scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
       },
 
-      lineCount: function() {return lines.length;},
+      lineCount: function() {return doc.size;},
       getCursor: function(start) {
         if (start == null) start = sel.inverted;
         return copyPos(start ? sel.from : sel.to);
@@ -186,9 +229,9 @@ var CodeMirror = (function() {
         else setCursor(line, ch);
       }),
       setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
-      getLine: function(line) {if (isLine(line)) return lines[line].text;},
+      getLine: function(line) {if (isLine(line)) return getLine(line).text;},
       setLine: operation(function(line, text) {
-        if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length});
+        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}));
@@ -196,24 +239,41 @@ var CodeMirror = (function() {
       replaceRange: operation(replaceRange),
       getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
 
+      coordsFromIndex: 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});
+      },
+
       operation: function(f){return operation(f)();},
       refresh: function(){updateDisplay(true);},
       getInputField: function(){return input;},
       getWrapperElement: function(){return wrapper;},
-      getScrollerElement: function(){return scroller;}
+      getScrollerElement: function(){return scroller;},
+      getGutterElement: function(){return gutter;}
     };
 
+    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 setValue(code) {
-      history = null;
       var top = {line: 0, ch: 0};
-      updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
+      updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
                   splitLines(code), top, top);
-      history = new History();
+      updateInput = true;
     }
     function getValue(code) {
       var text = [];
-      for (var i = 0, l = lines.length; i < l; ++i)
-        text.push(lines[i].text);
+      doc.iter(0, doc.size, function(line) { text.push(line.text); });
       return text.join("\n");
     }
 
@@ -221,17 +281,17 @@ var CodeMirror = (function() {
       // Check whether this is a click in a widget
       for (var n = e_target(e); n != wrapper; n = n.parentNode)
         if (n.parentNode == code && n != mover) return;
-      var ld = lastDoubleClick; lastDoubleClick = null;
-      // First, see if this is a click in the gutter
+
+      // 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);
+            options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
           return e_preventDefault(e);
         }
 
       var start = posFromMouse(e);
-      
+
       switch (e_button(e)) {
       case 3:
         if (gecko && !mac) onContextMenu(e);
@@ -246,18 +306,39 @@ var CodeMirror = (function() {
       if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
 
       if (!focused) onFocus();
-      e_preventDefault(e);
-      if (ld && +new Date - ld < 400) return selectLine(start.line);
 
-      setCursor(start.line, start.ch, true);
+      var now = +new Date;
+      if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
+        e_preventDefault(e);
+        setTimeout(focusInput, 20);
+        return selectLine(start.line);
+      } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
+        lastDoubleClick = {time: now, pos: start};
+        e_preventDefault(e);
+        return selectWordAt(start);
+      } else { lastClick = {time: now, pos: start}; }
+
       var last = start, going;
-      // And then we have to see if it's a drag event, in which case
-      // the dragged-over text must be selected.
-      function end() {
-        focusInput();
-        updateInput = true;
-        move(); up();
+      if (dragAndDrop && !posEq(sel.from, sel.to) &&
+          !posLess(start, sel.from) && !posLess(sel.to, start)) {
+        // Let the drag handler handle this.
+        if (webkit) lineSpace.draggable = true;
+        var up = connect(targetDocument, "mouseup", operation(function(e2) {
+          if (webkit) lineSpace.draggable = false;
+          draggingText = false;
+          up();
+          if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
+            e_preventDefault(e2);
+            setCursor(start.line, start.ch, true);
+            focusInput();
+          }
+        }), true);
+        draggingText = true;
+        return;
       }
+      e_preventDefault(e);
+      setCursor(start.line, start.ch, true);
+
       function extend(e) {
         var cur = posFromMouse(e, true);
         if (cur && !posEq(cur, last)) {
@@ -281,15 +362,19 @@ var CodeMirror = (function() {
         var cur = posFromMouse(e);
         if (cur) setSelectionUser(start, cur);
         e_preventDefault(e);
-        end();
+        focusInput();
+        updateInput = true;
+        move(); up();
       }), true);
     }
-    function onDblClick(e) {
-      var pos = posFromMouse(e);
-      if (!pos) return;
-      selectWordAt(pos);
+    function onDoubleClick(e) {
+      for (var n = e_target(e); n != wrapper; n = n.parentNode)
+        if (n.parentNode == gutterText) return e_preventDefault(e);
+      var start = posFromMouse(e);
+      if (!start) return;
+      lastDoubleClick = {time: +new Date, pos: start};
       e_preventDefault(e);
-      lastDoubleClick = +new Date;
+      selectWordAt(start);
     }
     function onDrop(e) {
       e.preventDefault();
@@ -300,7 +385,13 @@ var CodeMirror = (function() {
           var reader = new FileReader;
           reader.onload = function() {
             text[i] = reader.result;
-            if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos));
+            if (++read == n) {
+	      pos = clipPos(pos);
+	      operation(function() {
+                var end = replaceRange(text.join(""), pos, pos);
+                setSelectionUser(pos, end);
+              })();
+	    }
           };
           reader.readAsText(file);
         }
@@ -310,11 +401,24 @@ var CodeMirror = (function() {
       else {
         try {
           var text = e.dataTransfer.getData("Text");
-          if (text) replaceRange(text, pos, pos);
+          if (text) {
+	    var end = replaceRange(text, pos, pos);
+	    var curFrom = sel.from, curTo = sel.to;
+	    setSelectionUser(pos, end);
+            if (draggingText) replaceRange("", curFrom, curTo);
+	    focusInput();
+	  }
         }
         catch(e){}
       }
     }
+    function onDragStart(e) {
+      var txt = getSelection();
+      // This will reset escapeElement
+      htmlEscape(txt);
+      e.dataTransfer.setDragImage(escapeElement, 0, 0);
+      e.dataTransfer.setData("Text", txt);
+    }
     function onKeyDown(e) {
       if (!focused) onFocus();
 
@@ -340,6 +444,7 @@ var CodeMirror = (function() {
         if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z
         if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y
       }
+      if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } }
 
       // Key id to use in the movementKeys map. We also pass it to
       // fastPoll in order to 'self learn'. We need this because
@@ -347,15 +452,19 @@ var CodeMirror = (function() {
       // its start when it is inverted and a movement key is pressed
       // (and later restore it again), shouldn't be used for
       // non-movement keys.
-      curKeyId = (mod ? "c" : "") + code;
-      if (sel.inverted && movementKeys.hasOwnProperty(curKeyId)) {
+      curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code;
+      if (sel.inverted && movementKeys[curKeyId] === true) {
         var range = selRange(input);
         if (range) {
           reducedSelection = {anchor: range.start};
           setSelRange(input, range.start, range.start);
         }
       }
+      // Don't save the key as a movementkey unless it had a modifier
+      if (!mod && !e.altKey) curKeyId = null;
       fastPoll(curKeyId);
+
+      if (options.pollForIME && keyMightStartIME(code)) slowPollInterval = 50;
     }
     function onKeyUp(e) {
       if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
@@ -364,6 +473,8 @@ var CodeMirror = (function() {
         updateInput = true;
       }
       if (e.keyCode == 16) shiftSelecting = null;
+
+      if (slowPollInterval < 2000 && !alwaysPollForIME) slowPollInterval = 2000;
     }
     function onKeyPress(e) {
       if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
@@ -406,7 +517,7 @@ var CodeMirror = (function() {
     function updateLines(from, to, newText, selFrom, selTo) {
       if (history) {
         var old = [];
-        for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text);
+        doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
         history.addChange(from.line, newText.length, old);
         while (history.done.length > options.undoDepth) history.done.shift();
       }
@@ -416,11 +527,11 @@ var CodeMirror = (function() {
       var change = from.pop();
       if (change) {
         var replaced = [], end = change.start + change.added;
-        for (var i = change.start; i < end; ++i) replaced.push(lines[i].text);
+        doc.iter(change.start, end, function(line) { replaced.push(line.text); });
         to.push({start: change.start, added: change.old.length, old: replaced});
         var pos = clipPos({line: change.start + change.old.length - 1,
                            ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
-        updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
+        updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
         updateInput = true;
       }
     }
@@ -429,51 +540,66 @@ var CodeMirror = (function() {
 
     function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
       var recomputeMaxLength = false, maxLineLength = maxLine.length;
-      for (var i = from.line; i <= to.line; ++i) {
-        if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;}
-      }
+      if (!options.lineWrapping)
+        doc.iter(from.line, to.line, function(line) {
+          if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
+        });
 
-      var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
+      var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
       // First adjust the line structure, taking some care to leave highlighting intact.
       if (firstLine == lastLine) {
         if (newText.length == 1)
           firstLine.replace(from.ch, to.ch, newText[0]);
         else {
           lastLine = firstLine.split(to.ch, newText[newText.length-1]);
-          var spliceargs = [from.line + 1, nlines];
-          firstLine.replace(from.ch, firstLine.text.length, newText[0]);
-          for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
-          spliceargs.push(lastLine);
-          lines.splice.apply(lines, spliceargs);
+          firstLine.replace(from.ch, null, newText[0]);
+          firstLine.fixMarkEnds(lastLine);
+          var added = [];
+          for (var i = 1, e = newText.length - 1; i < e; ++i)
+            added.push(Line.inheritMarks(newText[i], firstLine));
+          added.push(lastLine);
+          doc.insert(from.line + 1, added);
         }
       }
       else if (newText.length == 1) {
-        firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
-        lines.splice(from.line + 1, nlines);
+        firstLine.replace(from.ch, null, newText[0]);
+        lastLine.replace(null, to.ch, "");
+        firstLine.append(lastLine);
+        doc.remove(from.line + 1, nlines);
       }
       else {
-        var spliceargs = [from.line + 1, nlines - 1];
-        firstLine.replace(from.ch, firstLine.text.length, newText[0]);
-        lastLine.replace(0, to.ch, newText[newText.length-1]);
-        for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
-        lines.splice.apply(lines, spliceargs);
-      }
-
-
-      for (var i = from.line, e = i + newText.length; i < e; ++i) {
-        var l = lines[i].text;
-        if (l.length > maxLineLength) {
-          maxLine = l; maxLineLength = l.length; maxWidth = null;
-          recomputeMaxLength = false;
-        }
-      }
-      if (recomputeMaxLength) {
-        maxLineLength = 0; maxLine = ""; maxWidth = null;
-        for (var i = 0, e = lines.length; i < e; ++i) {
-          var l = lines[i].text;
+        var added = [];
+        firstLine.replace(from.ch, null, newText[0]);
+        lastLine.replace(null, to.ch, newText[newText.length-1]);
+        firstLine.fixMarkEnds(lastLine);
+        for (var i = 1, e = newText.length - 1; i < e; ++i)
+          added.push(Line.inheritMarks(newText[i], firstLine));
+        if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
+        doc.insert(from.line + 1, added);
+      }
+      if (options.lineWrapping) {
+        var perLine = scroller.clientWidth / charWidth() - 3;
+        doc.iter(from.line, from.line + newText.length, function(line) {
+          if (line.hidden) return;
+          var guess = Math.ceil(line.text.length / perLine) || 1;
+          if (guess != line.height) updateLineHeight(line, guess);
+        });
+      } else {
+        doc.iter(from.line, i + newText.length, function(line) {
+          var l = line.text;
           if (l.length > maxLineLength) {
-            maxLineLength = l.length; maxLine = l;
+            maxLine = l; maxLineLength = l.length; maxWidth = null;
+            recomputeMaxLength = false;
           }
+        });
+        if (recomputeMaxLength) {
+          maxLineLength = 0; maxLine = ""; maxWidth = null;
+          doc.iter(0, doc.size, function(line) {
+            var l = line.text;
+            if (l.length > maxLineLength) {
+              maxLineLength = l.length; maxLine = l;
+            }
+          });
         }
       }
 
@@ -485,12 +611,9 @@ var CodeMirror = (function() {
         if (task < from.line) newWork.push(task);
         else if (task > to.line) newWork.push(task + lendiff);
       }
-      if (newText.length < 5) {
-        highlightLines(from.line, from.line + newText.length);
-        newWork.push(from.line + newText.length);
-      } else {
-        newWork.push(from.line);
-      }
+      var hlEnd = from.line + Math.min(newText.length, 500);
+      highlightLines(from.line, hlEnd);
+      newWork.push(hlEnd);
       work = newWork;
       startWorker(100);
       // Remember that these lines changed, for updating the display
@@ -502,7 +625,7 @@ var CodeMirror = (function() {
       setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
 
       // Make sure the scroll-size div has the correct height.
-      code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
+      code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
     }
 
     function replaceRange(code, from, to) {
@@ -540,10 +663,10 @@ var CodeMirror = (function() {
 
     function getRange(from, to) {
       var l1 = from.line, l2 = to.line;
-      if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch);
-      var code = [lines[l1].text.slice(from.ch)];
-      for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text);
-      code.push(lines[l2].text.slice(0, to.ch));
+      if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
+      var code = [getLine(l1).text.slice(from.ch)];
+      doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
+      code.push(getLine(l2).text.slice(0, to.ch));
       return code.join("\n");
     }
     function getSelection() {
@@ -553,7 +676,7 @@ var CodeMirror = (function() {
     var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
     function slowPoll() {
       if (pollingFast) return;
-      poll.set(2000, function() {
+      poll.set(slowPollInterval, function() {
         startOperation();
         readInput();
         if (focused) slowPoll();
@@ -566,7 +689,10 @@ var CodeMirror = (function() {
       function p() {
         startOperation();
         var changed = readInput();
-        if (changed == "moved" && keyId) movementKeys[keyId] = true;
+        if (changed && keyId) {
+          if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true;
+          if (changed == "changed") movementKeys[keyId] = false;
+        }
         if (!changed && !missed) {missed = true; poll.set(80, p);}
         else {pollingFast = false; slowPoll();}
         endOperation();
@@ -648,14 +774,16 @@ var CodeMirror = (function() {
     // editor state.
     function prepareInput() {
       var text = [];
-      var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2);
-      for (var i = from; i < to; ++i) text.push(lines[i].text);
+      var from = Math.max(0, sel.from.line - 1), to = Math.min(doc.size, sel.to.line + 2);
+      doc.iter(from, to, function(line) { text.push(line.text); });
       text = input.value = text.join(lineSep);
       var startch = sel.from.ch, endch = sel.to.ch;
-      for (var i = from; i < sel.from.line; ++i)
-        startch += lineSep.length + lines[i].text.length;
-      for (var i = from; i < sel.to.line; ++i)
-        endch += lineSep.length + lines[i].text.length;
+      doc.iter(from, sel.from.line, function(line) {
+        startch += lineSep.length + line.text.length;
+      });
+      doc.iter(from, sel.to.line, function(line) {
+        endch += lineSep.length + line.text.length;
+      });
       editing = {text: text, from: from, to: to, start: startch, end: endch};
       setSelRange(input, startch, reducedSelection ? startch : endch);
     }
@@ -663,21 +791,29 @@ var CodeMirror = (function() {
       if (options.readOnly != "nocursor") input.focus();
     }
 
+    function scrollEditorIntoView() {
+      if (!cursor.getBoundingClientRect) return;
+      var rect = cursor.getBoundingClientRect();
+      var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
+      if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
+    }
     function scrollCursorIntoView() {
       var cursor = localCoords(sel.inverted ? sel.from : sel.to);
-      return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
+      var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
+      return scrollIntoView(x, cursor.y, x, cursor.yBot);
     }
     function scrollIntoView(x1, y1, x2, y2) {
-      var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
+      var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();
       y1 += pt; y2 += pt; x1 += pl; x2 += pl;
       var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
       if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
       else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
 
       var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
-      if (x1 < screenleft) {
+      var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
+      if (x1 < screenleft + gutterw) {
         if (x1 < 50) x1 = 0;
-        scroller.scrollLeft = Math.max(0, x1 - 10);
+        scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
         scrolled = true;
       }
       else if (x2 > screenw + screenleft) {
@@ -690,32 +826,103 @@ var CodeMirror = (function() {
     }
 
     function visibleLines() {
-      var lh = lineHeight(), top = scroller.scrollTop - paddingTop();
-      return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
-              to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
+      var lh = textHeight(), top = scroller.scrollTop - paddingTop();
+      var from_height = Math.max(0, Math.floor(top / lh));
+      var to_height = Math.ceil((top + scroller.clientHeight) / lh);
+      return {from: lineAtHeight(doc, from_height),
+              to: lineAtHeight(doc, to_height)};
     }
     // 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) {
       if (!scroller.clientWidth) {
-        showingFrom = showingTo = 0;
+        showingFrom = showingTo = displayOffset = 0;
         return;
       }
-      // First create a range of theoretically intact lines, and punch
-      // holes in that using the change info.
-      var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}];
+      // Compute the new visible window
+      var visible = visibleLines();
+      // Bail out if the visible area is already rendered and nothing changed.
+      if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;
+      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);
+
+      // 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) return;
+      intact.sort(function(a, b) {return a.domStart - b.domStart;});
+
+      var th = textHeight(), gutterDisplay = gutter.style.display;
+      lineDiv.style.display = gutter.style.display = "none";
+      patchDisplay(from, to, intact);
+      lineDiv.style.display = "";
+
+      // Position the mover div to align with the lines it's supposed
+      // to be showing (which will cover the visible display)
+      var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
+      if (different) lastHeight = scroller.clientHeight;
+      showingFrom = from; showingTo = to;
+      displayOffset = heightAtLine(doc, from);
+      mover.style.top = (displayOffset * th) + "px";
+      code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
+
+      // 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);
+
+      if (options.lineWrapping) {
+        maxWidth = scroller.clientWidth;
+        var curNode = lineDiv.firstChild;
+        doc.iter(showingFrom, showingTo, function(line) {
+          if (!line.hidden) {
+            var height = Math.round(curNode.offsetHeight / th) || 1;
+            if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}
+          }
+          curNode = curNode.nextSibling;
+        });
+      } else {
+        if (maxWidth == null) maxWidth = stringWidth(maxLine);
+        if (maxWidth > scroller.clientWidth) {
+          lineSpace.style.width = maxWidth + "px";
+          // Needed to prevent odd wrapping/hiding of widgets placed in here.
+          code.style.width = "";
+          code.style.width = scroller.scrollWidth + "px";
+        } else {
+          lineSpace.style.width = code.style.width = "";
+        }
+      }
+      gutter.style.display = gutterDisplay;
+      if (different || gutterDirty) updateGutter();
+      updateCursor();
+    }
+
+    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)
-            intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart});
-          else if (range.to <= change.from)
+          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})
+              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)});
@@ -723,158 +930,90 @@ var CodeMirror = (function() {
         }
         intact = intact2;
       }
+      return intact;
+    }
 
-      // Then, determine which lines we'd want to see, and which
-      // updates have to be made to get there.
-      var visible = visibleLines();
-      var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)),
-          to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)),
-          updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0;
-
-      for (var i = 0, l = intact.length; i < l; ++i) {
-        var range = intact[i];
-        if (range.to <= from) continue;
-        if (range.from >= to) break;
-        if (range.domStart > domPos || range.from > pos) {
-          updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos});
-          changedLines += range.from - pos;
+    function patchDisplay(from, to, intact) {
+      // The first pass removes the DOM nodes that aren't intact.
+      if (!intact.length) lineDiv.innerHTML = "";
+      else {
+        function killNode(node) {
+          var tmp = node.nextSibling;
+          node.parentNode.removeChild(node);
+          return tmp;
         }
-        pos = range.to;
-        domPos = range.domStart + (range.to - range.from);
-      }
-      if (domPos != domEnd || pos != to) {
-        changedLines += Math.abs(to - pos);
-        updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
-      }
-
-      if (!updates.length) return;
-      lineDiv.style.display = "none";
-      // If more than 30% of the screen needs update, just do a full
-      // redraw (which is quicker than patching)
-      if (changedLines > (visible.to - visible.from) * .3)
-        refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length));
-      // Otherwise, only update the stuff that needs updating.
-      else
-        patchDisplay(updates);
-      lineDiv.style.display = "";
-
-      // Position the mover div to align with the lines it's supposed
-      // to be showing (which will cover the visible display)
-      var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
-      showingFrom = from; showingTo = to;
-      mover.style.top = (from * lineHeight()) + "px";
-      if (different) {
-        lastHeight = scroller.clientHeight;
-        code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
-        updateGutter();
-      }
-
-      if (maxWidth == null) maxWidth = stringWidth(maxLine);
-      if (maxWidth > scroller.clientWidth) {
-        lineSpace.style.width = maxWidth + "px";
-        // Needed to prevent odd wrapping/hiding of widgets placed in here.
-        code.style.width = "";
-        code.style.width = scroller.scrollWidth + "px";
-      } else {
-        lineSpace.style.width = code.style.width = "";
+        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);
       }
-
-      // 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(updates) + " size=" + (showingTo - showingFrom) +
-                        " nodes=" + lineDiv.childNodes.length);
-      updateCursor();
-    }
-
-    function refreshDisplay(from, to) {
-      var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start);
-      for (var i = from; i < to; ++i) {
+      // This pass fills in the lines that actually changed.
+      var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
+      var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;
+      var scratch = targetDocument.createElement("div"), newElt;
+      doc.iter(from, to, function(line) {
         var ch1 = null, ch2 = null;
         if (inSel) {
           ch1 = 0;
-          if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;}
-        }
-        else if (sel.from.line == i) {
-          if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
+          if (sto == j) {inSel = false; ch2 = sel.to.ch;}
+        } else if (sfrom == j) {
+          if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
           else {inSel = true; ch1 = sel.from.ch;}
         }
-        html.push(lines[i].getHTML(ch1, ch2, true));
-      }
-      lineDiv.innerHTML = html.join("");
-    }
-    function patchDisplay(updates) {
-      // Slightly different algorithm for IE (badInnerHTML), since
-      // there .innerHTML on PRE nodes is dumb, and discards
-      // whitespace.
-      var sfrom = sel.from.line, sto = sel.to.line, off = 0,
-          scratch = badInnerHTML && targetDocument.createElement("div");
-      for (var i = 0, e = updates.length; i < e; ++i) {
-        var rec = updates[i];
-        var extra = (rec.to - rec.from) - rec.domSize;
-        var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null;
-        if (badInnerHTML)
-          for (var j = Math.max(-extra, rec.domSize); j > 0; --j)
-            lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
-        else if (extra) {
-          for (var j = Math.max(0, extra); j > 0; --j)
-            lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
-          for (var j = Math.max(0, -extra); j > 0; --j)
-            lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
-        }
-        var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from;
-        for (var j = rec.from; j < rec.to; ++j) {
-          var ch1 = null, ch2 = null;
-          if (inSel) {
-            ch1 = 0;
-            if (sto == j) {inSel = false; ch2 = sel.to.ch;}
-          }
-          else if (sfrom == j) {
-            if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
-            else {inSel = true; ch1 = sel.from.ch;}
-          }
-          if (badInnerHTML) {
-            scratch.innerHTML = lines[j].getHTML(ch1, ch2, true);
-            lineDiv.insertBefore(scratch.firstChild, nodeAfter);
-          }
-          else {
-            node.innerHTML = lines[j].getHTML(ch1, ch2, false);
-            node.className = lines[j].className || "";
-            node = node.nextSibling;
-          }
+        if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
+        if (!nextIntact || nextIntact.from > j) {
+          if (line.hidden) scratch.innerHTML = "<pre></pre>";
+          else scratch.innerHTML = line.getHTML(ch1, ch2, true);
+          lineDiv.insertBefore(scratch.firstChild, curNode);
+        } else {
+          curNode = curNode.nextSibling;
         }
-        off += extra;
-      }
+        ++j;
+      });
     }
 
     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 html = [];
-      for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) {
-        var marker = lines[i].gutterMarker;
-        var text = options.lineNumbers ? i + options.firstLineNumber : null;
-        if (marker && marker.text)
-          text = marker.text.replace("%N%", text != null ? text : "");
-        else if (text == null)
-          text = "\u00a0";
-        html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text, "</pre>");
-      }
+      var html = [], i = showingFrom;
+      doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
+        if (line.hidden) {
+          html.push("<pre></pre>");
+        } else {
+          var marker = line.gutterMarker;
+          var text = options.lineNumbers ? i + options.firstLineNumber : null;
+          if (marker && marker.text)
+            text = marker.text.replace("%N%", text != null ? text : "");
+          else if (text == null)
+            text = "\u00a0";
+          html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
+          for (var j = 1; j < line.height; ++j) html.push("<br> ");
+          html.push("</pre>");
+        }
+        ++i;
+      });
       gutter.style.display = "none";
       gutterText.innerHTML = html.join("");
-      var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
+      var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
       while (val.length + pad.length < minwidth) pad += "\u00a0";
       if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
       gutter.style.display = "";
       lineSpace.style.marginLeft = gutter.offsetWidth + "px";
+      gutterDirty = false;
     }
     function updateCursor() {
-      var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
-      var x = charX(head.line, head.ch) + "px", y = (head.line - showingFrom) * lh + "px";
-      inputDiv.style.top = (head.line * lh - scroller.scrollTop) + "px";
+      var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
+      var pos = localCoords(head, true);
+      var globalY = pos.y + displayOffset * textHeight();
+      inputDiv.style.top = Math.max(Math.min(globalY, scroller.offsetHeight), 0) + "px";
+      inputDiv.style.left = (pos.x - scroller.scrollLeft) + "px";
       if (posEq(sel.from, sel.to)) {
-        cursor.style.top = y; cursor.style.left = x;
+        cursor.style.top = pos.y + "px";
+        cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
         cursor.style.display = "";
       }
       else cursor.style.display = "none";
@@ -892,9 +1031,14 @@ var CodeMirror = (function() {
     // updateLines, since they have to be expressed in the line
     // numbers before the update.
     function setSelection(from, to, oldFrom, oldTo) {
+      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) from = skipHidden(from, oldFrom, sel.from.ch);
+      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;
@@ -902,7 +1046,6 @@ var CodeMirror = (function() {
       // Some ugly logic used to only mark the lines that actually did
       // see a change in selection as changed, rather than the whole
       // selected range.
-      if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
       if (posEq(from, to)) {
         if (!posEq(sel.from, sel.to))
           changes.push({from: oldFrom, to: oldTo + 1});
@@ -927,42 +1070,61 @@ var CodeMirror = (function() {
       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 (ch > oldCh || ch > line.text.length) ch = line.text.length;
+            return {line: lNo, ch: ch};
+          }
+          lNo += dir;
+        }
+      }
+      var line = getLine(pos.line);
+      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, lines.length-1));}
+    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 >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length};
-      var ch = pos.ch, linelen = lines[pos.line].text.length;
+      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 scrollPage(down) {
-      var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
-      setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true);
+      var linesPerPage = Math.floor(scroller.clientHeight / textHeight()), head = sel.inverted ? sel.from : sel.to;
+      var target = heightAtLine(doc, head.line) + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1));
+      setCursor(lineAtHeight(doc, target), head.ch, true);
     }
     function scrollEnd(top) {
-      var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
+      var pos = top ? {line: 0, ch: 0} : {line: doc.size - 1, ch: getLine(doc.size-1).text.length};
       setSelectionUser(pos, pos);
     }
     function selectAll() {
-      var endLine = lines.length - 1;
-      setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length});
+      var endLine = doc.size - 1;
+      setSelection({line: 0, ch: 0}, {line: endLine, ch: getLine(endLine).text.length});
     }
     function selectWordAt(pos) {
-      var line = lines[pos.line].text;
+      var line = getLine(pos.line).text;
       var start = pos.ch, end = pos.ch;
       while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
       while (end < line.length && /\w/.test(line.charAt(end))) ++end;
       setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
     }
     function selectLine(line) {
-      setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length});
+      setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
     }
     function handleEnter() {
       replaceSelection("\n", "end");
@@ -994,6 +1156,10 @@ var CodeMirror = (function() {
       }
       return true;
     }
+    function smartHome() {
+      var firstNonWS = Math.max(0, getLine(sel.from.line).text.search(/\S/));
+      setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
+    }
 
     function indentLine(n, how) {
       if (how == "smart") {
@@ -1001,9 +1167,9 @@ var CodeMirror = (function() {
         else var state = getStateBefore(n);
       }
 
-      var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
+      var line = getLine(n), curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
       if (how == "prev") {
-        if (n) indentation = lines[n-1].indentation();
+        if (n) indentation = getLine(n-1).indentation();
         else indentation = 0;
       }
       else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
@@ -1028,87 +1194,148 @@ var CodeMirror = (function() {
 
     function loadMode() {
       mode = CodeMirror.getMode(options, options.mode);
-      for (var i = 0, l = lines.length; i < l; ++i)
-        lines[i].stateAfter = null;
+      doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
       work = [0];
       startWorker();
     }
     function gutterChanged() {
       var visible = options.gutter || options.lineNumbers;
       gutter.style.display = visible ? "" : "none";
-      if (visible) updateGutter();
+      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.width = code.style.width = "";
+      } else {
+        wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
+        maxWidth = null; maxLine = "";
+        doc.iter(0, doc.size, function(line) {
+          if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
+          if (line.text.length > maxLine.length) maxLine = line.text;
+        });
+      }
+      changes.push({from: 0, to: doc.size});
+    }
+
+    function TextMarker() { this.set = []; }
+    TextMarker.prototype.clear = operation(function() {
+      for (var i = 0, e = this.set.length; i < e; ++i) {
+        var mk = this.set[i].marked;
+        if (!mk) continue;
+        for (var j = 0; j < mk.length; ++j)
+          if (mk[j].set == this.set) mk.splice(j--, 1);
+      }
+      // We don't know the exact lines that changed. Refreshing is
+      // cheaper than finding them.
+      changes.push({from: 0, to: doc.size});
+    });
+    TextMarker.prototype.find = function() {
+      var from, to;
+      for (var i = 0, e = this.set.length; i < e; ++i) {
+        var line = this.set[i], mk = line.marked;
+        for (var j = 0; j < mk.length; ++j) {
+          var mark = mk[j];
+          if (mark.set == this.set) {
+            if (mark.from != null || mark.to != null) {
+              var found = lineNo(line);
+              if (found != null) {
+                if (mark.from != null) from = {line: found, ch: mark.from};
+                if (mark.to != null) to = {line: found, ch: mark.to};
+              }
+            }
+          }
+        }
+      }
+      return {from: from, to: to};
+    };
 
     function markText(from, to, className) {
       from = clipPos(from); to = clipPos(to);
-      var accum = [];
+      var tm = new TextMarker();
       function add(line, from, to, className) {
-        var line = lines[line], mark = line.addMark(from, to, className);
-        mark.line = line;
-        accum.push(mark);
+        mark = getLine(line).addMark(new MarkedText(from, to, className, tm.set));
       }
       if (from.line == to.line) add(from.line, from.ch, to.ch, className);
       else {
         add(from.line, from.ch, null, className);
         for (var i = from.line + 1, e = to.line; i < e; ++i)
-          add(i, 0, null, className);
-        add(to.line, 0, to.ch, className);
+          add(i, null, null, className);
+        add(to.line, null, to.ch, className);
       }
       changes.push({from: from.line, to: to.line + 1});
-      return function() {
-        var start, end;
-        for (var i = 0; i < accum.length; ++i) {
-          var mark = accum[i], found = indexOf(lines, mark.line);
-          mark.line.removeMark(mark);
-          if (found > -1) {
-            if (start == null) start = found;
-            end = found;
-          }
-        }
-        if (start != null) changes.push({from: start, to: end + 1});
-      };
+      return tm;
+    }
+
+    function setBookmark(pos) {
+      pos = clipPos(pos);
+      var bm = new Bookmark(pos.ch);
+      getLine(pos.line).addMark(bm);
+      return bm;
     }
 
     function addGutterMarker(line, text, className) {
-      if (typeof line == "number") line = lines[clipLine(line)];
+      if (typeof line == "number") line = getLine(clipLine(line));
       line.gutterMarker = {text: text, style: className};
-      updateGutter();
+      gutterDirty = true;
       return line;
     }
     function removeGutterMarker(line) {
-      if (typeof line == "number") line = lines[clipLine(line)];
+      if (typeof line == "number") line = getLine(clipLine(line));
       line.gutterMarker = null;
-      updateGutter();
+      gutterDirty = true;
     }
-    function setLineClass(line, className) {
-      if (typeof line == "number") {
-        var no = line;
-        line = lines[clipLine(line)];
-      }
-      else {
-        var no = indexOf(lines, line);
-        if (no == -1) return null;
-      }
-      if (line.className != className) {
-        line.className = className;
-        changes.push({from: no, to: no + 1});
-      }
+
+    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});
       return line;
     }
+    function setLineClass(handle, className) {
+      return changeLine(handle, function(line) {
+        if (line.className != className) {
+          line.className = className;
+          return true;
+        }
+      });
+    }
+    function setLineHidden(handle, hidden) {
+      return changeLine(handle, function(line, no) {
+        if (line.hidden != hidden) {
+          line.hidden = hidden;
+          updateLineHeight(line, hidden ? 0 : 1);
+          if (hidden && (sel.from.line == no || sel.to.line == no))
+            setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),
+                         skipHidden(sel.to, sel.to.line, sel.to.ch));
+          return (gutterDirty = true);
+        }
+      });
+    }
 
     function lineInfo(line) {
       if (typeof line == "number") {
+        if (!isLine(line)) return null;
         var n = line;
-        line = lines[line];
+        line = getLine(line);
         if (!line) return null;
       }
       else {
-        var n = indexOf(lines, line);
-        if (n == -1) return null;
+        var n = lineNo(line);
+        if (n == null) return null;
       }
       var marker = line.gutterMarker;
-      return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
+      return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
+              markerClass: marker && marker.style, lineClass: line.className};
     }
 
     function stringWidth(str) {
@@ -1118,21 +1345,16 @@ var CodeMirror = (function() {
     }
     // These are used to go from pixel positions to character
     // positions, taking varying character widths into account.
-    function charX(line, pos) {
-      if (pos == 0) return 0;
-      measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>";
-      return measure.firstChild.firstChild.offsetWidth;
-    }
     function charFromX(line, x) {
       if (x <= 0) return 0;
-      var lineObj = lines[line], text = lineObj.text;
+      var lineObj = getLine(line), text = lineObj.text;
       function getX(len) {
         measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
         return measure.firstChild.firstChild.offsetWidth;
       }
       var from = 0, fromX = 0, to = text.length, toX;
       // Guess a suitable upper bound for our search.
-      var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
+      var estimated = Math.min(to, Math.ceil(x / charWidth()));
       for (;;) {
         var estX = getX(estimated);
         if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
@@ -1151,20 +1373,93 @@ var CodeMirror = (function() {
       }
     }
 
+    var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
+    function measureLine(line, ch) {
+      var extra = "";
+      // Include extra text at the end to make sure the measured line is wrapped in the right way.
+      if (options.lineWrapping) {
+        var end = line.text.indexOf(" ", ch + 2);
+        extra = line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0));
+      }
+      measure.innerHTML = "<pre>" + line.getHTML(null, null, false, ch) +
+        '<span id="CodeMirror-temp-' + tempId + '">' + (line.text.charAt(ch) || " ") + "</span>" +
+        extra + "</pre>";
+      var elt = document.getElementById("CodeMirror-temp-" + tempId);
+      var top = elt.offsetTop, left = elt.offsetLeft;
+      // Older IEs report zero offsets for spans directly after a wrap
+      if (ie && ch && top == 0 && left == 0) {
+        var backup = document.createElement("span");
+        backup.innerHTML = "x";
+        elt.parentNode.insertBefore(backup, elt.nextSibling);
+        top = backup.offsetTop;
+      }
+      return {top: top, left: left};
+    }
     function localCoords(pos, inLineWrap) {
-      var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0);
-      return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh};
+      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) {
+      if (y < 0) y = 0;
+      var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
+      var lineNo = lineAtHeight(doc, heightPos);
+      if (lineNo >= doc.size) return {line: doc.size - 1, ch: 0};
+      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};
+      function getX(len) {
+        var sp = measureLine(lineObj, len);
+        if (tw) {
+          var off = Math.round(sp.top / th);
+          return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
+        }
+        return sp.left;
+      }
+      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) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
+        var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
+        if (middleX > x) {to = middle; toX = middleX;}
+        else {from = middle; fromX = middleX;}
+      }
     }
     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};
     }
 
-    function lineHeight() {
-      var nlines = lineDiv.childNodes.length;
-      if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
-      measure.innerHTML = "<pre>x</pre>";
-      return measure.firstChild.offsetHeight || 1;
+    var cachedHeight, cachedFor;
+    function textHeight() {
+      var offsetHeight = lineDiv.offsetHeight;
+      if (offsetHeight == cachedFor) return cachedHeight;
+      cachedFor = offsetHeight;
+      measure.innerHTML = "<pre>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x</pre>";
+      return (cachedHeight = measure.firstChild.offsetHeight / 10 || 1);
+    }
+    var cachedWidth, cachedFor = 0;
+    function charWidth() {
+      if (scroller.clientWidth == cachedFor) return cachedWidth;
+      cachedFor = scroller.clientWidth;
+      return (cachedWidth = stringWidth("x"));
     }
     function paddingTop() {return lineSpace.offsetTop;}
     function paddingLeft() {return lineSpace.offsetLeft;}
@@ -1179,8 +1474,7 @@ var CodeMirror = (function() {
       if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
         return null;
       var offL = eltOffset(lineSpace, true);
-      var line = showingFrom + Math.floor((y - offL.top) / lineHeight());
-      return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)});
+      return coordsChar(x - offL.left, y - offL.top);
     }
     function onContextMenu(e) {
       var pos = posFromMouse(e);
@@ -1190,8 +1484,8 @@ var CodeMirror = (function() {
 
       var oldCSS = input.style.cssText;
       inputDiv.style.position = "absolute";
-      input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e_pageY(e) - 1) +
-        "px; left: " + (e_pageX(e) - 1) + "px; z-index: 1000; background: white; " +
+      input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
+        "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
         "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
       leaveInputAlone = true;
       var val = input.value = getSelection();
@@ -1206,7 +1500,7 @@ var CodeMirror = (function() {
         prepareInput();
         slowPoll();
       }
-      
+
       if (gecko) {
         e_stop(e);
         var mouseup = connect(window, "mouseup", function() {
@@ -1231,7 +1525,7 @@ var CodeMirror = (function() {
 
     var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
     function matchBrackets(autoclear) {
-      var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1;
+      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;
@@ -1255,18 +1549,16 @@ var CodeMirror = (function() {
           }
         }
       }
-      for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) {
-        var line = lines[i], first = i == head.line;
+      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)
-            : function() {};
-      var clear = operation(function(){one(); two();});
+          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;
     }
@@ -1280,66 +1572,69 @@ var CodeMirror = (function() {
       var minindent, minline;
       for (var search = n, lim = n - 40; search > lim; --search) {
         if (search == 0) return 0;
-        var line = lines[search-1];
+        var line = getLine(search-1);
         if (line.stateAfter) return search;
         var indented = line.indentation();
         if (minline == null || minindent > indented) {
-          minline = search;
+          minline = search - 1;
           minindent = indented;
         }
       }
       return minline;
     }
     function getStateBefore(n) {
-      var start = findStartLine(n), state = start && lines[start-1].stateAfter;
+      var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
       if (!state) state = startState(mode);
       else state = copyState(mode, state);
-      for (var i = start; i < n; ++i) {
-        var line = lines[i];
+      doc.iter(start, n, function(line) {
         line.highlight(mode, state);
         line.stateAfter = copyState(mode, state);
-      }
-      if (n < lines.length && !lines[n].stateAfter) work.push(n);
+      });
+      if (start < n) changes.push({from: start, to: n});
+      if (n < doc.size && !getLine(n).stateAfter) work.push(n);
       return state;
     }
     function highlightLines(start, end) {
       var state = getStateBefore(start);
-      for (var i = start; i < end; ++i) {
-        var line = lines[i];
+      doc.iter(start, end, function(line) {
         line.highlight(mode, state);
         line.stateAfter = copyState(mode, state);
-      }
+      });
     }
     function highlightWorker() {
       var end = +new Date + options.workTime;
       var foundWork = work.length;
       while (work.length) {
-        if (!lines[showingFrom].stateAfter) var task = showingFrom;
+        if (!getLine(showingFrom).stateAfter) var task = showingFrom;
         else var task = work.pop();
-        if (task >= lines.length) continue;
-        var start = findStartLine(task), state = start && lines[start-1].stateAfter;
+        if (task >= doc.size) continue;
+        var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
         if (state) state = copyState(mode, state);
         else state = startState(mode);
 
-        var unchanged = 0, compare = mode.compareStates;
-        for (var i = start, l = lines.length; i < l; ++i) {
-          var line = lines[i], hadState = line.stateAfter;
+        var unchanged = 0, compare = mode.compareStates, realChange = false,
+            i = start, bail = false;
+        doc.iter(i, doc.size, function(line) {
+          var hadState = line.stateAfter;
           if (+new Date > end) {
             work.push(i);
             startWorker(options.workDelay);
-            changes.push({from: task, to: i + 1});
-            return;
+            if (realChange) changes.push({from: task, to: i + 1});
+            return (bail = true);
           }
           var changed = line.highlight(mode, state);
+          if (changed) realChange = true;
           line.stateAfter = copyState(mode, state);
           if (compare) {
-            if (hadState && compare(hadState, state)) break;
+            if (hadState && compare(hadState, state)) return true;
           } else {
-            if (changed || !hadState) unchanged = 0;
-            else if (++unchanged > 3) break;
+            if (changed !== false || !hadState) unchanged = 0;
+            else if (++unchanged > 3) return true;
           }
-        }
-        changes.push({from: task, to: i + 1});
+          ++i;
+        });
+        if (bail) return;
+        if (realChange) changes.push({from: task, to: i + 1});
       }
       if (foundWork && options.onHighlightComplete)
         options.onHighlightComplete(instance);
@@ -1360,9 +1655,12 @@ var CodeMirror = (function() {
       var reScroll = false;
       if (selectionChanged) reScroll = !scrollCursorIntoView();
       if (changes.length) updateDisplay(changes);
-      else if (selectionChanged) updateCursor();
+      else {
+        if (selectionChanged) updateCursor();
+        if (gutterDirty) updateGutter();
+      }
       if (reScroll) scrollCursorIntoView();
-      if (selectionChanged) restartBlink();
+      if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
 
       // updateInput can be set to a boolean value to force/prevent an
       // update.
@@ -1406,7 +1704,7 @@ var CodeMirror = (function() {
       if (typeof query != "string") // Regexp match
         this.matches = function(reverse, pos) {
           if (reverse) {
-            var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0;
+            var line = getLine(pos.line).text.slice(0, pos.ch), match = line.match(query), start = 0;
             while (match) {
               var ind = line.indexOf(match[0]);
               start += ind;
@@ -1418,7 +1716,7 @@ var CodeMirror = (function() {
             }
           }
           else {
-            var line = lines[pos.line].text.slice(pos.ch), match = line.match(query),
+            var line = getLine(pos.line).text.slice(pos.ch), match = line.match(query),
                 start = match && pos.ch + line.indexOf(match[0]);
           }
           if (match)
@@ -1433,7 +1731,7 @@ var CodeMirror = (function() {
         // Different methods for single-line and multi-line queries
         if (target.length == 1)
           this.matches = function(reverse, pos) {
-            var line = fold(lines[pos.line].text), len = query.length, match;
+            var line = fold(getLine(pos.line).text), 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},
@@ -1441,14 +1739,14 @@ var CodeMirror = (function() {
           };
         else
           this.matches = function(reverse, pos) {
-            var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text);
+            var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(getLine(ln).text);
             var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
             if (reverse ? offsetA >= pos.ch || offsetA != match.length
                         : offsetA <= pos.ch || offsetA != line.length - match.length)
               return;
             for (;;) {
-              if (reverse ? !ln : ln == lines.length - 1) return;
-              line = fold(lines[ln += reverse ? -1 : 1].text);
+              if (reverse ? !ln : ln == doc.size - 1) return;
+              line = fold(getLine(ln += reverse ? -1 : 1).text);
               match = target[reverse ? --idx : ++idx];
               if (idx > 0 && idx < target.length - 1) {
                 if (line != match) return;
@@ -1484,10 +1782,10 @@ var CodeMirror = (function() {
           }
           if (reverse) {
             if (!pos.line) return savePosAndFail(0);
-            pos = {line: pos.line-1, ch: lines[pos.line-1].text.length};
+            pos = {line: pos.line-1, ch: getLine(pos.line-1).text.length};
           }
           else {
-            if (pos.line == lines.length - 1) return savePosAndFail(lines.length);
+            if (pos.line == doc.size - 1) return savePosAndFail(doc.size);
             pos = {line: pos.line+1, ch: 0};
           }
         }
@@ -1523,10 +1821,13 @@ var CodeMirror = (function() {
     enterMode: "indent",
     electricChars: true,
     onKeyEvent: null,
+    lineWrapping: false,
     lineNumbers: false,
     gutter: false,
+    fixedGutter: false,
     firstLineNumber: 1,
     readOnly: false,
+    smartHome: true,
     onChange: null,
     onCursorActivity: null,
     onGutterClick: null,
@@ -1537,6 +1838,7 @@ var CodeMirror = (function() {
     workDelay: 200,
     undoDepth: 40,
     tabindex: null,
+    pollForIME: false,
     document: window.document
   };
 
@@ -1572,7 +1874,7 @@ var CodeMirror = (function() {
   CodeMirror.listMIMEs = function() {
     var list = [];
     for (var m in mimeModes)
-      if (mimeModes.propertyIsEnumerable(m)) list.push(m);
+      if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
     return list;
   };
 
@@ -1634,11 +1936,11 @@ var CodeMirror = (function() {
     }
     return nstate;
   }
-  CodeMirror.startState = startState;
+  CodeMirror.copyState = copyState;
   function startState(mode, a1, a2) {
     return mode.startState ? mode.startState(a1, a2) : true;
   }
-  CodeMirror.copyState = copyState;
+  CodeMirror.startState = startState;
 
   // The character stream used by a mode's parser.
   function StringStream(string) {
@@ -1660,7 +1962,7 @@ var CodeMirror = (function() {
       if (ok) {++this.pos; return ch;}
     },
     eatWhile: function(match) {
-      var start = this.start;
+      var start = this.pos;
       while (this.eat(match)){}
       return this.pos > start;
     },
@@ -1695,18 +1997,98 @@ var CodeMirror = (function() {
   };
   CodeMirror.StringStream = StringStream;
 
+  function MarkedText(from, to, className, set) {
+    this.from = from; this.to = to; this.style = className; this.set = set;
+  }
+  MarkedText.prototype = {
+    attach: function(line) { this.set.push(line); },
+    detach: function(line) {
+      var ix = indexOf(this.set, line);
+      if (ix > -1) this.set.splice(ix, 1);
+    },
+    split: function(pos, lenBefore) {
+      if (this.to <= pos && this.to != null) return null;
+      var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
+      var to = this.to == null ? null : this.to - pos + lenBefore;
+      return new MarkedText(from, to, this.style, this.set); 
+    },
+    dup: function() { return new MarkedText(null, null, this.style, this.set); },
+    clipTo: function(fromOpen, from, toOpen, to, diff) {
+      if (this.from != null && this.from >= from)
+        this.from = Math.max(to, this.from) + diff;
+      if (this.to != null && this.to > from)
+        this.to = to < this.to ? this.to + diff : from;
+      if (fromOpen && to > this.from && (to < this.to || this.to == null))
+        this.from = null;
+      if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
+        this.to = null;
+    },
+    isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
+    sameSet: function(x) { return this.set == x.set; }
+  };
+
+  function Bookmark(pos) {
+    this.from = pos; this.to = pos; this.line = null;
+  }
+  Bookmark.prototype = {
+    attach: function(line) { this.line = line; },
+    detach: function(line) { if (this.line == line) this.line = null; },
+    split: function(pos, lenBefore) {
+      if (pos < this.from) {
+        this.from = this.to = (this.from - pos) + lenBefore;
+        return this;
+      }
+    },
+    isDead: function() { return this.from > this.to; },
+    clipTo: function(fromOpen, from, toOpen, to, diff) {
+      if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
+        this.from = 0; this.to = -1;
+      } else if (this.from > from) {
+        this.from = this.to = Math.max(to, this.from) + diff;
+      }
+    },
+    sameSet: function(x) { return false; },
+    find: function() {
+      if (!this.line || !this.line.parent) return null;
+      return {line: lineNo(this.line), ch: this.from};
+    },
+    clear: function() {
+      if (this.line) {
+        var found = indexOf(this.line.marked, this);
+        if (found != -1) this.line.marked.splice(found, 1);
+        this.line = null;
+      }
+    }
+  };
+
   // Line objects. These hold state related to a line, including
   // highlighting info (the styles array).
   function Line(text, styles) {
     this.styles = styles || [text, null];
-    this.stateAfter = null;
     this.text = text;
+    this.height = 1;
     this.marked = this.gutterMarker = this.className = null;
+    this.stateAfter = this.parent = this.hidden = null;
+  }
+  Line.inheritMarks = function(text, orig) {
+    var ln = new Line(text), mk = orig.marked;
+    if (mk) {
+      for (var i = 0; i < mk.length; ++i) {
+        if (mk[i].to == null && mk[i].style) {
+          var newmk = ln.marked || (ln.marked = []), mark = mk[i];
+          var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
+        }
+      }
+    }
+    return ln;
   }
   Line.prototype = {
     // Replace a piece of a line, keeping the styles around it intact.
-    replace: function(from, to, text) {
-      var st = [], mk = this.marked;
+    replace: function(from, to_, text) {
+      // Reset line class if the whole text was replaced.
+      if (!from && (to_ == null || to_ == this.text.length))
+        this.className = this.gutterMarker = null;
+      var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
       copyStyles(0, from, this.styles, st);
       if (text) st.push(text, null);
       copyStyles(to, this.text.length, this.styles, st);
@@ -1714,34 +2096,79 @@ var CodeMirror = (function() {
       this.text = this.text.slice(0, from) + text + this.text.slice(to);
       this.stateAfter = null;
       if (mk) {
-        var diff = text.length - (to - from), end = this.text.length;
-        function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;}
-        for (var i = 0; i < mk.length; ++i) {
-          var mark = mk[i], del = false;
-          if (mark.from >= end) del = true;
-          else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);}
-          if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;}
+        var diff = text.length - (to - from);
+        for (var i = 0, mark = mk[i]; i < mk.length; ++i) {
+          mark.clipTo(from == null, from || 0, to_ == null, to, diff);
+          if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
         }
       }
     },
-    // Split a line in two, again keeping styles intact.
+    // Split a part off a line, keeping styles and markers intact.
     split: function(pos, textBefore) {
-      var st = [textBefore, null];
+      var st = [textBefore, null], mk = this.marked;
       copyStyles(pos, this.text.length, this.styles, st);
-      return new Line(textBefore + this.text.slice(pos), st);
+      var taken = new Line(textBefore + this.text.slice(pos), st);
+      if (mk) {
+        for (var i = 0; i < mk.length; ++i) {
+          var mark = mk[i];
+          var newmark = mark.split(pos, textBefore.length);
+          if (newmark) {
+            if (!taken.marked) taken.marked = [];
+            taken.marked.push(newmark); newmark.attach(taken);
+          }
+        }
+      }
+      return taken;
     },
-    addMark: function(from, to, style) {
-      var mk = this.marked, mark = {from: from, to: to, style: style};
-      if (this.marked == null) this.marked = [];
-      this.marked.push(mark);
-      this.marked.sort(function(a, b){return a.from - b.from;});
-      return mark;
+    append: function(line) {
+      var mylen = this.text.length, mk = line.marked, mymk = this.marked;
+      this.text += line.text;
+      copyStyles(0, line.text.length, line.styles, this.styles);
+      if (mymk) {
+        for (var i = 0; i < mymk.length; ++i)
+          if (mymk[i].to == null) mymk[i].to = mylen;
+      }
+      if (mk && mk.length) {
+        if (!mymk) this.marked = mymk = [];
+        outer: for (var i = 0; i < mk.length; ++i) {
+          var mark = mk[i];
+          if (!mark.from) {
+            for (var j = 0; j < mymk.length; ++j) {
+              var mymark = mymk[j];
+              if (mymark.to == mylen && mymark.sameSet(mark)) {
+                mymark.to = mark.to == null ? null : mark.to + mylen;
+                if (mymark.isDead()) {
+                  mymark.detach(this);
+                  mk.splice(i--, 1);
+                }
+                continue outer;
+              }
+            }
+          }
+          mymk.push(mark);
+          mark.attach(this);
+          mark.from += mylen;
+          if (mark.to != null) mark.to += mylen;
+        }
+      }
     },
-    removeMark: function(mark) {
-      var mk = this.marked;
+    fixMarkEnds: function(other) {
+      var mk = this.marked, omk = other.marked;
       if (!mk) return;
-      for (var i = 0; i < mk.length; ++i)
-        if (mk[i] == mark) {mk.splice(i, 1); break;}
+      for (var i = 0; i < mk.length; ++i) {
+        var mark = mk[i], close = mark.to == null;
+        if (close && omk) {
+          for (var j = 0; j < omk.length; ++j)
+            if (omk[j].sameSet(mark)) {close = false; break;}
+        }
+        if (close) mark.to = this.text.length;
+      }
+    },
+    addMark: function(mark) {
+      mark.attach(this);
+      if (this.marked == null) this.marked = [];
+      this.marked.push(mark);
+      this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
     },
     // Run the given mode's parser over a line, update the styles
     // array, which contains alternating fragments of text and CSS
@@ -1769,10 +2196,10 @@ var CodeMirror = (function() {
       }
       if (st.length != pos) {st.length = pos; changed = true;}
       if (pos && st[pos-2] != prevWord) changed = true;
-      // Short lines with simple highlights always count as changed,
-      // because they are likely to highlight the same way in various
-      // contexts.
-      return changed || (st.length < 5 && this.text.length < 10);
+      // Short lines with simple highlights return null, and are
+      // counted as changed by the driver because they are likely to
+      // highlight the same way in various contexts.
+      return changed || (st.length < 5 && this.text.length < 10 ? null : false);
     },
     // Fetch the parser token for a given character. Useful for hacks
     // that want to inspect the mode state (say, for completion).
@@ -1792,11 +2219,14 @@ var CodeMirror = (function() {
     // Produces an HTML fragment for the line, taking selection,
     // marking, and highlighting into account.
     getHTML: function(sfrom, sto, includePre, endAt) {
-      var html = [];
+      var html = [], first = true;
       if (includePre)
         html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
       function span(text, style) {
         if (!text) return;
+        // Work around a bug where, in some compat modes, IE ignores leading spaces
+        if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
+        first = false;
         if (style) html.push('<span class="', style, '">', htmlEscape(text), "</span>");
         else html.push(htmlEscape(text));
       }
@@ -1809,10 +2239,10 @@ var CodeMirror = (function() {
         span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
       else if (!marked && sfrom == null)
         for (var i = 0, ch = 0; ch < len; i+=2) {
-          var str = st[i], l = str.length;
+          var str = st[i], style = st[i+1], l = str.length;
           if (ch + l > len) str = str.slice(0, len - ch);
           ch += l;
-          span(str, "cm-" + st[i+1]);
+          span(str, style && "cm-" + style);
         }
       else {
         var pos = 0, i = 0, text = "", style, sg = 0;
@@ -1856,6 +2286,11 @@ var CodeMirror = (function() {
       }
       if (includePre) html.push("</pre>");
       return html.join("");
+    },
+    cleanUp: function() {
+      this.parent = null;
+      if (this.marked)
+        for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
     }
   };
   // Utility used by replace and split above
@@ -1874,6 +2309,191 @@ var CodeMirror = (function() {
     }
   }
 
+  // Data structure that holds the sequence of lines.
+  function LeafChunk(lines) {
+    this.lines = lines;
+    this.parent = null;
+    for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
+      lines[i].parent = this;
+      height += lines[i].height;
+    }
+    this.height = height;
+  }
+  LeafChunk.prototype = {
+    chunkSize: function() { return this.lines.length; },
+    remove: function(at, n) {
+      for (var i = at, e = at + n; i < e; ++i) {
+        var line = this.lines[i];
+        line.cleanUp();
+        this.height -= line.height;
+      }
+      this.lines.splice(at, n);
+    },
+    collapse: function(lines) {
+      lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
+    },
+    insertHeight: function(at, lines, height) {
+      this.height += height;
+      this.lines.splice.apply(this.lines, [at, 0].concat(lines));
+      for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
+    },
+    iterN: function(at, n, op) {
+      for (var e = at + n; at < e; ++at)
+        if (op(this.lines[at])) return true;
+    }
+  };
+  function BranchChunk(children) {
+    this.children = children;
+    var size = 0, height = 0;
+    for (var i = 0, e = children.length; i < e; ++i) {
+      var ch = children[i];
+      size += ch.chunkSize(); height += ch.height;
+      ch.parent = this;
+    }
+    this.size = size;
+    this.height = height;
+    this.parent = null;
+  }
+  BranchChunk.prototype = {
+    chunkSize: function() { return this.size; },
+    remove: 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);
+          this.height -= oldHeight - child.height;
+          if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
+          if ((n -= rm) == 0) break;
+          at = 0;
+        } else at -= sz;
+      }
+      if (this.size - n < 25) {
+        var lines = [];
+        this.collapse(lines);
+        this.children = [new LeafChunk(lines)];
+      }
+    },
+    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) {
+      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);
+          if (child.lines && child.lines.length > 50) {
+            while (child.lines.length > 50) {
+              var spilled = child.lines.splice(child.lines.length - 25, 25);
+              var newleaf = new LeafChunk(spilled);
+              child.height -= newleaf.height;
+              this.children.splice(i + 1, 0, newleaf);
+              newleaf.parent = this;
+            }
+            this.maybeSpill();
+          }
+          break;
+        }
+        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();
+    },
+    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;
+      }
+    }
+  };
+
+  function getLineAt(chunk, n) {
+    for (;;) {
+      for (var i = 0, e = chunk.children.length; i < e; ++i) {
+        var child = chunk.children[i], sz = child.chunkSize();
+        if (n < sz) { chunk = child; break; }
+        n -= sz;
+      }
+      if (chunk.lines) return chunk.lines[n];
+    }
+  }
+  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) {
+        if (chunk.children[i] == cur) break;
+        no += chunk.children[i].chunkSize();
+      }
+    }
+    return no;
+  }
+  function lineAtHeight(chunk, h) {
+    var n = 0;
+    outer: do {
+      for (var i = 0, e = chunk.children.length; i < e; ++i) {
+        var child = chunk.children[i], ch = child.height;
+        if (h < ch) { chunk = child; continue outer; }
+        h -= ch;
+        n += child.chunkSize();
+      }
+      return n;
+    } while (!chunk.lines);
+    for (var i = 0, e = chunk.lines.length; i < e; ++i) {
+      var line = chunk.lines[i], lh = line.height;
+      if (h < lh) break;
+      h -= lh;
+    }
+    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;
+      }
+      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() {
@@ -1930,16 +2550,6 @@ var CodeMirror = (function() {
     else if (e.button & 2) return 3;
     else if (e.button & 4) return 2;
   }
-  function e_pageX(e) {
-    if (e.pageX != null) return e.pageX;
-    var doc = e_target(e).ownerDocument;
-    return e.clientX + doc.body.scrollLeft + doc.documentElement.scrollLeft;
-  }
-  function e_pageY(e) {
-    if (e.pageY != null) return e.pageY;
-    var doc = e_target(e).ownerDocument;
-    return e.clientY + doc.body.scrollTop + doc.documentElement.scrollTop;
-  }
 
   // Event handler registration. If disconnect is true, it'll return a
   // function that unregisters the handler.
@@ -1958,16 +2568,18 @@ var CodeMirror = (function() {
   function Delayed() {this.id = null;}
   Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
 
-  // Some IE versions don't preserve whitespace when setting the
-  // innerHTML of a PRE tag.
-  var badInnerHTML = (function() {
-    var pre = document.createElement("pre");
-    pre.innerHTML = " "; return !pre.innerHTML;
-  })();
+  // Detect drag-and-drop
+  var dragAndDrop = function() {
+    // IE8 has ondragstart and ondrop properties, but doesn't seem to
+    // actually support ondragstart the way it's supposed to work.
+    if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
+    var div = document.createElement('div');
+    return "draggable" in div;
+  }();
 
   var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
   var ie = /MSIE \d/.test(navigator.userAgent);
-  var safari = /Apple Computer/.test(navigator.vendor);
+  var webkit = /WebKit\//.test(navigator.userAgent);
 
   var lineSep = "\n";
   // Feature-detect whether newlines in textareas are converted to \r\n
@@ -1979,6 +2591,7 @@ var CodeMirror = (function() {
 
   var tabSize = 8;
   var mac = /Mac/.test(navigator.platform);
+  var win = /Win/.test(navigator.platform);
   var movementKeys = {};
   for (var i = 35; i <= 40; ++i)
     movementKeys[i] = movementKeys["c" + i] = true;
@@ -2001,21 +2614,44 @@ var CodeMirror = (function() {
     if (elt.currentStyle) return elt.currentStyle;
     return window.getComputedStyle(elt, null);
   }
+
   // Find the position of an element by following the offsetParent chain.
   // If screen==true, it returns screen (rather than page) coordinates.
   function eltOffset(node, screen) {
-    var doc = node.ownerDocument.body;
-    var x = 0, y = 0, skipDoc = false;
+    var bod = node.ownerDocument.body;
+    var x = 0, y = 0, skipBody = false;
     for (var n = node; n; n = n.offsetParent) {
-      x += n.offsetLeft; y += n.offsetTop;
+      var ol = n.offsetLeft, ot = n.offsetTop;
+      // Firefox reports weird inverted offsets when the body has a border.
+      if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }
+      else { x += ol, y += ot; }
       if (screen && computedStyle(n).position == "fixed")
-        skipDoc = true;
+        skipBody = true;
     }
-    var e = screen && !skipDoc ? null : doc;
+    var e = screen && !skipBody ? null : bod;
     for (var n = node.parentNode; n != e; n = n.parentNode)
       if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
     return {left: x, top: y};
   }
+  // Use the faster and saner getBoundingClientRect method when possible.
+  if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {
+    // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
+    // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
+    try { var box  turned value cannot be changed externally (they are kept in sync as the element moves within the page)    ;  Žÿ         ЖŽÿ  uêAAß*  €
CAß*  cAß*                          ’ø^Bß*  ЖŽÿ  €Bß*          À‹Žÿ         ŠŽÿ  k BAß*  6                    è     è!     è!     `–Žÿ  ЖŽÿ  x´^Bß*          ÀŒŽÿ          ’âAAß*          † BAß*  `–Žÿ  `âAAß*  Ÿ–Žÿ         À¯ÂBß*  uêAAß*    ÂBß*  ؤÂBß*  ¨©ÂBß*  €
CAß*          x´^Bß*          ÀŒŽÿ          ‹Žÿ  k BAß*  à!     à!     `–Žÿ  ЖŽÿ  H~>Bß*           Žÿ         ’âAAß*          † BAß*  `–Žÿ  `âAAß*  Ÿ–Žÿ  –Žÿ  ˆ–Žÿ  8cAß*  Œp˜        ЖŽÿ  uêAAß*  €
CAß*                   Žÿ         pŒŽÿ  k BAß*          G       H   I       J   K           M   N   O       P   `–Žÿ  ЖŽÿ  H5Bß*          @Žÿ         ’âAAß*          † BAß*  `–Žÿ         0¯ÂBß*  uêAAß*   À^Bß*   iòAß*    ÂBß*  ؤÂBß*  ¨©ÂBß*  €
CAß*          H5Bß*          @Žÿ         €Žÿ  k BAß*  85Bß*          @Žÿ         °Žÿ  k BAß*  (5Bß*          @Žÿ         àŽÿ  k BAß*  5Bß*          @Žÿ         ŽŽÿ  k BAß*  	       ¨®ÂBß*  uêAAß*         ¨©ÂBß*  ˆŽÿ          Š¸òAß*  eéAAß*         ؤÂBß*  °ŽŽÿ          y¸òAß*  eéAAß*           ÂBß*  àŽŽÿ          Z¸òAß*  eéAAß*  ØdòAß*   À^Bß*   iòAß*    ÂBß*  ØÄ^Bß*  ؤÂBß*  ¨©ÂBß*  €
CAß*          8cAß*                        -¾Aß*  ЖŽÿ  pûñAß*          Žÿ          `Žÿ  k BAß*      5   6   7   8   :   <   =   >       ?       @   B   D       `–Žÿ  ЖŽÿ  à-»Aß*                 ЖŽÿ  uêAAß*         cAß*  Žÿ          -¾Aß*  eéAAß*  cAß*          Õ_ at fÆS         Qöl±íÁCŠAß*  ЖŽÿ  à-»Aß*           ‘Žÿ         pŽÿ  k BAß*  u]ÞÓ	4Q ÷U^Qʉ§ ¶uª¹ñìò*Ä“v`–Žÿ  ЖŽÿ  H~‡Aß*          ÄÃAß*          ˜Žÿ  ¦Ð%Cß*  ¦Ð%Cß*         ÁAß*  Ÿ–Žÿ  ЗŽÿ  ˆ–Žÿ  ð’Žÿ  Ï^Bß*  uêAAß*  ØÄ^Bß*  P™Žÿ  ¢Ð%Cß*   ˜Žÿ          °“Žÿ                 ÿÿÿÿÿÿÿÿ¢Ð%Cß*          Ø–Žÿ                 = 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;
+  };
+
   // Get a node's text content.
   function eltText(node) {
     return node.textContent || node.innerText || node.nodeValue || "";
@@ -2026,11 +2662,17 @@ var CodeMirror = (function() {
   function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
   function copyPos(x) {return {line: x.line, ch: x.ch};}
 
-  var escapeElement = document.createElement("div");
+  var escapeElement = document.createElement("pre");
   function htmlEscape(str) {
-    escapeElement.innerText = escapeElement.textContent = str;
+    if (badTextContent) {
+      escapeElement.innerHTML = "";
+      escapeElement.appendChild(document.createTextNode(str));
+    } else {
+      escapeElement.textContent = str;
+    }
     return escapeElement.innerHTML;
   }
+  var badTextContent = htmlEscape("\t") != "\t";
   CodeMirror.htmlEscape = htmlEscape;
 
   // Used to position the cursor after an undo/redo by finding the
@@ -2073,7 +2715,7 @@ var CodeMirror = (function() {
       try {return {start: te.selectionStart, end: te.selectionEnd};}
       catch(e) {return null;}
     };
-    if (safari)
+    if (webkit)
       // On Safari, selection set with setSelectionRange are in a sort
       // of limbo wrt their anchor. If you press shift-left in them,
       // the anchor is put at the end, and the selection expanded to
@@ -2140,5 +2782,4 @@ var CodeMirror = (function() {
   CodeMirror.defineMIME("text/plain", "null");
 
   return CodeMirror;
-})()
-;
\ No newline at end of file
+})();
diff --git a/js/functions.js b/js/functions.js
index c9574a1..12c6a65 100644
--- a/js/functions.js
+++ b/js/functions.js
@@ -2053,7 +2053,7 @@ $(document).ready(function() {
 
                             //Sort the table
                             $(tables_table).PMA_sort_table('th');
-                            
+
                             // Adjust summary row
                             PMA_adjustTotals();
                         }
@@ -3434,7 +3434,13 @@ $(document).ready(function() {
 $(document).ready(function() {
     var elm = $('#sqlquery');
     if (elm.length > 0 && typeof CodeMirror != 'undefined') {
-        codemirror_editor = CodeMirror.fromTextArea(elm[0], {lineNumbers: true, matchBrackets: true, indentUnit: 4, mode: "text/x-mysql"});
+        codemirror_editor = CodeMirror.fromTextArea(elm[0], {
+            lineNumbers: true,
+            matchBrackets: true,
+            indentUnit: 4,
+            mode: "text/x-mysql",
+            lineWrapping: true
+        });
     }
 });
 
@@ -3645,11 +3651,11 @@ $(document).ready(function () {
 
 /**
  * Toggles row colors of a set of 'tr' elements starting from a given element
- * 
+ *
  * @param $start Starting element
  */
-function toggleRowColors($start) 
-{  
+function toggleRowColors($start)
+{
     for (var $curr_row = $start; $curr_row.length > 0; $curr_row = $curr_row.next()) {
         if ($curr_row.hasClass('odd')) {
             $curr_row.removeClass('odd').addClass('even');
diff --git a/themes/original/css/theme_right.css.php b/themes/original/css/theme_right.css.php
index 71fa37e..5b5dd1c 100644
--- a/themes/original/css/theme_right.css.php
+++ b/themes/original/css/theme_right.css.php
@@ -2198,12 +2198,16 @@ fieldset .disabled-field td {
 }
 
 .CodeMirror-scroll {
-  height:             <?php echo ceil($GLOBALS['cfg']['TextareaRows'] * 1.2); ?>em;
   overflow: auto;
+  height:             <?php echo ceil($GLOBALS['cfg']['TextareaRows'] * 1.2); ?>em;
+  /* This is needed to prevent an IE[67] bug where the scrolled content
+     is visible outside of the scrolling box. */
+  position: relative;
 }
 
 .CodeMirror-gutter {
   position: absolute; left: 0; top: 0;
+  z-index: 10;
   background-color: #f7f7f7;
   border-right: 1px solid #eee;
   min-width: 2em;
@@ -2213,6 +2217,7 @@ fieldset .disabled-field td {
   color: #aaa;
   text-align: right;
   padding: .4em .2em .4em .4em;
+  white-space: pre !important;
 }
 .CodeMirror-lines {
   padding: .4em;
@@ -2227,6 +2232,16 @@ fieldset .disabled-field td {
   font-family: inherit;
   font-size: inherit;
   padding: 0; margin: 0;
+  white-space: pre;
+  word-wrap: normal;
+}
+
+.CodeMirror-wrap pre {
+  word-wrap: break-word;
+  white-space: pre-wrap;
+}
+.CodeMirror-wrap .CodeMirror-scroll {
+  overflow-x: hidden;
 }
 
 .CodeMirror textarea {
diff --git a/themes/pmahomme/css/theme_right.css.php b/themes/pmahomme/css/theme_right.css.php
index 8cce259..0e01c47 100644
--- a/themes/pmahomme/css/theme_right.css.php
+++ b/themes/pmahomme/css/theme_right.css.php
@@ -2620,10 +2620,14 @@ fieldset .disabled-field td {
 .CodeMirror-scroll {
   overflow: auto;
   height:             <?php echo ceil($GLOBALS['cfg']['TextareaRows'] * 1.2); ?>em;
+  /* This is needed to prevent an IE[67] bug where the scrolled content
+     is visible outside of the scrolling box. */
+  position: relative;
 }
 
 .CodeMirror-gutter {
   position: absolute; left: 0; top: 0;
+  z-index: 10;
   background-color: #f7f7f7;
   border-right: 1px solid #eee;
   min-width: 2em;
@@ -2633,6 +2637,7 @@ fieldset .disabled-field td {
   color: #aaa;
   text-align: right;
   padding: .4em .2em .4em .4em;
+  white-space: pre !important;
 }
 .CodeMirror-lines {
   padding: .4em;
@@ -2647,6 +2652,16 @@ fieldset .disabled-field td {
   font-family: inherit;
   font-size: inherit;
   padding: 0; margin: 0;
+  white-space: pre;
+  word-wrap: normal;
+}
+
+.CodeMirror-wrap pre {
+  word-wrap: break-word;
+  white-space: pre-wrap;
+}
+.CodeMirror-wrap .CodeMirror-scroll {
+  overflow-x: hidden;
 }
 
 .CodeMirror textarea {


hooks/post-receive
-- 
phpMyAdmin




More information about the Git mailing list