Skip to content
This repository
Browse code

Updated codemirror

  • Loading branch information...
commit f4622788681a21a3e4ec73bf228182f8b3609958 1 parent d41a046
Arkadiy Zabazhanov pyromaniac authored
952 app/assets/javascripts/puffer/codemirror.js
... ... @@ -1,4 +1,4 @@
1   -// CodeMirror version 2.2
  1 +// CodeMirror version 2.24
2 2 //
3 3 // All functions that need access to the editor's state live inside
4 4 // the CodeMirror function. Below that, at the bottom of the file,
@@ -6,7 +6,7 @@
6 6
7 7 // CodeMirror is the only global var we claim
8 8 var CodeMirror = (function() {
9   - // This is the function that produces an editor instance. It's
  9 + // This is the function that produces an editor instance. Its
10 10 // closure is used to store the editor state.
11 11 function CodeMirror(place, givenOptions) {
12 12 // Determine effective options based on given values and defaults.
@@ -15,24 +15,23 @@ var CodeMirror = (function() {
15 15 if (defaults.hasOwnProperty(opt))
16 16 options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
17 17
18   - var targetDocument = options["document"];
19 18 // The element in which the editor lives.
20   - var wrapper = targetDocument.createElement("div");
  19 + var wrapper = document.createElement("div");
21 20 wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
22 21 // This mess creates the base DOM structure for the editor.
23 22 wrapper.innerHTML =
24 23 '<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
25   - '<textarea style="position: absolute; padding: 0; width: 1px;" wrap="off" ' +
  24 + '<textarea style="position: absolute; padding: 0; width: 1px; height: 1em" wrap="off" ' +
26 25 'autocorrect="off" autocapitalize="off"></textarea></div>' +
27 26 '<div class="CodeMirror-scroll" tabindex="-1">' +
28 27 '<div style="position: relative">' + // Set to the height of the text, causes scrolling
29 28 '<div style="position: relative">' + // Moved around its parent to cover visible view
30 29 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
31 30 // Provides positioning relative to (visible) text origin
32   - '<div class="CodeMirror-lines"><div style="position: relative">' +
33   - '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden"></div>' +
  31 + '<div class="CodeMirror-lines"><div style="position: relative; z-index: 0">' +
  32 + '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden;"></div>' +
34 33 '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
35   - '<div></div>' + // This DIV contains the actual code
  34 + '<div style="position: relative; z-index: -1"></div><div></div>' + // DIVs containing the selection and the actual code
36 35 '</div></div></div></div></div>';
37 36 if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
38 37 // I've never seen more elegant code in my life.
@@ -40,13 +39,18 @@ var CodeMirror = (function() {
40 39 scroller = wrapper.lastChild, code = scroller.firstChild,
41 40 mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
42 41 lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
43   - cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
  42 + cursor = measure.nextSibling, selectionDiv = cursor.nextSibling,
  43 + lineDiv = selectionDiv.nextSibling;
44 44 themeChanged();
45 45 // Needed to hide big blue blinking cursor on Mobile Safari
46   - if (/AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)) input.style.width = "0px";
  46 + if (ios) input.style.width = "0px";
47 47 if (!webkit) lineSpace.draggable = true;
  48 + lineSpace.style.outline = "none";
48 49 if (options.tabindex != null) input.tabIndex = options.tabindex;
  50 + if (options.autofocus) focusInput();
49 51 if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
  52 + // Needed to handle Tab key in KHTML
  53 + if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
50 54
51 55 // Check for problem with IE innerHTML not working when we have a
52 56 // P (or similar) parent node.
@@ -71,19 +75,21 @@ var CodeMirror = (function() {
71 75 var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
72 76 // Selection-related flags. shiftSelecting obviously tracks
73 77 // whether the user is holding shift.
74   - var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false;
  78 + var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText,
  79 + overwrite = false, suppressEdits = false;
75 80 // Variables used by startOperation/endOperation to track what
76 81 // happened during the operation.
77 82 var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
78 83 gutterDirty, callbacks;
79 84 // Current visible range (may be bigger than the view window).
80 85 var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
81   - // bracketHighlighted is used to remember that a backet has been
  86 + // bracketHighlighted is used to remember that a bracket has been
82 87 // marked.
83 88 var bracketHighlighted;
84 89 // Tracks the maximum line length so that the horizontal scrollbar
85 90 // can be kept static when scrolling.
86   - var maxLine = "", maxWidth, tabText = computeTabText();
  91 + var maxLine = "", maxWidth;
  92 + var tabCache = {};
87 93
88 94 // Initialize the content.
89 95 operation(function(){setValue(options.value || ""); updateInput = false;})();
@@ -92,13 +98,13 @@ var CodeMirror = (function() {
92 98 // Register our event handlers.
93 99 connect(scroller, "mousedown", operation(onMouseDown));
94 100 connect(scroller, "dblclick", operation(onDoubleClick));
95   - connect(lineSpace, "dragstart", onDragStart);
96 101 connect(lineSpace, "selectstart", e_preventDefault);
97 102 // Gecko browsers fire contextmenu *after* opening the menu, at
98 103 // which point we can't mess with it anymore. Context menu is
99 104 // handled in onMouseDown for Gecko.
100 105 if (!gecko) connect(scroller, "contextmenu", onContextMenu);
101 106 connect(scroller, "scroll", function() {
  107 + lastScrollPos = scroller.scrollTop;
102 108 updateDisplay([]);
103 109 if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
104 110 if (options.onScroll) options.onScroll(instance);
@@ -111,17 +117,32 @@ var CodeMirror = (function() {
111 117 connect(input, "focus", onFocus);
112 118 connect(input, "blur", onBlur);
113 119
114   - connect(scroller, "dragenter", e_stop);
115   - connect(scroller, "dragover", e_stop);
116   - connect(scroller, "drop", operation(onDrop));
  120 + if (options.dragDrop) {
  121 + connect(lineSpace, "dragstart", onDragStart);
  122 + function drag_(e) {
  123 + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
  124 + e_stop(e);
  125 + }
  126 + connect(scroller, "dragenter", drag_);
  127 + connect(scroller, "dragover", drag_);
  128 + connect(scroller, "drop", operation(onDrop));
  129 + }
117 130 connect(scroller, "paste", function(){focusInput(); fastPoll();});
118 131 connect(input, "paste", fastPoll);
119   - connect(input, "cut", operation(function(){replaceSelection("");}));
  132 + connect(input, "cut", operation(function(){
  133 + if (!options.readOnly) replaceSelection("");
  134 + }));
  135 +
  136 + // Needed to handle Tab key in KHTML
  137 + if (khtml) connect(code, "mouseup", function() {
  138 + if (document.activeElement == input) input.blur();
  139 + focusInput();
  140 + });
120 141
121 142 // IE throws unspecified error in certain cases, when
122 143 // trying to access activeElement before onload
123   - var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
124   - if (hasFocus) setTimeout(onFocus, 20);
  144 + var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
  145 + if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
125 146 else onBlur();
126 147
127 148 function isLine(l) {return l >= 0 && l < doc.size;}
@@ -135,23 +156,30 @@ var CodeMirror = (function() {
135 156 setValue: operation(setValue),
136 157 getSelection: getSelection,
137 158 replaceSelection: operation(replaceSelection),
138   - focus: function(){focusInput(); onFocus(); fastPoll();},
  159 + focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
139 160 setOption: function(option, value) {
140 161 var oldVal = options[option];
141 162 options[option] = value;
142 163 if (option == "mode" || option == "indentUnit") loadMode();
143   - else if (option == "readOnly" && value) {onBlur(); input.blur();}
  164 + else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
  165 + else if (option == "readOnly" && !value) {resetInput(true);}
144 166 else if (option == "theme") themeChanged();
145 167 else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
146   - else if (option == "tabSize") operation(tabsChanged)();
147   - if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
148   - operation(gutterChanged)();
  168 + else if (option == "tabSize") updateDisplay(true);
  169 + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") {
  170 + gutterChanged();
  171 + updateDisplay(true);
  172 + }
149 173 },
150 174 getOption: function(option) {return options[option];},
151 175 undo: operation(undo),
152 176 redo: operation(redo),
153 177 indentLine: operation(function(n, dir) {
154   - if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
  178 + if (typeof dir != "string") {
  179 + if (dir == null) dir = options.smartIndent ? "smart" : "prev";
  180 + else dir = dir ? "add" : "subtract";
  181 + }
  182 + if (isLine(n)) indentLine(n, dir);
155 183 }),
156 184 indentSelection: operation(indentSelected),
157 185 historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
@@ -165,17 +193,23 @@ var CodeMirror = (function() {
165 193 line = clipLine(line == null ? doc.size - 1: line);
166 194 return getStateBefore(line + 1);
167 195 },
168   - cursorCoords: function(start){
  196 + cursorCoords: function(start, mode) {
169 197 if (start == null) start = sel.inverted;
170   - return pageCoords(start ? sel.from : sel.to);
  198 + return this.charCoords(start ? sel.from : sel.to, mode);
  199 + },
  200 + charCoords: function(pos, mode) {
  201 + pos = clipPos(pos);
  202 + if (mode == "local") return localCoords(pos, false);
  203 + if (mode == "div") return localCoords(pos, true);
  204 + return pageCoords(pos);
171 205 },
172   - charCoords: function(pos){return pageCoords(clipPos(pos));},
173 206 coordsChar: function(coords) {
174 207 var off = eltOffset(lineSpace);
175 208 return coordsChar(coords.x - off.left, coords.y - off.top);
176 209 },
177 210 markText: operation(markText),
178 211 setBookmark: setBookmark,
  212 + findMarksAt: findMarksAt,
179 213 setMarker: operation(addGutterMarker),
180 214 clearMarker: operation(removeGutterMarker),
181 215 setLineClass: operation(setLineClass),
@@ -243,12 +277,21 @@ var CodeMirror = (function() {
243 277 replaceRange: operation(replaceRange),
244 278 getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
245 279
  280 + triggerOnKeyDown: operation(onKeyDown),
246 281 execCommand: function(cmd) {return commands[cmd](instance);},
247 282 // Stuff used by commands, probably not much use to outside code.
248 283 moveH: operation(moveH),
249 284 deleteH: operation(deleteH),
250 285 moveV: operation(moveV),
251   - toggleOverwrite: function() {overwrite = !overwrite;},
  286 + toggleOverwrite: function() {
  287 + if(overwrite){
  288 + overwrite = false;
  289 + cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
  290 + } else {
  291 + overwrite = true;
  292 + cursor.className += " CodeMirror-overwrite";
  293 + }
  294 + },
252 295
253 296 posFromIndex: function(off) {
254 297 var lineNo = 0, ch;
@@ -268,9 +311,19 @@ var CodeMirror = (function() {
268 311 });
269 312 return index;
270 313 },
  314 + scrollTo: function(x, y) {
  315 + if (x != null) scroller.scrollLeft = x;
  316 + if (y != null) scroller.scrollTop = y;
  317 + updateDisplay([]);
  318 + },
271 319
272 320 operation: function(f){return operation(f)();},
273   - refresh: function(){updateDisplay(true);},
  321 + compoundChange: function(f){return compoundChange(f);},
  322 + refresh: function(){
  323 + updateDisplay(true);
  324 + if (scroller.scrollHeight > lastScrollPos)
  325 + scroller.scrollTop = lastScrollPos;
  326 + },
274 327 getInputField: function(){return input;},
275 328 getWrapperElement: function(){return wrapper;},
276 329 getScrollerElement: function(){return scroller;},
@@ -290,14 +343,14 @@ var CodeMirror = (function() {
290 343 splitLines(code), top, top);
291 344 updateInput = true;
292 345 }
293   - function getValue(code) {
  346 + function getValue() {
294 347 var text = [];
295 348 doc.iter(0, doc.size, function(line) { text.push(line.text); });
296 349 return text.join("\n");
297 350 }
298 351
299 352 function onMouseDown(e) {
300   - setShift(e.shiftKey);
  353 + setShift(e_prop(e, "shiftKey"));
301 354 // Check whether this is a click in a widget
302 355 for (var n = e_target(e); n != wrapper; n = n.parentNode)
303 356 if (n.parentNode == code && n != mover) return;
@@ -339,21 +392,25 @@ var CodeMirror = (function() {
339 392 } else { lastClick = {time: now, pos: start}; }
340 393
341 394 var last = start, going;
342   - if (dragAndDrop && !posEq(sel.from, sel.to) &&
  395 + if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
343 396 !posLess(start, sel.from) && !posLess(sel.to, start)) {
344 397 // Let the drag handler handle this.
345 398 if (webkit) lineSpace.draggable = true;
346   - var up = connect(targetDocument, "mouseup", operation(function(e2) {
  399 + function dragEnd(e2) {
347 400 if (webkit) lineSpace.draggable = false;
348 401 draggingText = false;
349   - up();
  402 + up(); drop();
350 403 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
351 404 e_preventDefault(e2);
352 405 setCursor(start.line, start.ch, true);
353 406 focusInput();
354 407 }
355   - }), true);
  408 + }
  409 + var up = connect(document, "mouseup", operation(dragEnd), true);
  410 + var drop = connect(scroller, "drop", operation(dragEnd), true);
356 411 draggingText = true;
  412 + // IE's approach to draggable
  413 + if (lineSpace.dragDrop) lineSpace.dragDrop();
357 414 return;
358 415 }
359 416 e_preventDefault(e);
@@ -372,12 +429,7 @@ var CodeMirror = (function() {
372 429 }
373 430 }
374 431
375   - var move = connect(targetDocument, "mousemove", operation(function(e) {
376   - clearTimeout(going);
377   - e_preventDefault(e);
378   - extend(e);
379   - }), true);
380   - var up = connect(targetDocument, "mouseup", operation(function(e) {
  432 + function done(e) {
381 433 clearTimeout(going);
382 434 var cur = posFromMouse(e);
383 435 if (cur) setSelectionUser(start, cur);
@@ -385,7 +437,14 @@ var CodeMirror = (function() {
385 437 focusInput();
386 438 updateInput = true;
387 439 move(); up();
  440 + }
  441 + var move = connect(document, "mousemove", operation(function(e) {
  442 + clearTimeout(going);
  443 + e_preventDefault(e);
  444 + if (!ie && !e_button(e)) done(e);
  445 + else extend(e);
388 446 }), true);
  447 + var up = connect(document, "mouseup", operation(done), true);
389 448 }
390 449 function onDoubleClick(e) {
391 450 for (var n = e_target(e); n != wrapper; n = n.parentNode)
@@ -397,6 +456,7 @@ var CodeMirror = (function() {
397 456 selectWordAt(start);
398 457 }
399 458 function onDrop(e) {
  459 + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
400 460 e.preventDefault();
401 461 var pos = posFromMouse(e, true), files = e.dataTransfer.files;
402 462 if (!pos || options.readOnly) return;
@@ -406,12 +466,12 @@ var CodeMirror = (function() {
406 466 reader.onload = function() {
407 467 text[i] = reader.result;
408 468 if (++read == n) {
409   - pos = clipPos(pos);
410   - operation(function() {
  469 + pos = clipPos(pos);
  470 + operation(function() {
411 471 var end = replaceRange(text.join(""), pos, pos);
412 472 setSelectionUser(pos, end);
413 473 })();
414   - }
  474 + }
415 475 };
416 476 reader.readAsText(file);
417 477 }
@@ -422,87 +482,130 @@ var CodeMirror = (function() {
422 482 try {
423 483 var text = e.dataTransfer.getData("Text");
424 484 if (text) {
425   - var end = replaceRange(text, pos, pos);
426   - var curFrom = sel.from, curTo = sel.to;
427   - setSelectionUser(pos, end);
428   - if (draggingText) replaceRange("", curFrom, curTo);
429   - focusInput();
430   - }
  485 + compoundChange(function() {
  486 + var curFrom = sel.from, curTo = sel.to;
  487 + setSelectionUser(pos, pos);
  488 + if (draggingText) replaceRange("", curFrom, curTo);
  489 + replaceSelection(text);
  490 + focusInput();
  491 + });
  492 + }
431 493 }
432 494 catch(e){}
433 495 }
434 496 }
435 497 function onDragStart(e) {
436 498 var txt = getSelection();
437   - // This will reset escapeElement
438   - htmlEscape(txt);
439   - e.dataTransfer.setDragImage(escapeElement, 0, 0);
440 499 e.dataTransfer.setData("Text", txt);
441   - }
442   - function handleKeyBinding(e) {
443   - var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift;
444   - if (name == null || e.altGraphKey) {
445   - if (next) options.keyMap = next;
446   - return null;
447   - }
448   - if (e.altKey) name = "Alt-" + name;
449   - if (e.ctrlKey) name = "Ctrl-" + name;
450   - if (e.metaKey) name = "Cmd-" + name;
451   - if (e.shiftKey && (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) {
452   - dropShift = true;
453   - } else {
454   - bound = lookupKey(name, options.extraKeys, options.keyMap);
  500 +
  501 + // Use dummy image instead of default browsers image.
  502 + if (gecko || chrome) {
  503 + var img = document.createElement('img');
  504 + img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image
  505 + e.dataTransfer.setDragImage(img, 0, 0);
455 506 }
  507 + }
  508 +
  509 + function doHandleBinding(bound, dropShift) {
456 510 if (typeof bound == "string") {
457   - if (commands.propertyIsEnumerable(bound)) bound = commands[bound];
458   - else bound = null;
459   - }
460   - if (next && (bound || !isModifierKey(e))) options.keyMap = next;
461   - if (!bound) return false;
462   - if (dropShift) {
463   - var prevShift = shiftSelecting;
464   - shiftSelecting = null;
  511 + bound = commands[bound];
  512 + if (!bound) return false;
  513 + }
  514 + var prevShift = shiftSelecting;
  515 + try {
  516 + if (options.readOnly) suppressEdits = true;
  517 + if (dropShift) shiftSelecting = null;
465 518 bound(instance);
  519 + } catch(e) {
  520 + if (e != Pass) throw e;
  521 + return false;
  522 + } finally {
466 523 shiftSelecting = prevShift;
467   - } else bound(instance);
468   - e_preventDefault(e);
  524 + suppressEdits = false;
  525 + }
469 526 return true;
470 527 }
471   - var lastStoppedKey = null;
  528 + function handleKeyBinding(e) {
  529 + // Handle auto keymap transitions
  530 + var startMap = getKeyMap(options.keyMap), next = startMap.auto;
  531 + clearTimeout(maybeTransition);
  532 + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
  533 + if (getKeyMap(options.keyMap) == startMap) {
  534 + options.keyMap = (next.call ? next.call(null, instance) : next);
  535 + }
  536 + }, 50);
  537 +
  538 + var name = keyNames[e_prop(e, "keyCode")], handled = false;
  539 + if (name == null || e.altGraphKey) return false;
  540 + if (e_prop(e, "altKey")) name = "Alt-" + name;
  541 + if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
  542 + if (e_prop(e, "metaKey")) name = "Cmd-" + name;
  543 +
  544 + var stopped = false;
  545 + function stop() { stopped = true; }
  546 +
  547 + if (e_prop(e, "shiftKey")) {
  548 + handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
  549 + function(b) {return doHandleBinding(b, true);}, stop)
  550 + || lookupKey(name, options.extraKeys, options.keyMap, function(b) {
  551 + if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
  552 + }, stop);
  553 + } else {
  554 + handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop);
  555 + }
  556 + if (stopped) handled = false;
  557 + if (handled) {
  558 + e_preventDefault(e);
  559 + if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
  560 + }
  561 + return handled;
  562 + }
  563 + function handleCharBinding(e, ch) {
  564 + var handled = lookupKey("'" + ch + "'", options.extraKeys,
  565 + options.keyMap, function(b) { return doHandleBinding(b, true); });
  566 + if (handled) e_preventDefault(e);
  567 + return handled;
  568 + }
  569 +
  570 + var lastStoppedKey = null, maybeTransition;
472 571 function onKeyDown(e) {
473 572 if (!focused) onFocus();
474   - var code = e.keyCode;
  573 + if (ie && e.keyCode == 27) { e.returnValue = false; }
  574 + if (pollingFast) { if (readInput()) pollingFast = false; }
  575 + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
  576 + var code = e_prop(e, "keyCode");
475 577 // IE does strange things with escape.
476   - if (ie && code == 27) { e.returnValue = false; }
477   - setShift(code == 16 || e.shiftKey);
  578 + setShift(code == 16 || e_prop(e, "shiftKey"));
478 579 // First give onKeyEvent option a chance to handle this.
479   - if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
480 580 var handled = handleKeyBinding(e);
481 581 if (window.opera) {
482   - lastStoppedKey = handled ? e.keyCode : null;
  582 + lastStoppedKey = handled ? code : null;
483 583 // Opera has no cut event... we try to at least catch the key combo
484   - if (!handled && (mac ? e.metaKey : e.ctrlKey) && e.keyCode == 88)
  584 + if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
485 585 replaceSelection("");
486 586 }
487 587 }
488 588 function onKeyPress(e) {
489   - if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
  589 + if (pollingFast) readInput();
490 590 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
491   - if (window.opera && !e.which && handleKeyBinding(e)) return;
492   - if (options.electricChars && mode.electricChars) {
493   - var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
  591 + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
  592 + if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
  593 + if (((window.opera && !e.which) || khtml) && handleKeyBinding(e)) return;
  594 + var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
  595 + if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
494 596 if (mode.electricChars.indexOf(ch) > -1)
495 597 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
496 598 }
  599 + if (handleCharBinding(e, ch)) return;
497 600 fastPoll();
498 601 }
499 602 function onKeyUp(e) {
500 603 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
501   - if (e.keyCode == 16) shiftSelecting = null;
  604 + if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
502 605 }
503 606
504 607 function onFocus() {
505   - if (options.readOnly) return;
  608 + if (options.readOnly == "nocursor") return;
506 609 if (!focused) {
507 610 if (options.onFocus) options.onFocus(instance);
508 611 focused = true;
@@ -517,6 +620,10 @@ var CodeMirror = (function() {
517 620 if (focused) {
518 621 if (options.onBlur) options.onBlur(instance);
519 622 focused = false;
  623 + if (bracketHighlighted)
  624 + operation(function(){
  625 + if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
  626 + })();
520 627 wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
521 628 }
522 629 clearInterval(blinker);
@@ -526,6 +633,7 @@ var CodeMirror = (function() {
526 633 // Replace the range from from to to by the strings in newText.
527 634 // Afterwards, set the selection to selFrom, selTo.
528 635 function updateLines(from, to, newText, selFrom, selTo) {
  636 + if (suppressEdits) return;
529 637 if (history) {
530 638 var old = [];
531 639 doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
@@ -535,24 +643,28 @@ var CodeMirror = (function() {
535 643 updateLinesNoUndo(from, to, newText, selFrom, selTo);
536 644 }
537 645 function unredoHelper(from, to) {
538   - var change = from.pop();
539   - if (change) {
  646 + if (!from.length) return;
  647 + var set = from.pop(), out = [];
  648 + for (var i = set.length - 1; i >= 0; i -= 1) {
  649 + var change = set[i];
540 650 var replaced = [], end = change.start + change.added;
541 651 doc.iter(change.start, end, function(line) { replaced.push(line.text); });
542   - to.push({start: change.start, added: change.old.length, old: replaced});
  652 + out.push({start: change.start, added: change.old.length, old: replaced});
543 653 var pos = clipPos({line: change.start + change.old.length - 1,
544 654 ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
545 655 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
546   - updateInput = true;
547 656 }
  657 + updateInput = true;
  658 + to.push(out);
548 659 }
549 660 function undo() {unredoHelper(history.done, history.undone);}
550 661 function redo() {unredoHelper(history.undone, history.done);}
551 662
552 663 function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
  664 + if (suppressEdits) return;
553 665 var recomputeMaxLength = false, maxLineLength = maxLine.length;
554 666 if (!options.lineWrapping)
555   - doc.iter(from.line, to.line, function(line) {
  667 + doc.iter(from.line, to.line + 1, function(line) {
556 668 if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
557 669 });
558 670 if (from.line != to.line || newText.length > 1) gutterDirty = true;
@@ -600,14 +712,14 @@ var CodeMirror = (function() {
600 712 doc.insert(from.line + 1, added);
601 713 }
602 714 if (options.lineWrapping) {
603   - var perLine = scroller.clientWidth / charWidth() - 3;
  715 + var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
604 716 doc.iter(from.line, from.line + newText.length, function(line) {
605 717 if (line.hidden) return;
606 718 var guess = Math.ceil(line.text.length / perLine) || 1;
607 719 if (guess != line.height) updateLineHeight(line, guess);
608 720 });
609 721 } else {
610   - doc.iter(from.line, i + newText.length, function(line) {
  722 + doc.iter(from.line, from.line + newText.length, function(line) {
611 723 var l = line.text;
612 724 if (l.length > maxLineLength) {
613 725 maxLine = l; maxLineLength = l.length; maxWidth = null;
@@ -651,7 +763,8 @@ var CodeMirror = (function() {
651 763 setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
652 764
653 765 // Make sure the scroll-size div has the correct height.
654   - code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
  766 + if (scroller.clientHeight)
  767 + code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
655 768 }
656 769
657 770 function replaceRange(code, from, to) {
@@ -729,7 +842,7 @@ var CodeMirror = (function() {
729 842 // supported or compatible enough yet to rely on.)
730 843 var prevInput = "";
731 844 function readInput() {
732   - if (leaveInputAlone || !focused || hasSelection(input)) return false;
  845 + if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false;
733 846 var text = input.value;
734 847 if (text == prevInput) return false;
735 848 shiftSelecting = null;
@@ -747,12 +860,12 @@ var CodeMirror = (function() {
747 860 if (!posEq(sel.from, sel.to)) {
748 861 prevInput = "";
749 862 input.value = getSelection();
750   - input.select();
  863 + selectInput(input);
751 864 } else if (user) prevInput = input.value = "";
752 865 }
753 866
754 867 function focusInput() {
755   - if (!options.readOnly) input.focus();
  868 + if (options.readOnly != "nocursor") input.focus();
756 869 }
757 870
758 871 function scrollEditorIntoView() {
@@ -769,16 +882,17 @@ var CodeMirror = (function() {
769 882 return scrollIntoView(x, cursor.y, x, cursor.yBot);
770 883 }
771 884 function scrollIntoView(x1, y1, x2, y2) {
772   - var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();
  885 + var pl = paddingLeft(), pt = paddingTop();
773 886 y1 += pt; y2 += pt; x1 += pl; x2 += pl;
774 887 var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
775   - if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
776   - else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
  888 + if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1); scrolled = true;}
  889 + else if (y2 > screentop + screen) {scroller.scrollTop = y2 - screen; scrolled = true;}
777 890
778 891 var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
779 892 var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
780   - if (x1 < screenleft + gutterw) {
781   - if (x1 < 50) x1 = 0;
  893 + var atLeft = x1 < gutterw + pl + 10;
  894 + if (x1 < screenleft + gutterw || atLeft) {
  895 + if (atLeft) x1 = 0;
782 896 scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
783 897 scrolled = true;
784 898 }
@@ -793,10 +907,10 @@ var CodeMirror = (function() {
793 907
794 908 function visibleLines() {
795 909 var lh = textHeight(), top = scroller.scrollTop - paddingTop();
796   - var from_height = Math.max(0, Math.floor(top / lh));
797   - var to_height = Math.ceil((top + scroller.clientHeight) / lh);
798   - return {from: lineAtHeight(doc, from_height),
799   - to: lineAtHeight(doc, to_height)};
  910 + var fromHeight = Math.max(0, Math.floor(top / lh));
  911 + var toHeight = Math.ceil((top + scroller.clientHeight) / lh);
  912 + return {from: lineAtHeight(doc, fromHeight),
  913 + to: lineAtHeight(doc, toHeight)};
800 914 }
801 915 // Uses a set of changes plus the current scroll position to
802 916 // determine which DOM updates have to be made, and makes the
@@ -809,7 +923,7 @@ var CodeMirror = (function() {
809 923 // Compute the new visible window
810 924 var visible = visibleLines();
811 925 // Bail out if the visible area is already rendered and nothing changed.
812   - if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;
  926 + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return;
813 927 var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
814 928 if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
815 929 if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
@@ -827,13 +941,13 @@ var CodeMirror = (function() {
827 941 if (range.from >= range.to) intact.splice(i--, 1);
828 942 else intactLines += range.to - range.from;
829 943 }
830   - if (intactLines == to - from) return;
  944 + if (intactLines == to - from && from == showingFrom && to == showingTo) return;
831 945 intact.sort(function(a, b) {return a.domStart - b.domStart;});
832 946
833 947 var th = textHeight(), gutterDisplay = gutter.style.display;
834   - lineDiv.style.display = gutter.style.display = "none";
  948 + lineDiv.style.display = "none";
835 949 patchDisplay(from, to, intact);
836   - lineDiv.style.display = "";
  950 + lineDiv.style.display = gutter.style.display = "";
837 951
838 952 // Position the mover div to align with the lines it's supposed
839 953 // to be showing (which will cover the visible display)
@@ -844,7 +958,8 @@ var CodeMirror = (function() {
844 958 showingFrom = from; showingTo = to;
845 959 displayOffset = heightAtLine(doc, from);
846 960 mover.style.top = (displayOffset * th) + "px";
847   - code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
  961 + if (scroller.clientHeight)
  962 + code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
848 963
849 964 // Since this is all rather error prone, it is honoured with the
850 965 // only assertion in the whole file.
@@ -852,16 +967,26 @@ var CodeMirror = (function() {
852 967 throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
853 968 " nodes=" + lineDiv.childNodes.length);
854 969
855   - if (options.lineWrapping) {
  970 + function checkHeights() {
856 971 maxWidth = scroller.clientWidth;
857   - var curNode = lineDiv.firstChild;
  972 + var curNode = lineDiv.firstChild, heightChanged = false;
858 973 doc.iter(showingFrom, showingTo, function(line) {
859 974 if (!line.hidden) {
860 975 var height = Math.round(curNode.offsetHeight / th) || 1;
861   - if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}
  976 + if (line.height != height) {
  977 + updateLineHeight(line, height);
  978 + gutterDirty = heightChanged = true;
  979 + }
862 980 }
863 981 curNode = curNode.nextSibling;
864 982 });
  983 + if (heightChanged)
  984 + code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
  985 + return heightChanged;
  986 + }
  987 +
  988 + if (options.lineWrapping) {
  989 + checkHeights();
865 990 } else {
866 991 if (maxWidth == null) maxWidth = stringWidth(maxLine);
867 992 if (maxWidth > scroller.clientWidth) {
@@ -873,9 +998,13 @@ var CodeMirror = (function() {
873 998 lineSpace.style.width = code.style.width = "";
874 999 }
875 1000 }
  1001 +
876 1002 gutter.style.display = gutterDisplay;
877   - if (different || gutterDirty) updateGutter();
878   - updateCursor();
  1003 + if (different || gutterDirty) {
  1004 + // If the gutter grew in size, re-check heights. If those changed, re-draw gutter.
  1005 + updateGutter() && options.lineWrapping && checkHeights() && updateGutter();
  1006 + }
  1007 + updateSelection();
879 1008 if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
880 1009 return true;
881 1010 }
@@ -922,21 +1051,20 @@ var CodeMirror = (function() {
922 1051 }
923 1052 // This pass fills in the lines that actually changed.
924 1053 var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
925   - var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;
926   - var scratch = targetDocument.createElement("div"), newElt;
  1054 + var scratch = document.createElement("div");
927 1055 doc.iter(from, to, function(line) {
928   - var ch1 = null, ch2 = null;
929   - if (inSel) {
930   - ch1 = 0;
931   - if (sto == j) {inSel = false; ch2 = sel.to.ch;}
932   - } else if (sfrom == j) {
933   - if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
934   - else {inSel = true; ch1 = sel.from.ch;}
935   - }
936 1056 if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
937 1057 if (!nextIntact || nextIntact.from > j) {
938   - if (line.hidden) scratch.innerHTML = "<pre></pre>";
939   - else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);
  1058 + if (line.hidden) var html = scratch.innerHTML = "<pre></pre>";
  1059 + else {
  1060 + var html = '<pre' + (line.className ? ' class="' + line.className + '"' : '') + '>'
  1061 + + line.getHTML(makeTab) + '</pre>';
  1062 + // Kludge to make sure the styled element lies behind the selection (by z-index)
  1063 + if (line.bgClassName)
  1064 + html = '<div style="position: relative"><pre class="' + line.bgClassName +
  1065 + '" style="position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2">&#160;</pre>' + html + "</div>";
  1066 + }
  1067 + scratch.innerHTML = html;
940 1068 lineDiv.insertBefore(scratch.firstChild, curNode);
941 1069 } else {
942 1070 curNode = curNode.nextSibling;
@@ -949,7 +1077,7 @@ var CodeMirror = (function() {
949 1077 if (!options.gutter && !options.lineNumbers) return;
950 1078 var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
951 1079 gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
952   - var html = [], i = showingFrom;
  1080 + var html = [], i = showingFrom, normalNode;
953 1081 doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
954 1082 if (line.hidden) {
955 1083 html.push("<pre></pre>");
@@ -963,30 +1091,62 @@ var CodeMirror = (function() {
963 1091 html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
964 1092 for (var j = 1; j < line.height; ++j) html.push("<br/>&#160;");
965 1093 html.push("</pre>");
  1094 + if (!marker) normalNode = i;
966 1095 }
967 1096 ++i;
968 1097 });
969 1098 gutter.style.display = "none";
970 1099 gutterText.innerHTML = html.join("");
971   - var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
972   - while (val.length + pad.length < minwidth) pad += "\u00a0";
973   - if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
  1100 + // Make sure scrolling doesn't cause number gutter size to pop
  1101 + if (normalNode != null) {
  1102 + var node = gutterText.childNodes[normalNode - showingFrom];
  1103 + var minwidth = String(doc.size).length, val = eltText(node), pad = "";
  1104 + while (val.length + pad.length < minwidth) pad += "\u00a0";
  1105 + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild);
  1106 + }
974 1107 gutter.style.display = "";
  1108 + var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2;
975 1109 lineSpace.style.marginLeft = gutter.offsetWidth + "px";
976 1110 gutterDirty = false;
  1111 + return resized;
977 1112 }
978   - function updateCursor() {
979   - var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
980   - var pos = localCoords(head, true);
  1113 + function updateSelection() {
  1114 + var collapsed = posEq(sel.from, sel.to);
  1115 + var fromPos = localCoords(sel.from, true);
  1116 + var toPos = collapsed ? fromPos : localCoords(sel.to, true);
  1117 + var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
981 1118 var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
982   - inputDiv.style.top = (pos.y + lineOff.top - wrapOff.top) + "px";
983   - inputDiv.style.left = (pos.x + lineOff.left - wrapOff.left) + "px";
984   - if (posEq(sel.from, sel.to)) {
985   - cursor.style.top = pos.y + "px";
986   - cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
  1119 + inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
  1120 + inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
  1121 + if (collapsed) {
  1122 + cursor.style.top = headPos.y + "px";
  1123 + cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
987 1124 cursor.style.display = "";
  1125 + selectionDiv.style.display = "none";
  1126 + } else {
  1127 + var sameLine = fromPos.y == toPos.y, html = "";
  1128 + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
  1129 + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight;
  1130 + function add(left, top, right, height) {
  1131 + var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px"
  1132 + : "right: " + right + "px";
  1133 + html += '<div class="CodeMirror-selected" style="position: absolute; left: ' + left +
  1134 + 'px; top: ' + top + 'px; ' + rstyle + '; height: ' + height + 'px"></div>';
  1135 + }
  1136 + if (sel.from.ch && fromPos.y >= 0) {
  1137 + var right = sameLine ? clientWidth - toPos.x : 0;
  1138 + add(fromPos.x, fromPos.y, right, th);
  1139 + }
  1140 + var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
  1141 + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;
  1142 + if (middleHeight > 0.2 * th)
  1143 + add(0, middleStart, 0, middleHeight);
  1144 + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th)
  1145 + add(0, toPos.y, clientWidth - toPos.x, th);
  1146 + selectionDiv.innerHTML = html;
  1147 + cursor.style.display = "none";
  1148 + selectionDiv.style.display = "";
988 1149 }
989   - else cursor.style.display = "none";
990 1150 }
991 1151
992 1152 function setShift(val) {
@@ -1012,37 +1172,32 @@ var CodeMirror = (function() {
1012 1172 if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
1013 1173
1014 1174 // Skip over hidden lines.
1015   - if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);
  1175 + if (from.line != oldFrom) {
  1176 + var from1 = skipHidden(from, oldFrom, sel.from.ch);
  1177 + // If there is no non-hidden line left, force visibility on current line
  1178 + if (!from1) setLineHidden(from.line, false);
  1179 + else from = from1;
  1180 + }
1016 1181 if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
1017 1182
1018 1183 if (posEq(from, to)) sel.inverted = false;
1019 1184 else if (posEq(from, sel.to)) sel.inverted = false;
1020 1185 else if (posEq(to, sel.from)) sel.inverted = true;
1021 1186
1022   - // Some ugly logic used to only mark the lines that actually did
1023   - // see a change in selection as changed, rather than the whole
1024   - // selected range.
1025   - if (posEq(from, to)) {
1026   - if (!posEq(sel.from, sel.to))
1027   - changes.push({from: oldFrom, to: oldTo + 1});
1028   - }
1029   - else if (posEq(sel.from, sel.to)) {
1030   - changes.push({from: from.line, to: to.line + 1});
1031   - }
1032   - else {
1033   - if (!posEq(from, sel.from)) {
1034   - if (from.line < oldFrom)
1035   - changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1});
1036   - else
1037   - changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1});
1038   - }
1039   - if (!posEq(to, sel.to)) {
1040   - if (to.line < oldTo)
1041   - changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1});
1042   - else
1043   - changes.push({from: Math.max(from.line, oldTo), to: to.line + 1});
  1187 + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) {
  1188 + var head = sel.inverted ? from : to;
  1189 + if (head.line != sel.from.line && sel.from.line < doc.size) {
  1190 + var oldLine = getLine(sel.from.line);
  1191 + if (/^\s+$/.test(oldLine.text))
  1192 + setTimeout(operation(function() {
  1193 + if (oldLine.parent && /^\s+$/.test(oldLine.text)) {
  1194 + var no = lineNo(oldLine);
  1195 + replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
  1196 + }
  1197 + }, 10));
1044 1198 }
1045 1199 }
  1200 +
1046 1201 sel.from = from; sel.to = to;
1047 1202 selectionChanged = true;
1048 1203 }
@@ -1053,13 +1208,14 @@ var CodeMirror = (function() {
1053 1208 var line = getLine(lNo);
1054 1209 if (!line.hidden) {
1055 1210 var ch = pos.ch;
1056   - if (ch > oldCh || ch > line.text.length) ch = line.text.length;
  1211 + if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length;
1057 1212 return {line: lNo, ch: ch};
1058 1213 }
1059 1214 lNo += dir;
1060 1215 }
1061 1216 }
1062 1217 var line = getLine(pos.line);
  1218 + var toEnd = pos.ch == line.text.length && pos.ch != oldCh;
1063 1219 if (!line.hidden) return pos;
1064 1220 if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
1065 1221 else return getNonHidden(-1) || getNonHidden(1);
@@ -1123,9 +1279,10 @@ var CodeMirror = (function() {
1123 1279 function moveV(dir, unit) {
1124 1280 var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
1125 1281 if (goalColumn != null) pos.x = goalColumn;
1126   - if (unit == "page") dist = scroller.clientHeight;
  1282 + if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
1127 1283 else if (unit == "line") dist = textHeight();
1128 1284 var target = coordsChar(pos.x, pos.y + dist * dir + 2);
  1285 + if (unit == "page") scroller.scrollTop += localCoords(target, true).y - pos.y;
1129 1286 setCursor(target.line, target.ch, true);
1130 1287 goalColumn = pos.x;
1131 1288 }
@@ -1138,7 +1295,7 @@ var CodeMirror = (function() {
1138 1295 setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
1139 1296 }
1140 1297 function selectLine(line) {
1141   - setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
  1298 + setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0}));
1142 1299 }
1143 1300 function indentSelected(mode) {
1144 1301 if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
@@ -1211,16 +1368,14 @@ var CodeMirror = (function() {
1211 1368 }
1212 1369 changes.push({from: 0, to: doc.size});
1213 1370 }
1214   - function computeTabText() {
1215   - for (var str = '<span class="cm-tab">', i = 0; i < options.tabSize; ++i) str += " ";
1216   - return str + "</span>";
1217   - }
1218   - function tabsChanged() {
1219   - tabText = computeTabText();
1220   - updateDisplay(true);
  1371 + function makeTab(col) {
  1372 + var w = options.tabSize - col % options.tabSize, cached = tabCache[w];
  1373 + if (cached) return cached;
  1374 + for (var str = '<span class="cm-tab">', i = 0; i < w; ++i) str += " ";
  1375 + return (tabCache[w] = {html: str + "</span>", width: w});
1221 1376 }
1222 1377 function themeChanged() {
1223   - scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") +
  1378 + scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") +
1224 1379 options.theme.replace(/(^|\s)\s*/g, " cm-s-");
1225 1380 }
1226 1381
@@ -1233,7 +1388,7 @@ var CodeMirror = (function() {
1233 1388 var lineN = lineNo(line);
1234 1389 min = Math.min(min, lineN); max = Math.max(max, lineN);
1235 1390 for (var j = 0; j < mk.length; ++j)
1236   - if (mk[j].set == this.set) mk.splice(j--, 1);
  1391 + if (mk[j].marker == this) mk.splice(j--, 1);
1237 1392 }
1238 1393 if (min != Infinity)
1239 1394 changes.push({from: min, to: max + 1});
@@ -1244,7 +1399,7 @@ var CodeMirror = (function() {
1244 1399 var line = this.set[i], mk = line.marked;
1245 1400 for (var j = 0; j < mk.length; ++j) {
1246 1401 var mark = mk[j];
1247   - if (mark.set == this.set) {
  1402 + if (mark.marker == this) {
1248 1403 if (mark.from != null || mark.to != null) {
1249 1404 var found = lineNo(line);
1250 1405 if (found != null) {
@@ -1261,8 +1416,9 @@ var CodeMirror = (function() {
1261 1416 function markText(from, to, className) {
1262 1417 from = clipPos(from); to = clipPos(to);
1263 1418 var tm = new TextMarker();
  1419 + if (!posLess(from, to)) return tm;
1264 1420 function add(line, from, to, className) {
1265   - getLine(line).addMark(new MarkedText(from, to, className, tm.set));
  1421 + getLine(line).addMark(new MarkedText(from, to, className, tm));
1266 1422 }
1267 1423 if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1268 1424 else {
@@ -1282,6 +1438,19 @@ var CodeMirror = (function() {
1282 1438 return bm;
1283 1439 }
1284 1440
  1441 + function findMarksAt(pos) {
  1442 + pos = clipPos(pos);
  1443 + var markers = [], marked = getLine(pos.line).marked;
  1444 + if (!marked) return markers;
  1445 + for (var i = 0, e = marked.length; i < e; ++i) {
  1446 + var m = marked[i];
  1447 + if ((m.from == null || m.from <= pos.ch) &&
  1448 + (m.to == null || m.to >= pos.ch))
  1449 + markers.push(m.marker || m);
  1450 + }
  1451 + return markers;
  1452 + }
  1453 +
1285 1454 function addGutterMarker(line, text, className) {
1286 1455 if (typeof line == "number") line = getLine(clipLine(line));
1287 1456 line.gutterMarker = {text: text, style: className};
@@ -1303,10 +1472,11 @@ var CodeMirror = (function() {
1303 1472 else return null;
1304 1473 return line;
1305 1474 }
1306   - function setLineClass(handle, className) {
  1475 + function setLineClass(handle, className, bgClassName) {
1307 1476 return changeLine(handle, function(line) {
1308   - if (line.className != className) {
  1477 + if (line.className != className || line.bgClassName != bgClassName) {
1309 1478 line.className = className;
  1479 + line.bgClassName = bgClassName;
1310 1480 return true;
1311 1481 }
1312 1482 });
@@ -1316,9 +1486,14 @@ var CodeMirror = (function() {
1316 1486 if (line.hidden != hidden) {
1317 1487 line.hidden = hidden;
1318 1488 updateLineHeight(line, hidden ? 0 : 1);
1319   - if (hidden && (sel.from.line == no || sel.to.line == no))
1320   - setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),
1321   - skipHidden(sel.to, sel.to.line, sel.to.ch));
  1489 + var fline = sel.from.line, tline = sel.to.line;
  1490 + if (hidden && (fline == no || tline == no)) {
  1491 + var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
  1492 + var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
  1493 + // Can't hide the last visible line, we'd have no place to put the cursor
  1494 + if (!to) return;
  1495 + setSelection(from, to);
  1496 + }
1322 1497 return (gutterDirty = true);
1323 1498 }
1324 1499 });
@@ -1337,7 +1512,7 @@ var CodeMirror = (function() {
1337 1512 }
1338 1513 var marker = line.gutterMarker;
1339 1514 return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
1340   - markerClass: marker && marker.style, lineClass: line.className};
  1515 + markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName};
1341 1516 }
1342 1517
1343 1518 function stringWidth(str) {