46373bfb7711b50071fdccc0698a8904418089d8
[WebKit-https.git] / Source / WebInspectorUI / Tools / PrettyPrinting / codemirror.js
1 // CodeMirror is the only global var we claim
2 window.CodeMirror = (function() {
3   "use strict";
4
5   // BROWSER SNIFFING
6
7   // Crude, but necessary to handle a number of hard-to-feature-detect
8   // bugs and behavior differences.
9   var gecko = /gecko\/\d/i.test(navigator.userAgent);
10   // IE11 currently doesn't count as 'ie', since it has almost none of
11   // the same bugs as earlier versions. Use ie_gt10 to handle
12   // incompatibilities in that version.
13   var ie = /MSIE \d/.test(navigator.userAgent);
14   var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
15   var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
16   var ie_gt10 = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent);
17   var webkit = /WebKit\//.test(navigator.userAgent);
18   var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
19   var chrome = /Chrome\//.test(navigator.userAgent);
20   var opera = /Opera\//.test(navigator.userAgent);
21   var safari = /Apple Computer/.test(navigator.vendor);
22   var khtml = /KHTML\//.test(navigator.userAgent);
23   var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
24   var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
25   var phantom = /PhantomJS/.test(navigator.userAgent);
26
27   var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
28   // This is woefully incomplete. Suggestions for alternative methods welcome.
29   var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
30   var mac = ios || /Mac/.test(navigator.platform);
31   var windows = /win/i.test(navigator.platform);
32
33   var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
34   if (opera_version) opera_version = Number(opera_version[1]);
35   if (opera_version && opera_version >= 15) { opera = false; webkit = true; }
36   // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
37   var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
38   var captureMiddleClick = gecko || (ie && !ie_lt9);
39
40   // Optimize some code when these features are not used
41   var sawReadOnlySpans = false, sawCollapsedSpans = false;
42
43   // CONSTRUCTOR
44
45   function CodeMirror(place, options) {
46     if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
47
48     this.options = options = options || {};
49     // Determine effective options based on given values and defaults.
50     for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
51       options[opt] = defaults[opt];
52     setGuttersForLineNumbers(options);
53
54     var docStart = typeof options.value == "string" ? 0 : options.value.first;
55     var display = this.display = makeDisplay(place, docStart);
56     display.wrapper.CodeMirror = this;
57     updateGutters(this);
58     if (options.autofocus && !mobile) focusInput(this);
59
60     this.state = {keyMaps: [],
61                   overlays: [],
62                   modeGen: 0,
63                   overwrite: false, focused: false,
64                   suppressEdits: false, pasteIncoming: false,
65                   draggingText: false,
66                   highlight: new Delayed()};
67
68     themeChanged(this);
69     if (options.lineWrapping)
70       this.display.wrapper.className += " CodeMirror-wrap";
71
72     var doc = options.value;
73     if (typeof doc == "string") doc = new Doc(options.value, options.mode);
74     operation(this, attachDoc)(this, doc);
75
76     // Override magic textarea content restore that IE sometimes does
77     // on our hidden textarea on reload
78     if (ie) setTimeout(bind(resetInput, this, true), 20);
79
80     registerEventHandlers(this);
81     // IE throws unspecified error in certain cases, when
82     // trying to access activeElement before onload
83     var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
84     if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
85     else onBlur(this);
86
87     operation(this, function() {
88       for (var opt in optionHandlers)
89         if (optionHandlers.propertyIsEnumerable(opt))
90           optionHandlers[opt](this, options[opt], Init);
91       for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
92     })();
93   }
94
95   // DISPLAY CONSTRUCTOR
96
97   function makeDisplay(place, docStart) {
98     var d = {};
99
100     var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
101     if (webkit) input.style.width = "1000px";
102     else input.setAttribute("wrap", "off");
103     // if border: 0; -- iOS fails to open keyboard (issue #1287)
104     if (ios) input.style.border = "1px solid black";
105     input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
106
107     // Wraps and hides input textarea
108     d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
109     // The actual fake scrollbars.
110     d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
111     d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
112     d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
113     d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
114     // DIVs containing the selection and the actual code
115     d.lineDiv = elt("div", null, "CodeMirror-code");
116     d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
117     // Blinky cursor, and element used to ensure cursor fits at the end of a line
118     d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
119     // Secondary cursor, shown when on a 'jump' in bi-directional text
120     d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
121     // Used to measure text size
122     d.measure = elt("div", null, "CodeMirror-measure");
123     // Wraps everything that needs to exist inside the vertically-padded coordinate system
124     d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
125                          null, "position: relative; outline: none");
126     // Moved around its parent to cover visible view
127     d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
128     // Set to the height of the text, causes scrolling
129     d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
130     // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
131     d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
132     // Will contain the gutters, if any
133     d.gutters = elt("div", null, "CodeMirror-gutters");
134     d.lineGutter = null;
135     // Provides scrolling
136     d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
137     d.scroller.setAttribute("tabIndex", "-1");
138     // The element in which the editor lives.
139     d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
140                             d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
141     // Work around IE7 z-index bug
142     if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
143     if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
144
145     // Needed to hide big blue blinking cursor on Mobile Safari
146     if (ios) input.style.width = "0px";
147     if (!webkit) d.scroller.draggable = true;
148     // Needed to handle Tab key in KHTML
149     if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
150     // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
151     else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
152
153     // Current visible range (may be bigger than the view window).
154     d.viewOffset = d.lastSizeC = 0;
155     d.showingFrom = d.showingTo = docStart;
156
157     // Used to only resize the line number gutter when necessary (when
158     // the amount of lines crosses a boundary that makes its width change)
159     d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
160     // See readInput and resetInput
161     d.prevInput = "";
162     // Set to true when a non-horizontal-scrolling widget is added. As
163     // an optimization, widget aligning is skipped when d is false.
164     d.alignWidgets = false;
165     // Flag that indicates whether we currently expect input to appear
166     // (after some event like 'keypress' or 'input') and are polling
167     // intensively.
168     d.pollingFast = false;
169     // Self-resetting timeout for the poller
170     d.poll = new Delayed();
171
172     d.cachedCharWidth = d.cachedTextHeight = null;
173     d.measureLineCache = [];
174     d.measureLineCachePos = 0;
175
176     // Tracks when resetInput has punted to just putting a short
177     // string instead of the (large) selection.
178     d.inaccurateSelection = false;
179
180     // Tracks the maximum line length so that the horizontal scrollbar
181     // can be kept static when scrolling.
182     d.maxLine = null;
183     d.maxLineLength = 0;
184     d.maxLineChanged = false;
185
186     // Used for measuring wheel scrolling granularity
187     d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
188
189     return d;
190   }
191
192   // STATE UPDATES
193
194   // Used to get the editor into a consistent state again when options change.
195
196   function loadMode(cm) {
197     cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
198     cm.doc.iter(function(line) {
199       if (line.stateAfter) line.stateAfter = null;
200       if (line.styles) line.styles = null;
201     });
202     cm.doc.frontier = cm.doc.first;
203     startWorker(cm, 100);
204     cm.state.modeGen++;
205     if (cm.curOp) regChange(cm);
206   }
207
208   function wrappingChanged(cm) {
209     if (cm.options.lineWrapping) {
210       cm.display.wrapper.className += " CodeMirror-wrap";
211       cm.display.sizer.style.minWidth = "";
212     } else {
213       cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
214       computeMaxLength(cm);
215     }
216     estimateLineHeights(cm);
217     regChange(cm);
218     clearCaches(cm);
219     setTimeout(function(){updateScrollbars(cm);}, 100);
220   }
221
222   function estimateHeight(cm) {
223     var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
224     var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
225     return function(line) {
226       if (lineIsHidden(cm.doc, line))
227         return 0;
228       else if (wrapping)
229         return (Math.ceil(line.text.length / perLine) || 1) * th;
230       else
231         return th;
232     };
233   }
234
235   function estimateLineHeights(cm) {
236     var doc = cm.doc, est = estimateHeight(cm);
237     doc.iter(function(line) {
238       var estHeight = est(line);
239       if (estHeight != line.height) updateLineHeight(line, estHeight);
240     });
241   }
242
243   function keyMapChanged(cm) {
244     var map = keyMap[cm.options.keyMap], style = map.style;
245     cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
246       (style ? " cm-keymap-" + style : "");
247     cm.state.disableInput = map.disableInput;
248   }
249
250   function themeChanged(cm) {
251     cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
252       cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
253     clearCaches(cm);
254   }
255
256   function guttersChanged(cm) {
257     updateGutters(cm);
258     regChange(cm);
259     setTimeout(function(){alignHorizontally(cm);}, 20);
260   }
261
262   function updateGutters(cm) {
263     var gutters = cm.display.gutters, specs = cm.options.gutters;
264     removeChildren(gutters);
265     for (var i = 0; i < specs.length; ++i) {
266       var gutterClass = specs[i];
267       var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
268       if (gutterClass == "CodeMirror-linenumbers") {
269         cm.display.lineGutter = gElt;
270         gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
271       }
272     }
273     gutters.style.display = i ? "" : "none";
274   }
275
276   function lineLength(doc, line) {
277     if (line.height == 0) return 0;
278     var len = line.text.length, merged, cur = line;
279     while (merged = collapsedSpanAtStart(cur)) {
280       var found = merged.find();
281       cur = getLine(doc, found.from.line);
282       len += found.from.ch - found.to.ch;
283     }
284     cur = line;
285     while (merged = collapsedSpanAtEnd(cur)) {
286       var found = merged.find();
287       len -= cur.text.length - found.from.ch;
288       cur = getLine(doc, found.to.line);
289       len += cur.text.length - found.to.ch;
290     }
291     return len;
292   }
293
294   function computeMaxLength(cm) {
295     var d = cm.display, doc = cm.doc;
296     d.maxLine = getLine(doc, doc.first);
297     d.maxLineLength = lineLength(doc, d.maxLine);
298     d.maxLineChanged = true;
299     doc.iter(function(line) {
300       var len = lineLength(doc, line);
301       if (len > d.maxLineLength) {
302         d.maxLineLength = len;
303         d.maxLine = line;
304       }
305     });
306   }
307
308   // Make sure the gutters options contains the element
309   // "CodeMirror-linenumbers" when the lineNumbers option is true.
310   function setGuttersForLineNumbers(options) {
311     var found = indexOf(options.gutters, "CodeMirror-linenumbers");
312     if (found == -1 && options.lineNumbers) {
313       options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]);
314     } else if (found > -1 && !options.lineNumbers) {
315       options.gutters = options.gutters.slice(0);
316       options.gutters.splice(found, 1);
317     }
318   }
319
320   // SCROLLBARS
321
322   // Re-synchronize the fake scrollbars with the actual size of the
323   // content. Optionally force a scrollTop.
324   function updateScrollbars(cm) {
325     var d = cm.display, docHeight = cm.doc.height;
326     var totalHeight = docHeight + paddingVert(d);
327     d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
328     d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
329     var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
330     var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
331     var needsV = scrollHeight > (d.scroller.clientHeight + 1);
332     if (needsV) {
333       d.scrollbarV.style.display = "block";
334       d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
335       d.scrollbarV.firstChild.style.height =
336         (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
337     } else {
338       d.scrollbarV.style.display = "";
339       d.scrollbarV.firstChild.style.height = "0";
340     }
341     if (needsH) {
342       d.scrollbarH.style.display = "block";
343       d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
344       d.scrollbarH.firstChild.style.width =
345         (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
346     } else {
347       d.scrollbarH.style.display = "";
348       d.scrollbarH.firstChild.style.width = "0";
349     }
350     if (needsH && needsV) {
351       d.scrollbarFiller.style.display = "block";
352       d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
353     } else d.scrollbarFiller.style.display = "";
354     if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
355       d.gutterFiller.style.display = "block";
356       d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
357       d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
358     } else d.gutterFiller.style.display = "";
359
360     if (mac_geLion && scrollbarWidth(d.measure) === 0) {
361       d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
362       d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none";
363     }
364   }
365
366   function visibleLines(display, doc, viewPort) {
367     var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
368     if (typeof viewPort == "number") top = viewPort;
369     else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
370     top = Math.floor(top - paddingTop(display));
371     var bottom = Math.ceil(top + height);
372     return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
373   }
374
375   // LINE NUMBERS
376
377   function alignHorizontally(cm) {
378     var display = cm.display;
379     if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
380     var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
381     var gutterW = display.gutters.offsetWidth, l = comp + "px";
382     for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
383       for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
384     }
385     if (cm.options.fixedGutter)
386       display.gutters.style.left = (comp + gutterW) + "px";
387   }
388
389   function maybeUpdateLineNumberWidth(cm) {
390     if (!cm.options.lineNumbers) return false;
391     var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
392     if (last.length != display.lineNumChars) {
393       var test = display.measure.appendChild(elt("div", [elt("div", last)],
394                                                  "CodeMirror-linenumber CodeMirror-gutter-elt"));
395       var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
396       display.lineGutter.style.width = "";
397       display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
398       display.lineNumWidth = display.lineNumInnerWidth + padding;
399       display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
400       display.lineGutter.style.width = display.lineNumWidth + "px";
401       return true;
402     }
403     return false;
404   }
405
406   function lineNumberFor(options, i) {
407     return String(options.lineNumberFormatter(i + options.firstLineNumber));
408   }
409   function compensateForHScroll(display) {
410     return getRect(display.scroller).left - getRect(display.sizer).left;
411   }
412
413   // DISPLAY DRAWING
414
415   function updateDisplay(cm, changes, viewPort, forced) {
416     var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
417     var visible = visibleLines(cm.display, cm.doc, viewPort);
418     for (var first = true;; first = false) {
419       var oldWidth = cm.display.scroller.clientWidth;
420       if (!updateDisplayInner(cm, changes, visible, forced)) break;
421       updated = true;
422       changes = [];
423       updateSelection(cm);
424       updateScrollbars(cm);
425       if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) {
426         forced = true;
427         continue;
428       }
429       forced = false;
430
431       // Clip forced viewport to actual scrollable area
432       if (viewPort)
433         viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
434                             typeof viewPort == "number" ? viewPort : viewPort.top);
435       visible = visibleLines(cm.display, cm.doc, viewPort);
436       if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
437         break;
438     }
439
440     if (updated) {
441       signalLater(cm, "update", cm);
442       if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
443         signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
444     }
445     return updated;
446   }
447
448   // Uses a set of changes plus the current scroll position to
449   // determine which DOM updates have to be made, and makes the
450   // updates.
451   function updateDisplayInner(cm, changes, visible, forced) {
452     var display = cm.display, doc = cm.doc;
453     if (!display.wrapper.clientWidth) {
454       display.showingFrom = display.showingTo = doc.first;
455       display.viewOffset = 0;
456       return;
457     }
458
459     // Bail out if the visible area is already rendered and nothing changed.
460     if (!forced && changes.length == 0 &&
461         visible.from > display.showingFrom && visible.to < display.showingTo)
462       return;
463
464     if (maybeUpdateLineNumberWidth(cm))
465       changes = [{from: doc.first, to: doc.first + doc.size}];
466     var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
467     display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
468
469     // Used to determine which lines need their line numbers updated
470     var positionsChangedFrom = Infinity;
471     if (cm.options.lineNumbers)
472       for (var i = 0; i < changes.length; ++i)
473         if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; }
474
475     var end = doc.first + doc.size;
476     var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
477     var to = Math.min(end, visible.to + cm.options.viewportMargin);
478     if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
479     if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
480     if (sawCollapsedSpans) {
481       from = lineNo(visualLine(doc, getLine(doc, from)));
482       while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
483     }
484
485     // Create a range of theoretically intact lines, and punch holes
486     // in that using the change info.
487     var intact = [{from: Math.max(display.showingFrom, doc.first),
488                    to: Math.min(display.showingTo, end)}];
489     if (intact[0].from >= intact[0].to) intact = [];
490     else intact = computeIntact(intact, changes);
491     // When merged lines are present, we might have to reduce the
492     // intact ranges because changes in continued fragments of the
493     // intact lines do require the lines to be redrawn.
494     if (sawCollapsedSpans)
495       for (var i = 0; i < intact.length; ++i) {
496         var range = intact[i], merged;
497         while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
498           var newTo = merged.find().from.line;
499           if (newTo > range.from) range.to = newTo;
500           else { intact.splice(i--, 1); break; }
501         }
502       }
503
504     // Clip off the parts that won't be visible
505     var intactLines = 0;
506     for (var i = 0; i < intact.length; ++i) {
507       var range = intact[i];
508       if (range.from < from) range.from = from;
509       if (range.to > to) range.to = to;
510       if (range.from >= range.to) intact.splice(i--, 1);
511       else intactLines += range.to - range.from;
512     }
513     if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
514       updateViewOffset(cm);
515       return;
516     }
517     intact.sort(function(a, b) {return a.from - b.from;});
518
519     // Avoid crashing on IE's "unspecified error" when in iframes
520     try {
521       var focused = document.activeElement;
522     } catch(e) {}
523     if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
524     patchDisplay(cm, from, to, intact, positionsChangedFrom);
525     display.lineDiv.style.display = "";
526     if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
527
528     var different = from != display.showingFrom || to != display.showingTo ||
529       display.lastSizeC != display.wrapper.clientHeight;
530     // This is just a bogus formula that detects when the editor is
531     // resized or the font size changes.
532     if (different) {
533       display.lastSizeC = display.wrapper.clientHeight;
534       startWorker(cm, 400);
535     }
536     display.showingFrom = from; display.showingTo = to;
537
538     updateHeightsInViewport(cm);
539     updateViewOffset(cm);
540
541     return true;
542   }
543
544   function updateHeightsInViewport(cm) {
545     var display = cm.display;
546     var prevBottom = display.lineDiv.offsetTop;
547     for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
548       if (ie_lt8) {
549         var bot = node.offsetTop + node.offsetHeight;
550         height = bot - prevBottom;
551         prevBottom = bot;
552       } else {
553         var box = getRect(node);
554         height = box.bottom - box.top;
555       }
556       var diff = node.lineObj.height - height;
557       if (height < 2) height = textHeight(display);
558       if (diff > .001 || diff < -.001) {
559         updateLineHeight(node.lineObj, height);
560         var widgets = node.lineObj.widgets;
561         if (widgets) for (var i = 0; i < widgets.length; ++i)
562           widgets[i].height = widgets[i].node.offsetHeight;
563       }
564     }
565   }
566
567   function updateViewOffset(cm) {
568     var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
569     // Position the mover div to align with the current virtual scroll position
570     cm.display.mover.style.top = off + "px";
571   }
572
573   function computeIntact(intact, changes) {
574     for (var i = 0, l = changes.length || 0; i < l; ++i) {
575       var change = changes[i], intact2 = [], diff = change.diff || 0;
576       for (var j = 0, l2 = intact.length; j < l2; ++j) {
577         var range = intact[j];
578         if (change.to <= range.from && change.diff) {
579           intact2.push({from: range.from + diff, to: range.to + diff});
580         } else if (change.to <= range.from || change.from >= range.to) {
581           intact2.push(range);
582         } else {
583           if (change.from > range.from)
584             intact2.push({from: range.from, to: change.from});
585           if (change.to < range.to)
586             intact2.push({from: change.to + diff, to: range.to + diff});
587         }
588       }
589       intact = intact2;
590     }
591     return intact;
592   }
593
594   function getDimensions(cm) {
595     var d = cm.display, left = {}, width = {};
596     for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
597       left[cm.options.gutters[i]] = n.offsetLeft;
598       width[cm.options.gutters[i]] = n.offsetWidth;
599     }
600     return {fixedPos: compensateForHScroll(d),
601             gutterTotalWidth: d.gutters.offsetWidth,
602             gutterLeft: left,
603             gutterWidth: width,
604             wrapperWidth: d.wrapper.clientWidth};
605   }
606
607   function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
608     var dims = getDimensions(cm);
609     var display = cm.display, lineNumbers = cm.options.lineNumbers;
610     if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
611       removeChildren(display.lineDiv);
612     var container = display.lineDiv, cur = container.firstChild;
613
614     function rm(node) {
615       var next = node.nextSibling;
616       if (webkit && mac && cm.display.currentWheelTarget == node) {
617         node.style.display = "none";
618         node.lineObj = null;
619       } else {
620         node.parentNode.removeChild(node);
621       }
622       return next;
623     }
624
625     var nextIntact = intact.shift(), lineN = from;
626     cm.doc.iter(from, to, function(line) {
627       if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
628       if (lineIsHidden(cm.doc, line)) {
629         if (line.height != 0) updateLineHeight(line, 0);
630         if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
631           var w = line.widgets[i];
632           if (w.showIfHidden) {
633             var prev = cur.previousSibling;
634             if (/pre/i.test(prev.nodeName)) {
635               var wrap = elt("div", null, null, "position: relative");
636               prev.parentNode.replaceChild(wrap, prev);
637               wrap.appendChild(prev);
638               prev = wrap;
639             }
640             var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
641             if (!w.handleMouseEvents) wnode.ignoreEvents = true;
642             positionLineWidget(w, wnode, prev, dims);
643           }
644         }
645       } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
646         // This line is intact. Skip to the actual node. Update its
647         // line number if needed.
648         while (cur.lineObj != line) cur = rm(cur);
649         if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
650           setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
651         cur = cur.nextSibling;
652       } else {
653         // For lines with widgets, make an attempt to find and reuse
654         // the existing element, so that widgets aren't needlessly
655         // removed and re-inserted into the dom
656         if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
657           if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
658         // This line needs to be generated.
659         var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
660         if (lineNode != reuse) {
661           container.insertBefore(lineNode, cur);
662         } else {
663           while (cur != reuse) cur = rm(cur);
664           cur = cur.nextSibling;
665         }
666
667         lineNode.lineObj = line;
668       }
669       ++lineN;
670     });
671     while (cur) cur = rm(cur);
672   }
673
674   function buildLineElement(cm, line, lineNo, dims, reuse) {
675     var built = buildLineContent(cm, line), lineElement = built.pre;
676     var markers = line.gutterMarkers, display = cm.display, wrap;
677
678     var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass;
679     if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets)
680       return lineElement;
681
682     // Lines with gutter elements, widgets or a background class need
683     // to be wrapped again, and have the extra elements added to the
684     // wrapper div
685
686     if (reuse) {
687       reuse.alignable = null;
688       var isOk = true, widgetsSeen = 0, insertBefore = null;
689       for (var n = reuse.firstChild, next; n; n = next) {
690         next = n.nextSibling;
691         if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
692           reuse.removeChild(n);
693         } else {
694           for (var i = 0; i < line.widgets.length; ++i) {
695             var widget = line.widgets[i];
696             if (widget.node == n.firstChild) {
697               if (!widget.above && !insertBefore) insertBefore = n;
698               positionLineWidget(widget, n, reuse, dims);
699               ++widgetsSeen;
700               break;
701             }
702           }
703           if (i == line.widgets.length) { isOk = false; break; }
704         }
705       }
706       reuse.insertBefore(lineElement, insertBefore);
707       if (isOk && widgetsSeen == line.widgets.length) {
708         wrap = reuse;
709         reuse.className = line.wrapClass || "";
710       }
711     }
712     if (!wrap) {
713       wrap = elt("div", null, line.wrapClass, "position: relative");
714       wrap.appendChild(lineElement);
715     }
716     // Kludge to make sure the styled element lies behind the selection (by z-index)
717     if (bgClass)
718       wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild);
719     if (cm.options.lineNumbers || markers) {
720       var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
721                                              (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
722                                          wrap.firstChild);
723       if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
724       if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
725         wrap.lineNumber = gutterWrap.appendChild(
726           elt("div", lineNumberFor(cm.options, lineNo),
727               "CodeMirror-linenumber CodeMirror-gutter-elt",
728               "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
729               + display.lineNumInnerWidth + "px"));
730       if (markers)
731         for (var k = 0; k < cm.options.gutters.length; ++k) {
732           var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
733           if (found)
734             gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
735                                        dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
736         }
737     }
738     if (ie_lt8) wrap.style.zIndex = 2;
739     if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
740       var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
741       if (!widget.handleMouseEvents) node.ignoreEvents = true;
742       positionLineWidget(widget, node, wrap, dims);
743       if (widget.above)
744         wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
745       else
746         wrap.appendChild(node);
747       signalLater(widget, "redraw");
748     }
749     return wrap;
750   }
751
752   function positionLineWidget(widget, node, wrap, dims) {
753     if (widget.noHScroll) {
754       (wrap.alignable || (wrap.alignable = [])).push(node);
755       var width = dims.wrapperWidth;
756       node.style.left = dims.fixedPos + "px";
757       if (!widget.coverGutter) {
758         width -= dims.gutterTotalWidth;
759         node.style.paddingLeft = dims.gutterTotalWidth + "px";
760       }
761       node.style.width = width + "px";
762     }
763     if (widget.coverGutter) {
764       node.style.zIndex = 5;
765       node.style.position = "relative";
766       if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
767     }
768   }
769
770   // SELECTION / CURSOR
771
772   function updateSelection(cm) {
773     var display = cm.display;
774     var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
775     if (collapsed || cm.options.showCursorWhenSelecting)
776       updateSelectionCursor(cm);
777     else
778       display.cursor.style.display = display.otherCursor.style.display = "none";
779     if (!collapsed)
780       updateSelectionRange(cm);
781     else
782       display.selectionDiv.style.display = "none";
783
784     // Move the hidden textarea near the cursor to prevent scrolling artifacts
785     if (cm.options.moveInputWithCursor) {
786       var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
787       var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
788       display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
789                                                         headPos.top + lineOff.top - wrapOff.top)) + "px";
790       display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
791                                                          headPos.left + lineOff.left - wrapOff.left)) + "px";
792     }
793   }
794
795   // No selection, plain cursor
796   function updateSelectionCursor(cm) {
797     var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
798     display.cursor.style.left = pos.left + "px";
799     display.cursor.style.top = pos.top + "px";
800     display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
801     display.cursor.style.display = "";
802
803     if (pos.other) {
804       display.otherCursor.style.display = "";
805       display.otherCursor.style.left = pos.other.left + "px";
806       display.otherCursor.style.top = pos.other.top + "px";
807       display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
808     } else { display.otherCursor.style.display = "none"; }
809   }
810
811   // Highlight selection
812   function updateSelectionRange(cm) {
813     var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
814     var fragment = document.createDocumentFragment();
815     var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
816
817     function add(left, top, width, bottom) {
818       if (top < 0) top = 0;
819       fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
820                                "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
821                                "px; height: " + (bottom - top) + "px"));
822     }
823
824     function drawForLine(line, fromArg, toArg) {
825       var lineObj = getLine(doc, line);
826       var lineLen = lineObj.text.length;
827       var start, end;
828       function coords(ch, bias) {
829         return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
830       }
831
832       iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
833         var leftPos = coords(from, "left"), rightPos, left, right;
834         if (from == to) {
835           rightPos = leftPos;
836           left = right = leftPos.left;
837         } else {
838           rightPos = coords(to - 1, "right");
839           if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
840           left = leftPos.left;
841           right = rightPos.right;
842         }
843         if (fromArg == null && from == 0) left = pl;
844         if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
845           add(left, leftPos.top, null, leftPos.bottom);
846           left = pl;
847           if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
848         }
849         if (toArg == null && to == lineLen) right = clientWidth;
850         if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
851           start = leftPos;
852         if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
853           end = rightPos;
854         if (left < pl + 1) left = pl;
855         add(left, rightPos.top, right - left, rightPos.bottom);
856       });
857       return {start: start, end: end};
858     }
859
860     if (sel.from.line == sel.to.line) {
861       drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
862     } else {
863       var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
864       var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
865       var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
866       var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
867       if (singleVLine) {
868         if (leftEnd.top < rightStart.top - 2) {
869           add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
870           add(pl, rightStart.top, rightStart.left, rightStart.bottom);
871         } else {
872           add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
873         }
874       }
875       if (leftEnd.bottom < rightStart.top)
876         add(pl, leftEnd.bottom, null, rightStart.top);
877     }
878
879     removeChildrenAndAdd(display.selectionDiv, fragment);
880     display.selectionDiv.style.display = "";
881   }
882
883   // Cursor-blinking
884   function restartBlink(cm) {
885     if (!cm.state.focused) return;
886     var display = cm.display;
887     clearInterval(display.blinker);
888     var on = true;
889     display.cursor.style.visibility = display.otherCursor.style.visibility = "";
890     if (cm.options.cursorBlinkRate > 0)
891       display.blinker = setInterval(function() {
892         display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
893       }, cm.options.cursorBlinkRate);
894   }
895
896   // HIGHLIGHT WORKER
897
898   function startWorker(cm, time) {
899     if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
900       cm.state.highlight.set(time, bind(highlightWorker, cm));
901   }
902
903   function highlightWorker(cm) {
904     var doc = cm.doc;
905     if (doc.frontier < doc.first) doc.frontier = doc.first;
906     if (doc.frontier >= cm.display.showingTo) return;
907     var end = +new Date + cm.options.workTime;
908     var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
909     var changed = [], prevChange;
910     doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
911       if (doc.frontier >= cm.display.showingFrom) { // Visible
912         var oldStyles = line.styles;
913         line.styles = highlightLine(cm, line, state, true);
914         var ischange = !oldStyles || oldStyles.length != line.styles.length;
915         for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
916         if (ischange) {
917           if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
918           else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
919         }
920         line.stateAfter = copyState(doc.mode, state);
921       } else {
922         processLine(cm, line.text, state);
923         line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
924       }
925       ++doc.frontier;
926       if (+new Date > end) {
927         startWorker(cm, cm.options.workDelay);
928         return true;
929       }
930     });
931     if (changed.length)
932       operation(cm, function() {
933         for (var i = 0; i < changed.length; ++i)
934           regChange(this, changed[i].start, changed[i].end);
935       })();
936   }
937
938   // Finds the line to start with when starting a parse. Tries to
939   // find a line with a stateAfter, so that it can start with a
940   // valid state. If that fails, it returns the line with the
941   // smallest indentation, which tends to need the least context to
942   // parse correctly.
943   function findStartLine(cm, n, precise) {
944     var minindent, minline, doc = cm.doc;
945     var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
946     for (var search = n; search > lim; --search) {
947       if (search <= doc.first) return doc.first;
948       var line = getLine(doc, search - 1);
949       if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
950       var indented = countColumn(line.text, null, cm.options.tabSize);
951       if (minline == null || minindent > indented) {
952         minline = search - 1;
953         minindent = indented;
954       }
955     }
956     return minline;
957   }
958
959   function getStateBefore(cm, n, precise) {
960     var doc = cm.doc, display = cm.display;
961     if (!doc.mode.startState) return true;
962     var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
963     if (!state) state = startState(doc.mode);
964     else state = copyState(doc.mode, state);
965     doc.iter(pos, n, function(line) {
966       processLine(cm, line.text, state);
967       var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
968       line.stateAfter = save ? copyState(doc.mode, state) : null;
969       ++pos;
970     });
971     if (precise) doc.frontier = pos;
972     return state;
973   }
974
975   // POSITION MEASUREMENT
976
977   function paddingTop(display) {return display.lineSpace.offsetTop;}
978   function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
979   function paddingLeft(display) {
980     var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
981     return e.offsetLeft;
982   }
983
984   function measureChar(cm, line, ch, data, bias) {
985     var dir = -1;
986     data = data || measureLine(cm, line);
987     if (data.crude) {
988       var left = data.left + ch * data.width;
989       return {left: left, right: left + data.width, top: data.top, bottom: data.bottom};
990     }
991
992     for (var pos = ch;; pos += dir) {
993       var r = data[pos];
994       if (r) break;
995       if (dir < 0 && pos == 0) dir = 1;
996     }
997     bias = pos > ch ? "left" : pos < ch ? "right" : bias;
998     if (bias == "left" && r.leftSide) r = r.leftSide;
999     else if (bias == "right" && r.rightSide) r = r.rightSide;
1000     return {left: pos < ch ? r.right : r.left,
1001             right: pos > ch ? r.left : r.right,
1002             top: r.top,
1003             bottom: r.bottom};
1004   }
1005
1006   function findCachedMeasurement(cm, line) {
1007     var cache = cm.display.measureLineCache;
1008     for (var i = 0; i < cache.length; ++i) {
1009       var memo = cache[i];
1010       if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
1011           cm.display.scroller.clientWidth == memo.width &&
1012           memo.classes == line.textClass + "|" + line.wrapClass)
1013         return memo;
1014     }
1015   }
1016
1017   function clearCachedMeasurement(cm, line) {
1018     var exists = findCachedMeasurement(cm, line);
1019     if (exists) exists.text = exists.measure = exists.markedSpans = null;
1020   }
1021
1022   function measureLine(cm, line) {
1023     // First look in the cache
1024     var cached = findCachedMeasurement(cm, line);
1025     if (cached) return cached.measure;
1026
1027     // Failing that, recompute and store result in cache
1028     var measure = measureLineInner(cm, line);
1029     var cache = cm.display.measureLineCache;
1030     var memo = {text: line.text, width: cm.display.scroller.clientWidth,
1031                 markedSpans: line.markedSpans, measure: measure,
1032                 classes: line.textClass + "|" + line.wrapClass};
1033     if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
1034     else cache.push(memo);
1035     return measure;
1036   }
1037
1038   function measureLineInner(cm, line) {
1039     if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom)
1040       return crudelyMeasureLine(cm, line);
1041
1042     var display = cm.display, measure = emptyArray(line.text.length);
1043     var pre = buildLineContent(cm, line, measure, true).pre;
1044
1045     // IE does not cache element positions of inline elements between
1046     // calls to getBoundingClientRect. This makes the loop below,
1047     // which gathers the positions of all the characters on the line,
1048     // do an amount of layout work quadratic to the number of
1049     // characters. When line wrapping is off, we try to improve things
1050     // by first subdividing the line into a bunch of inline blocks, so
1051     // that IE can reuse most of the layout information from caches
1052     // for those blocks. This does interfere with line wrapping, so it
1053     // doesn't work when wrapping is on, but in that case the
1054     // situation is slightly better, since IE does cache line-wrapping
1055     // information and only recomputes per-line.
1056     if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
1057       var fragment = document.createDocumentFragment();
1058       var chunk = 10, n = pre.childNodes.length;
1059       for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
1060         var wrap = elt("div", null, null, "display: inline-block");
1061         for (var j = 0; j < chunk && n; ++j) {
1062           wrap.appendChild(pre.firstChild);
1063           --n;
1064         }
1065         fragment.appendChild(wrap);
1066       }
1067       pre.appendChild(fragment);
1068     }
1069
1070     removeChildrenAndAdd(display.measure, pre);
1071
1072     var outer = getRect(display.lineDiv);
1073     var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
1074     // Work around an IE7/8 bug where it will sometimes have randomly
1075     // replaced our pre with a clone at this point.
1076     if (ie_lt9 && display.measure.first != pre)
1077       removeChildrenAndAdd(display.measure, pre);
1078
1079     function measureRect(rect) {
1080       var top = rect.top - outer.top, bot = rect.bottom - outer.top;
1081       if (bot > maxBot) bot = maxBot;
1082       if (top < 0) top = 0;
1083       for (var i = vranges.length - 2; i >= 0; i -= 2) {
1084         var rtop = vranges[i], rbot = vranges[i+1];
1085         if (rtop > bot || rbot < top) continue;
1086         if (rtop <= top && rbot >= bot ||
1087             top <= rtop && bot >= rbot ||
1088             Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
1089           vranges[i] = Math.min(top, rtop);
1090           vranges[i+1] = Math.max(bot, rbot);
1091           break;
1092         }
1093       }
1094       if (i < 0) { i = vranges.length; vranges.push(top, bot); }
1095       return {left: rect.left - outer.left,
1096               right: rect.right - outer.left,
1097               top: i, bottom: null};
1098     }
1099     function finishRect(rect) {
1100       rect.bottom = vranges[rect.top+1];
1101       rect.top = vranges[rect.top];
1102     }
1103
1104     for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1105       var node = cur, rect = null;
1106       // A widget might wrap, needs special care
1107       if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
1108         if (cur.firstChild.nodeType == 1) node = cur.firstChild;
1109         var rects = node.getClientRects();
1110         if (rects.length > 1) {
1111           rect = data[i] = measureRect(rects[0]);
1112           rect.rightSide = measureRect(rects[rects.length - 1]);
1113         }
1114       }
1115       if (!rect) rect = data[i] = measureRect(getRect(node));
1116       if (cur.measureRight) rect.right = getRect(cur.measureRight).left;
1117       if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));
1118     }
1119     removeChildren(cm.display.measure);
1120     for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
1121       finishRect(cur);
1122       if (cur.leftSide) finishRect(cur.leftSide);
1123       if (cur.rightSide) finishRect(cur.rightSide);
1124     }
1125     return data;
1126   }
1127
1128   function crudelyMeasureLine(cm, line) {
1129     var copy = new Line(line.text.slice(0, 100), null);
1130     if (line.textClass) copy.textClass = line.textClass;
1131     var measure = measureLineInner(cm, copy);
1132     var left = measureChar(cm, copy, 0, measure, "left");
1133     var right = measureChar(cm, copy, 99, measure, "right");
1134     return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100};
1135   }
1136
1137   function measureLineWidth(cm, line) {
1138     var hasBadSpan = false;
1139     if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
1140       var sp = line.markedSpans[i];
1141       if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
1142     }
1143     var cached = !hasBadSpan && findCachedMeasurement(cm, line);
1144     if (cached || line.text.length >= cm.options.crudeMeasuringFrom)
1145       return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right;
1146
1147     var pre = buildLineContent(cm, line, null, true).pre;
1148     var end = pre.appendChild(zeroWidthElement(cm.display.measure));
1149     removeChildrenAndAdd(cm.display.measure, pre);
1150     return getRect(end).right - getRect(cm.display.lineDiv).left;
1151   }
1152
1153   function clearCaches(cm) {
1154     cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
1155     cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
1156     if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
1157     cm.display.lineNumChars = null;
1158   }
1159
1160   function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
1161   function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
1162
1163   // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
1164   function intoCoordSystem(cm, lineObj, rect, context) {
1165     if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
1166       var size = widgetHeight(lineObj.widgets[i]);
1167       rect.top += size; rect.bottom += size;
1168     }
1169     if (context == "line") return rect;
1170     if (!context) context = "local";
1171     var yOff = heightAtLine(cm, lineObj);
1172     if (context == "local") yOff += paddingTop(cm.display);
1173     else yOff -= cm.display.viewOffset;
1174     if (context == "page" || context == "window") {
1175       var lOff = getRect(cm.display.lineSpace);
1176       yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
1177       var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
1178       rect.left += xOff; rect.right += xOff;
1179     }
1180     rect.top += yOff; rect.bottom += yOff;
1181     return rect;
1182   }
1183
1184   // Context may be "window", "page", "div", or "local"/null
1185   // Result is in "div" coords
1186   function fromCoordSystem(cm, coords, context) {
1187     if (context == "div") return coords;
1188     var left = coords.left, top = coords.top;
1189     // First move into "page" coordinate system
1190     if (context == "page") {
1191       left -= pageScrollX();
1192       top -= pageScrollY();
1193     } else if (context == "local" || !context) {
1194       var localBox = getRect(cm.display.sizer);
1195       left += localBox.left;
1196       top += localBox.top;
1197     }
1198
1199     var lineSpaceBox = getRect(cm.display.lineSpace);
1200     return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
1201   }
1202
1203   function charCoords(cm, pos, context, lineObj, bias) {
1204     if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1205     return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
1206   }
1207
1208   function cursorCoords(cm, pos, context, lineObj, measurement) {
1209     lineObj = lineObj || getLine(cm.doc, pos.line);
1210     if (!measurement) measurement = measureLine(cm, lineObj);
1211     function get(ch, right) {
1212       var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
1213       if (right) m.left = m.right; else m.right = m.left;
1214       return intoCoordSystem(cm, lineObj, m, context);
1215     }
1216     function getBidi(ch, partPos) {
1217       var part = order[partPos], right = part.level % 2;
1218       if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
1219         part = order[--partPos];
1220         ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
1221         right = true;
1222       } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
1223         part = order[++partPos];
1224         ch = bidiLeft(part) - part.level % 2;
1225         right = false;
1226       }
1227       if (right && ch == part.to && ch > part.from) return get(ch - 1);
1228       return get(ch, right);
1229     }
1230     var order = getOrder(lineObj), ch = pos.ch;
1231     if (!order) return get(ch);
1232     var partPos = getBidiPartAt(order, ch);
1233     var val = getBidi(ch, partPos);
1234     if (bidiOther != null) val.other = getBidi(ch, bidiOther);
1235     return val;
1236   }
1237
1238   function PosWithInfo(line, ch, outside, xRel) {
1239     var pos = new Pos(line, ch);
1240     pos.xRel = xRel;
1241     if (outside) pos.outside = true;
1242     return pos;
1243   }
1244
1245   // Coords must be lineSpace-local
1246   function coordsChar(cm, x, y) {
1247     var doc = cm.doc;
1248     y += cm.display.viewOffset;
1249     if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
1250     var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1251     if (lineNo > last)
1252       return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
1253     if (x < 0) x = 0;
1254
1255     for (;;) {
1256       var lineObj = getLine(doc, lineNo);
1257       var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1258       var merged = collapsedSpanAtEnd(lineObj);
1259       var mergedPos = merged && merged.find();
1260       if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
1261         lineNo = mergedPos.to.line;
1262       else
1263         return found;
1264     }
1265   }
1266
1267   function coordsCharInner(cm, lineObj, lineNo, x, y) {
1268     var innerOff = y - heightAtLine(cm, lineObj);
1269     var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
1270     var measurement = measureLine(cm, lineObj);
1271
1272     function getX(ch) {
1273       var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
1274                             lineObj, measurement);
1275       wrongLine = true;
1276       if (innerOff > sp.bottom) return sp.left - adjust;
1277       else if (innerOff < sp.top) return sp.left + adjust;
1278       else wrongLine = false;
1279       return sp.left;
1280     }
1281
1282     var bidi = getOrder(lineObj), dist = lineObj.text.length;
1283     var from = lineLeft(lineObj), to = lineRight(lineObj);
1284     var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1285
1286     if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
1287     // Do a binary search between these bounds.
1288     for (;;) {
1289       if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1290         var ch = x < fromX || x - fromX <= toX - x ? from : to;
1291         var xDiff = x - (ch == from ? fromX : toX);
1292         while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1293         var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
1294                               xDiff < 0 ? -1 : xDiff ? 1 : 0);
1295         return pos;
1296       }
1297       var step = Math.ceil(dist / 2), middle = from + step;
1298       if (bidi) {
1299         middle = from;
1300         for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1301       }
1302       var middleX = getX(middle);
1303       if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
1304       else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
1305     }
1306   }
1307
1308   var measureText;
1309   function textHeight(display) {
1310     if (display.cachedTextHeight != null) return display.cachedTextHeight;
1311     if (measureText == null) {
1312       measureText = elt("pre");
1313       // Measure a bunch of lines, for browsers that compute
1314       // fractional heights.
1315       for (var i = 0; i < 49; ++i) {
1316         measureText.appendChild(document.createTextNode("x"));
1317         measureText.appendChild(elt("br"));
1318       }
1319       measureText.appendChild(document.createTextNode("x"));
1320     }
1321     removeChildrenAndAdd(display.measure, measureText);
1322     var height = measureText.offsetHeight / 50;
1323     if (height > 3) display.cachedTextHeight = height;
1324     removeChildren(display.measure);
1325     return height || 1;
1326   }
1327
1328   function charWidth(display) {
1329     if (display.cachedCharWidth != null) return display.cachedCharWidth;
1330     var anchor = elt("span", "x");
1331     var pre = elt("pre", [anchor]);
1332     removeChildrenAndAdd(display.measure, pre);
1333     var width = anchor.offsetWidth;
1334     if (width > 2) display.cachedCharWidth = width;
1335     return width || 10;
1336   }
1337
1338   // OPERATIONS
1339
1340   // Operations are used to wrap changes in such a way that each
1341   // change won't have to update the cursor and display (which would
1342   // be awkward, slow, and error-prone), but instead updates are
1343   // batched and then all combined and executed at once.
1344
1345   var nextOpId = 0;
1346   function startOperation(cm) {
1347     cm.curOp = {
1348       // An array of ranges of lines that have to be updated. See
1349       // updateDisplay.
1350       changes: [],
1351       forceUpdate: false,
1352       updateInput: null,
1353       userSelChange: null,
1354       textChanged: null,
1355       selectionChanged: false,
1356       cursorActivity: false,
1357       updateMaxLine: false,
1358       updateScrollPos: false,
1359       id: ++nextOpId
1360     };
1361     if (!delayedCallbackDepth++) delayedCallbacks = [];
1362   }
1363
1364   function endOperation(cm) {
1365     var op = cm.curOp, doc = cm.doc, display = cm.display;
1366     cm.curOp = null;
1367
1368     if (op.updateMaxLine) computeMaxLength(cm);
1369     if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
1370       var width = measureLineWidth(cm, display.maxLine);
1371       display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1372       display.maxLineChanged = false;
1373       var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
1374       if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
1375         setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
1376     }
1377     var newScrollPos, updated;
1378     if (op.updateScrollPos) {
1379       newScrollPos = op.updateScrollPos;
1380     } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
1381       var coords = cursorCoords(cm, doc.sel.head);
1382       newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1383     }
1384     if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) {
1385       updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate);
1386       if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
1387     }
1388     if (!updated && op.selectionChanged) updateSelection(cm);
1389     if (op.updateScrollPos) {
1390       var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, newScrollPos.scrollTop));
1391       var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, newScrollPos.scrollLeft));
1392       display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
1393       display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
1394       alignHorizontally(cm);
1395       if (op.scrollToPos)
1396         scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from),
1397                           clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin);
1398     } else if (newScrollPos) {
1399       scrollCursorIntoView(cm);
1400     }
1401     if (op.selectionChanged) restartBlink(cm);
1402
1403     if (cm.state.focused && op.updateInput)
1404       resetInput(cm, op.userSelChange);
1405
1406     var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
1407     if (hidden) for (var i = 0; i < hidden.length; ++i)
1408       if (!hidden[i].lines.length) signal(hidden[i], "hide");
1409     if (unhidden) for (var i = 0; i < unhidden.length; ++i)
1410       if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
1411
1412     var delayed;
1413     if (!--delayedCallbackDepth) {
1414       delayed = delayedCallbacks;
1415       delayedCallbacks = null;
1416     }
1417     if (op.textChanged)
1418       signal(cm, "change", cm, op.textChanged);
1419     if (op.cursorActivity) signal(cm, "cursorActivity", cm);
1420     if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
1421   }
1422
1423   // Wraps a function in an operation. Returns the wrapped function.
1424   function operation(cm1, f) {
1425     return function() {
1426       var cm = cm1 || this, withOp = !cm.curOp;
1427       if (withOp) startOperation(cm);
1428       try { var result = f.apply(cm, arguments); }
1429       finally { if (withOp) endOperation(cm); }
1430       return result;
1431     };
1432   }
1433   function docOperation(f) {
1434     return function() {
1435       var withOp = this.cm && !this.cm.curOp, result;
1436       if (withOp) startOperation(this.cm);
1437       try { result = f.apply(this, arguments); }
1438       finally { if (withOp) endOperation(this.cm); }
1439       return result;
1440     };
1441   }
1442   function runInOp(cm, f) {
1443     var withOp = !cm.curOp, result;
1444     if (withOp) startOperation(cm);
1445     try { result = f(); }
1446     finally { if (withOp) endOperation(cm); }
1447     return result;
1448   }
1449
1450   function regChange(cm, from, to, lendiff) {
1451     if (from == null) from = cm.doc.first;
1452     if (to == null) to = cm.doc.first + cm.doc.size;
1453     cm.curOp.changes.push({from: from, to: to, diff: lendiff});
1454   }
1455
1456   // INPUT HANDLING
1457
1458   function slowPoll(cm) {
1459     if (cm.display.pollingFast) return;
1460     cm.display.poll.set(cm.options.pollInterval, function() {
1461       readInput(cm);
1462       if (cm.state.focused) slowPoll(cm);
1463     });
1464   }
1465
1466   function fastPoll(cm) {
1467     var missed = false;
1468     cm.display.pollingFast = true;
1469     function p() {
1470       var changed = readInput(cm);
1471       if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
1472       else {cm.display.pollingFast = false; slowPoll(cm);}
1473     }
1474     cm.display.poll.set(20, p);
1475   }
1476
1477   // prevInput is a hack to work with IME. If we reset the textarea
1478   // on every change, that breaks IME. So we look for changes
1479   // compared to the previous content instead. (Modern browsers have
1480   // events that indicate IME taking place, but these are not widely
1481   // supported or compatible enough yet to rely on.)
1482   function readInput(cm) {
1483     var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1484     if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
1485     if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
1486       input.value = input.value.substring(0, input.value.length - 1);
1487       cm.state.fakedLastChar = false;
1488     }
1489     var text = input.value;
1490     if (text == prevInput && posEq(sel.from, sel.to)) return false;
1491     if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
1492       resetInput(cm, true);
1493       return false;
1494     }
1495
1496     var withOp = !cm.curOp;
1497     if (withOp) startOperation(cm);
1498     sel.shift = false;
1499     var same = 0, l = Math.min(prevInput.length, text.length);
1500     while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1501     var from = sel.from, to = sel.to;
1502     if (same < prevInput.length)
1503       from = Pos(from.line, from.ch - (prevInput.length - same));
1504     else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1505       to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
1506
1507     var updateInput = cm.curOp.updateInput;
1508     var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),
1509                        origin: cm.state.pasteIncoming ? "paste" : "+input"};
1510     makeChange(cm.doc, changeEvent, "end");
1511     cm.curOp.updateInput = updateInput;
1512     signalLater(cm, "inputRead", cm, changeEvent);
1513
1514     if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
1515     else cm.display.prevInput = text;
1516     if (withOp) endOperation(cm);
1517     cm.state.pasteIncoming = false;
1518     return true;
1519   }
1520
1521   function resetInput(cm, user) {
1522     var minimal, selected, doc = cm.doc;
1523     if (!posEq(doc.sel.from, doc.sel.to)) {
1524       cm.display.prevInput = "";
1525       minimal = hasCopyEvent &&
1526         (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1527       var content = minimal ? "-" : selected || cm.getSelection();
1528       cm.display.input.value = content;
1529       if (cm.state.focused) selectInput(cm.display.input);
1530       if (ie && !ie_lt9) cm.display.inputHasSelection = content;
1531     } else if (user) {
1532       cm.display.prevInput = cm.display.input.value = "";
1533       if (ie && !ie_lt9) cm.display.inputHasSelection = null;
1534     }
1535     cm.display.inaccurateSelection = minimal;
1536   }
1537
1538   function focusInput(cm) {
1539     if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
1540       cm.display.input.focus();
1541   }
1542
1543   function isReadOnly(cm) {
1544     return cm.options.readOnly || cm.doc.cantEdit;
1545   }
1546
1547   // EVENT HANDLERS
1548
1549   function registerEventHandlers(cm) {
1550     var d = cm.display;
1551     on(d.scroller, "mousedown", operation(cm, onMouseDown));
1552     if (ie)
1553       on(d.scroller, "dblclick", operation(cm, function(e) {
1554         if (signalDOMEvent(cm, e)) return;
1555         var pos = posFromMouse(cm, e);
1556         if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
1557         e_preventDefault(e);
1558         var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
1559         extendSelection(cm.doc, word.from, word.to);
1560       }));
1561     else
1562       on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
1563     on(d.lineSpace, "selectstart", function(e) {
1564       if (!eventInWidget(d, e)) e_preventDefault(e);
1565     });
1566     // Gecko browsers fire contextmenu *after* opening the menu, at
1567     // which point we can't mess with it anymore. Context menu is
1568     // handled in onMouseDown for Gecko.
1569     if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1570
1571     on(d.scroller, "scroll", function() {
1572       if (d.scroller.clientHeight) {
1573         setScrollTop(cm, d.scroller.scrollTop);
1574         setScrollLeft(cm, d.scroller.scrollLeft, true);
1575         signal(cm, "scroll", cm);
1576       }
1577     });
1578     on(d.scrollbarV, "scroll", function() {
1579       if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
1580     });
1581     on(d.scrollbarH, "scroll", function() {
1582       if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
1583     });
1584
1585     on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
1586     on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
1587
1588     function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
1589     on(d.scrollbarH, "mousedown", reFocus);
1590     on(d.scrollbarV, "mousedown", reFocus);
1591     // Prevent wrapper from ever scrolling
1592     on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1593
1594     var resizeTimer;
1595     function onResize() {
1596       if (resizeTimer == null) resizeTimer = setTimeout(function() {
1597         resizeTimer = null;
1598         // Might be a text scaling operation, clear size caches.
1599         d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
1600         clearCaches(cm);
1601         runInOp(cm, bind(regChange, cm));
1602       }, 100);
1603     }
1604     on(window, "resize", onResize);
1605     // Above handler holds on to the editor and its data structures.
1606     // Here we poll to unregister it when the editor is no longer in
1607     // the document, so that it can be garbage-collected.
1608     function unregister() {
1609       for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
1610       if (p) setTimeout(unregister, 5000);
1611       else off(window, "resize", onResize);
1612     }
1613     setTimeout(unregister, 5000);
1614
1615     on(d.input, "keyup", operation(cm, function(e) {
1616       if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1617       if (e.keyCode == 16) cm.doc.sel.shift = false;
1618     }));
1619     on(d.input, "input", function() {
1620       if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
1621       fastPoll(cm);
1622     });
1623     on(d.input, "keydown", operation(cm, onKeyDown));
1624     on(d.input, "keypress", operation(cm, onKeyPress));
1625     on(d.input, "focus", bind(onFocus, cm));
1626     on(d.input, "blur", bind(onBlur, cm));
1627
1628     function drag_(e) {
1629       if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1630       e_stop(e);
1631     }
1632     if (cm.options.dragDrop) {
1633       on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
1634       on(d.scroller, "dragenter", drag_);
1635       on(d.scroller, "dragover", drag_);
1636       on(d.scroller, "drop", operation(cm, onDrop));
1637     }
1638     on(d.scroller, "paste", function(e) {
1639       if (eventInWidget(d, e)) return;
1640       focusInput(cm);
1641       fastPoll(cm);
1642     });
1643     on(d.input, "paste", function() {
1644       // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
1645       // Add a char to the end of textarea before paste occur so that
1646       // selection doesn't span to the end of textarea.
1647       if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
1648         var start = d.input.selectionStart, end = d.input.selectionEnd;
1649         d.input.value += "$";
1650         d.input.selectionStart = start;
1651         d.input.selectionEnd = end;
1652         cm.state.fakedLastChar = true;
1653       }
1654       cm.state.pasteIncoming = true;
1655       fastPoll(cm);
1656     });
1657
1658     function prepareCopy() {
1659       if (d.inaccurateSelection) {
1660         d.prevInput = "";
1661         d.inaccurateSelection = false;
1662         d.input.value = cm.getSelection();
1663         selectInput(d.input);
1664       }
1665     }
1666     on(d.input, "cut", prepareCopy);
1667     on(d.input, "copy", prepareCopy);
1668
1669     // Needed to handle Tab key in KHTML
1670     if (khtml) on(d.sizer, "mouseup", function() {
1671         if (document.activeElement == d.input) d.input.blur();
1672         focusInput(cm);
1673     });
1674   }
1675
1676   function eventInWidget(display, e) {
1677     for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
1678       if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
1679     }
1680   }
1681
1682   function posFromMouse(cm, e, liberal) {
1683     var display = cm.display;
1684     if (!liberal) {
1685       var target = e_target(e);
1686       if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1687           target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1688           target == display.scrollbarFiller || target == display.gutterFiller) return null;
1689     }
1690     var x, y, space = getRect(display.lineSpace);
1691     // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1692     try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1693     return coordsChar(cm, x - space.left, y - space.top);
1694   }
1695
1696   var lastClick, lastDoubleClick;
1697   function onMouseDown(e) {
1698     if (signalDOMEvent(this, e)) return;
1699     var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1700     sel.shift = e.shiftKey;
1701
1702     if (eventInWidget(display, e)) {
1703       if (!webkit) {
1704         display.scroller.draggable = false;
1705         setTimeout(function(){display.scroller.draggable = true;}, 100);
1706       }
1707       return;
1708     }
1709     if (clickInGutter(cm, e)) return;
1710     var start = posFromMouse(cm, e);
1711
1712     switch (e_button(e)) {
1713     case 3:
1714       if (captureMiddleClick) onContextMenu.call(cm, cm, e);
1715       return;
1716     case 2:
1717       if (webkit) cm.state.lastMiddleDown = +new Date;
1718       if (start) extendSelection(cm.doc, start);
1719       setTimeout(bind(focusInput, cm), 20);
1720       e_preventDefault(e);
1721       return;
1722     }
1723     // For button 1, if it was clicked inside the editor
1724     // (posFromMouse returning non-null), we have to adjust the
1725     // selection.
1726     if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
1727
1728     if (!cm.state.focused) onFocus(cm);
1729
1730     var now = +new Date, type = "single";
1731     if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
1732       type = "triple";
1733       e_preventDefault(e);
1734       setTimeout(bind(focusInput, cm), 20);
1735       selectLine(cm, start.line);
1736     } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
1737       type = "double";
1738       lastDoubleClick = {time: now, pos: start};
1739       e_preventDefault(e);
1740       var word = findWordAt(getLine(doc, start.line).text, start);
1741       extendSelection(cm.doc, word.from, word.to);
1742     } else { lastClick = {time: now, pos: start}; }
1743
1744     var last = start;
1745     if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
1746         !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
1747       var dragEnd = operation(cm, function(e2) {
1748         if (webkit) display.scroller.draggable = false;
1749         cm.state.draggingText = false;
1750         off(document, "mouseup", dragEnd);
1751         off(display.scroller, "drop", dragEnd);
1752         if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
1753           e_preventDefault(e2);
1754           extendSelection(cm.doc, start);
1755           focusInput(cm);
1756         }
1757       });
1758       // Let the drag handler handle this.
1759       if (webkit) display.scroller.draggable = true;
1760       cm.state.draggingText = dragEnd;
1761       // IE's approach to draggable
1762       if (display.scroller.dragDrop) display.scroller.dragDrop();
1763       on(document, "mouseup", dragEnd);
1764       on(display.scroller, "drop", dragEnd);
1765       return;
1766     }
1767     e_preventDefault(e);
1768     if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1769
1770     var startstart = sel.from, startend = sel.to, lastPos = start;
1771
1772     function doSelect(cur) {
1773       if (posEq(lastPos, cur)) return;
1774       lastPos = cur;
1775
1776       if (type == "single") {
1777         extendSelection(cm.doc, clipPos(doc, start), cur);
1778         return;
1779       }
1780
1781       startstart = clipPos(doc, startstart);
1782       startend = clipPos(doc, startend);
1783       if (type == "double") {
1784         var word = findWordAt(getLine(doc, cur.line).text, cur);
1785         if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
1786         else extendSelection(cm.doc, startstart, word.to);
1787       } else if (type == "triple") {
1788         if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
1789         else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
1790       }
1791     }
1792
1793     var editorSize = getRect(display.wrapper);
1794     // Used to ensure timeout re-tries don't fire when another extend
1795     // happened in the meantime (clearTimeout isn't reliable -- at
1796     // least on Chrome, the timeouts still happen even when cleared,
1797     // if the clear happens after their scheduled firing time).
1798     var counter = 0;
1799
1800     function extend(e) {
1801       var curCount = ++counter;
1802       var cur = posFromMouse(cm, e, true);
1803       if (!cur) return;
1804       if (!posEq(cur, last)) {
1805         if (!cm.state.focused) onFocus(cm);
1806         last = cur;
1807         doSelect(cur);
1808         var visible = visibleLines(display, doc);
1809         if (cur.line >= visible.to || cur.line < visible.from)
1810           setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
1811       } else {
1812         var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
1813         if (outside) setTimeout(operation(cm, function() {
1814           if (counter != curCount) return;
1815           display.scroller.scrollTop += outside;
1816           extend(e);
1817         }), 50);
1818       }
1819     }
1820
1821     function done(e) {
1822       counter = Infinity;
1823       e_preventDefault(e);
1824       focusInput(cm);
1825       off(document, "mousemove", move);
1826       off(document, "mouseup", up);
1827     }
1828
1829     var move = operation(cm, function(e) {
1830       if (!ie && !e_button(e)) done(e);
1831       else extend(e);
1832     });
1833     var up = operation(cm, done);
1834     on(document, "mousemove", move);
1835     on(document, "mouseup", up);
1836   }
1837
1838   function gutterEvent(cm, e, type, prevent, signalfn) {
1839     try { var mX = e.clientX, mY = e.clientY; }
1840     catch(e) { return false; }
1841     if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false;
1842     if (prevent) e_preventDefault(e);
1843
1844     var display = cm.display;
1845     var lineBox = getRect(display.lineDiv);
1846
1847     if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
1848     mY -= lineBox.top - display.viewOffset;
1849
1850     for (var i = 0; i < cm.options.gutters.length; ++i) {
1851       var g = display.gutters.childNodes[i];
1852       if (g && getRect(g).right >= mX) {
1853         var line = lineAtHeight(cm.doc, mY);
1854         var gutter = cm.options.gutters[i];
1855         signalfn(cm, type, cm, line, gutter, e);
1856         return e_defaultPrevented(e);
1857       }
1858     }
1859   }
1860
1861   function contextMenuInGutter(cm, e) {
1862     if (!hasHandler(cm, "gutterContextMenu")) return false;
1863     return gutterEvent(cm, e, "gutterContextMenu", false, signal);
1864   }
1865
1866   function clickInGutter(cm, e) {
1867     return gutterEvent(cm, e, "gutterClick", true, signalLater);
1868   }
1869
1870   // Kludge to work around strange IE behavior where it'll sometimes
1871   // re-fire a series of drag-related events right after the drop (#1551)
1872   var lastDrop = 0;
1873
1874   function onDrop(e) {
1875     var cm = this;
1876     if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
1877       return;
1878     e_preventDefault(e);
1879     if (ie) lastDrop = +new Date;
1880     var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1881     if (!pos || isReadOnly(cm)) return;
1882     if (files && files.length && window.FileReader && window.File) {
1883       var n = files.length, text = Array(n), read = 0;
1884       var loadFile = function(file, i) {
1885         var reader = new FileReader;
1886         reader.onload = function() {
1887           text[i] = reader.result;
1888           if (++read == n) {
1889             pos = clipPos(cm.doc, pos);
1890             makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
1891           }
1892         };
1893         reader.readAsText(file);
1894       };
1895       for (var i = 0; i < n; ++i) loadFile(files[i], i);
1896     } else {
1897       // Don't do a replace if the drop happened inside of the selected text.
1898       if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
1899         cm.state.draggingText(e);
1900         // Ensure the editor is re-focused
1901         setTimeout(bind(focusInput, cm), 20);
1902         return;
1903       }
1904       try {
1905         var text = e.dataTransfer.getData("Text");
1906         if (text) {
1907           var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
1908           setSelection(cm.doc, pos, pos);
1909           if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
1910           cm.replaceSelection(text, null, "paste");
1911           focusInput(cm);
1912         }
1913       }
1914       catch(e){}
1915     }
1916   }
1917
1918   function onDragStart(cm, e) {
1919     if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
1920     if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
1921
1922     var txt = cm.getSelection();
1923     e.dataTransfer.setData("Text", txt);
1924
1925     // Use dummy image instead of default browsers image.
1926     // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1927     if (e.dataTransfer.setDragImage && !safari) {
1928       var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
1929       img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
1930       if (opera) {
1931         img.width = img.height = 1;
1932         cm.display.wrapper.appendChild(img);
1933         // Force a relayout, or Opera won't use our image for some obscure reason
1934         img._top = img.offsetTop;
1935       }
1936       e.dataTransfer.setDragImage(img, 0, 0);
1937       if (opera) img.parentNode.removeChild(img);
1938     }
1939   }
1940
1941   function setScrollTop(cm, val) {
1942     if (Math.abs(cm.doc.scrollTop - val) < 2) return;
1943     cm.doc.scrollTop = val;
1944     if (!gecko) updateDisplay(cm, [], val);
1945     if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1946     if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1947     if (gecko) updateDisplay(cm, []);
1948     startWorker(cm, 100);
1949   }
1950   function setScrollLeft(cm, val, isScroller) {
1951     if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
1952     val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
1953     cm.doc.scrollLeft = val;
1954     alignHorizontally(cm);
1955     if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
1956     if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
1957   }
1958
1959   // Since the delta values reported on mouse wheel events are
1960   // unstandardized between browsers and even browser versions, and
1961   // generally horribly unpredictable, this code starts by measuring
1962   // the scroll effect that the first few mouse wheel events have,
1963   // and, from that, detects the way it can convert deltas to pixel
1964   // offsets afterwards.
1965   //
1966   // The reason we want to know the amount a wheel event will scroll
1967   // is that it gives us a chance to update the display before the
1968   // actual scrolling happens, reducing flickering.
1969
1970   var wheelSamples = 0, wheelPixelsPerUnit = null;
1971   // Fill in a browser-detected starting value on browsers where we
1972   // know one. These don't have to be accurate -- the result of them
1973   // being wrong would just be a slight flicker on the first wheel
1974   // scroll (if it is large enough).
1975   if (ie) wheelPixelsPerUnit = -.53;
1976   else if (gecko) wheelPixelsPerUnit = 15;
1977   else if (chrome) wheelPixelsPerUnit = -.7;
1978   else if (safari) wheelPixelsPerUnit = -1/3;
1979
1980   function onScrollWheel(cm, e) {
1981     var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
1982     if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
1983     if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1984     else if (dy == null) dy = e.wheelDelta;
1985
1986     var display = cm.display, scroll = display.scroller;
1987     // Quit if there's nothing to scroll here
1988     if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
1989           dy && scroll.scrollHeight > scroll.clientHeight)) return;
1990
1991     // Webkit browsers on OS X abort momentum scrolls when the target
1992     // of the scroll event is removed from the scrollable element.
1993     // This hack (see related code in patchDisplay) makes sure the
1994     // element is kept around.
1995     if (dy && mac && webkit) {
1996       for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
1997         if (cur.lineObj) {
1998           cm.display.currentWheelTarget = cur;
1999           break;
2000         }
2001       }
2002     }
2003
2004     // On some browsers, horizontal scrolling will cause redraws to
2005     // happen before the gutter has been realigned, causing it to
2006     // wriggle around in a most unseemly way. When we have an
2007     // estimated pixels/delta value, we just handle horizontal
2008     // scrolling entirely here. It'll be slightly off from native, but
2009     // better than glitching out.
2010     if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
2011       if (dy)
2012         setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
2013       setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
2014       e_preventDefault(e);
2015       display.wheelStartX = null; // Abort measurement, if in progress
2016       return;
2017     }
2018
2019     if (dy && wheelPixelsPerUnit != null) {
2020       var pixels = dy * wheelPixelsPerUnit;
2021       var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
2022       if (pixels < 0) top = Math.max(0, top + pixels - 50);
2023       else bot = Math.min(cm.doc.height, bot + pixels + 50);
2024       updateDisplay(cm, [], {top: top, bottom: bot});
2025     }
2026
2027     if (wheelSamples < 20) {
2028       if (display.wheelStartX == null) {
2029         display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
2030         display.wheelDX = dx; display.wheelDY = dy;
2031         setTimeout(function() {
2032           if (display.wheelStartX == null) return;
2033           var movedX = scroll.scrollLeft - display.wheelStartX;
2034           var movedY = scroll.scrollTop - display.wheelStartY;
2035           var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
2036             (movedX && display.wheelDX && movedX / display.wheelDX);
2037           display.wheelStartX = display.wheelStartY = null;
2038           if (!sample) return;
2039           wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
2040           ++wheelSamples;
2041         }, 200);
2042       } else {
2043         display.wheelDX += dx; display.wheelDY += dy;
2044       }
2045     }
2046   }
2047
2048   function doHandleBinding(cm, bound, dropShift) {
2049     if (typeof bound == "string") {
2050       bound = commands[bound];
2051       if (!bound) return false;
2052     }
2053     // Ensure previous input has been read, so that the handler sees a
2054     // consistent view of the document
2055     if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
2056     var doc = cm.doc, prevShift = doc.sel.shift, done = false;
2057     try {
2058       if (isReadOnly(cm)) cm.state.suppressEdits = true;
2059       if (dropShift) doc.sel.shift = false;
2060       done = bound(cm) != Pass;
2061     } finally {
2062       doc.sel.shift = prevShift;
2063       cm.state.suppressEdits = false;
2064     }
2065     return done;
2066   }
2067
2068   function allKeyMaps(cm) {
2069     var maps = cm.state.keyMaps.slice(0);
2070     if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
2071     maps.push(cm.options.keyMap);
2072     return maps;
2073   }
2074
2075   var maybeTransition;
2076   function handleKeyBinding(cm, e) {
2077     // Handle auto keymap transitions
2078     var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
2079     clearTimeout(maybeTransition);
2080     if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
2081       if (getKeyMap(cm.options.keyMap) == startMap) {
2082         cm.options.keyMap = (next.call ? next.call(null, cm) : next);
2083         keyMapChanged(cm);
2084       }
2085     }, 50);
2086
2087     var name = keyName(e, true), handled = false;
2088     if (!name) return false;
2089     var keymaps = allKeyMaps(cm);
2090
2091     if (e.shiftKey) {
2092       // First try to resolve full name (including 'Shift-'). Failing
2093       // that, see if there is a cursor-motion command (starting with
2094       // 'go') bound to the keyname without 'Shift-'.
2095       handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
2096              || lookupKey(name, keymaps, function(b) {
2097                   if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
2098                     return doHandleBinding(cm, b);
2099                 });
2100     } else {
2101       handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
2102     }
2103
2104     if (handled) {
2105       e_preventDefault(e);
2106       restartBlink(cm);
2107       if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
2108       signalLater(cm, "keyHandled", cm, name, e);
2109     }
2110     return handled;
2111   }
2112
2113   function handleCharBinding(cm, e, ch) {
2114     var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
2115                             function(b) { return doHandleBinding(cm, b, true); });
2116     if (handled) {
2117       e_preventDefault(e);
2118       restartBlink(cm);
2119       signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
2120     }
2121     return handled;
2122   }
2123
2124   var lastStoppedKey = null;
2125   function onKeyDown(e) {
2126     var cm = this;
2127     if (!cm.state.focused) onFocus(cm);
2128     if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2129     if (ie && e.keyCode == 27) e.returnValue = false;
2130     var code = e.keyCode;
2131     // IE does strange things with escape.
2132     cm.doc.sel.shift = code == 16 || e.shiftKey;
2133     // First give onKeyEvent option a chance to handle this.
2134     var handled = handleKeyBinding(cm, e);
2135     if (opera) {
2136       lastStoppedKey = handled ? code : null;
2137       // Opera has no cut event... we try to at least catch the key combo
2138       if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
2139         cm.replaceSelection("");
2140     }
2141   }
2142
2143   function onKeyPress(e) {
2144     var cm = this;
2145     if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2146     var keyCode = e.keyCode, charCode = e.charCode;
2147     if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
2148     if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
2149     var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
2150     if (this.options.electricChars && this.doc.mode.electricChars &&
2151         this.options.smartIndent && !isReadOnly(this) &&
2152         this.doc.mode.electricChars.indexOf(ch) > -1)
2153       setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
2154     if (handleCharBinding(cm, e, ch)) return;
2155     if (ie && !ie_lt9) cm.display.inputHasSelection = null;
2156     fastPoll(cm);
2157   }
2158
2159   function onFocus(cm) {
2160     if (cm.options.readOnly == "nocursor") return;
2161     if (!cm.state.focused) {
2162       signal(cm, "focus", cm);
2163       cm.state.focused = true;
2164       if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
2165         cm.display.wrapper.className += " CodeMirror-focused";
2166       if (!cm.curOp) {
2167         resetInput(cm, true);
2168         if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
2169       }
2170     }
2171     slowPoll(cm);
2172     restartBlink(cm);
2173   }
2174   function onBlur(cm) {
2175     if (cm.state.focused) {
2176       signal(cm, "blur", cm);
2177       cm.state.focused = false;
2178       cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
2179     }
2180     clearInterval(cm.display.blinker);
2181     setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
2182   }
2183
2184   var detectingSelectAll;
2185   function onContextMenu(cm, e) {
2186     if (signalDOMEvent(cm, e, "contextmenu")) return;
2187     var display = cm.display, sel = cm.doc.sel;
2188     if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
2189
2190     var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
2191     if (!pos || opera) return; // Opera is difficult.
2192
2193     // Reset the current text selection only if the click is done outside of the selection
2194     // and 'resetSelectionOnContextMenu' option is true.
2195     var reset = cm.options.resetSelectionOnContextMenu;
2196     if (reset && (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)))
2197       operation(cm, setSelection)(cm.doc, pos, pos);
2198
2199     var oldCSS = display.input.style.cssText;
2200     display.inputDiv.style.position = "absolute";
2201     display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
2202       "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
2203       "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
2204     focusInput(cm);
2205     resetInput(cm, true);
2206     // Adds "Select all" to context menu in FF
2207     if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
2208
2209     function prepareSelectAllHack() {
2210       if (display.input.selectionStart != null) {
2211         var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value);
2212         display.prevInput = "\u200b";
2213         display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
2214       }
2215     }
2216     function rehide() {
2217       display.inputDiv.style.position = "relative";
2218       display.input.style.cssText = oldCSS;
2219       if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
2220       slowPoll(cm);
2221
2222       // Try to detect the user choosing select-all
2223       if (display.input.selectionStart != null) {
2224         if (!ie || ie_lt9) prepareSelectAllHack();
2225         clearTimeout(detectingSelectAll);
2226         var i = 0, poll = function(){
2227           if (display.prevInput == " " && display.input.selectionStart == 0)
2228             operation(cm, commands.selectAll)(cm);
2229           else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
2230           else resetInput(cm);
2231         };
2232         detectingSelectAll = setTimeout(poll, 200);
2233       }
2234     }
2235
2236     if (ie && !ie_lt9) prepareSelectAllHack();
2237     if (captureMiddleClick) {
2238       e_stop(e);
2239       var mouseup = function() {
2240         off(window, "mouseup", mouseup);
2241         setTimeout(rehide, 20);
2242       };
2243       on(window, "mouseup", mouseup);
2244     } else {
2245       setTimeout(rehide, 50);
2246     }
2247   }
2248
2249   // UPDATING
2250
2251   var changeEnd = CodeMirror.changeEnd = function(change) {
2252     if (!change.text) return change.to;
2253     return Pos(change.from.line + change.text.length - 1,
2254                lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
2255   };
2256
2257   // Make sure a position will be valid after the given change.
2258   function clipPostChange(doc, change, pos) {
2259     if (!posLess(change.from, pos)) return clipPos(doc, pos);
2260     var diff = (change.text.length - 1) - (change.to.line - change.from.line);
2261     if (pos.line > change.to.line + diff) {
2262       var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
2263       if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
2264       return clipToLen(pos, getLine(doc, preLine).text.length);
2265     }
2266     if (pos.line == change.to.line + diff)
2267       return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
2268                        getLine(doc, change.to.line).text.length - change.to.ch);
2269     var inside = pos.line - change.from.line;
2270     return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
2271   }
2272
2273   // Hint can be null|"end"|"start"|"around"|{anchor,head}
2274   function computeSelAfterChange(doc, change, hint) {
2275     if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
2276       return {anchor: clipPostChange(doc, change, hint.anchor),
2277               head: clipPostChange(doc, change, hint.head)};
2278
2279     if (hint == "start") return {anchor: change.from, head: change.from};
2280
2281     var end = changeEnd(change);
2282     if (hint == "around") return {anchor: change.from, head: end};
2283     if (hint == "end") return {anchor: end, head: end};
2284
2285     // hint is null, leave the selection alone as much as possible
2286     var adjustPos = function(pos) {
2287       if (posLess(pos, change.from)) return pos;
2288       if (!posLess(change.to, pos)) return end;
2289
2290       var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
2291       if (pos.line == change.to.line) ch += end.ch - change.to.ch;
2292       return Pos(line, ch);
2293     };
2294     return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
2295   }
2296
2297   function filterChange(doc, change, update) {
2298     var obj = {
2299       canceled: false,
2300       from: change.from,
2301       to: change.to,
2302       text: change.text,
2303       origin: change.origin,
2304       cancel: function() { this.canceled = true; }
2305     };
2306     if (update) obj.update = function(from, to, text, origin) {
2307       if (from) this.from = clipPos(doc, from);
2308       if (to) this.to = clipPos(doc, to);
2309       if (text) this.text = text;
2310       if (origin !== undefined) this.origin = origin;
2311     };
2312     signal(doc, "beforeChange", doc, obj);
2313     if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
2314
2315     if (obj.canceled) return null;
2316     return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
2317   }
2318
2319   // Replace the range from from to to by the strings in replacement.
2320   // change is a {from, to, text [, origin]} object
2321   function makeChange(doc, change, selUpdate, ignoreReadOnly) {
2322     if (doc.cm) {
2323       if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
2324       if (doc.cm.state.suppressEdits) return;
2325     }
2326
2327     if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
2328       change = filterChange(doc, change, true);
2329       if (!change) return;
2330     }
2331
2332     // Possibly split or suppress the update based on the presence
2333     // of read-only spans in its range.
2334     var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
2335     if (split) {
2336       for (var i = split.length - 1; i >= 1; --i)
2337         makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
2338       if (split.length)
2339         makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
2340     } else {
2341       makeChangeNoReadonly(doc, change, selUpdate);
2342     }
2343   }
2344
2345   function makeChangeNoReadonly(doc, change, selUpdate) {
2346     if (change.text.length == 1 && change.text[0] == "" && posEq(change.from, change.to)) return;
2347     var selAfter = computeSelAfterChange(doc, change, selUpdate);
2348     addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
2349
2350     makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
2351     var rebased = [];
2352
2353     linkedDocs(doc, function(doc, sharedHist) {
2354       if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2355         rebaseHist(doc.history, change);
2356         rebased.push(doc.history);
2357       }
2358       makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
2359     });
2360   }
2361
2362   function makeChangeFromHistory(doc, type) {
2363     if (doc.cm && doc.cm.state.suppressEdits) return;
2364
2365     var hist = doc.history;
2366     var event = (type == "undo" ? hist.done : hist.undone).pop();
2367     if (!event) return;
2368
2369     var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
2370                 anchorAfter: event.anchorBefore, headAfter: event.headBefore,
2371                 generation: hist.generation};
2372     (type == "undo" ? hist.undone : hist.done).push(anti);
2373     hist.generation = event.generation || ++hist.maxGeneration;
2374
2375     var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
2376
2377     for (var i = event.changes.length - 1; i >= 0; --i) {
2378       var change = event.changes[i];
2379       change.origin = type;
2380       if (filter && !filterChange(doc, change, false)) {
2381         (type == "undo" ? hist.done : hist.undone).length = 0;
2382         return;
2383       }
2384
2385       anti.changes.push(historyChangeFromChange(doc, change));
2386
2387       var after = i ? computeSelAfterChange(doc, change, null)
2388                     : {anchor: event.anchorBefore, head: event.headBefore};
2389       makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
2390       var rebased = [];
2391
2392       linkedDocs(doc, function(doc, sharedHist) {
2393         if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2394           rebaseHist(doc.history, change);
2395           rebased.push(doc.history);
2396         }
2397         makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
2398       });
2399     }
2400   }
2401
2402   function shiftDoc(doc, distance) {
2403     function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
2404     doc.first += distance;
2405     if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
2406     doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
2407     doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
2408   }
2409
2410   function makeChangeSingleDoc(doc, change, selAfter, spans) {
2411     if (doc.cm && !doc.cm.curOp)
2412       return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
2413
2414     if (change.to.line < doc.first) {
2415       shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
2416       return;
2417     }
2418     if (change.from.line > doc.lastLine()) return;
2419
2420     // Clip the change to the size of this doc
2421     if (change.from.line < doc.first) {
2422       var shift = change.text.length - 1 - (doc.first - change.from.line);
2423       shiftDoc(doc, shift);
2424       change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
2425                 text: [lst(change.text)], origin: change.origin};
2426     }
2427     var last = doc.lastLine();
2428     if (change.to.line > last) {
2429       change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
2430                 text: [change.text[0]], origin: change.origin};
2431     }
2432
2433     change.removed = getBetween(doc, change.from, change.to);
2434
2435     if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
2436     if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
2437     else updateDoc(doc, change, spans, selAfter);
2438   }
2439
2440   function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
2441     var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
2442
2443     var recomputeMaxLength = false, checkWidthStart = from.line;
2444     if (!cm.options.lineWrapping) {
2445       checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
2446       doc.iter(checkWidthStart, to.line + 1, function(line) {
2447         if (line == display.maxLine) {
2448           recomputeMaxLength = true;
2449           return true;
2450         }
2451       });
2452     }
2453
2454     if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
2455       cm.curOp.cursorActivity = true;
2456
2457     updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
2458
2459     if (!cm.options.lineWrapping) {
2460       doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
2461         var len = lineLength(doc, line);
2462         if (len > display.maxLineLength) {
2463           display.maxLine = line;
2464           display.maxLineLength = len;
2465           display.maxLineChanged = true;
2466           recomputeMaxLength = false;
2467         }
2468       });
2469       if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
2470     }
2471
2472     // Adjust frontier, schedule worker
2473     doc.frontier = Math.min(doc.frontier, from.line);
2474     startWorker(cm, 400);
2475
2476     var lendiff = change.text.length - (to.line - from.line) - 1;
2477     // Remember that these lines changed, for updating the display
2478     regChange(cm, from.line, to.line + 1, lendiff);
2479
2480     if (hasHandler(cm, "change")) {
2481       var changeObj = {from: from, to: to,
2482                        text: change.text,
2483                        removed: change.removed,
2484                        origin: change.origin};
2485       if (cm.curOp.textChanged) {
2486         for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
2487         cur.next = changeObj;
2488       } else cm.curOp.textChanged = changeObj;
2489     }
2490   }
2491
2492   function replaceRange(doc, code, from, to, origin) {
2493     if (!to) to = from;
2494     if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
2495     if (typeof code == "string") code = splitLines(code);
2496     makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
2497   }
2498
2499   // POSITION OBJECT
2500
2501   function Pos(line, ch) {
2502     if (!(this instanceof Pos)) return new Pos(line, ch);
2503     this.line = line; this.ch = ch;
2504   }
2505   CodeMirror.Pos = Pos;
2506
2507   function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2508   function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2509   function copyPos(x) {return Pos(x.line, x.ch);}
2510
2511   // SELECTION
2512
2513   function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
2514   function clipPos(doc, pos) {
2515     if (pos.line < doc.first) return Pos(doc.first, 0);
2516     var last = doc.first + doc.size - 1;
2517     if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
2518     return clipToLen(pos, getLine(doc, pos.line).text.length);
2519   }
2520   function clipToLen(pos, linelen) {
2521     var ch = pos.ch;
2522     if (ch == null || ch > linelen) return Pos(pos.line, linelen);
2523     else if (ch < 0) return Pos(pos.line, 0);
2524     else return pos;
2525   }
2526   function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
2527
2528   // If shift is held, this will move the selection anchor. Otherwise,
2529   // it'll set the whole selection.
2530   function extendSelection(doc, pos, other, bias) {
2531     if (doc.sel.shift || doc.sel.extend) {
2532       var anchor = doc.sel.anchor;
2533       if (other) {
2534         var posBefore = posLess(pos, anchor);
2535         if (posBefore != posLess(other, anchor)) {
2536           anchor = pos;
2537           pos = other;
2538         } else if (posBefore != posLess(pos, other)) {
2539           pos = other;
2540         }
2541       }
2542       setSelection(doc, anchor, pos, bias);
2543     } else {
2544       setSelection(doc, pos, other || pos, bias);
2545     }
2546     if (doc.cm) doc.cm.curOp.userSelChange = true;
2547   }
2548
2549   function filterSelectionChange(doc, anchor, head) {
2550     var obj = {anchor: anchor, head: head};
2551     signal(doc, "beforeSelectionChange", doc, obj);
2552     if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
2553     obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
2554     return obj;
2555   }
2556
2557   // Update the selection. Last two args are only used by
2558   // updateDoc, since they have to be expressed in the line
2559   // numbers before the update.
2560   function setSelection(doc, anchor, head, bias, checkAtomic) {
2561     if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
2562       var filtered = filterSelectionChange(doc, anchor, head);
2563       head = filtered.head;
2564       anchor = filtered.anchor;
2565     }
2566
2567     var sel = doc.sel;
2568     sel.goalColumn = null;
2569     if (bias == null) bias = posLess(head, sel.head) ? -1 : 1;
2570     // Skip over atomic spans.
2571     if (checkAtomic || !posEq(anchor, sel.anchor))
2572       anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
2573     if (checkAtomic || !posEq(head, sel.head))
2574       head = skipAtomic(doc, head, bias, checkAtomic != "push");
2575
2576     if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
2577
2578     sel.anchor = anchor; sel.head = head;
2579     var inv = posLess(head, anchor);
2580     sel.from = inv ? head : anchor;
2581     sel.to = inv ? anchor : head;
2582
2583     if (doc.cm)
2584       doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
2585         doc.cm.curOp.cursorActivity = true;
2586
2587     signalLater(doc, "cursorActivity", doc);
2588   }
2589
2590   function reCheckSelection(cm) {
2591     setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
2592   }
2593
2594   function skipAtomic(doc, pos, bias, mayClear) {
2595     var flipped = false, curPos = pos;
2596     var dir = bias || 1;
2597     doc.cantEdit = false;
2598     search: for (;;) {
2599       var line = getLine(doc, curPos.line);
2600       if (line.markedSpans) {
2601         for (var i = 0; i < line.markedSpans.length; ++i) {
2602           var sp = line.markedSpans[i], m = sp.marker;
2603           if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2604               (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2605             if (mayClear) {
2606               signal(m, "beforeCursorEnter");
2607               if (m.explicitlyCleared) {
2608                 if (!line.markedSpans) break;
2609                 else {--i; continue;}
2610               }
2611             }
2612             if (!m.atomic) continue;
2613             var newPos = m.find()[dir < 0 ? "from" : "to"];
2614             if (posEq(newPos, curPos)) {
2615               newPos.ch += dir;
2616               if (newPos.ch < 0) {
2617                 if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
2618                 else newPos = null;
2619               } else if (newPos.ch > line.text.length) {
2620                 if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
2621                 else newPos = null;
2622               }
2623               if (!newPos) {
2624                 if (flipped) {
2625                   // Driven in a corner -- no valid cursor position found at all
2626                   // -- try again *with* clearing, if we didn't already
2627                   if (!mayClear) return skipAtomic(doc, pos, bias, true);
2628                   // Otherwise, turn off editing until further notice, and return the start of the doc
2629                   doc.cantEdit = true;
2630                   return Pos(doc.first, 0);
2631                 }
2632                 flipped = true; newPos = pos; dir = -dir;
2633               }
2634             }
2635             curPos = newPos;
2636             continue search;
2637           }
2638         }
2639       }
2640       return curPos;
2641     }
2642   }
2643
2644   // SCROLLING
2645
2646   function scrollCursorIntoView(cm) {
2647     var coords = scrollPosIntoView(cm, cm.doc.sel.head, null, cm.options.cursorScrollMargin);
2648     if (!cm.state.focused) return;
2649     var display = cm.display, box = getRect(display.sizer), doScroll = null;
2650     if (coords.top + box.top < 0) doScroll = true;
2651     else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2652     if (doScroll != null && !phantom) {
2653       var hidden = display.cursor.style.display == "none";
2654       if (hidden) {
2655         display.cursor.style.display = "";
2656         display.cursor.style.left = coords.left + "px";
2657         display.cursor.style.top = (coords.top - display.viewOffset) + "px";
2658       }
2659       display.cursor.scrollIntoView(doScroll);
2660       if (hidden) display.cursor.style.display = "none";
2661     }
2662   }
2663
2664   function scrollPosIntoView(cm, pos, end, margin) {
2665     if (margin == null) margin = 0;
2666     for (;;) {
2667       var changed = false, coords = cursorCoords(cm, pos);
2668       var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
2669       var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left),
2670                                          Math.min(coords.top, endCoords.top) - margin,
2671                                          Math.max(coords.left, endCoords.left),
2672                                          Math.max(coords.bottom, endCoords.bottom) + margin);
2673       var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
2674       if (scrollPos.scrollTop != null) {
2675         setScrollTop(cm, scrollPos.scrollTop);
2676         if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
2677       }
2678       if (scrollPos.scrollLeft != null) {
2679         setScrollLeft(cm, scrollPos.scrollLeft);
2680         if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
2681       }
2682       if (!changed) return coords;
2683     }
2684   }
2685
2686   function scrollIntoView(cm, x1, y1, x2, y2) {
2687     var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
2688     if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
2689     if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
2690   }
2691
2692   function calculateScrollPos(cm, x1, y1, x2, y2) {
2693     var display = cm.display, snapMargin = textHeight(cm.display);
2694     if (y1 < 0) y1 = 0;
2695     var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2696     var docBottom = cm.doc.height + paddingVert(display);
2697     var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
2698     if (y1 < screentop) {
2699       result.scrollTop = atTop ? 0 : y1;
2700     } else if (y2 > screentop + screen) {
2701       var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
2702       if (newTop != screentop) result.scrollTop = newTop;
2703     }
2704
2705     var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2706     x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
2707     var gutterw = display.gutters.offsetWidth;
2708     var atLeft = x1 < gutterw + 10;
2709     if (x1 < screenleft + gutterw || atLeft) {
2710       if (atLeft) x1 = 0;
2711       result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
2712     } else if (x2 > screenw + screenleft - 3) {
2713       result.scrollLeft = x2 + 10 - screenw;
2714     }
2715     return result;
2716   }
2717
2718   function updateScrollPos(cm, left, top) {
2719     cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
2720                                 scrollTop: top == null ? cm.doc.scrollTop : top};
2721   }
2722
2723   function addToScrollPos(cm, left, top) {
2724     var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});
2725     var scroll = cm.display.scroller;
2726     pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));
2727     pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));
2728   }
2729
2730   // API UTILITIES
2731
2732   function indentLine(cm, n, how, aggressive) {
2733     var doc = cm.doc;
2734     if (how == null) how = "add";
2735     if (how == "smart") {
2736       if (!cm.doc.mode.indent) how = "prev";
2737       else var state = getStateBefore(cm, n);
2738     }
2739
2740     var tabSize = cm.options.tabSize;
2741     var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
2742     var curSpaceString = line.text.match(/^\s*/)[0], indentation;
2743     if (how == "smart") {
2744       indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2745       if (indentation == Pass) {
2746         if (!aggressive) return;
2747         how = "prev";
2748       }
2749     }
2750     if (how == "prev") {
2751       if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
2752       else indentation = 0;
2753     } else if (how == "add") {
2754       indentation = curSpace + cm.options.indentUnit;
2755     } else if (how == "subtract") {
2756       indentation = curSpace - cm.options.indentUnit;
2757     } else if (typeof how == "number") {
2758       indentation = curSpace + how;
2759     }
2760     indentation = Math.max(0, indentation);
2761
2762     var indentString = "", pos = 0;
2763     if (cm.options.indentWithTabs)
2764       for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
2765     if (pos < indentation) indentString += spaceStr(indentation - pos);
2766
2767     if (indentString != curSpaceString)
2768       replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
2769     else if (doc.sel.head.line == n && doc.sel.head.ch < curSpaceString.length)
2770       setSelection(doc, Pos(n, curSpaceString.length), Pos(n, curSpaceString.length), 1);
2771     line.stateAfter = null;
2772   }
2773
2774   function changeLine(cm, handle, op) {
2775     var no = handle, line = handle, doc = cm.doc;
2776     if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
2777     else no = lineNo(handle);
2778     if (no == null) return null;
2779     if (op(line, no)) regChange(cm, no, no + 1);
2780     else return null;
2781     return line;
2782   }
2783
2784   function findPosH(doc, pos, dir, unit, visually) {
2785     var line = pos.line, ch = pos.ch, origDir = dir;
2786     var lineObj = getLine(doc, line);
2787     var possible = true;
2788     function findNextLine() {
2789       var l = line + dir;
2790       if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
2791       line = l;
2792       return lineObj = getLine(doc, l);
2793     }
2794     function moveOnce(boundToLine) {
2795       var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
2796       if (next == null) {
2797         if (!boundToLine && findNextLine()) {
2798           if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
2799           else ch = dir < 0 ? lineObj.text.length : 0;
2800         } else return (possible = false);
2801       } else ch = next;
2802       return true;
2803     }
2804
2805     if (unit == "char") moveOnce();
2806     else if (unit == "column") moveOnce(true);
2807     else if (unit == "word" || unit == "group") {
2808       var sawType = null, group = unit == "group";
2809       for (var first = true;; first = false) {
2810         if (dir < 0 && !moveOnce(!first)) break;
2811         var cur = lineObj.text.charAt(ch) || "\n";
2812         var type = isWordChar(cur) ? "w"
2813           : !group ? null
2814           : /\s/.test(cur) ? null
2815           : "p";
2816         if (sawType && sawType != type) {
2817           if (dir < 0) {dir = 1; moveOnce();}
2818           break;
2819         }
2820         if (type) sawType = type;
2821         if (dir > 0 && !moveOnce(!first)) break;
2822       }
2823     }
2824     var result = skipAtomic(doc, Pos(line, ch), origDir, true);
2825     if (!possible) result.hitSide = true;
2826     return result;
2827   }
2828
2829   function findPosV(cm, pos, dir, unit) {
2830     var doc = cm.doc, x = pos.left, y;
2831     if (unit == "page") {
2832       var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2833       y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
2834     } else if (unit == "line") {
2835       y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2836     }
2837     for (;;) {
2838       var target = coordsChar(cm, x, y);
2839       if (!target.outside) break;
2840       if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
2841       y += dir * 5;
2842     }
2843     return target;
2844   }
2845
2846   function findWordAt(line, pos) {
2847     var start = pos.ch, end = pos.ch;
2848     if (line) {
2849       if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
2850       var startChar = line.charAt(start);
2851       var check = isWordChar(startChar) ? isWordChar
2852         : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
2853         : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2854       while (start > 0 && check(line.charAt(start - 1))) --start;
2855       while (end < line.length && check(line.charAt(end))) ++end;
2856     }
2857     return {from: Pos(pos.line, start), to: Pos(pos.line, end)};
2858   }
2859
2860   function selectLine(cm, line) {
2861     extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
2862   }
2863
2864   // PROTOTYPE
2865
2866   // The publicly visible API. Note that operation(null, f) means
2867   // 'wrap f in an operation, performed on its `this` parameter'
2868
2869   CodeMirror.prototype = {
2870     constructor: CodeMirror,
2871     focus: function(){window.focus(); focusInput(this); fastPoll(this);},
2872
2873     setOption: function(option, value) {
2874       var options = this.options, old = options[option];
2875       if (options[option] == value && option != "mode") return;
2876       options[option] = value;
2877       if (optionHandlers.hasOwnProperty(option))
2878         operation(this, optionHandlers[option])(this, value, old);
2879     },
2880
2881     getOption: function(option) {return this.options[option];},
2882     getDoc: function() {return this.doc;},
2883
2884     addKeyMap: function(map, bottom) {
2885       this.state.keyMaps[bottom ? "push" : "unshift"](map);
2886     },
2887     removeKeyMap: function(map) {
2888       var maps = this.state.keyMaps;
2889       for (var i = 0; i < maps.length; ++i)
2890         if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
2891           maps.splice(i, 1);
2892           return true;
2893         }
2894     },
2895
2896     addOverlay: operation(null, function(spec, options) {
2897       var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
2898       if (mode.startState) throw new Error("Overlays may not be stateful.");
2899       this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
2900       this.state.modeGen++;
2901       regChange(this);
2902     }),
2903     removeOverlay: operation(null, function(spec) {
2904       var overlays = this.state.overlays;
2905       for (var i = 0; i < overlays.length; ++i) {
2906         var cur = overlays[i].modeSpec;
2907         if (cur == spec || typeof spec == "string" && cur.name == spec) {
2908           overlays.splice(i, 1);
2909           this.state.modeGen++;
2910           regChange(this);
2911           return;
2912         }
2913       }
2914     }),
2915
2916     indentLine: operation(null, function(n, dir, aggressive) {
2917       if (typeof dir != "string" && typeof dir != "number") {
2918         if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2919         else dir = dir ? "add" : "subtract";
2920       }
2921       if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
2922     }),
2923     indentSelection: operation(null, function(how) {
2924       var sel = this.doc.sel;
2925       if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
2926       var e = sel.to.line - (sel.to.ch ? 0 : 1);
2927       for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
2928     }),
2929
2930     // Fetch the parser token for a given character. Useful for hacks
2931     // that want to inspect the mode state (say, for completion).
2932     getTokenAt: function(pos, precise) {
2933       var doc = this.doc;
2934       pos = clipPos(doc, pos);
2935       var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
2936       var line = getLine(doc, pos.line);
2937       var stream = new StringStream(line.text, this.options.tabSize);
2938       while (stream.pos < pos.ch && !stream.eol()) {
2939         stream.start = stream.pos;
2940         var style = mode.token(stream, state);
2941       }
2942       return {start: stream.start,
2943               end: stream.pos,
2944               string: stream.current(),
2945               className: style || null, // Deprecated, use 'type' instead
2946               type: style || null,
2947               state: state};
2948     },
2949
2950     getTokenTypeAt: function(pos) {
2951       pos = clipPos(this.doc, pos);
2952       var styles = getLineStyles(this, getLine(this.doc, pos.line));
2953       var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
2954       if (ch == 0) return styles[2];
2955       for (;;) {
2956         var mid = (before + after) >> 1;
2957         if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
2958         else if (styles[mid * 2 + 1] < ch) before = mid + 1;
2959         else return styles[mid * 2 + 2];
2960       }
2961     },
2962
2963     getModeAt: function(pos) {
2964       var mode = this.doc.mode;
2965       if (!mode.innerMode) return mode;
2966       return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
2967     },
2968
2969     getHelper: function(pos, type) {
2970       if (!helpers.hasOwnProperty(type)) return;
2971       var help = helpers[type], mode = this.getModeAt(pos);
2972       return mode[type] && help[mode[type]] ||
2973         mode.helperType && help[mode.helperType] ||
2974         help[mode.name];
2975     },
2976
2977     getStateAfter: function(line, precise) {
2978       var doc = this.doc;
2979       line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
2980       return getStateBefore(this, line + 1, precise);
2981     },
2982
2983     cursorCoords: function(start, mode) {
2984       var pos, sel = this.doc.sel;
2985       if (start == null) pos = sel.head;
2986       else if (typeof start == "object") pos = clipPos(this.doc, start);
2987       else pos = start ? sel.from : sel.to;
2988       return cursorCoords(this, pos, mode || "page");
2989     },
2990
2991     charCoords: function(pos, mode) {
2992       return charCoords(this, clipPos(this.doc, pos), mode || "page");
2993     },
2994
2995     coordsChar: function(coords, mode) {
2996       coords = fromCoordSystem(this, coords, mode || "page");
2997       return coordsChar(this, coords.left, coords.top);
2998     },
2999
3000     lineAtHeight: function(height, mode) {
3001       height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
3002       return lineAtHeight(this.doc, height + this.display.viewOffset);
3003     },
3004     heightAtLine: function(line, mode) {
3005       var end = false, last = this.doc.first + this.doc.size - 1;
3006       if (line < this.doc.first) line = this.doc.first;
3007       else if (line > last) { line = last; end = true; }
3008       var lineObj = getLine(this.doc, line);
3009       return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
3010         (end ? lineObj.height : 0);
3011     },
3012
3013     defaultTextHeight: function() { return textHeight(this.display); },
3014     defaultCharWidth: function() { return charWidth(this.display); },
3015
3016     setGutterMarker: operation(null, function(line, gutterID, value) {
3017       return changeLine(this, line, function(line) {
3018         var markers = line.gutterMarkers || (line.gutterMarkers = {});
3019         markers[gutterID] = value;
3020         if (!value && isEmpty(markers)) line.gutterMarkers = null;
3021         return true;
3022       });
3023     }),
3024
3025     clearGutter: operation(null, function(gutterID) {
3026       var cm = this, doc = cm.doc, i = doc.first;
3027       doc.iter(function(line) {
3028         if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
3029           line.gutterMarkers[gutterID] = null;
3030           regChange(cm, i, i + 1);
3031           if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
3032         }
3033         ++i;
3034       });
3035     }),
3036
3037     addLineClass: operation(null, function(handle, where, cls) {
3038       return changeLine(this, handle, function(line) {
3039         var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
3040         if (!line[prop]) line[prop] = cls;
3041         else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
3042         else line[prop] += " " + cls;
3043         return true;
3044       });
3045     }),
3046
3047     removeLineClass: operation(null, function(handle, where, cls) {
3048       return changeLine(this, handle, function(line) {
3049         var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
3050         var cur = line[prop];
3051         if (!cur) return false;
3052         else if (cls == null) line[prop] = null;
3053         else {
3054           var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
3055           if (!found) return false;
3056           var end = found.index + found[0].length;
3057           line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
3058         }
3059         return true;
3060       });
3061     }),
3062
3063     addLineWidget: operation(null, function(handle, node, options) {
3064       return addLineWidget(this, handle, node, options);
3065     }),
3066
3067     removeLineWidget: function(widget) { widget.clear(); },
3068
3069     lineInfo: function(line) {
3070       if (typeof line == "number") {
3071         if (!isLine(this.doc, line)) return null;
3072         var n = line;
3073         line = getLine(this.doc, line);
3074         if (!line) return null;
3075       } else {
3076         var n = lineNo(line);
3077         if (n == null) return null;
3078       }
3079       return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
3080               textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
3081               widgets: line.widgets};
3082     },
3083
3084     getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
3085
3086     addWidget: function(pos, node, scroll, vert, horiz) {
3087       var display = this.display;
3088       pos = cursorCoords(this, clipPos(this.doc, pos));
3089       var top = pos.bottom, left = pos.left;
3090       node.style.position = "absolute";
3091       display.sizer.appendChild(node);
3092       if (vert == "over") {
3093         top = pos.top;
3094       } else if (vert == "above" || vert == "near") {
3095         var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
3096         hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
3097         // Default to positioning above (if specified and possible); otherwise default to positioning below
3098         if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
3099           top = pos.top - node.offsetHeight;
3100         else if (pos.bottom + node.offsetHeight <= vspace)
3101           top = pos.bottom;
3102         if (left + node.offsetWidth > hspace)
3103           left = hspace - node.offsetWidth;
3104       }
3105       node.style.top = top + "px";
3106       node.style.left = node.style.right = "";
3107       if (horiz == "right") {
3108         left = display.sizer.clientWidth - node.offsetWidth;
3109         node.style.right = "0px";
3110       } else {
3111         if (horiz == "left") left = 0;
3112         else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
3113         node.style.left = left + "px";
3114       }
3115       if (scroll)
3116         scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
3117     },
3118
3119     triggerOnKeyDown: operation(null, onKeyDown),
3120
3121     execCommand: function(cmd) {return commands[cmd](this);},
3122
3123     findPosH: function(from, amount, unit, visually) {
3124       var dir = 1;
3125       if (amount < 0) { dir = -1; amount = -amount; }
3126       for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
3127         cur = findPosH(this.doc, cur, dir, unit, visually);
3128         if (cur.hitSide) break;
3129       }
3130       return cur;
3131     },
3132
3133     moveH: operation(null, function(dir, unit) {
3134       var sel = this.doc.sel, pos;
3135       if (sel.shift || sel.extend || posEq(sel.from, sel.to))
3136         pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
3137       else
3138         pos = dir < 0 ? sel.from : sel.to;
3139       extendSelection(this.doc, pos, pos, dir);
3140     }),
3141
3142     deleteH: operation(null, function(dir, unit) {
3143       var sel = this.doc.sel;
3144       if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
3145       else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
3146       this.curOp.userSelChange = true;
3147     }),
3148
3149     findPosV: function(from, amount, unit, goalColumn) {
3150       var dir = 1, x = goalColumn;
3151       if (amount < 0) { dir = -1; amount = -amount; }
3152       for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
3153         var coords = cursorCoords(this, cur, "div");
3154         if (x == null) x = coords.left;
3155         else coords.left = x;
3156         cur = findPosV(this, coords, dir, unit);
3157         if (cur.hitSide) break;
3158       }
3159       return cur;
3160     },
3161
3162     moveV: operation(null, function(dir, unit) {
3163       var sel = this.doc.sel;
3164       var pos = cursorCoords(this, sel.head, "div");
3165       if (sel.goalColumn != null) pos.left = sel.goalColumn;
3166       var target = findPosV(this, pos, dir, unit);
3167
3168       if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
3169       extendSelection(this.doc, target, target, dir);
3170       sel.goalColumn = pos.left;
3171     }),
3172
3173     toggleOverwrite: function(value) {
3174       if (value != null && value == this.state.overwrite) return;
3175       if (this.state.overwrite = !this.state.overwrite)
3176         this.display.cursor.className += " CodeMirror-overwrite";
3177       else
3178         this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
3179     },
3180     hasFocus: function() { return this.state.focused; },
3181
3182     scrollTo: operation(null, function(x, y) {
3183       updateScrollPos(this, x, y);
3184     }),
3185     getScrollInfo: function() {
3186       var scroller = this.display.scroller, co = scrollerCutOff;
3187       return {left: scroller.scrollLeft, top: scroller.scrollTop,
3188               height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
3189               clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
3190     },
3191
3192     scrollIntoView: operation(null, function(range, margin) {
3193       if (range == null) range = {from: this.doc.sel.head, to: null};
3194       else if (typeof range == "number") range = {from: Pos(range, 0), to: null};
3195       else if (range.from == null) range = {from: range, to: null};
3196       if (!range.to) range.to = range.from;
3197       if (!margin) margin = 0;
3198
3199       var coords = range;
3200       if (range.from.line != null) {
3201         this.curOp.scrollToPos = {from: range.from, to: range.to, margin: margin};
3202         coords = {from: cursorCoords(this, range.from),
3203                   to: cursorCoords(this, range.to)};
3204       }
3205       var sPos = calculateScrollPos(this, Math.min(coords.from.left, coords.to.left),
3206                                     Math.min(coords.from.top, coords.to.top) - margin,
3207                                     Math.max(coords.from.right, coords.to.right),
3208                                     Math.max(coords.from.bottom, coords.to.bottom) + margin);
3209       updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
3210     }),
3211
3212     setSize: operation(null, function(width, height) {
3213       function interpret(val) {
3214         return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
3215       }
3216       if (width != null) this.display.wrapper.style.width = interpret(width);
3217       if (height != null) this.display.wrapper.style.height = interpret(height);
3218       if (this.options.lineWrapping)
3219         this.display.measureLineCache.length = this.display.measureLineCachePos = 0;
3220       this.curOp.forceUpdate = true;
3221     }),
3222
3223     operation: function(f){return runInOp(this, f);},
3224
3225     refresh: operation(null, function() {
3226       var badHeight = this.display.cachedTextHeight == null;
3227       clearCaches(this);
3228       updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);
3229       regChange(this);
3230       if (badHeight) estimateLineHeights(this);
3231     }),
3232
3233     swapDoc: operation(null, function(doc) {
3234       var old = this.doc;
3235       old.cm = null;
3236       attachDoc(this, doc);
3237       clearCaches(this);
3238       resetInput(this, true);
3239       updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
3240       signalLater(this, "swapDoc", this, old);
3241       return old;
3242     }),
3243
3244     getInputField: function(){return this.display.input;},
3245     getWrapperElement: function(){return this.display.wrapper;},
3246     getScrollerElement: function(){return this.display.scroller;},
3247     getGutterElement: function(){return this.display.gutters;}
3248   };
3249   eventMixin(CodeMirror);
3250
3251   // OPTION DEFAULTS
3252
3253   var optionHandlers = CodeMirror.optionHandlers = {};
3254
3255   // The default configuration options.
3256   var defaults = CodeMirror.defaults = {};
3257
3258   function option(name, deflt, handle, notOnInit) {
3259     CodeMirror.defaults[name] = deflt;
3260     if (handle) optionHandlers[name] =
3261       notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
3262   }
3263
3264   var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
3265
3266   // These two are, on init, called from the constructor because they
3267   // have to be initialized before the editor can start at all.
3268   option("value", "", function(cm, val) {
3269     cm.setValue(val);
3270   }, true);
3271   option("mode", null, function(cm, val) {
3272     cm.doc.modeOption = val;
3273     loadMode(cm);
3274   }, true);
3275
3276   option("indentUnit", 2, loadMode, true);
3277   option("indentWithTabs", false);
3278   option("smartIndent", true);
3279   option("tabSize", 4, function(cm) {
3280     loadMode(cm);
3281     clearCaches(cm);
3282     regChange(cm);
3283   }, true);
3284   option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) {
3285     cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
3286     cm.refresh();
3287   }, true);
3288   option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true);
3289   option("electricChars", true);
3290   option("rtlMoveVisually", !windows);
3291   option("wholeLineUpdateBefore", true);
3292
3293   option("theme", "default", function(cm) {
3294     themeChanged(cm);
3295     guttersChanged(cm);
3296   }, true);
3297   option("keyMap", "default", keyMapChanged);
3298   option("extraKeys", null);
3299
3300   option("onKeyEvent", null);
3301   option("onDragEvent", null);
3302
3303   option("lineWrapping", false, wrappingChanged, true);
3304   option("gutters", [], function(cm) {
3305     setGuttersForLineNumbers(cm.options);
3306     guttersChanged(cm);
3307   }, true);
3308   option("fixedGutter", true, function(cm, val) {
3309     cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
3310     cm.refresh();
3311   }, true);
3312   option("coverGutterNextToScrollbar", false, updateScrollbars, true);
3313   option("lineNumbers", false, function(cm) {
3314     setGuttersForLineNumbers(cm.options);
3315     guttersChanged(cm);
3316   }, true);
3317   option("firstLineNumber", 1, guttersChanged, true);
3318   option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
3319   option("showCursorWhenSelecting", false, updateSelection, true);
3320
3321   option("resetSelectionOnContextMenu", true);
3322
3323   option("readOnly", false, function(cm, val) {
3324     if (val == "nocursor") {
3325       onBlur(cm);
3326       cm.display.input.blur();
3327       cm.display.disabled = true;
3328     } else {
3329       cm.display.disabled = false;
3330       if (!val) resetInput(cm, true);
3331     }
3332   });
3333   option("dragDrop", true);
3334
3335   option("cursorBlinkRate", 530);
3336   option("cursorScrollMargin", 0);
3337   option("cursorHeight", 1);
3338   option("workTime", 100);
3339   option("workDelay", 100);
3340   option("flattenSpans", true);
3341   option("pollInterval", 100);
3342   option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
3343   option("historyEventDelay", 500);
3344   option("viewportMargin", 10, function(cm){cm.refresh();}, true);
3345   option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
3346   option("crudeMeasuringFrom", 10000);
3347   option("moveInputWithCursor", true, function(cm, val) {
3348     if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
3349   });
3350
3351   option("tabindex", null, function(cm, val) {
3352     cm.display.input.tabIndex = val || "";
3353   });
3354   option("autofocus", null);
3355
3356   // MODE DEFINITION AND QUERYING
3357
3358   // Known modes, by name and by MIME
3359   var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
3360
3361   CodeMirror.defineMode = function(name, mode) {
3362     if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
3363     if (arguments.length > 2) {
3364       mode.dependencies = [];
3365       for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
3366     }
3367     modes[name] = mode;
3368   };
3369
3370   CodeMirror.defineMIME = function(mime, spec) {
3371     mimeModes[mime] = spec;
3372   };
3373
3374   CodeMirror.resolveMode = function(spec) {
3375     if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
3376       spec = mimeModes[spec];
3377     } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
3378       var found = mimeModes[spec.name];
3379       spec = createObj(found, spec);
3380       spec.name = found.name;
3381     } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
3382       return CodeMirror.resolveMode("application/xml");
3383     }
3384     if (typeof spec == "string") return {name: spec};
3385     else return spec || {name: "null"};
3386   };
3387
3388   CodeMirror.getMode = function(options, spec) {
3389     var spec = CodeMirror.resolveMode(spec);
3390     var mfactory = modes[spec.name];
3391     if (!mfactory) return CodeMirror.getMode(options, "text/plain");
3392     var modeObj = mfactory(options, spec);
3393     if (modeExtensions.hasOwnProperty(spec.name)) {
3394       var exts = modeExtensions[spec.name];
3395       for (var prop in exts) {
3396         if (!exts.hasOwnProperty(prop)) continue;
3397         if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
3398         modeObj[prop] = exts[prop];
3399       }
3400     }
3401     modeObj.name = spec.name;
3402
3403     return modeObj;
3404   };
3405
3406   CodeMirror.defineMode("null", function() {
3407     return {token: function(stream) {stream.skipToEnd();}};
3408   });
3409   CodeMirror.defineMIME("text/plain", "null");
3410
3411   var modeExtensions = CodeMirror.modeExtensions = {};
3412   CodeMirror.extendMode = function(mode, properties) {
3413     var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
3414     copyObj(properties, exts);
3415   };
3416
3417   // EXTENSIONS
3418
3419   CodeMirror.defineExtension = function(name, func) {
3420     CodeMirror.prototype[name] = func;
3421   };
3422   CodeMirror.defineDocExtension = function(name, func) {
3423     Doc.prototype[name] = func;
3424   };
3425   CodeMirror.defineOption = option;
3426
3427   var initHooks = [];
3428   CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
3429
3430   var helpers = CodeMirror.helpers = {};
3431   CodeMirror.registerHelper = function(type, name, value) {
3432     if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {};
3433     helpers[type][name] = value;
3434   };
3435
3436   // UTILITIES
3437
3438   CodeMirror.isWordChar = isWordChar;
3439
3440   // MODE STATE HANDLING
3441
3442   // Utility functions for working with state. Exported because modes
3443   // sometimes need to do this.
3444   function copyState(mode, state) {
3445     if (state === true) return state;
3446     if (mode.copyState) return mode.copyState(state);
3447     var nstate = {};
3448     for (var n in state) {
3449       var val = state[n];
3450       if (val instanceof Array) val = val.concat([]);
3451       nstate[n] = val;
3452     }
3453     return nstate;
3454   }
3455   CodeMirror.copyState = copyState;
3456
3457   function startState(mode, a1, a2) {
3458     return mode.startState ? mode.startState(a1, a2) : true;
3459   }
3460   CodeMirror.startState = startState;
3461
3462   CodeMirror.innerMode = function(mode, state) {
3463     while (mode.innerMode) {
3464       var info = mode.innerMode(state);
3465       if (!info || info.mode == mode) break;
3466       state = info.state;
3467       mode = info.mode;
3468     }
3469     return info || {mode: mode, state: state};
3470   };
3471
3472   // STANDARD COMMANDS
3473
3474   var commands = CodeMirror.commands = {
3475     selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},
3476     killLine: function(cm) {
3477       var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
3478       if (!sel && cm.getLine(from.line).length == from.ch)
3479         cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete");
3480       else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete");
3481     },
3482     deleteLine: function(cm) {
3483       var l = cm.getCursor().line;
3484       cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
3485     },
3486     delLineLeft: function(cm) {
3487       var cur = cm.getCursor();
3488       cm.replaceRange("", Pos(cur.line, 0), cur, "+delete");
3489     },
3490     undo: function(cm) {cm.undo();},
3491     redo: function(cm) {cm.redo();},
3492     goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
3493     goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
3494     goLineStart: function(cm) {
3495       cm.extendSelection(lineStart(cm, cm.getCursor().line));
3496     },
3497     goLineStartSmart: function(cm) {
3498       var cur = cm.getCursor(), start = lineStart(cm, cur.line);
3499       var line = cm.getLineHandle(start.line);
3500       var order = getOrder(line);
3501       if (!order || order[0].level == 0) {
3502         var firstNonWS = Math.max(0, line.text.search(/\S/));
3503         var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
3504         cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));
3505       } else cm.extendSelection(start);
3506     },
3507     goLineEnd: function(cm) {
3508       cm.extendSelection(lineEnd(cm, cm.getCursor().line));
3509     },
3510     goLineRight: function(cm) {
3511       var top = cm.charCoords(cm.getCursor(), "div").top + 5;
3512       cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"));
3513     },
3514     goLineLeft: function(cm) {
3515       var top = cm.charCoords(cm.getCursor(), "div").top + 5;
3516       cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div"));
3517     },
3518     goLineUp: function(cm) {cm.moveV(-1, "line");},
3519     goLineDown: function(cm) {cm.moveV(1, "line");},
3520     goPageUp: function(cm) {cm.moveV(-1, "page");},
3521     goPageDown: function(cm) {cm.moveV(1, "page");},
3522     goCharLeft: function(cm) {cm.moveH(-1, "char");},
3523     goCharRight: function(cm) {cm.moveH(1, "char");},
3524     goColumnLeft: function(cm) {cm.moveH(-1, "column");},
3525     goColumnRight: function(cm) {cm.moveH(1, "column");},
3526     goWordLeft: function(cm) {cm.moveH(-1, "word");},
3527     goGroupRight: function(cm) {cm.moveH(1, "group");},
3528     goGroupLeft: function(cm) {cm.moveH(-1, "group");},
3529     goWordRight: function(cm) {cm.moveH(1, "word");},
3530     delCharBefore: function(cm) {cm.deleteH(-1, "char");},
3531     delCharAfter: function(cm) {cm.deleteH(1, "char");},
3532     delWordBefore: function(cm) {cm.deleteH(-1, "word");},
3533     delWordAfter: function(cm) {cm.deleteH(1, "word");},
3534     delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
3535     delGroupAfter: function(cm) {cm.deleteH(1, "group");},
3536     indentAuto: function(cm) {cm.indentSelection("smart");},
3537     indentMore: function(cm) {cm.indentSelection("add");},
3538     indentLess: function(cm) {cm.indentSelection("subtract");},
3539     insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");},
3540     defaultTab: function(cm) {
3541       if (cm.somethingSelected()) cm.indentSelection("add");
3542       else cm.replaceSelection("\t", "end", "+input");
3543     },
3544     transposeChars: function(cm) {
3545       var cur = cm.getCursor(), line = cm.getLine(cur.line);
3546       if (cur.ch > 0 && cur.ch < line.length - 1)
3547         cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
3548                         Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
3549     },
3550     newlineAndIndent: function(cm) {
3551       operation(cm, function() {
3552         cm.replaceSelection("\n", "end", "+input");
3553         cm.indentLine(cm.getCursor().line, null, true);
3554       })();
3555     },
3556     toggleOverwrite: function(cm) {cm.toggleOverwrite();}
3557   };
3558
3559   // STANDARD KEYMAPS
3560
3561   var keyMap = CodeMirror.keyMap = {};
3562   keyMap.basic = {
3563     "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
3564     "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
3565     "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
3566     "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3567     "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
3568   };
3569   // Note that the save and find-related commands aren't defined by
3570   // default. Unknown commands are simply ignored.
3571   keyMap.pcDefault = {
3572     "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3573     "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3574     "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3575     "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3576     "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3577     "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3578     fallthrough: "basic"
3579   };
3580   keyMap.macDefault = {
3581     "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3582     "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
3583     "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
3584     "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
3585     "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3586     "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
3587     fallthrough: ["basic", "emacsy"]
3588   };
3589   keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
3590   keyMap.emacsy = {
3591     "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
3592     "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
3593     "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
3594     "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
3595   };
3596
3597   // KEYMAP DISPATCH
3598
3599   function getKeyMap(val) {
3600     if (typeof val == "string") return keyMap[val];
3601     else return val;
3602   }
3603
3604   function lookupKey(name, maps, handle) {
3605     function lookup(map) {
3606       map = getKeyMap(map);
3607       var found = map[name];
3608       if (found === false) return "stop";
3609       if (found != null && handle(found)) return true;
3610       if (map.nofallthrough) return "stop";
3611
3612       var fallthrough = map.fallthrough;
3613       if (fallthrough == null) return false;
3614       if (Object.prototype.toString.call(fallthrough) != "[object Array]")
3615         return lookup(fallth