commit f2dd82b0d3defdedb01c5222e802d56ba47bd93f Author: Ariya Hidayat Date: Tue Mar 27 17:56:26 2012 -0700 CodeMirror version 2.23. diff --git a/assets/codemirror/codemirror.css b/assets/codemirror/codemirror.css index 5eadb24..2d79f4a 100644 --- a/assets/codemirror/codemirror.css +++ b/assets/codemirror/codemirror.css @@ -9,6 +9,7 @@ /* This is needed to prevent an IE[67] bug where the scrolled content is visible outside of the scrolling box. */ position: relative; + outline: none; } .CodeMirror-gutter { @@ -27,6 +28,7 @@ } .CodeMirror-lines { padding: .4em; + white-space: pre; } .CodeMirror pre { diff --git a/assets/codemirror/codemirror.js b/assets/codemirror/codemirror.js index 9c6e65e..5434a8d 100644 --- a/assets/codemirror/codemirror.js +++ b/assets/codemirror/codemirror.js @@ -1,4 +1,4 @@ -// CodeMirror version 2.22 +// CodeMirror version 2.23 // // All functions that need access to the editor's state live inside // the CodeMirror function. Below that, at the bottom of the file, @@ -15,9 +15,8 @@ var CodeMirror = (function() { if (defaults.hasOwnProperty(opt)) options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; - var targetDocument = options["document"]; // The element in which the editor lives. - var wrapper = targetDocument.createElement("div"); + var wrapper = document.createElement("div"); wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""); // This mess creates the base DOM structure for the editor. wrapper.innerHTML = @@ -48,7 +47,10 @@ var CodeMirror = (function() { if (!webkit) lineSpace.draggable = true; lineSpace.style.outline = "none"; if (options.tabindex != null) input.tabIndex = options.tabindex; + if (options.autofocus) focusInput(); if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + // Needed to handle Tab key in KHTML + if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; // Check for problem with IE innerHTML not working when we have a // P (or similar) parent node. @@ -81,12 +83,13 @@ var CodeMirror = (function() { gutterDirty, callbacks; // Current visible range (may be bigger than the view window). var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; - // bracketHighlighted is used to remember that a backet has been + // bracketHighlighted is used to remember that a bracket has been // marked. var bracketHighlighted; // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. var maxLine = "", maxWidth; + var tabCache = {}; // Initialize the content. operation(function(){setValue(options.value || ""); updateInput = false;})(); @@ -124,10 +127,16 @@ var CodeMirror = (function() { if (!options.readOnly) replaceSelection(""); })); + // Needed to handle Tab key in KHTML + if (khtml) connect(code, "mouseup", function() { + if (document.activeElement == input) input.blur(); + focusInput(); + }); + // IE throws unspecified error in certain cases, when // trying to access activeElement before onload - var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { } - if (hasFocus) setTimeout(onFocus, 20); + var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { } + if (hasFocus || options.autofocus) setTimeout(onFocus, 20); else onBlur(); function isLine(l) {return l >= 0 && l < doc.size;} @@ -178,17 +187,23 @@ var CodeMirror = (function() { line = clipLine(line == null ? doc.size - 1: line); return getStateBefore(line + 1); }, - cursorCoords: function(start){ + cursorCoords: function(start, mode) { if (start == null) start = sel.inverted; - return pageCoords(start ? sel.from : sel.to); + return this.charCoords(start ? sel.from : sel.to, mode); + }, + charCoords: function(pos, mode) { + pos = clipPos(pos); + if (mode == "local") return localCoords(pos, false); + if (mode == "div") return localCoords(pos, true); + return pageCoords(pos); }, - charCoords: function(pos){return pageCoords(clipPos(pos));}, coordsChar: function(coords) { var off = eltOffset(lineSpace); return coordsChar(coords.x - off.left, coords.y - off.top); }, markText: operation(markText), setBookmark: setBookmark, + findMarksAt: findMarksAt, setMarker: operation(addGutterMarker), clearMarker: operation(removeGutterMarker), setLineClass: operation(setLineClass), @@ -256,6 +271,7 @@ var CodeMirror = (function() { replaceRange: operation(replaceRange), getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, + triggerOnKeyDown: operation(onKeyDown), execCommand: function(cmd) {return commands[cmd](instance);}, // Stuff used by commands, probably not much use to outside code. moveH: operation(moveH), @@ -373,7 +389,7 @@ var CodeMirror = (function() { !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) { + var up = connect(document, "mouseup", operation(function(e2) { if (webkit) lineSpace.draggable = false; draggingText = false; up(); @@ -384,6 +400,8 @@ var CodeMirror = (function() { } }), true); draggingText = true; + // IE's approach to draggable + if (lineSpace.dragDrop) lineSpace.dragDrop(); return; } e_preventDefault(e); @@ -402,12 +420,7 @@ var CodeMirror = (function() { } } - var move = connect(targetDocument, "mousemove", operation(function(e) { - clearTimeout(going); - e_preventDefault(e); - extend(e); - }), true); - var up = connect(targetDocument, "mouseup", operation(function(e) { + function done(e) { clearTimeout(going); var cur = posFromMouse(e); if (cur) setSelectionUser(start, cur); @@ -415,7 +428,14 @@ var CodeMirror = (function() { focusInput(); updateInput = true; move(); up(); + } + var move = connect(document, "mousemove", operation(function(e) { + clearTimeout(going); + e_preventDefault(e); + if (!ie && !e_button(e)) done(e); + else extend(e); }), true); + var up = connect(document, "mouseup", operation(done), true); } function onDoubleClick(e) { for (var n = e_target(e); n != wrapper; n = n.parentNode) @@ -464,11 +484,14 @@ var CodeMirror = (function() { } function onDragStart(e) { var txt = getSelection(); - // Disabled until further notice. Doesn't work on most browsers, - // and crashes Safari (issue #332). - //htmlEscape(txt); - //e.dataTransfer.setDragImage(escapeElement, 0, 0); e.dataTransfer.setData("Text", txt); + + // Use dummy image instead of default browsers image. + if (gecko || chrome) { + var img = document.createElement('img'); + img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image + e.dataTransfer.setDragImage(img, 0, 0); + } } function doHandleBinding(bound, dropShift) { @@ -506,13 +529,19 @@ var CodeMirror = (function() { if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; if (e_prop(e, "metaKey")) name = "Cmd-" + name; - if (e_prop(e, "shiftKey")) + if (e_prop(e, "shiftKey")) { handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, - function(b) {return doHandleBinding(b, true);}); - if (!handled) + function(b) {return doHandleBinding(b, true);}) + || lookupKey(name, options.extraKeys, options.keyMap, function(b) { + if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); + }); + } else { handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding); - - if (handled) e_preventDefault(e); + } + if (handled) { + e_preventDefault(e); + if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } + } return handled; } function handleCharBinding(e, ch) { @@ -545,7 +574,7 @@ var CodeMirror = (function() { if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} - if (window.opera && !e.which && handleKeyBinding(e)) return; + if (((window.opera && !e.which) || khtml) && handleKeyBinding(e)) return; var ch = String.fromCharCode(charCode == null ? keyCode : charCode); if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { if (mode.electricChars.indexOf(ch) > -1) @@ -837,11 +866,11 @@ var CodeMirror = (function() { return scrollIntoView(x, cursor.y, x, cursor.yBot); } function scrollIntoView(x1, y1, x2, y2) { - var pl = paddingLeft(), pt = paddingTop(), lh = textHeight(); + var pl = paddingLeft(), pt = paddingTop(); y1 += pt; y2 += pt; x1 += pl; x2 += pl; var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; - if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;} - else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;} + if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1); scrolled = true;} + else if (y2 > screentop + screen) {scroller.scrollTop = y2 - screen; scrolled = true;} var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; var gutterw = options.fixedGutter ? gutter.clientWidth : 0; @@ -921,7 +950,7 @@ var CodeMirror = (function() { throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + " nodes=" + lineDiv.childNodes.length); - if (options.lineWrapping) { + function checkHeights() { maxWidth = scroller.clientWidth; var curNode = lineDiv.firstChild, heightChanged = false; doc.iter(showingFrom, showingTo, function(line) { @@ -936,6 +965,11 @@ var CodeMirror = (function() { }); if (heightChanged) code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + return heightChanged; + } + + if (options.lineWrapping) { + checkHeights(); } else { if (maxWidth == null) maxWidth = stringWidth(maxLine); if (maxWidth > scroller.clientWidth) { @@ -947,8 +981,12 @@ var CodeMirror = (function() { lineSpace.style.width = code.style.width = ""; } } + gutter.style.display = gutterDisplay; - if (different || gutterDirty) updateGutter(); + if (different || gutterDirty) { + // If the gutter grew in size, re-check heights. If those changed, re-draw gutter. + updateGutter() && options.lineWrapping && checkHeights() && updateGutter(); + } updateSelection(); if (!suppressCallback && options.onUpdate) options.onUpdate(instance); return true; @@ -996,16 +1034,17 @@ var CodeMirror = (function() { } // This pass fills in the lines that actually changed. var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; - var scratch = targetDocument.createElement("div"), newElt; + var scratch = document.createElement("div"); doc.iter(from, to, function(line) { if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); if (!nextIntact || nextIntact.from > j) { if (line.hidden) var html = scratch.innerHTML = "
";
           else {
-            var html = '
' + line.getHTML(makeTab) + '
'; + var html = '' + + line.getHTML(makeTab) + ''; // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.className) - html = '
 
' + html + "
"; } scratch.innerHTML = html; @@ -1021,7 +1060,7 @@ var CodeMirror = (function() { if (!options.gutter && !options.lineNumbers) return; var hText = mover.offsetHeight, hEditor = scroller.clientHeight; gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; - var html = [], i = showingFrom; + var html = [], i = showingFrom, normalNode; doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { if (line.hidden) { html.push("
");
@@ -1035,17 +1074,24 @@ var CodeMirror = (function() {
           html.push((marker && marker.style ? '
' : "
"), text);
           for (var j = 1; j < line.height; ++j) html.push("
 "); html.push("
"); + if (!marker) normalNode = i; } ++i; }); gutter.style.display = "none"; gutterText.innerHTML = html.join(""); - var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; - while (val.length + pad.length < minwidth) pad += "\u00a0"; - if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); + // Make sure scrolling doesn't cause number gutter size to pop + if (normalNode != null) { + var node = gutterText.childNodes[normalNode - showingFrom]; + var minwidth = String(doc.size).length, val = eltText(node), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); + } gutter.style.display = ""; + var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; lineSpace.style.marginLeft = gutter.offsetWidth + "px"; gutterDirty = false; + return resized; } function updateSelection() { var collapsed = posEq(sel.from, sel.to); @@ -1066,16 +1112,18 @@ var CodeMirror = (function() { html += '
'; } + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; if (sel.from.ch && fromPos.y >= 0) { - var right = sameLine ? lineSpace.clientWidth - toPos.x : 0; + var right = sameLine ? clientWidth - toPos.x : 0; add(fromPos.x, fromPos.y, right, th); } var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); - var middleHeight = Math.min(toPos.y, lineSpace.clientHeight) - middleStart; + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; if (middleHeight > 0.2 * th) add(0, middleStart, 0, middleHeight); - if ((!sameLine || !sel.from.ch) && toPos.y < lineSpace.clientHeight - .5 * th) - add(0, toPos.y, lineSpace.clientWidth - toPos.x, th); + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) + add(0, toPos.y, clientWidth - toPos.x, th); selectionDiv.innerHTML = html; cursor.style.display = "none"; selectionDiv.style.display = ""; @@ -1105,7 +1153,12 @@ var CodeMirror = (function() { if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} // Skip over hidden lines. - if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch); + if (from.line != oldFrom) { + var from1 = skipHidden(from, oldFrom, sel.from.ch); + // If there is no non-hidden line left, force visibility on current line + if (!from1) setLineHidden(from.line, false); + else from = from1; + } if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); if (posEq(from, to)) sel.inverted = false; @@ -1114,7 +1167,7 @@ var CodeMirror = (function() { if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { var head = sel.inverted ? from : to; - if (head.line != sel.from.line) { + if (head.line != sel.from.line && sel.from.line < doc.size) { var oldLine = getLine(sel.from.line); if (/^\s+$/.test(oldLine.text)) setTimeout(operation(function() { @@ -1209,6 +1262,7 @@ var CodeMirror = (function() { if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); else if (unit == "line") dist = textHeight(); var target = coordsChar(pos.x, pos.y + dist * dir + 2); + if (unit == "page") scroller.scrollTop += localCoords(target, true).y - pos.y; setCursor(target.line, target.ch, true); goalColumn = pos.x; } @@ -1295,9 +1349,10 @@ var CodeMirror = (function() { changes.push({from: 0, to: doc.size}); } function makeTab(col) { - var w = options.tabSize - col % options.tabSize; + var w = options.tabSize - col % options.tabSize, cached = tabCache[w]; + if (cached) return cached; for (var str = '', i = 0; i < w; ++i) str += " "; - return {html: str + "", width: w}; + return (tabCache[w] = {html: str + "", width: w}); } function themeChanged() { scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") + @@ -1313,7 +1368,7 @@ var CodeMirror = (function() { var lineN = lineNo(line); min = Math.min(min, lineN); max = Math.max(max, lineN); for (var j = 0; j < mk.length; ++j) - if (mk[j].set == this.set) mk.splice(j--, 1); + if (mk[j].marker == this) mk.splice(j--, 1); } if (min != Infinity) changes.push({from: min, to: max + 1}); @@ -1324,7 +1379,7 @@ var CodeMirror = (function() { 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.marker == this) { if (mark.from != null || mark.to != null) { var found = lineNo(line); if (found != null) { @@ -1341,8 +1396,9 @@ var CodeMirror = (function() { function markText(from, to, className) { from = clipPos(from); to = clipPos(to); var tm = new TextMarker(); + if (!posLess(from, to)) return tm; function add(line, from, to, className) { - getLine(line).addMark(new MarkedText(from, to, className, tm.set)); + getLine(line).addMark(new MarkedText(from, to, className, tm)); } if (from.line == to.line) add(from.line, from.ch, to.ch, className); else { @@ -1362,6 +1418,19 @@ var CodeMirror = (function() { return bm; } + function findMarksAt(pos) { + pos = clipPos(pos); + var markers = [], marked = getLine(pos.line).marked; + if (!marked) return markers; + for (var i = 0, e = marked.length; i < e; ++i) { + var m = marked[i]; + if ((m.from == null || m.from <= pos.ch) && + (m.to == null || m.to >= pos.ch)) + markers.push(m.marker || m); + } + return markers; + } + function addGutterMarker(line, text, className) { if (typeof line == "number") line = getLine(clipLine(line)); line.gutterMarker = {text: text, style: className}; @@ -1383,10 +1452,11 @@ var CodeMirror = (function() { else return null; return line; } - function setLineClass(handle, className) { + function setLineClass(handle, className, bgClassName) { return changeLine(handle, function(line) { - if (line.className != className) { + if (line.className != className || line.bgClassName != bgClassName) { line.className = className; + line.bgClassName = bgClassName; return true; } }); @@ -1400,6 +1470,8 @@ var CodeMirror = (function() { if (hidden && (fline == no || tline == no)) { var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + // Can't hide the last visible line, we'd have no place to put the cursor + if (!to) return; setSelection(from, to); } return (gutterDirty = true); @@ -1420,7 +1492,7 @@ var CodeMirror = (function() { } var marker = line.gutterMarker; return {line: n, handle: line, text: line.text, markerText: marker && marker.text, - markerClass: marker && marker.style, lineClass: line.className}; + markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; } function stringWidth(str) { @@ -1464,7 +1536,7 @@ var CodeMirror = (function() { 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); + var end = line.text.indexOf(" ", ch + 6); extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0))); } measure.innerHTML = "
" + line.getHTML(makeTab, ch) +
@@ -1823,7 +1895,7 @@ var CodeMirror = (function() {
     pollInterval: 100,
     undoDepth: 40,
     tabindex: null,
-    document: window.document
+    autofocus: null
   };
 
   var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
@@ -1831,7 +1903,7 @@ var CodeMirror = (function() {
   var win = /Win/.test(navigator.platform);
 
   // Known modes, by name and by MIME
-  var modes = {}, mimeModes = {};
+  var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
   CodeMirror.defineMode = function(name, mode) {
     if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
     modes[name] = mode;
@@ -1842,6 +1914,8 @@ var CodeMirror = (function() {
   CodeMirror.resolveMode = function(spec) {
     if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
       spec = mimeModes[spec];
+    else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
+      return CodeMirror.resolveMode("application/xml");
     if (typeof spec == "string") return {name: spec};
     else return spec || {name: "null"};
   };
@@ -1926,7 +2000,7 @@ var CodeMirror = (function() {
   keyMap.basic = {
     "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
     "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
-    "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess",
+    "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "insertTab", "Shift-Tab": "indentAuto",
     "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
   };
   // Note that the save and find-related commands aren't defined by
@@ -1937,6 +2011,7 @@ var CodeMirror = (function() {
     "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
     "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
     "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
+    "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
     fallthrough: "basic"
   };
   keyMap.macDefault = {
@@ -1945,6 +2020,7 @@ var CodeMirror = (function() {
     "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
     "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
     "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
+    "Cmd-[": "indentLess", "Cmd-]": "indentMore",
     fallthrough: ["basic", "emacsy"]
   };
   keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
@@ -1987,6 +2063,8 @@ var CodeMirror = (function() {
     options.value = textarea.value;
     if (!options.tabindex && textarea.tabindex)
       options.tabindex = textarea.tabindex;
+    if (options.autofocus == null && textarea.getAttribute("autofocus") != null)
+      options.autofocus = true;
 
     function save() {textarea.value = instance.getValue();}
     if (textarea.form) {
@@ -2098,34 +2176,34 @@ var CodeMirror = (function() {
   };
   CodeMirror.StringStream = StringStream;
 
-  function MarkedText(from, to, className, set) {
-    this.from = from; this.to = to; this.style = className; this.set = set;
+  function MarkedText(from, to, className, marker) {
+    this.from = from; this.to = to; this.style = className; this.marker = marker;
   }
   MarkedText.prototype = {
-    attach: function(line) { this.set.push(line); },
+    attach: function(line) { this.marker.set.push(line); },
     detach: function(line) {
-      var ix = indexOf(this.set, line);
-      if (ix > -1) this.set.splice(ix, 1);
+      var ix = indexOf(this.marker.set, line);
+      if (ix > -1) this.marker.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);
+      return new MarkedText(from, to, this.style, this.marker);
     },
-    dup: function() { return new MarkedText(null, null, this.style, this.set); },
+    dup: function() { return new MarkedText(null, null, this.style, this.marker); },
     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;
+      else if (this.from != null && this.from >= from)
+        this.from = Math.max(to, this.from) + diff;
       if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
         this.to = null;
+      else if (this.to != null && this.to > from)
+        this.to = to < this.to ? this.to + diff : from;
     },
     isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
-    sameSet: function(x) { return this.set == x.set; }
+    sameSet: function(x) { return this.marker == x.marker; }
   };
 
   function Bookmark(pos) {
@@ -2168,7 +2246,7 @@ var CodeMirror = (function() {
     this.styles = styles || [text, null];
     this.text = text;
     this.height = 1;
-    this.marked = this.gutterMarker = this.className = this.handlers = null;
+    this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null;
     this.stateAfter = this.parent = this.hidden = null;
   }
   Line.inheritMarks = function(text, orig) {
@@ -2214,6 +2292,7 @@ var CodeMirror = (function() {
           if (newmark) {
             if (!taken.marked) taken.marked = [];
             taken.marked.push(newmark); newmark.attach(taken);
+            if (newmark == mark) mk.splice(i--, 1);
           }
         }
       }
@@ -2637,10 +2716,10 @@ var CodeMirror = (function() {
         if (start < last.start) {
           for (var i = last.start - start - 1; i >= 0; --i)
             last.old.unshift(old[i]);
-          last.added += last.start - start;
+          oldoff = Math.min(0, added - old.length);
+          last.added += last.start - start + oldoff;
           last.start = start;
-        }
-        else if (last.start < start) {
+        } else if (last.start < start) {
           oldoff = start - last.start;
           added += oldoff;
         }
@@ -2707,19 +2786,21 @@ var CodeMirror = (function() {
 
   var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
 
-  // Detect drag-and-drop
-  var dragAndDrop = function() {
-    // IE8 has ondragstart and ondrop properties, but doesn't seem to
-    // actually support ondragstart the way it's supposed to work.
-    if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
-    var div = document.createElement('div');
-    return "draggable" in div;
-  }();
-
   var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
   var ie = /MSIE \d/.test(navigator.userAgent);
   var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
   var webkit = /WebKit\//.test(navigator.userAgent);
+  var chrome = /Chrome\//.test(navigator.userAgent);
+  var khtml = /KHTML\//.test(navigator.userAgent);
+
+  // Detect drag-and-drop
+  var dragAndDrop = function() {
+    // There is *some* kind of drag-and-drop support in IE6-8, but I
+    // couldn't get it to work yet.
+    if (ie_lt9) return false;
+    var div = document.createElement('div');
+    return "draggable" in div || "dragDrop" in div;
+  }();
 
   var lineSep = "\n";
   // Feature-detect whether newlines in textareas are converted to \r\n
@@ -2873,7 +2954,7 @@ var CodeMirror = (function() {
   var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
                   19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
                   36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
-                  46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",",
+                  46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 127: "Delete", 186: ";", 187: "=", 188: ",",
                   189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp",
                   63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right",
                   63233: "Down", 63302: "Insert", 63272: "Delete"};
diff --git a/assets/codemirror/javascript.js b/assets/codemirror/javascript.js
index b9388bc..462f486 100644
--- a/assets/codemirror/javascript.js
+++ b/assets/codemirror/javascript.js
@@ -319,8 +319,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
         kwAllowed: true,
         cc: [],
         lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
-        localVars: null,
-        context: null,
+        localVars: parserConfig.localVars,
+        context: parserConfig.localVars && {vars: parserConfig.localVars},
         indented: 0
       };
     },
@@ -334,7 +334,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
       if (stream.eatSpace()) return null;
       var style = state.tokenize(stream, state);
       if (type == "comment") return style;
-      state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
+      state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
       state.kwAllowed = type != '.';
       return parseJS(state, style, type, content, stream);
     },