Web Inspector: CodeMirror 4 CSS mode new state data structure breaks helpers.
[WebKit-https.git] / Source / WebInspectorUI / Tools / PrettyPrinting / codemirror.js
1 // This is CodeMirror (http://codemirror.net), a code editor
2 // implemented in JavaScript on top of the browser's DOM.
3 //
4 // You can find some technical background for some of the code below
5 // at http://marijnhaverbeke.nl/blog/#cm-internals .
6
7 (function(mod) {
8   if (typeof exports == "object" && typeof module == "object") // CommonJS
9     module.exports = mod();
10   else if (typeof define == "function" && define.amd) // AMD
11     return define([], mod);
12   else // Plain browser env
13     this.CodeMirror = mod();
14 })(function() {
15   "use strict";
16
17   // BROWSER SNIFFING
18
19   // Kludges for bugs and behavior differences that can't be feature
20   // detected are enabled based on userAgent etc sniffing.
21
22   var gecko = /gecko\/\d/i.test(navigator.userAgent);
23   // ie_uptoN means Internet Explorer version N or lower
24   var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
25   var ie_upto7 = ie_upto10 && (document.documentMode == null || document.documentMode < 8);
26   var ie_upto8 = ie_upto10 && (document.documentMode == null || document.documentMode < 9);
27   var ie_upto9 = ie_upto10 && (document.documentMode == null || document.documentMode < 10);
28   var ie_11up = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent);
29   var ie = ie_upto10 || ie_11up;
30   var webkit = /WebKit\//.test(navigator.userAgent);
31   var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
32   var chrome = /Chrome\//.test(navigator.userAgent);
33   var presto = /Opera\//.test(navigator.userAgent);
34   var safari = /Apple Computer/.test(navigator.vendor);
35   var khtml = /KHTML\//.test(navigator.userAgent);
36   var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
37   var phantom = /PhantomJS/.test(navigator.userAgent);
38
39   var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
40   // This is woefully incomplete. Suggestions for alternative methods welcome.
41   var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
42   var mac = ios || /Mac/.test(navigator.platform);
43   var windows = /win/i.test(navigator.platform);
44
45   var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
46   if (presto_version) presto_version = Number(presto_version[1]);
47   if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
48   // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
49   var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
50   var captureRightClick = gecko || (ie && !ie_upto8);
51
52   // Optimize some code when these features are not used.
53   var sawReadOnlySpans = false, sawCollapsedSpans = false;
54
55   // EDITOR CONSTRUCTOR
56
57   // A CodeMirror instance represents an editor. This is the object
58   // that user code is usually dealing with.
59
60   function CodeMirror(place, options) {
61     if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
62
63     this.options = options = options || {};
64     // Determine effective options based on given values and defaults.
65     copyObj(defaults, options, false);
66     setGuttersForLineNumbers(options);
67
68     var doc = options.value;
69     if (typeof doc == "string") doc = new Doc(doc, options.mode);
70     this.doc = doc;
71
72     var display = this.display = new Display(place, doc);
73     display.wrapper.CodeMirror = this;
74     updateGutters(this);
75     themeChanged(this);
76     if (options.lineWrapping)
77       this.display.wrapper.className += " CodeMirror-wrap";
78     if (options.autofocus && !mobile) focusInput(this);
79
80     this.state = {
81       keyMaps: [],  // stores maps added by addKeyMap
82       overlays: [], // highlighting overlays, as added by addOverlay
83       modeGen: 0,   // bumped when mode/overlay changes, used to invalidate highlighting info
84       overwrite: false, focused: false,
85       suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
86       pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput
87       draggingText: false,
88       highlight: new Delayed() // stores highlight worker timeout
89     };
90
91     // Override magic textarea content restore that IE sometimes does
92     // on our hidden textarea on reload
93     if (ie_upto10) setTimeout(bind(resetInput, this, true), 20);
94
95     registerEventHandlers(this);
96
97     var cm = this;
98     runInOp(this, function() {
99       cm.curOp.forceUpdate = true;
100       attachDoc(cm, doc);
101
102       if ((options.autofocus && !mobile) || activeElt() == display.input)
103         setTimeout(bind(onFocus, cm), 20);
104       else
105         onBlur(cm);
106
107       for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
108         optionHandlers[opt](cm, options[opt], Init);
109       for (var i = 0; i < initHooks.length; ++i) initHooks[i](cm);
110     });
111   }
112
113   // DISPLAY CONSTRUCTOR
114
115   // The display handles the DOM integration, both for input reading
116   // and content drawing. It holds references to DOM nodes and
117   // display-related state.
118
119   function Display(place, doc) {
120     var d = this;
121
122     // The semihidden textarea that is focused when the editor is
123     // focused, and receives input.
124     var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
125     // The textarea is kept positioned near the cursor to prevent the
126     // fact that it'll be scrolled into view on input from scrolling
127     // our fake cursor out of view. On webkit, when wrap=off, paste is
128     // very slow. So make the area wide instead.
129     if (webkit) input.style.width = "1000px";
130     else input.setAttribute("wrap", "off");
131     // If border: 0; -- iOS fails to open keyboard (issue #1287)
132     if (ios) input.style.border = "1px solid black";
133     input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
134
135     // Wraps and hides input textarea
136     d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
137     // The fake scrollbar elements.
138     d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
139     d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
140     // Covers bottom-right square when both scrollbars are present.
141     d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
142     // Covers bottom of gutter when coverGutterNextToScrollbar is on
143     // and h scrollbar is present.
144     d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
145     // Will contain the actual code, positioned to cover the viewport.
146     d.lineDiv = elt("div", null, "CodeMirror-code");
147     // Elements are added to these to represent selection and cursors.
148     d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
149     d.cursorDiv = elt("div", null, "CodeMirror-cursors");
150     // A visibility: hidden element used to find the size of things.
151     d.measure = elt("div", null, "CodeMirror-measure");
152     // When lines outside of the viewport are measured, they are drawn in this.
153     d.lineMeasure = elt("div", null, "CodeMirror-measure");
154     // Wraps everything that needs to exist inside the vertically-padded coordinate system
155     d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
156                       null, "position: relative; outline: none");
157     // Moved around its parent to cover visible view.
158     d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
159     // Set to the height of the document, allowing scrolling.
160     d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
161     // Behavior of elts with overflow: auto and padding is
162     // inconsistent across browsers. This is used to ensure the
163     // scrollable area is big enough.
164     d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
165     // Will contain the gutters, if any.
166     d.gutters = elt("div", null, "CodeMirror-gutters");
167     d.lineGutter = null;
168     // Actual scrollable element.
169     d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
170     d.scroller.setAttribute("tabIndex", "-1");
171     // The element in which the editor lives.
172     d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
173                             d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
174
175     // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
176     if (ie_upto7) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
177     // Needed to hide big blue blinking cursor on Mobile Safari
178     if (ios) input.style.width = "0px";
179     if (!webkit) d.scroller.draggable = true;
180     // Needed to handle Tab key in KHTML
181     if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
182     // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
183     if (ie_upto7) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px";
184
185     if (place.appendChild) place.appendChild(d.wrapper);
186     else place(d.wrapper);
187
188     // Current rendered range (may be bigger than the view window).
189     d.viewFrom = d.viewTo = doc.first;
190     // Information about the rendered lines.
191     d.view = [];
192     // Holds info about a single rendered line when it was rendered
193     // for measurement, while not in view.
194     d.externalMeasured = null;
195     // Empty space (in pixels) above the view
196     d.viewOffset = 0;
197     d.lastSizeC = 0;
198     d.updateLineNumbers = null;
199
200     // Used to only resize the line number gutter when necessary (when
201     // the amount of lines crosses a boundary that makes its width change)
202     d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
203     // See readInput and resetInput
204     d.prevInput = "";
205     // Set to true when a non-horizontal-scrolling line widget is
206     // added. As an optimization, line widget aligning is skipped when
207     // this is false.
208     d.alignWidgets = false;
209     // Flag that indicates whether we expect input to appear real soon
210     // now (after some event like 'keypress' or 'input') and are
211     // polling intensively.
212     d.pollingFast = false;
213     // Self-resetting timeout for the poller
214     d.poll = new Delayed();
215
216     d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
217
218     // Tracks when resetInput has punted to just putting a short
219     // string into the textarea instead of the full selection.
220     d.inaccurateSelection = false;
221
222     // Tracks the maximum line length so that the horizontal scrollbar
223     // can be kept static when scrolling.
224     d.maxLine = null;
225     d.maxLineLength = 0;
226     d.maxLineChanged = false;
227
228     // Used for measuring wheel scrolling granularity
229     d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
230
231     // True when shift is held down.
232     d.shift = false;
233
234     // Used to track whether anything happened since the context menu
235     // was opened.
236     d.selForContextMenu = null;
237   }
238
239   // STATE UPDATES
240
241   // Used to get the editor into a consistent state again when options change.
242
243   function loadMode(cm) {
244     cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
245     resetModeState(cm);
246   }
247
248   function resetModeState(cm) {
249     cm.doc.iter(function(line) {
250       if (line.stateAfter) line.stateAfter = null;
251       if (line.styles) line.styles = null;
252     });
253     cm.doc.frontier = cm.doc.first;
254     startWorker(cm, 100);
255     cm.state.modeGen++;
256     if (cm.curOp) regChange(cm);
257   }
258
259   function wrappingChanged(cm) {
260     if (cm.options.lineWrapping) {
261       addClass(cm.display.wrapper, "CodeMirror-wrap");
262       cm.display.sizer.style.minWidth = "";
263     } else {
264       rmClass(cm.display.wrapper, "CodeMirror-wrap");
265       findMaxLine(cm);
266     }
267     estimateLineHeights(cm);
268     regChange(cm);
269     clearCaches(cm);
270     setTimeout(function(){updateScrollbars(cm);}, 100);
271   }
272
273   // Returns a function that estimates the height of a line, to use as
274   // first approximation until the line becomes visible (and is thus
275   // properly measurable).
276   function estimateHeight(cm) {
277     var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
278     var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
279     return function(line) {
280       if (lineIsHidden(cm.doc, line)) return 0;
281
282       var widgetsHeight = 0;
283       if (line.widgets) for (var i = 0; i < line.widgets.length; i++) {
284         if (line.widgets[i].height) widgetsHeight += line.widgets[i].height;
285       }
286
287       if (wrapping)
288         return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th;
289       else
290         return widgetsHeight + th;
291     };
292   }
293
294   function estimateLineHeights(cm) {
295     var doc = cm.doc, est = estimateHeight(cm);
296     doc.iter(function(line) {
297       var estHeight = est(line);
298       if (estHeight != line.height) updateLineHeight(line, estHeight);
299     });
300   }
301
302   function keyMapChanged(cm) {
303     var map = keyMap[cm.options.keyMap], style = map.style;
304     cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
305       (style ? " cm-keymap-" + style : "");
306   }
307
308   function themeChanged(cm) {
309     cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
310       cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
311     clearCaches(cm);
312   }
313
314   function guttersChanged(cm) {
315     updateGutters(cm);
316     regChange(cm);
317     setTimeout(function(){alignHorizontally(cm);}, 20);
318   }
319
320   // Rebuild the gutter elements, ensure the margin to the left of the
321   // code matches their width.
322   function updateGutters(cm) {
323     var gutters = cm.display.gutters, specs = cm.options.gutters;
324     removeChildren(gutters);
325     for (var i = 0; i < specs.length; ++i) {
326       var gutterClass = specs[i];
327       var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
328       if (gutterClass == "CodeMirror-linenumbers") {
329         cm.display.lineGutter = gElt;
330         gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
331       }
332     }
333     gutters.style.display = i ? "" : "none";
334     updateGutterSpace(cm);
335   }
336
337   function updateGutterSpace(cm) {
338     var width = cm.display.gutters.offsetWidth;
339     cm.display.sizer.style.marginLeft = width + "px";
340     cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
341   }
342
343   // Compute the character length of a line, taking into account
344   // collapsed ranges (see markText) that might hide parts, and join
345   // other lines onto it.
346   function lineLength(line) {
347     if (line.height == 0) return 0;
348     var len = line.text.length, merged, cur = line;
349     while (merged = collapsedSpanAtStart(cur)) {
350       var found = merged.find(0, true);
351       cur = found.from.line;
352       len += found.from.ch - found.to.ch;
353     }
354     cur = line;
355     while (merged = collapsedSpanAtEnd(cur)) {
356       var found = merged.find(0, true);
357       len -= cur.text.length - found.from.ch;
358       cur = found.to.line;
359       len += cur.text.length - found.to.ch;
360     }
361     return len;
362   }
363
364   // Find the longest line in the document.
365   function findMaxLine(cm) {
366     var d = cm.display, doc = cm.doc;
367     d.maxLine = getLine(doc, doc.first);
368     d.maxLineLength = lineLength(d.maxLine);
369     d.maxLineChanged = true;
370     doc.iter(function(line) {
371       var len = lineLength(line);
372       if (len > d.maxLineLength) {
373         d.maxLineLength = len;
374         d.maxLine = line;
375       }
376     });
377   }
378
379   // Make sure the gutters options contains the element
380   // "CodeMirror-linenumbers" when the lineNumbers option is true.
381   function setGuttersForLineNumbers(options) {
382     var found = indexOf(options.gutters, "CodeMirror-linenumbers");
383     if (found == -1 && options.lineNumbers) {
384       options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]);
385     } else if (found > -1 && !options.lineNumbers) {
386       options.gutters = options.gutters.slice(0);
387       options.gutters.splice(found, 1);
388     }
389   }
390
391   // SCROLLBARS
392
393   // Prepare DOM reads needed to update the scrollbars. Done in one
394   // shot to minimize update/measure roundtrips.
395   function measureForScrollbars(cm) {
396     var scroll = cm.display.scroller;
397     return {
398       clientHeight: scroll.clientHeight,
399       barHeight: cm.display.scrollbarV.clientHeight,
400       scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth,
401       barWidth: cm.display.scrollbarH.clientWidth,
402       docHeight: Math.round(cm.doc.height + paddingVert(cm.display))
403     };
404   }
405
406   // Re-synchronize the fake scrollbars with the actual size of the
407   // content.
408   function updateScrollbars(cm, measure) {
409     if (!measure) measure = measureForScrollbars(cm);
410     var d = cm.display;
411     var scrollHeight = measure.docHeight + scrollerCutOff;
412     var needsH = measure.scrollWidth > measure.clientWidth;
413     var needsV = scrollHeight > measure.clientHeight;
414     if (needsV) {
415       d.scrollbarV.style.display = "block";
416       d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
417       // A bug in IE8 can cause this value to be negative, so guard it.
418       d.scrollbarV.firstChild.style.height =
419         Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px";
420     } else {
421       d.scrollbarV.style.display = "";
422       d.scrollbarV.firstChild.style.height = "0";
423     }
424     if (needsH) {
425       d.scrollbarH.style.display = "block";
426       d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
427       d.scrollbarH.firstChild.style.width =
428         (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px";
429     } else {
430       d.scrollbarH.style.display = "";
431       d.scrollbarH.firstChild.style.width = "0";
432     }
433     if (needsH && needsV) {
434       d.scrollbarFiller.style.display = "block";
435       d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
436     } else d.scrollbarFiller.style.display = "";
437     if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
438       d.gutterFiller.style.display = "block";
439       d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
440       d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
441     } else d.gutterFiller.style.display = "";
442
443     if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) {
444       if (scrollbarWidth(d.measure) === 0) {
445         var w = mac && !mac_geMountainLion ? "12px" : "18px";
446         d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w;
447         var barMouseDown = function(e) {
448           if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
449             operation(cm, onMouseDown)(e);
450         };
451         on(d.scrollbarV, "mousedown", barMouseDown);
452         on(d.scrollbarH, "mousedown", barMouseDown);
453       }
454       cm.state.checkedOverlayScrollbar = true;
455     }
456   }
457
458   // Compute the lines that are visible in a given viewport (defaults
459   // the the current scroll position). viewPort may contain top,
460   // height, and ensure (see op.scrollToPos) properties.
461   function visibleLines(display, doc, viewPort) {
462     var top = viewPort && viewPort.top != null ? viewPort.top : display.scroller.scrollTop;
463     top = Math.floor(top - paddingTop(display));
464     var bottom = viewPort && viewPort.bottom != null ? viewPort.bottom : top + display.wrapper.clientHeight;
465
466     var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
467     // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
468     // forces those lines into the viewport (if possible).
469     if (viewPort && viewPort.ensure) {
470       var ensureFrom = viewPort.ensure.from.line, ensureTo = viewPort.ensure.to.line;
471       if (ensureFrom < from)
472         return {from: ensureFrom,
473                 to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)};
474       if (Math.min(ensureTo, doc.lastLine()) >= to)
475         return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight),
476                 to: ensureTo};
477     }
478     return {from: from, to: to};
479   }
480
481   // LINE NUMBERS
482
483   // Re-align line numbers and gutter marks to compensate for
484   // horizontal scrolling.
485   function alignHorizontally(cm) {
486     var display = cm.display, view = display.view;
487     if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
488     var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
489     var gutterW = display.gutters.offsetWidth, left = comp + "px";
490     for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
491       if (cm.options.fixedGutter && view[i].gutter)
492         view[i].gutter.style.left = left;
493       var align = view[i].alignable;
494       if (align) for (var j = 0; j < align.length; j++)
495         align[j].style.left = left;
496     }
497     if (cm.options.fixedGutter)
498       display.gutters.style.left = (comp + gutterW) + "px";
499   }
500
501   // Used to ensure that the line number gutter is still the right
502   // size for the current document size. Returns true when an update
503   // is needed.
504   function maybeUpdateLineNumberWidth(cm) {
505     if (!cm.options.lineNumbers) return false;
506     var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
507     if (last.length != display.lineNumChars) {
508       var test = display.measure.appendChild(elt("div", [elt("div", last)],
509                                                  "CodeMirror-linenumber CodeMirror-gutter-elt"));
510       var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
511       display.lineGutter.style.width = "";
512       display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
513       display.lineNumWidth = display.lineNumInnerWidth + padding;
514       display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
515       display.lineGutter.style.width = display.lineNumWidth + "px";
516       updateGutterSpace(cm);
517       return true;
518     }
519     return false;
520   }
521
522   function lineNumberFor(options, i) {
523     return String(options.lineNumberFormatter(i + options.firstLineNumber));
524   }
525
526   // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
527   // but using getBoundingClientRect to get a sub-pixel-accurate
528   // result.
529   function compensateForHScroll(display) {
530     return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
531   }
532
533   // DISPLAY DRAWING
534
535   // Updates the display, selection, and scrollbars, using the
536   // information in display.view to find out which nodes are no longer
537   // up-to-date. Tries to bail out early when no changes are needed,
538   // unless forced is true.
539   // Returns true if an actual update happened, false otherwise.
540   function updateDisplay(cm, viewPort, forced) {
541     var oldFrom = cm.display.viewFrom, oldTo = cm.display.viewTo, updated;
542     var visible = visibleLines(cm.display, cm.doc, viewPort);
543     for (var first = true;; first = false) {
544       var oldWidth = cm.display.scroller.clientWidth;
545       if (!updateDisplayInner(cm, visible, forced)) break;
546       updated = true;
547
548       // If the max line changed since it was last measured, measure it,
549       // and ensure the document's width matches it.
550       if (cm.display.maxLineChanged && !cm.options.lineWrapping)
551         adjustContentWidth(cm);
552
553       var barMeasure = measureForScrollbars(cm);
554       updateSelection(cm);
555       setDocumentHeight(cm, barMeasure);
556       updateScrollbars(cm, barMeasure);
557       if (webkit && cm.options.lineWrapping)
558         checkForWebkitWidthBug(cm, barMeasure); // (Issue #2420)
559       if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) {
560         forced = true;
561         continue;
562       }
563       forced = false;
564
565       // Clip forced viewport to actual scrollable area.
566       if (viewPort && viewPort.top != null)
567         viewPort = {top: Math.min(barMeasure.docHeight - scrollerCutOff - barMeasure.clientHeight, viewPort.top)};
568       // Updated line heights might result in the drawn area not
569       // actually covering the viewport. Keep looping until it does.
570       visible = visibleLines(cm.display, cm.doc, viewPort);
571       if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo)
572         break;
573     }
574
575     cm.display.updateLineNumbers = null;
576     if (updated) {
577       signalLater(cm, "update", cm);
578       if (cm.display.viewFrom != oldFrom || cm.display.viewTo != oldTo)
579         signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
580     }
581     return updated;
582   }
583
584   // Does the actual updating of the line display. Bails out
585   // (returning false) when there is nothing to be done and forced is
586   // false.
587   function updateDisplayInner(cm, visible, forced) {
588     var display = cm.display, doc = cm.doc;
589     if (!display.wrapper.offsetWidth) {
590       resetView(cm);
591       return;
592     }
593
594     // Bail out if the visible area is already rendered and nothing changed.
595     if (!forced && visible.from >= display.viewFrom && visible.to <= display.viewTo &&
596         countDirtyView(cm) == 0)
597       return;
598
599     if (maybeUpdateLineNumberWidth(cm))
600       resetView(cm);
601     var dims = getDimensions(cm);
602
603     // Compute a suitable new viewport (from & to)
604     var end = doc.first + doc.size;
605     var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
606     var to = Math.min(end, visible.to + cm.options.viewportMargin);
607     if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
608     if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
609     if (sawCollapsedSpans) {
610       from = visualLineNo(cm.doc, from);
611       to = visualLineEndNo(cm.doc, to);
612     }
613
614     var different = from != display.viewFrom || to != display.viewTo ||
615       display.lastSizeC != display.wrapper.clientHeight;
616     adjustView(cm, from, to);
617
618     display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
619     // Position the mover div to align with the current scroll position
620     cm.display.mover.style.top = display.viewOffset + "px";
621
622     var toUpdate = countDirtyView(cm);
623     if (!different && toUpdate == 0 && !forced) return;
624
625     // For big changes, we hide the enclosing element during the
626     // update, since that speeds up the operations on most browsers.
627     var focused = activeElt();
628     if (toUpdate > 4) display.lineDiv.style.display = "none";
629     patchDisplay(cm, display.updateLineNumbers, dims);
630     if (toUpdate > 4) display.lineDiv.style.display = "";
631     // There might have been a widget with a focused element that got
632     // hidden or updated, if so re-focus it.
633     if (focused && activeElt() != focused && focused.offsetHeight) focused.focus();
634
635     // Prevent selection and cursors from interfering with the scroll
636     // width.
637     removeChildren(display.cursorDiv);
638     removeChildren(display.selectionDiv);
639
640     if (different) {
641       display.lastSizeC = display.wrapper.clientHeight;
642       startWorker(cm, 400);
643     }
644
645     updateHeightsInViewport(cm);
646
647     return true;
648   }
649
650   function adjustContentWidth(cm) {
651     var display = cm.display;
652     var width = measureChar(cm, display.maxLine, display.maxLine.text.length).left;
653     display.maxLineChanged = false;
654     var minWidth = Math.max(0, width + 3);
655     var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + minWidth + scrollerCutOff - display.scroller.clientWidth);
656     display.sizer.style.minWidth = minWidth + "px";
657     if (maxScrollLeft < cm.doc.scrollLeft)
658       setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
659   }
660
661   function setDocumentHeight(cm, measure) {
662     cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px";
663     cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px";
664   }
665
666
667   function checkForWebkitWidthBug(cm, measure) {
668     // Work around Webkit bug where it sometimes reserves space for a
669     // non-existing phantom scrollbar in the scroller (Issue #2420)
670     if (cm.display.sizer.offsetWidth + cm.display.gutters.offsetWidth < cm.display.scroller.clientWidth - 1) {
671       cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = "0px";
672       cm.display.gutters.style.height = measure.docHeight + "px";
673     }
674   }
675
676   // Read the actual heights of the rendered lines, and update their
677   // stored heights to match.
678   function updateHeightsInViewport(cm) {
679     var display = cm.display;
680     var prevBottom = display.lineDiv.offsetTop;
681     for (var i = 0; i < display.view.length; i++) {
682       var cur = display.view[i], height;
683       if (cur.hidden) continue;
684       if (ie_upto7) {
685         var bot = cur.node.offsetTop + cur.node.offsetHeight;
686         height = bot - prevBottom;
687         prevBottom = bot;
688       } else {
689         var box = cur.node.getBoundingClientRect();
690         height = box.bottom - box.top;
691       }
692       var diff = cur.line.height - height;
693       if (height < 2) height = textHeight(display);
694       if (diff > .001 || diff < -.001) {
695         updateLineHeight(cur.line, height);
696         updateWidgetHeight(cur.line);
697         if (cur.rest) for (var j = 0; j < cur.rest.length; j++)
698           updateWidgetHeight(cur.rest[j]);
699       }
700     }
701   }
702
703   // Read and store the height of line widgets associated with the
704   // given line.
705   function updateWidgetHeight(line) {
706     if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
707       line.widgets[i].height = line.widgets[i].node.offsetHeight;
708   }
709
710   // Do a bulk-read of the DOM positions and sizes needed to draw the
711   // view, so that we don't interleave reading and writing to the DOM.
712   function getDimensions(cm) {
713     var d = cm.display, left = {}, width = {};
714     for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
715       left[cm.options.gutters[i]] = n.offsetLeft;
716       width[cm.options.gutters[i]] = n.offsetWidth;
717     }
718     return {fixedPos: compensateForHScroll(d),
719             gutterTotalWidth: d.gutters.offsetWidth,
720             gutterLeft: left,
721             gutterWidth: width,
722             wrapperWidth: d.wrapper.clientWidth};
723   }
724
725   // Sync the actual display DOM structure with display.view, removing
726   // nodes for lines that are no longer in view, and creating the ones
727   // that are not there yet, and updating the ones that are out of
728   // date.
729   function patchDisplay(cm, updateNumbersFrom, dims) {
730     var display = cm.display, lineNumbers = cm.options.lineNumbers;
731     var container = display.lineDiv, cur = container.firstChild;
732
733     function rm(node) {
734       var next = node.nextSibling;
735       // Works around a throw-scroll bug in OS X Webkit
736       if (webkit && mac && cm.display.currentWheelTarget == node)
737         node.style.display = "none";
738       else
739         node.parentNode.removeChild(node);
740       return next;
741     }
742
743     var view = display.view, lineN = display.viewFrom;
744     // Loop over the elements in the view, syncing cur (the DOM nodes
745     // in display.lineDiv) with the view as we go.
746     for (var i = 0; i < view.length; i++) {
747       var lineView = view[i];
748       if (lineView.hidden) {
749       } else if (!lineView.node) { // Not drawn yet
750         var node = buildLineElement(cm, lineView, lineN, dims);
751         container.insertBefore(node, cur);
752       } else { // Already drawn
753         while (cur != lineView.node) cur = rm(cur);
754         var updateNumber = lineNumbers && updateNumbersFrom != null &&
755           updateNumbersFrom <= lineN && lineView.lineNumber;
756         if (lineView.changes) {
757           if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false;
758           updateLineForChanges(cm, lineView, lineN, dims);
759         }
760         if (updateNumber) {
761           removeChildren(lineView.lineNumber);
762           lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));
763         }
764         cur = lineView.node.nextSibling;
765       }
766       lineN += lineView.size;
767     }
768     while (cur) cur = rm(cur);
769   }
770
771   // When an aspect of a line changes, a string is added to
772   // lineView.changes. This updates the relevant part of the line's
773   // DOM structure.
774   function updateLineForChanges(cm, lineView, lineN, dims) {
775     for (var j = 0; j < lineView.changes.length; j++) {
776       var type = lineView.changes[j];
777       if (type == "text") updateLineText(cm, lineView);
778       else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
779       else if (type == "class") updateLineClasses(lineView);
780       else if (type == "widget") updateLineWidgets(lineView, dims);
781     }
782     lineView.changes = null;
783   }
784
785   // Lines with gutter elements, widgets or a background class need to
786   // be wrapped, and have the extra elements added to the wrapper div
787   function ensureLineWrapped(lineView) {
788     if (lineView.node == lineView.text) {
789       lineView.node = elt("div", null, null, "position: relative");
790       if (lineView.text.parentNode)
791         lineView.text.parentNode.replaceChild(lineView.node, lineView.text);
792       lineView.node.appendChild(lineView.text);
793       if (ie_upto7) lineView.node.style.zIndex = 2;
794     }
795     return lineView.node;
796   }
797
798   function updateLineBackground(lineView) {
799     var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
800     if (cls) cls += " CodeMirror-linebackground";
801     if (lineView.background) {
802       if (cls) lineView.background.className = cls;
803       else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }
804     } else if (cls) {
805       var wrap = ensureLineWrapped(lineView);
806       lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild);
807     }
808   }
809
810   // Wrapper around buildLineContent which will reuse the structure
811   // in display.externalMeasured when possible.
812   function getLineContent(cm, lineView) {
813     var ext = cm.display.externalMeasured;
814     if (ext && ext.line == lineView.line) {
815       cm.display.externalMeasured = null;
816       lineView.measure = ext.measure;
817       return ext.built;
818     }
819     return buildLineContent(cm, lineView);
820   }
821
822   // Redraw the line's text. Interacts with the background and text
823   // classes because the mode may output tokens that influence these
824   // classes.
825   function updateLineText(cm, lineView) {
826     var cls = lineView.text.className;
827     var built = getLineContent(cm, lineView);
828     if (lineView.text == lineView.node) lineView.node = built.pre;
829     lineView.text.parentNode.replaceChild(built.pre, lineView.text);
830     lineView.text = built.pre;
831     if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
832       lineView.bgClass = built.bgClass;
833       lineView.textClass = built.textClass;
834       updateLineClasses(lineView);
835     } else if (cls) {
836       lineView.text.className = cls;
837     }
838   }
839
840   function updateLineClasses(lineView) {
841     updateLineBackground(lineView);
842     if (lineView.line.wrapClass)
843       ensureLineWrapped(lineView).className = lineView.line.wrapClass;
844     else if (lineView.node != lineView.text)
845       lineView.node.className = "";
846     var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass;
847     lineView.text.className = textClass || "";
848   }
849
850   function updateLineGutter(cm, lineView, lineN, dims) {
851     if (lineView.gutter) {
852       lineView.node.removeChild(lineView.gutter);
853       lineView.gutter = null;
854     }
855     var markers = lineView.line.gutterMarkers;
856     if (cm.options.lineNumbers || markers) {
857       var wrap = ensureLineWrapped(lineView);
858       var gutterWrap = lineView.gutter =
859         wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
860                               (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
861                           lineView.text);
862       if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
863         lineView.lineNumber = gutterWrap.appendChild(
864           elt("div", lineNumberFor(cm.options, lineN),
865               "CodeMirror-linenumber CodeMirror-gutter-elt",
866               "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
867               + cm.display.lineNumInnerWidth + "px"));
868       if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) {
869         var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
870         if (found)
871           gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
872                                      dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
873       }
874     }
875   }
876
877   function updateLineWidgets(lineView, dims) {
878     if (lineView.alignable) lineView.alignable = null;
879     for (var node = lineView.node.firstChild, next; node; node = next) {
880       var next = node.nextSibling;
881       if (node.className == "CodeMirror-linewidget")
882         lineView.node.removeChild(node);
883     }
884     insertLineWidgets(lineView, dims);
885   }
886
887   // Build a line's DOM representation from scratch
888   function buildLineElement(cm, lineView, lineN, dims) {
889     var built = getLineContent(cm, lineView);
890     lineView.text = lineView.node = built.pre;
891     if (built.bgClass) lineView.bgClass = built.bgClass;
892     if (built.textClass) lineView.textClass = built.textClass;
893
894     updateLineClasses(lineView);
895     updateLineGutter(cm, lineView, lineN, dims);
896     insertLineWidgets(lineView, dims);
897     return lineView.node;
898   }
899
900   // A lineView may contain multiple logical lines (when merged by
901   // collapsed spans). The widgets for all of them need to be drawn.
902   function insertLineWidgets(lineView, dims) {
903     insertLineWidgetsFor(lineView.line, lineView, dims, true);
904     if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
905       insertLineWidgetsFor(lineView.rest[i], lineView, dims, false);
906   }
907
908   function insertLineWidgetsFor(line, lineView, dims, allowAbove) {
909     if (!line.widgets) return;
910     var wrap = ensureLineWrapped(lineView);
911     for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
912       var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
913       if (!widget.handleMouseEvents) node.ignoreEvents = true;
914       positionLineWidget(widget, node, lineView, dims);
915       if (allowAbove && widget.above)
916         wrap.insertBefore(node, lineView.gutter || lineView.text);
917       else
918         wrap.appendChild(node);
919       signalLater(widget, "redraw");
920     }
921   }
922
923   function positionLineWidget(widget, node, lineView, dims) {
924     if (widget.noHScroll) {
925       (lineView.alignable || (lineView.alignable = [])).push(node);
926       var width = dims.wrapperWidth;
927       node.style.left = dims.fixedPos + "px";
928       if (!widget.coverGutter) {
929         width -= dims.gutterTotalWidth;
930         node.style.paddingLeft = dims.gutterTotalWidth + "px";
931       }
932       node.style.width = width + "px";
933     }
934     if (widget.coverGutter) {
935       node.style.zIndex = 5;
936       node.style.position = "relative";
937       if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
938     }
939   }
940
941   // POSITION OBJECT
942
943   // A Pos instance represents a position within the text.
944   var Pos = CodeMirror.Pos = function(line, ch) {
945     if (!(this instanceof Pos)) return new Pos(line, ch);
946     this.line = line; this.ch = ch;
947   };
948
949   // Compare two positions, return 0 if they are the same, a negative
950   // number when a is less, and a positive number otherwise.
951   var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
952
953   function copyPos(x) {return Pos(x.line, x.ch);}
954   function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
955   function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
956
957   // SELECTION / CURSOR
958
959   // Selection objects are immutable. A new one is created every time
960   // the selection changes. A selection is one or more non-overlapping
961   // (and non-touching) ranges, sorted, and an integer that indicates
962   // which one is the primary selection (the one that's scrolled into
963   // view, that getCursor returns, etc).
964   function Selection(ranges, primIndex) {
965     this.ranges = ranges;
966     this.primIndex = primIndex;
967   }
968
969   Selection.prototype = {
970     primary: function() { return this.ranges[this.primIndex]; },
971     equals: function(other) {
972       if (other == this) return true;
973       if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false;
974       for (var i = 0; i < this.ranges.length; i++) {
975         var here = this.ranges[i], there = other.ranges[i];
976         if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false;
977       }
978       return true;
979     },
980     deepCopy: function() {
981       for (var out = [], i = 0; i < this.ranges.length; i++)
982         out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head));
983       return new Selection(out, this.primIndex);
984     },
985     somethingSelected: function() {
986       for (var i = 0; i < this.ranges.length; i++)
987         if (!this.ranges[i].empty()) return true;
988       return false;
989     },
990     contains: function(pos, end) {
991       if (!end) end = pos;
992       for (var i = 0; i < this.ranges.length; i++) {
993         var range = this.ranges[i];
994         if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
995           return i;
996       }
997       return -1;
998     }
999   };
1000
1001   function Range(anchor, head) {
1002     this.anchor = anchor; this.head = head;
1003   }
1004
1005   Range.prototype = {
1006     from: function() { return minPos(this.anchor, this.head); },
1007     to: function() { return maxPos(this.anchor, this.head); },
1008     empty: function() {
1009       return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch;
1010     }
1011   };
1012
1013   // Take an unsorted, potentially overlapping set of ranges, and
1014   // build a selection out of it. 'Consumes' ranges array (modifying
1015   // it).
1016   function normalizeSelection(ranges, primIndex) {
1017     var prim = ranges[primIndex];
1018     ranges.sort(function(a, b) { return cmp(a.from(), b.from()); });
1019     primIndex = indexOf(ranges, prim);
1020     for (var i = 1; i < ranges.length; i++) {
1021       var cur = ranges[i], prev = ranges[i - 1];
1022       if (cmp(prev.to(), cur.from()) >= 0) {
1023         var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());
1024         var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;
1025         if (i <= primIndex) --primIndex;
1026         ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
1027       }
1028     }
1029     return new Selection(ranges, primIndex);
1030   }
1031
1032   function simpleSelection(anchor, head) {
1033     return new Selection([new Range(anchor, head || anchor)], 0);
1034   }
1035
1036   // Most of the external API clips given positions to make sure they
1037   // actually exist within the document.
1038   function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
1039   function clipPos(doc, pos) {
1040     if (pos.line < doc.first) return Pos(doc.first, 0);
1041     var last = doc.first + doc.size - 1;
1042     if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
1043     return clipToLen(pos, getLine(doc, pos.line).text.length);
1044   }
1045   function clipToLen(pos, linelen) {
1046     var ch = pos.ch;
1047     if (ch == null || ch > linelen) return Pos(pos.line, linelen);
1048     else if (ch < 0) return Pos(pos.line, 0);
1049     else return pos;
1050   }
1051   function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
1052   function clipPosArray(doc, array) {
1053     for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]);
1054     return out;
1055   }
1056
1057   // SELECTION UPDATES
1058
1059   // The 'scroll' parameter given to many of these indicated whether
1060   // the new cursor position should be scrolled into view after
1061   // modifying the selection.
1062
1063   // If shift is held or the extend flag is set, extends a range to
1064   // include a given position (and optionally a second position).
1065   // Otherwise, simply returns the range between the given positions.
1066   // Used for cursor motion and such.
1067   function extendRange(doc, range, head, other) {
1068     if (doc.cm && doc.cm.display.shift || doc.extend) {
1069       var anchor = range.anchor;
1070       if (other) {
1071         var posBefore = cmp(head, anchor) < 0;
1072         if (posBefore != (cmp(other, anchor) < 0)) {
1073           anchor = head;
1074           head = other;
1075         } else if (posBefore != (cmp(head, other) < 0)) {
1076           head = other;
1077         }
1078       }
1079       return new Range(anchor, head);
1080     } else {
1081       return new Range(other || head, head);
1082     }
1083   }
1084
1085   // Extend the primary selection range, discard the rest.
1086   function extendSelection(doc, head, other, options) {
1087     setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options);
1088   }
1089
1090   // Extend all selections (pos is an array of selections with length
1091   // equal the number of selections)
1092   function extendSelections(doc, heads, options) {
1093     for (var out = [], i = 0; i < doc.sel.ranges.length; i++)
1094       out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null);
1095     var newSel = normalizeSelection(out, doc.sel.primIndex);
1096     setSelection(doc, newSel, options);
1097   }
1098
1099   // Updates a single range in the selection.
1100   function replaceOneSelection(doc, i, range, options) {
1101     var ranges = doc.sel.ranges.slice(0);
1102     ranges[i] = range;
1103     setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options);
1104   }
1105
1106   // Reset the selection to a single range.
1107   function setSimpleSelection(doc, anchor, head, options) {
1108     setSelection(doc, simpleSelection(anchor, head), options);
1109   }
1110
1111   // Give beforeSelectionChange handlers a change to influence a
1112   // selection update.
1113   function filterSelectionChange(doc, sel) {
1114     var obj = {
1115       ranges: sel.ranges,
1116       update: function(ranges) {
1117         this.ranges = [];
1118         for (var i = 0; i < ranges.length; i++)
1119           this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
1120                                      clipPos(doc, ranges[i].head));
1121       }
1122     };
1123     signal(doc, "beforeSelectionChange", doc, obj);
1124     if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
1125     if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1);
1126     else return sel;
1127   }
1128
1129   function setSelectionReplaceHistory(doc, sel, options) {
1130     var done = doc.history.done, last = lst(done);
1131     if (last && last.ranges) {
1132       done[done.length - 1] = sel;
1133       setSelectionNoUndo(doc, sel, options);
1134     } else {
1135       setSelection(doc, sel, options);
1136     }
1137   }
1138
1139   // Set a new selection.
1140   function setSelection(doc, sel, options) {
1141     setSelectionNoUndo(doc, sel, options);
1142     addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
1143   }
1144
1145   function setSelectionNoUndo(doc, sel, options) {
1146     if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
1147       sel = filterSelectionChange(doc, sel);
1148
1149     var bias = cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1;
1150     setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
1151
1152     if (!(options && options.scroll === false) && doc.cm)
1153       ensureCursorVisible(doc.cm);
1154   }
1155
1156   function setSelectionInner(doc, sel) {
1157     if (sel.equals(doc.sel)) return;
1158
1159     doc.sel = sel;
1160
1161     if (doc.cm) {
1162       doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
1163       signalCursorActivity(doc.cm);
1164     }
1165     signalLater(doc, "cursorActivity", doc);
1166   }
1167
1168   // Verify that the selection does not partially select any atomic
1169   // marked ranges.
1170   function reCheckSelection(doc) {
1171     setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll);
1172   }
1173
1174   // Return a selection that does not partially select any atomic
1175   // ranges.
1176   function skipAtomicInSelection(doc, sel, bias, mayClear) {
1177     var out;
1178     for (var i = 0; i < sel.ranges.length; i++) {
1179       var range = sel.ranges[i];
1180       var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
1181       var newHead = skipAtomic(doc, range.head, bias, mayClear);
1182       if (out || newAnchor != range.anchor || newHead != range.head) {
1183         if (!out) out = sel.ranges.slice(0, i);
1184         out[i] = new Range(newAnchor, newHead);
1185       }
1186     }
1187     return out ? normalizeSelection(out, sel.primIndex) : sel;
1188   }
1189
1190   // Ensure a given position is not inside an atomic range.
1191   function skipAtomic(doc, pos, bias, mayClear) {
1192     var flipped = false, curPos = pos;
1193     var dir = bias || 1;
1194     doc.cantEdit = false;
1195     search: for (;;) {
1196       var line = getLine(doc, curPos.line);
1197       if (line.markedSpans) {
1198         for (var i = 0; i < line.markedSpans.length; ++i) {
1199           var sp = line.markedSpans[i], m = sp.marker;
1200           if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
1201               (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
1202             if (mayClear) {
1203               signal(m, "beforeCursorEnter");
1204               if (m.explicitlyCleared) {
1205                 if (!line.markedSpans) break;
1206                 else {--i; continue;}
1207               }
1208             }
1209             if (!m.atomic) continue;
1210             var newPos = m.find(dir < 0 ? -1 : 1);
1211             if (cmp(newPos, curPos) == 0) {
1212               newPos.ch += dir;
1213               if (newPos.ch < 0) {
1214                 if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
1215                 else newPos = null;
1216               } else if (newPos.ch > line.text.length) {
1217                 if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
1218                 else newPos = null;
1219               }
1220               if (!newPos) {
1221                 if (flipped) {
1222                   // Driven in a corner -- no valid cursor position found at all
1223                   // -- try again *with* clearing, if we didn't already
1224                   if (!mayClear) return skipAtomic(doc, pos, bias, true);
1225                   // Otherwise, turn off editing until further notice, and return the start of the doc
1226                   doc.cantEdit = true;
1227                   return Pos(doc.first, 0);
1228                 }
1229                 flipped = true; newPos = pos; dir = -dir;
1230               }
1231             }
1232             curPos = newPos;
1233             continue search;
1234           }
1235         }
1236       }
1237       return curPos;
1238     }
1239   }
1240
1241   // SELECTION DRAWING
1242
1243   // Redraw the selection and/or cursor
1244   function updateSelection(cm) {
1245     var display = cm.display, doc = cm.doc;
1246     var curFragment = document.createDocumentFragment();
1247     var selFragment = document.createDocumentFragment();
1248
1249     for (var i = 0; i < doc.sel.ranges.length; i++) {
1250       var range = doc.sel.ranges[i];
1251       var collapsed = range.empty();
1252       if (collapsed || cm.options.showCursorWhenSelecting)
1253         drawSelectionCursor(cm, range, curFragment);
1254       if (!collapsed)
1255         drawSelectionRange(cm, range, selFragment);
1256     }
1257
1258     // Move the hidden textarea near the cursor to prevent scrolling artifacts
1259     if (cm.options.moveInputWithCursor) {
1260       var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
1261       var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
1262       var top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
1263                                      headPos.top + lineOff.top - wrapOff.top));
1264       var left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
1265                                       headPos.left + lineOff.left - wrapOff.left));
1266       display.inputDiv.style.top = top + "px";
1267       display.inputDiv.style.left = left + "px";
1268     }
1269
1270     removeChildrenAndAdd(display.cursorDiv, curFragment);
1271     removeChildrenAndAdd(display.selectionDiv, selFragment);
1272   }
1273
1274   // Draws a cursor for the given range
1275   function drawSelectionCursor(cm, range, output) {
1276     var pos = cursorCoords(cm, range.head, "div");
1277
1278     var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
1279     cursor.style.left = pos.left + "px";
1280     cursor.style.top = pos.top + "px";
1281     cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
1282
1283     if (pos.other) {
1284       // Secondary cursor, shown when on a 'jump' in bi-directional text
1285       var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
1286       otherCursor.style.display = "";
1287       otherCursor.style.left = pos.other.left + "px";
1288       otherCursor.style.top = pos.other.top + "px";
1289       otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
1290     }
1291   }
1292
1293   // Draws the given range as a highlighted selection
1294   function drawSelectionRange(cm, range, output) {
1295     var display = cm.display, doc = cm.doc;
1296     var fragment = document.createDocumentFragment();
1297     var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right;
1298
1299     function add(left, top, width, bottom) {
1300       if (top < 0) top = 0;
1301       top = Math.round(top);
1302       bottom = Math.round(bottom);
1303       fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
1304                                "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) +
1305                                "px; height: " + (bottom - top) + "px"));
1306     }
1307
1308     function drawForLine(line, fromArg, toArg) {
1309       var lineObj = getLine(doc, line);
1310       var lineLen = lineObj.text.length;
1311       var start, end;
1312       function coords(ch, bias) {
1313         return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
1314       }
1315
1316       iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
1317         var leftPos = coords(from, "left"), rightPos, left, right;
1318         if (from == to) {
1319           rightPos = leftPos;
1320           left = right = leftPos.left;
1321         } else {
1322           rightPos = coords(to - 1, "right");
1323           if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
1324           left = leftPos.left;
1325           right = rightPos.right;
1326         }
1327         if (fromArg == null && from == 0) left = leftSide;
1328         if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
1329           add(left, leftPos.top, null, leftPos.bottom);
1330           left = leftSide;
1331           if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
1332         }
1333         if (toArg == null && to == lineLen) right = rightSide;
1334         if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
1335           start = leftPos;
1336         if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
1337           end = rightPos;
1338         if (left < leftSide + 1) left = leftSide;
1339         add(left, rightPos.top, right - left, rightPos.bottom);
1340       });
1341       return {start: start, end: end};
1342     }
1343
1344     var sFrom = range.from(), sTo = range.to();
1345     if (sFrom.line == sTo.line) {
1346       drawForLine(sFrom.line, sFrom.ch, sTo.ch);
1347     } else {
1348       var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
1349       var singleVLine = visualLine(fromLine) == visualLine(toLine);
1350       var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;
1351       var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;
1352       if (singleVLine) {
1353         if (leftEnd.top < rightStart.top - 2) {
1354           add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
1355           add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
1356         } else {
1357           add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
1358         }
1359       }
1360       if (leftEnd.bottom < rightStart.top)
1361         add(leftSide, leftEnd.bottom, null, rightStart.top);
1362     }
1363
1364     output.appendChild(fragment);
1365   }
1366
1367   // Cursor-blinking
1368   function restartBlink(cm) {
1369     if (!cm.state.focused) return;
1370     var display = cm.display;
1371     clearInterval(display.blinker);
1372     var on = true;
1373     display.cursorDiv.style.visibility = "";
1374     if (cm.options.cursorBlinkRate > 0)
1375       display.blinker = setInterval(function() {
1376         display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
1377       }, cm.options.cursorBlinkRate);
1378   }
1379
1380   // HIGHLIGHT WORKER
1381
1382   function startWorker(cm, time) {
1383     if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
1384       cm.state.highlight.set(time, bind(highlightWorker, cm));
1385   }
1386
1387   function highlightWorker(cm) {
1388     var doc = cm.doc;
1389     if (doc.frontier < doc.first) doc.frontier = doc.first;
1390     if (doc.frontier >= cm.display.viewTo) return;
1391     var end = +new Date + cm.options.workTime;
1392     var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
1393
1394     runInOp(cm, function() {
1395     doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
1396       if (doc.frontier >= cm.display.viewFrom) { // Visible
1397         var oldStyles = line.styles;
1398         var highlighted = highlightLine(cm, line, state, true);
1399         line.styles = highlighted.styles;
1400         if (highlighted.classes) line.styleClasses = highlighted.classes;
1401         else if (line.styleClasses) line.styleClasses = null;
1402         var ischange = !oldStyles || oldStyles.length != line.styles.length;
1403         for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
1404         if (ischange) regLineChange(cm, doc.frontier, "text");
1405         line.stateAfter = copyState(doc.mode, state);
1406       } else {
1407         processLine(cm, line.text, state);
1408         line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
1409       }
1410       ++doc.frontier;
1411       if (+new Date > end) {
1412         startWorker(cm, cm.options.workDelay);
1413         return true;
1414       }
1415     });
1416     });
1417   }
1418
1419   // Finds the line to start with when starting a parse. Tries to
1420   // find a line with a stateAfter, so that it can start with a
1421   // valid state. If that fails, it returns the line with the
1422   // smallest indentation, which tends to need the least context to
1423   // parse correctly.
1424   function findStartLine(cm, n, precise) {
1425     var minindent, minline, doc = cm.doc;
1426     var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
1427     for (var search = n; search > lim; --search) {
1428       if (search <= doc.first) return doc.first;
1429       var line = getLine(doc, search - 1);
1430       if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
1431       var indented = countColumn(line.text, null, cm.options.tabSize);
1432       if (minline == null || minindent > indented) {
1433         minline = search - 1;
1434         minindent = indented;
1435       }
1436     }
1437     return minline;
1438   }
1439
1440   function getStateBefore(cm, n, precise) {
1441     var doc = cm.doc, display = cm.display;
1442     if (!doc.mode.startState) return true;
1443     var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
1444     if (!state) state = startState(doc.mode);
1445     else state = copyState(doc.mode, state);
1446     doc.iter(pos, n, function(line) {
1447       processLine(cm, line.text, state);
1448       var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo;
1449       line.stateAfter = save ? copyState(doc.mode, state) : null;
1450       ++pos;
1451     });
1452     if (precise) doc.frontier = pos;
1453     return state;
1454   }
1455
1456   // POSITION MEASUREMENT
1457
1458   function paddingTop(display) {return display.lineSpace.offsetTop;}
1459   function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
1460   function paddingH(display) {
1461     if (display.cachedPaddingH) return display.cachedPaddingH;
1462     var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
1463     var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
1464     var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
1465     if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
1466     return data;
1467   }
1468
1469   // Ensure the lineView.wrapping.heights array is populated. This is
1470   // an array of bottom offsets for the lines that make up a drawn
1471   // line. When lineWrapping is on, there might be more than one
1472   // height.
1473   function ensureLineHeights(cm, lineView, rect) {
1474     var wrapping = cm.options.lineWrapping;
1475     var curWidth = wrapping && cm.display.scroller.clientWidth;
1476     if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
1477       var heights = lineView.measure.heights = [];
1478       if (wrapping) {
1479         lineView.measure.width = curWidth;
1480         var rects = lineView.text.firstChild.getClientRects();
1481         for (var i = 0; i < rects.length - 1; i++) {
1482           var cur = rects[i], next = rects[i + 1];
1483           if (Math.abs(cur.bottom - next.bottom) > 2)
1484             heights.push((cur.bottom + next.top) / 2 - rect.top);
1485         }
1486       }
1487       heights.push(rect.bottom - rect.top);
1488     }
1489   }
1490
1491   // Find a line map (mapping character offsets to text nodes) and a
1492   // measurement cache for the given line number. (A line view might
1493   // contain multiple lines when collapsed ranges are present.)
1494   function mapFromLineView(lineView, line, lineN) {
1495     if (lineView.line == line)
1496       return {map: lineView.measure.map, cache: lineView.measure.cache};
1497     for (var i = 0; i < lineView.rest.length; i++)
1498       if (lineView.rest[i] == line)
1499         return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]};
1500     for (var i = 0; i < lineView.rest.length; i++)
1501       if (lineNo(lineView.rest[i]) > lineN)
1502         return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true};
1503   }
1504
1505   // Render a line into the hidden node display.externalMeasured. Used
1506   // when measurement is needed for a line that's not in the viewport.
1507   function updateExternalMeasurement(cm, line) {
1508     line = visualLine(line);
1509     var lineN = lineNo(line);
1510     var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
1511     view.lineN = lineN;
1512     var built = view.built = buildLineContent(cm, view);
1513     view.text = built.pre;
1514     removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
1515     return view;
1516   }
1517
1518   // Get a {top, bottom, left, right} box (in line-local coordinates)
1519   // for a given character.
1520   function measureChar(cm, line, ch, bias) {
1521     return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias);
1522   }
1523
1524   // Find a line view that corresponds to the given line number.
1525   function findViewForLine(cm, lineN) {
1526     if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
1527       return cm.display.view[findViewIndex(cm, lineN)];
1528     var ext = cm.display.externalMeasured;
1529     if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
1530       return ext;
1531   }
1532
1533   // Measurement can be split in two steps, the set-up work that
1534   // applies to the whole line, and the measurement of the actual
1535   // character. Functions like coordsChar, that need to do a lot of
1536   // measurements in a row, can thus ensure that the set-up work is
1537   // only done once.
1538   function prepareMeasureForLine(cm, line) {
1539     var lineN = lineNo(line);
1540     var view = findViewForLine(cm, lineN);
1541     if (view && !view.text)
1542       view = null;
1543     else if (view && view.changes)
1544       updateLineForChanges(cm, view, lineN, getDimensions(cm));
1545     if (!view)
1546       view = updateExternalMeasurement(cm, line);
1547
1548     var info = mapFromLineView(view, line, lineN);
1549     return {
1550       line: line, view: view, rect: null,
1551       map: info.map, cache: info.cache, before: info.before,
1552       hasHeights: false
1553     };
1554   }
1555
1556   // Given a prepared measurement object, measures the position of an
1557   // actual character (or fetches it from the cache).
1558   function measureCharPrepared(cm, prepared, ch, bias) {
1559     if (prepared.before) ch = -1;
1560     var key = ch + (bias || ""), found;
1561     if (prepared.cache.hasOwnProperty(key)) {
1562       found = prepared.cache[key];
1563     } else {
1564       if (!prepared.rect)
1565         prepared.rect = prepared.view.text.getBoundingClientRect();
1566       if (!prepared.hasHeights) {
1567         ensureLineHeights(cm, prepared.view, prepared.rect);
1568         prepared.hasHeights = true;
1569       }
1570       found = measureCharInner(cm, prepared, ch, bias);
1571       if (!found.bogus) prepared.cache[key] = found;
1572     }
1573     return {left: found.left, right: found.right, top: found.top, bottom: found.bottom};
1574   }
1575
1576   var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
1577
1578   function measureCharInner(cm, prepared, ch, bias) {
1579     var map = prepared.map;
1580
1581     var node, start, end, collapse;
1582     // First, search the line map for the text node corresponding to,
1583     // or closest to, the target character.
1584     for (var i = 0; i < map.length; i += 3) {
1585       var mStart = map[i], mEnd = map[i + 1];
1586       if (ch < mStart) {
1587         start = 0; end = 1;
1588         collapse = "left";
1589       } else if (ch < mEnd) {
1590         start = ch - mStart;
1591         end = start + 1;
1592       } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
1593         end = mEnd - mStart;
1594         start = end - 1;
1595         if (ch >= mEnd) collapse = "right";
1596       }
1597       if (start != null) {
1598         node = map[i + 2];
1599         if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
1600           collapse = bias;
1601         if (bias == "left" && start == 0)
1602           while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
1603             node = map[(i -= 3) + 2];
1604             collapse = "left";
1605           }
1606         if (bias == "right" && start == mEnd - mStart)
1607           while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
1608             node = map[(i += 3) + 2];
1609             collapse = "right";
1610           }
1611         break;
1612       }
1613     }
1614
1615     var rect;
1616     if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
1617       while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
1618       while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
1619       if (ie_upto8 && start == 0 && end == mEnd - mStart) {
1620         rect = node.parentNode.getBoundingClientRect();
1621       } else if (ie && cm.options.lineWrapping) {
1622         var rects = range(node, start, end).getClientRects();
1623         if (rects.length)
1624           rect = rects[bias == "right" ? rects.length - 1 : 0];
1625         else
1626           rect = nullRect;
1627       } else {
1628         rect = range(node, start, end).getBoundingClientRect() || nullRect;
1629       }
1630     } else { // If it is a widget, simply get the box for the whole widget.
1631       if (start > 0) collapse = bias = "right";
1632       var rects;
1633       if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
1634         rect = rects[bias == "right" ? rects.length - 1 : 0];
1635       else
1636         rect = node.getBoundingClientRect();
1637     }
1638     if (ie_upto8 && !start && (!rect || !rect.left && !rect.right)) {
1639       var rSpan = node.parentNode.getClientRects()[0];
1640       if (rSpan)
1641         rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom};
1642       else
1643         rect = nullRect;
1644     }
1645
1646     var top, bot = (rect.bottom + rect.top) / 2 - prepared.rect.top;
1647     var heights = prepared.view.measure.heights;
1648     for (var i = 0; i < heights.length - 1; i++)
1649       if (bot < heights[i]) break;
1650     top = i ? heights[i - 1] : 0; bot = heights[i];
1651     var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
1652                   right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
1653                   top: top, bottom: bot};
1654     if (!rect.left && !rect.right) result.bogus = true;
1655     return result;
1656   }
1657
1658   function clearLineMeasurementCacheFor(lineView) {
1659     if (lineView.measure) {
1660       lineView.measure.cache = {};
1661       lineView.measure.heights = null;
1662       if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
1663         lineView.measure.caches[i] = {};
1664     }
1665   }
1666
1667   function clearLineMeasurementCache(cm) {
1668     cm.display.externalMeasure = null;
1669     removeChildren(cm.display.lineMeasure);
1670     for (var i = 0; i < cm.display.view.length; i++)
1671       clearLineMeasurementCacheFor(cm.display.view[i]);
1672   }
1673
1674   function clearCaches(cm) {
1675     clearLineMeasurementCache(cm);
1676     cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;
1677     if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
1678     cm.display.lineNumChars = null;
1679   }
1680
1681   function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
1682   function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
1683
1684   // Converts a {top, bottom, left, right} box from line-local
1685   // coordinates into another coordinate system. Context may be one of
1686   // "line", "div" (display.lineDiv), "local"/null (editor), or "page".
1687   function intoCoordSystem(cm, lineObj, rect, context) {
1688     if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
1689       var size = widgetHeight(lineObj.widgets[i]);
1690       rect.top += size; rect.bottom += size;
1691     }
1692     if (context == "line") return rect;
1693     if (!context) context = "local";
1694     var yOff = heightAtLine(lineObj);
1695     if (context == "local") yOff += paddingTop(cm.display);
1696     else yOff -= cm.display.viewOffset;
1697     if (context == "page" || context == "window") {
1698       var lOff = cm.display.lineSpace.getBoundingClientRect();
1699       yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
1700       var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
1701       rect.left += xOff; rect.right += xOff;
1702     }
1703     rect.top += yOff; rect.bottom += yOff;
1704     return rect;
1705   }
1706
1707   // Coverts a box from "div" coords to another coordinate system.
1708   // Context may be "window", "page", "div", or "local"/null.
1709   function fromCoordSystem(cm, coords, context) {
1710     if (context == "div") return coords;
1711     var left = coords.left, top = coords.top;
1712     // First move into "page" coordinate system
1713     if (context == "page") {
1714       left -= pageScrollX();
1715       top -= pageScrollY();
1716     } else if (context == "local" || !context) {
1717       var localBox = cm.display.sizer.getBoundingClientRect();
1718       left += localBox.left;
1719       top += localBox.top;
1720     }
1721
1722     var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
1723     return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
1724   }
1725
1726   function charCoords(cm, pos, context, lineObj, bias) {
1727     if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1728     return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context);
1729   }
1730
1731   // Returns a box for a given cursor position, which may have an
1732   // 'other' property containing the position of the secondary cursor
1733   // on a bidi boundary.
1734   function cursorCoords(cm, pos, context, lineObj, preparedMeasure) {
1735     lineObj = lineObj || getLine(cm.doc, pos.line);
1736     if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj);
1737     function get(ch, right) {
1738       var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left");
1739       if (right) m.left = m.right; else m.right = m.left;
1740       return intoCoordSystem(cm, lineObj, m, context);
1741     }
1742     function getBidi(ch, partPos) {
1743       var part = order[partPos], right = part.level % 2;
1744       if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
1745         part = order[--partPos];
1746         ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
1747         right = true;
1748       } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
1749         part = order[++partPos];
1750         ch = bidiLeft(part) - part.level % 2;
1751         right = false;
1752       }
1753       if (right && ch == part.to && ch > part.from) return get(ch - 1);
1754       return get(ch, right);
1755     }
1756     var order = getOrder(lineObj), ch = pos.ch;
1757     if (!order) return get(ch);
1758     var partPos = getBidiPartAt(order, ch);
1759     var val = getBidi(ch, partPos);
1760     if (bidiOther != null) val.other = getBidi(ch, bidiOther);
1761     return val;
1762   }
1763
1764   // Used to cheaply estimate the coordinates for a position. Used for
1765   // intermediate scroll updates.
1766   function estimateCoords(cm, pos) {
1767     var left = 0, pos = clipPos(cm.doc, pos);
1768     if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch;
1769     var lineObj = getLine(cm.doc, pos.line);
1770     var top = heightAtLine(lineObj) + paddingTop(cm.display);
1771     return {left: left, right: left, top: top, bottom: top + lineObj.height};
1772   }
1773
1774   // Positions returned by coordsChar contain some extra information.
1775   // xRel is the relative x position of the input coordinates compared
1776   // to the found position (so xRel > 0 means the coordinates are to
1777   // the right of the character position, for example). When outside
1778   // is true, that means the coordinates lie outside the line's
1779   // vertical range.
1780   function PosWithInfo(line, ch, outside, xRel) {
1781     var pos = Pos(line, ch);
1782     pos.xRel = xRel;
1783     if (outside) pos.outside = true;
1784     return pos;
1785   }
1786
1787   // Compute the character position closest to the given coordinates.
1788   // Input must be lineSpace-local ("div" coordinate system).
1789   function coordsChar(cm, x, y) {
1790     var doc = cm.doc;
1791     y += cm.display.viewOffset;
1792     if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
1793     var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1794     if (lineN > last)
1795       return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
1796     if (x < 0) x = 0;
1797
1798     var lineObj = getLine(doc, lineN);
1799     for (;;) {
1800       var found = coordsCharInner(cm, lineObj, lineN, x, y);
1801       var merged = collapsedSpanAtEnd(lineObj);
1802       var mergedPos = merged && merged.find(0, true);
1803       if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
1804         lineN = lineNo(lineObj = mergedPos.to.line);
1805       else
1806         return found;
1807     }
1808   }
1809
1810   function coordsCharInner(cm, lineObj, lineNo, x, y) {
1811     var innerOff = y - heightAtLine(lineObj);
1812     var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
1813     var preparedMeasure = prepareMeasureForLine(cm, lineObj);
1814
1815     function getX(ch) {
1816       var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure);
1817       wrongLine = true;
1818       if (innerOff > sp.bottom) return sp.left - adjust;
1819       else if (innerOff < sp.top) return sp.left + adjust;
1820       else wrongLine = false;
1821       return sp.left;
1822     }
1823
1824     var bidi = getOrder(lineObj), dist = lineObj.text.length;
1825     var from = lineLeft(lineObj), to = lineRight(lineObj);
1826     var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1827
1828     if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
1829     // Do a binary search between these bounds.
1830     for (;;) {
1831       if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1832         var ch = x < fromX || x - fromX <= toX - x ? from : to;
1833         var xDiff = x - (ch == from ? fromX : toX);
1834         while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
1835         var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
1836                               xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
1837         return pos;
1838       }
1839       var step = Math.ceil(dist / 2), middle = from + step;
1840       if (bidi) {
1841         middle = from;
1842         for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1843       }
1844       var middleX = getX(middle);
1845       if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
1846       else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
1847     }
1848   }
1849
1850   var measureText;
1851   // Compute the default text height.
1852   function textHeight(display) {
1853     if (display.cachedTextHeight != null) return display.cachedTextHeight;
1854     if (measureText == null) {
1855       measureText = elt("pre");
1856       // Measure a bunch of lines, for browsers that compute
1857       // fractional heights.
1858       for (var i = 0; i < 49; ++i) {
1859         measureText.appendChild(document.createTextNode("x"));
1860         measureText.appendChild(elt("br"));
1861       }
1862       measureText.appendChild(document.createTextNode("x"));
1863     }
1864     removeChildrenAndAdd(display.measure, measureText);
1865     var height = measureText.offsetHeight / 50;
1866     if (height > 3) display.cachedTextHeight = height;
1867     removeChildren(display.measure);
1868     return height || 1;
1869   }
1870
1871   // Compute the default character width.
1872   function charWidth(display) {
1873     if (display.cachedCharWidth != null) return display.cachedCharWidth;
1874     var anchor = elt("span", "xxxxxxxxxx");
1875     var pre = elt("pre", [anchor]);
1876     removeChildrenAndAdd(display.measure, pre);
1877     var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
1878     if (width > 2) display.cachedCharWidth = width;
1879     return width || 10;
1880   }
1881
1882   // OPERATIONS
1883
1884   // Operations are used to wrap a series of changes to the editor
1885   // state in such a way that each change won't have to update the
1886   // cursor and display (which would be awkward, slow, and
1887   // error-prone). Instead, display updates are batched and then all
1888   // combined and executed at once.
1889
1890   var nextOpId = 0;
1891   // Start a new operation.
1892   function startOperation(cm) {
1893     cm.curOp = {
1894       viewChanged: false,      // Flag that indicates that lines might need to be redrawn
1895       startHeight: cm.doc.height, // Used to detect need to update scrollbar
1896       forceUpdate: false,      // Used to force a redraw
1897       updateInput: null,       // Whether to reset the input textarea
1898       typing: false,           // Whether this reset should be careful to leave existing text (for compositing)
1899       changeObjs: null,        // Accumulated changes, for firing change events
1900       cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
1901       selectionChanged: false, // Whether the selection needs to be redrawn
1902       updateMaxLine: false,    // Set when the widest line needs to be determined anew
1903       scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
1904       scrollToPos: null,       // Used to scroll to a specific position
1905       id: ++nextOpId           // Unique ID
1906     };
1907     if (!delayedCallbackDepth++) delayedCallbacks = [];
1908   }
1909
1910   // Finish an operation, updating the display and signalling delayed events
1911   function endOperation(cm) {
1912     var op = cm.curOp, doc = cm.doc, display = cm.display;
1913     cm.curOp = null;
1914
1915     if (op.updateMaxLine) findMaxLine(cm);
1916
1917     // If it looks like an update might be needed, call updateDisplay
1918     if (op.viewChanged || op.forceUpdate || op.scrollTop != null ||
1919         op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
1920                            op.scrollToPos.to.line >= display.viewTo) ||
1921         display.maxLineChanged && cm.options.lineWrapping) {
1922       var updated = updateDisplay(cm, {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
1923       if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
1924     }
1925     // If no update was run, but the selection changed, redraw that.
1926     if (!updated && op.selectionChanged) updateSelection(cm);
1927     if (!updated && op.startHeight != cm.doc.height) updateScrollbars(cm);
1928
1929     // Propagate the scroll position to the actual DOM scroller
1930     if (op.scrollTop != null && display.scroller.scrollTop != op.scrollTop) {
1931       var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
1932       display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
1933     }
1934     if (op.scrollLeft != null && display.scroller.scrollLeft != op.scrollLeft) {
1935       var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
1936       display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
1937       alignHorizontally(cm);
1938     }
1939     // If we need to scroll a specific position into view, do so.
1940     if (op.scrollToPos) {
1941       var coords = scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from),
1942                                      clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin);
1943       if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords);
1944     }
1945
1946     if (op.selectionChanged) restartBlink(cm);
1947
1948     if (cm.state.focused && op.updateInput)
1949       resetInput(cm, op.typing);
1950
1951     // Fire events for markers that are hidden/unidden by editing or
1952     // undoing
1953     var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
1954     if (hidden) for (var i = 0; i < hidden.length; ++i)
1955       if (!hidden[i].lines.length) signal(hidden[i], "hide");
1956     if (unhidden) for (var i = 0; i < unhidden.length; ++i)
1957       if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
1958
1959     var delayed;
1960     if (!--delayedCallbackDepth) {
1961       delayed = delayedCallbacks;
1962       delayedCallbacks = null;
1963     }
1964     // Fire change events, and delayed event handlers
1965     if (op.changeObjs)
1966       signal(cm, "changes", cm, op.changeObjs);
1967     if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
1968     if (op.cursorActivityHandlers)
1969       for (var i = 0; i < op.cursorActivityHandlers.length; i++)
1970         op.cursorActivityHandlers[i](cm);
1971   }
1972
1973   // Run the given function in an operation
1974   function runInOp(cm, f) {
1975     if (cm.curOp) return f();
1976     startOperation(cm);
1977     try { return f(); }
1978     finally { endOperation(cm); }
1979   }
1980   // Wraps a function in an operation. Returns the wrapped function.
1981   function operation(cm, f) {
1982     return function() {
1983       if (cm.curOp) return f.apply(cm, arguments);
1984       startOperation(cm);
1985       try { return f.apply(cm, arguments); }
1986       finally { endOperation(cm); }
1987     };
1988   }
1989   // Used to add methods to editor and doc instances, wrapping them in
1990   // operations.
1991   function methodOp(f) {
1992     return function() {
1993       if (this.curOp) return f.apply(this, arguments);
1994       startOperation(this);
1995       try { return f.apply(this, arguments); }
1996       finally { endOperation(this); }
1997     };
1998   }
1999   function docMethodOp(f) {
2000     return function() {
2001       var cm = this.cm;
2002       if (!cm || cm.curOp) return f.apply(this, arguments);
2003       startOperation(cm);
2004       try { return f.apply(this, arguments); }
2005       finally { endOperation(cm); }
2006     };
2007   }
2008
2009   // VIEW TRACKING
2010
2011   // These objects are used to represent the visible (currently drawn)
2012   // part of the document. A LineView may correspond to multiple
2013   // logical lines, if those are connected by collapsed ranges.
2014   function LineView(doc, line, lineN) {
2015     // The starting line
2016     this.line = line;
2017     // Continuing lines, if any
2018     this.rest = visualLineContinued(line);
2019     // Number of logical lines in this visual line
2020     this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
2021     this.node = this.text = null;
2022     this.hidden = lineIsHidden(doc, line);
2023   }
2024
2025   // Create a range of LineView objects for the given lines.
2026   function buildViewArray(cm, from, to) {
2027     var array = [], nextPos;
2028     for (var pos = from; pos < to; pos = nextPos) {
2029       var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
2030       nextPos = pos + view.size;
2031       array.push(view);
2032     }
2033     return array;
2034   }
2035
2036   // Updates the display.view data structure for a given change to the
2037   // document. From and to are in pre-change coordinates. Lendiff is
2038   // the amount of lines added or subtracted by the change. This is
2039   // used for changes that span multiple lines, or change the way
2040   // lines are divided into visual lines. regLineChange (below)
2041   // registers single-line changes.
2042   function regChange(cm, from, to, lendiff) {
2043     if (from == null) from = cm.doc.first;
2044     if (to == null) to = cm.doc.first + cm.doc.size;
2045     if (!lendiff) lendiff = 0;
2046
2047     var display = cm.display;
2048     if (lendiff && to < display.viewTo &&
2049         (display.updateLineNumbers == null || display.updateLineNumbers > from))
2050       display.updateLineNumbers = from;
2051
2052     cm.curOp.viewChanged = true;
2053
2054     if (from >= display.viewTo) { // Change after
2055       if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
2056         resetView(cm);
2057     } else if (to <= display.viewFrom) { // Change before
2058       if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
2059         resetView(cm);
2060       } else {
2061         display.viewFrom += lendiff;
2062         display.viewTo += lendiff;
2063       }
2064     } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
2065       resetView(cm);
2066     } else if (from <= display.viewFrom) { // Top overlap
2067       var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
2068       if (cut) {
2069         display.view = display.view.slice(cut.index);
2070         display.viewFrom = cut.lineN;
2071         display.viewTo += lendiff;
2072       } else {
2073         resetView(cm);
2074       }
2075     } else if (to >= display.viewTo) { // Bottom overlap
2076       var cut = viewCuttingPoint(cm, from, from, -1);
2077       if (cut) {
2078         display.view = display.view.slice(0, cut.index);
2079         display.viewTo = cut.lineN;
2080       } else {
2081         resetView(cm);
2082       }
2083     } else { // Gap in the middle
2084       var cutTop = viewCuttingPoint(cm, from, from, -1);
2085       var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
2086       if (cutTop && cutBot) {
2087         display.view = display.view.slice(0, cutTop.index)
2088           .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
2089           .concat(display.view.slice(cutBot.index));
2090         display.viewTo += lendiff;
2091       } else {
2092         resetView(cm);
2093       }
2094     }
2095
2096     var ext = display.externalMeasured;
2097     if (ext) {
2098       if (to < ext.lineN)
2099         ext.lineN += lendiff;
2100       else if (from < ext.lineN + ext.size)
2101         display.externalMeasured = null;
2102     }
2103   }
2104
2105   // Register a change to a single line. Type must be one of "text",
2106   // "gutter", "class", "widget"
2107   function regLineChange(cm, line, type) {
2108     cm.curOp.viewChanged = true;
2109     var display = cm.display, ext = cm.display.externalMeasured;
2110     if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
2111       display.externalMeasured = null;
2112
2113     if (line < display.viewFrom || line >= display.viewTo) return;
2114     var lineView = display.view[findViewIndex(cm, line)];
2115     if (lineView.node == null) return;
2116     var arr = lineView.changes || (lineView.changes = []);
2117     if (indexOf(arr, type) == -1) arr.push(type);
2118   }
2119
2120   // Clear the view.
2121   function resetView(cm) {
2122     cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
2123     cm.display.view = [];
2124     cm.display.viewOffset = 0;
2125   }
2126
2127   // Find the view element corresponding to a given line. Return null
2128   // when the line isn't visible.
2129   function findViewIndex(cm, n) {
2130     if (n >= cm.display.viewTo) return null;
2131     n -= cm.display.viewFrom;
2132     if (n < 0) return null;
2133     var view = cm.display.view;
2134     for (var i = 0; i < view.length; i++) {
2135       n -= view[i].size;
2136       if (n < 0) return i;
2137     }
2138   }
2139
2140   function viewCuttingPoint(cm, oldN, newN, dir) {
2141     var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
2142     if (!sawCollapsedSpans) return {index: index, lineN: newN};
2143     for (var i = 0, n = cm.display.viewFrom; i < index; i++)
2144       n += view[i].size;
2145     if (n != oldN) {
2146       if (dir > 0) {
2147         if (index == view.length - 1) return null;
2148         diff = (n + view[index].size) - oldN;
2149         index++;
2150       } else {
2151         diff = n - oldN;
2152       }
2153       oldN += diff; newN += diff;
2154     }
2155     while (visualLineNo(cm.doc, newN) != newN) {
2156       if (index == (dir < 0 ? 0 : view.length - 1)) return null;
2157       newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
2158       index += dir;
2159     }
2160     return {index: index, lineN: newN};
2161   }
2162
2163   // Force the view to cover a given range, adding empty view element
2164   // or clipping off existing ones as needed.
2165   function adjustView(cm, from, to) {
2166     var display = cm.display, view = display.view;
2167     if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
2168       display.view = buildViewArray(cm, from, to);
2169       display.viewFrom = from;
2170     } else {
2171       if (display.viewFrom > from)
2172         display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view);
2173       else if (display.viewFrom < from)
2174         display.view = display.view.slice(findViewIndex(cm, from));
2175       display.viewFrom = from;
2176       if (display.viewTo < to)
2177         display.view = display.view.concat(buildViewArray(cm, display.viewTo, to));
2178       else if (display.viewTo > to)
2179         display.view = display.view.slice(0, findViewIndex(cm, to));
2180     }
2181     display.viewTo = to;
2182   }
2183
2184   // Count the number of lines in the view whose DOM representation is
2185   // out of date (or nonexistent).
2186   function countDirtyView(cm) {
2187     var view = cm.display.view, dirty = 0;
2188     for (var i = 0; i < view.length; i++) {
2189       var lineView = view[i];
2190       if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty;
2191     }
2192     return dirty;
2193   }
2194
2195   // INPUT HANDLING
2196
2197   // Poll for input changes, using the normal rate of polling. This
2198   // runs as long as the editor is focused.
2199   function slowPoll(cm) {
2200     if (cm.display.pollingFast) return;
2201     cm.display.poll.set(cm.options.pollInterval, function() {
2202       readInput(cm);
2203       if (cm.state.focused) slowPoll(cm);
2204     });
2205   }
2206
2207   // When an event has just come in that is likely to add or change
2208   // something in the input textarea, we poll faster, to ensure that
2209   // the change appears on the screen quickly.
2210   function fastPoll(cm) {
2211     var missed = false;
2212     cm.display.pollingFast = true;
2213     function p() {
2214       var changed = readInput(cm);
2215       if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
2216       else {cm.display.pollingFast = false; slowPoll(cm);}
2217     }
2218     cm.display.poll.set(20, p);
2219   }
2220
2221   // Read input from the textarea, and update the document to match.
2222   // When something is selected, it is present in the textarea, and
2223   // selected (unless it is huge, in which case a placeholder is
2224   // used). When nothing is selected, the cursor sits after previously
2225   // seen text (can be empty), which is stored in prevInput (we must
2226   // not reset the textarea when typing, because that breaks IME).
2227   function readInput(cm) {
2228     var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
2229     // Since this is called a *lot*, try to bail out as cheaply as
2230     // possible when it is clear that nothing happened. hasSelection
2231     // will be the case when there is a lot of text in the textarea,
2232     // in which case reading its value would be expensive.
2233     if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput)
2234       return false;
2235     // See paste handler for more on the fakedLastChar kludge
2236     if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
2237       input.value = input.value.substring(0, input.value.length - 1);
2238       cm.state.fakedLastChar = false;
2239     }
2240     var text = input.value;
2241     // If nothing changed, bail.
2242     if (text == prevInput && !cm.somethingSelected()) return false;
2243     // Work around nonsensical selection resetting in IE9/10
2244     if (ie && !ie_upto8 && cm.display.inputHasSelection === text) {
2245       resetInput(cm);
2246       return false;
2247     }
2248
2249     var withOp = !cm.curOp;
2250     if (withOp) startOperation(cm);
2251     cm.display.shift = false;
2252
2253     if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput)
2254       prevInput = "\u200b";
2255     // Find the part of the input that is actually new
2256     var same = 0, l = Math.min(prevInput.length, text.length);
2257     while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
2258     var inserted = text.slice(same), textLines = splitLines(inserted);
2259
2260     // When pasing N lines into N selections, insert one line per selection
2261     var multiPaste = cm.state.pasteIncoming && textLines.length > 1 && doc.sel.ranges.length == textLines.length;
2262
2263     // Normal behavior is to insert the new text into every selection
2264     for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
2265       var range = doc.sel.ranges[i];
2266       var from = range.from(), to = range.to();
2267       // Handle deletion
2268       if (same < prevInput.length)
2269         from = Pos(from.line, from.ch - (prevInput.length - same));
2270       // Handle overwrite
2271       else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming)
2272         to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
2273       var updateInput = cm.curOp.updateInput;
2274       var changeEvent = {from: from, to: to, text: multiPaste ? [textLines[i]] : textLines,
2275                          origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
2276       makeChange(cm.doc, changeEvent);
2277       signalLater(cm, "inputRead", cm, changeEvent);
2278       // When an 'electric' character is inserted, immediately trigger a reindent
2279       if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
2280           cm.options.smartIndent && range.head.ch < 100 &&
2281           (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) {
2282         var mode = cm.getModeAt(range.head);
2283         if (mode.electricChars) {
2284           for (var j = 0; j < mode.electricChars.length; j++)
2285             if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
2286               indentLine(cm, range.head.line, "smart");
2287               break;
2288             }
2289         } else if (mode.electricInput) {
2290           var end = changeEnd(changeEvent);
2291           if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
2292             indentLine(cm, range.head.line, "smart");
2293         }
2294       }
2295     }
2296     ensureCursorVisible(cm);
2297     cm.curOp.updateInput = updateInput;
2298     cm.curOp.typing = true;
2299
2300     // Don't leave long text in the textarea, since it makes further polling slow
2301     if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
2302     else cm.display.prevInput = text;
2303     if (withOp) endOperation(cm);
2304     cm.state.pasteIncoming = cm.state.cutIncoming = false;
2305     return true;
2306   }
2307
2308   // Reset the input to correspond to the selection (or to be empty,
2309   // when not typing and nothing is selected)
2310   function resetInput(cm, typing) {
2311     var minimal, selected, doc = cm.doc;
2312     if (cm.somethingSelected()) {
2313       cm.display.prevInput = "";
2314       var range = doc.sel.primary();
2315       minimal = hasCopyEvent &&
2316         (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
2317       var content = minimal ? "-" : selected || cm.getSelection();
2318       cm.display.input.value = content;
2319       if (cm.state.focused) selectInput(cm.display.input);
2320       if (ie && !ie_upto8) cm.display.inputHasSelection = content;
2321     } else if (!typing) {
2322       cm.display.prevInput = cm.display.input.value = "";
2323       if (ie && !ie_upto8) cm.display.inputHasSelection = null;
2324     }
2325     cm.display.inaccurateSelection = minimal;
2326   }
2327
2328   function focusInput(cm) {
2329     if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input))
2330       cm.display.input.focus();
2331   }
2332
2333   function ensureFocus(cm) {
2334     if (!cm.state.focused) { focusInput(cm); onFocus(cm); }
2335   }
2336
2337   function isReadOnly(cm) {
2338     return cm.options.readOnly || cm.doc.cantEdit;
2339   }
2340
2341   // EVENT HANDLERS
2342
2343   // Attach the necessary event handlers when initializing the editor
2344   function registerEventHandlers(cm) {
2345     var d = cm.display;
2346     on(d.scroller, "mousedown", operation(cm, onMouseDown));
2347     // Older IE's will not fire a second mousedown for a double click
2348     if (ie_upto10)
2349       on(d.scroller, "dblclick", operation(cm, function(e) {
2350         if (signalDOMEvent(cm, e)) return;
2351         var pos = posFromMouse(cm, e);
2352         if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
2353         e_preventDefault(e);
2354         var word = findWordAt(cm.doc, pos);
2355         extendSelection(cm.doc, word.anchor, word.head);
2356       }));
2357     else
2358       on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
2359     // Prevent normal selection in the editor (we handle our own)
2360     on(d.lineSpace, "selectstart", function(e) {
2361       if (!eventInWidget(d, e)) e_preventDefault(e);
2362     });
2363     // Some browsers fire contextmenu *after* opening the menu, at
2364     // which point we can't mess with it anymore. Context menu is
2365     // handled in onMouseDown for these browsers.
2366     if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
2367
2368     // Sync scrolling between fake scrollbars and real scrollable
2369     // area, ensure viewport is updated when scrolling.
2370     on(d.scroller, "scroll", function() {
2371       if (d.scroller.clientHeight) {
2372         setScrollTop(cm, d.scroller.scrollTop);
2373         setScrollLeft(cm, d.scroller.scrollLeft, true);
2374         signal(cm, "scroll", cm);
2375       }
2376     });
2377     on(d.scrollbarV, "scroll", function() {
2378       if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
2379     });
2380     on(d.scrollbarH, "scroll", function() {
2381       if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
2382     });
2383
2384     // Listen to wheel events in order to try and update the viewport on time.
2385     on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
2386     on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
2387
2388     // Prevent clicks in the scrollbars from killing focus
2389     function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
2390     on(d.scrollbarH, "mousedown", reFocus);
2391     on(d.scrollbarV, "mousedown", reFocus);
2392     // Prevent wrapper from ever scrolling
2393     on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
2394
2395     // When the window resizes, we need to refresh active editors.
2396     var resizeTimer;
2397     function onResize() {
2398       if (resizeTimer == null) resizeTimer = setTimeout(function() {
2399         resizeTimer = null;
2400         // Might be a text scaling operation, clear size caches.
2401         d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = knownScrollbarWidth = null;
2402         cm.setSize();
2403       }, 100);
2404     }
2405     on(window, "resize", onResize);
2406     // The above handler holds on to the editor and its data
2407     // structures. Here we poll to unregister it when the editor is no
2408     // longer in the document, so that it can be garbage-collected.
2409     function unregister() {
2410       if (contains(document.body, d.wrapper)) setTimeout(unregister, 5000);
2411       else off(window, "resize", onResize);
2412     }
2413     setTimeout(unregister, 5000);
2414
2415     on(d.input, "keyup", operation(cm, onKeyUp));
2416     on(d.input, "input", function() {
2417       if (ie && !ie_upto8 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
2418       fastPoll(cm);
2419     });
2420     on(d.input, "keydown", operation(cm, onKeyDown));
2421     on(d.input, "keypress", operation(cm, onKeyPress));
2422     on(d.input, "focus", bind(onFocus, cm));
2423     on(d.input, "blur", bind(onBlur, cm));
2424
2425     function drag_(e) {
2426       if (!signalDOMEvent(cm, e)) e_stop(e);
2427     }
2428     if (cm.options.dragDrop) {
2429       on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
2430       on(d.scroller, "dragenter", drag_);
2431       on(d.scroller, "dragover", drag_);
2432       on(d.scroller, "drop", operation(cm, onDrop));
2433     }
2434     on(d.scroller, "paste", function(e) {
2435       if (eventInWidget(d, e)) return;
2436       cm.state.pasteIncoming = true;
2437       focusInput(cm);
2438       fastPoll(cm);
2439     });
2440     on(d.input, "paste", function() {
2441       // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
2442       // Add a char to the end of textarea before paste occur so that
2443       // selection doesn't span to the end of textarea.
2444       if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
2445         var start = d.input.selectionStart, end = d.input.selectionEnd;
2446         d.input.value += "$";
2447         d.input.selectionStart = start;
2448         d.input.selectionEnd = end;
2449         cm.state.fakedLastChar = true;
2450       }
2451       cm.state.pasteIncoming = true;
2452       fastPoll(cm);
2453     });
2454
2455     function prepareCopyCut(e) {
2456       if (cm.somethingSelected()) {
2457         if (d.inaccurateSelection) {
2458           d.prevInput = "";
2459           d.inaccurateSelection = false;
2460           d.input.value = cm.getSelection();
2461           selectInput(d.input);
2462         }
2463       } else {
2464         var text = "", ranges = [];
2465         for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
2466           var line = cm.doc.sel.ranges[i].head.line;
2467           var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
2468           ranges.push(lineRange);
2469           text += cm.getRange(lineRange.anchor, lineRange.head);
2470         }
2471         if (e.type == "cut") {
2472           cm.setSelections(ranges, null, sel_dontScroll);
2473         } else {
2474           d.prevInput = "";
2475           d.input.value = text;
2476           selectInput(d.input);
2477         }
2478       }
2479       if (e.type == "cut") cm.state.cutIncoming = true;
2480     }
2481     on(d.input, "cut", prepareCopyCut);
2482     on(d.input, "copy", prepareCopyCut);
2483
2484     // Needed to handle Tab key in KHTML
2485     if (khtml) on(d.sizer, "mouseup", function() {
2486       if (activeElt() == d.input) d.input.blur();
2487       focusInput(cm);
2488     });
2489   }
2490
2491   // MOUSE EVENTS
2492
2493   // Return true when the given mouse event happened in a widget
2494   function eventInWidget(display, e) {
2495     for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
2496       if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
2497     }
2498   }
2499
2500   // Given a mouse event, find the corresponding position. If liberal
2501   // is false, it checks whether a gutter or scrollbar was clicked,
2502   // and returns null if it was. forRect is used by rectangular
2503   // selections, and tries to estimate a character position even for
2504   // coordinates beyond the right of the text.
2505   function posFromMouse(cm, e, liberal, forRect) {
2506     var display = cm.display;
2507     if (!liberal) {
2508       var target = e_target(e);
2509       if (target == display.scrollbarH || target == display.scrollbarV ||
2510           target == display.scrollbarFiller || target == display.gutterFiller) return null;
2511     }
2512     var x, y, space = display.lineSpace.getBoundingClientRect();
2513     // Fails unpredictably on IE[67] when mouse is dragged around quickly.
2514     try { x = e.clientX - space.left; y = e.clientY - space.top; }
2515     catch (e) { return null; }
2516     var coords = coordsChar(cm, x, y), line;
2517     if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
2518       var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
2519       coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
2520     }
2521     return coords;
2522   }
2523
2524   // A mouse down can be a single click, double click, triple click,
2525   // start of selection drag, start of text drag, new cursor
2526   // (ctrl-click), rectangle drag (alt-drag), or xwin
2527   // middle-click-paste. Or it might be a click on something we should
2528   // not interfere with, such as a scrollbar or widget.
2529   function onMouseDown(e) {
2530     if (signalDOMEvent(this, e)) return;
2531     var cm = this, display = cm.display;
2532     display.shift = e.shiftKey;
2533
2534     if (eventInWidget(display, e)) {
2535       if (!webkit) {
2536         // Briefly turn off draggability, to allow widgets to do
2537         // normal dragging things.
2538         display.scroller.draggable = false;
2539         setTimeout(function(){display.scroller.draggable = true;}, 100);
2540       }
2541       return;
2542     }
2543     if (clickInGutter(cm, e)) return;
2544     var start = posFromMouse(cm, e);
2545     window.focus();
2546
2547     switch (e_button(e)) {
2548     case 1:
2549       if (start)
2550         leftButtonDown(cm, e, start);
2551       else if (e_target(e) == display.scroller)
2552         e_preventDefault(e);
2553       break;
2554     case 2:
2555       if (webkit) cm.state.lastMiddleDown = +new Date;
2556       if (start) extendSelection(cm.doc, start);
2557       setTimeout(bind(focusInput, cm), 20);
2558       e_preventDefault(e);
2559       break;
2560     case 3:
2561       if (captureRightClick) onContextMenu(cm, e);
2562       break;
2563     }
2564   }
2565
2566   var lastClick, lastDoubleClick;
2567   function leftButtonDown(cm, e, start) {
2568     setTimeout(bind(ensureFocus, cm), 0);
2569
2570     var now = +new Date, type;
2571     if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
2572       type = "triple";
2573     } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) {
2574       type = "double";
2575       lastDoubleClick = {time: now, pos: start};
2576     } else {
2577       type = "single";
2578       lastClick = {time: now, pos: start};
2579     }
2580
2581     var sel = cm.doc.sel, addNew = mac ? e.metaKey : e.ctrlKey;
2582     if (cm.options.dragDrop && dragAndDrop && !addNew && !isReadOnly(cm) &&
2583         type == "single" && sel.contains(start) > -1 && sel.somethingSelected())
2584       leftButtonStartDrag(cm, e, start);
2585     else
2586       leftButtonSelect(cm, e, start, type, addNew);
2587   }
2588
2589   // Start a text drag. When it ends, see if any dragging actually
2590   // happen, and treat as a click if it didn't.
2591   function leftButtonStartDrag(cm, e, start) {
2592     var display = cm.display;
2593     var dragEnd = operation(cm, function(e2) {
2594       if (webkit) display.scroller.draggable = false;
2595       cm.state.draggingText = false;
2596       off(document, "mouseup", dragEnd);
2597       off(display.scroller, "drop", dragEnd);
2598       if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
2599         e_preventDefault(e2);
2600         extendSelection(cm.doc, start);
2601         focusInput(cm);
2602         // Work around unexplainable focus problem in IE9 (#2127)
2603         if (ie_upto10 && !ie_upto8)
2604           setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
2605       }
2606     });
2607     // Let the drag handler handle this.
2608     if (webkit) display.scroller.draggable = true;
2609     cm.state.draggingText = dragEnd;
2610     // IE's approach to draggable
2611     if (display.scroller.dragDrop) display.scroller.dragDrop();
2612     on(document, "mouseup", dragEnd);
2613     on(display.scroller, "drop", dragEnd);
2614   }
2615
2616   // Normal selection, as opposed to text dragging.
2617   function leftButtonSelect(cm, e, start, type, addNew) {
2618     var display = cm.display, doc = cm.doc;
2619     e_preventDefault(e);
2620
2621     var ourRange, ourIndex, startSel = doc.sel;
2622     if (addNew && !e.shiftKey) {
2623       ourIndex = doc.sel.contains(start);
2624       if (ourIndex > -1)
2625         ourRange = doc.sel.ranges[ourIndex];
2626       else
2627         ourRange = new Range(start, start);
2628     } else {
2629       ourRange = doc.sel.primary();
2630     }
2631
2632     if (e.altKey) {
2633       type = "rect";
2634       if (!addNew) ourRange = new Range(start, start);
2635       start = posFromMouse(cm, e, true, true);
2636       ourIndex = -1;
2637     } else if (type == "double") {
2638       var word = findWordAt(doc, start);
2639       if (cm.display.shift || doc.extend)
2640         ourRange = extendRange(doc, ourRange, word.anchor, word.head);
2641       else
2642         ourRange = word;
2643     } else if (type == "triple") {
2644       var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)));
2645       if (cm.display.shift || doc.extend)
2646         ourRange = extendRange(doc, ourRange, line.anchor, line.head);
2647       else
2648         ourRange = line;
2649     } else {
2650       ourRange = extendRange(doc, ourRange, start);
2651     }
2652
2653     if (!addNew) {
2654       ourIndex = 0;
2655       setSelection(doc, new Selection([ourRange], 0), sel_mouse);
2656       startSel = doc.sel;
2657     } else if (ourIndex > -1) {
2658       replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
2659     } else {
2660       ourIndex = doc.sel.ranges.length;
2661       setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex),
2662                    {scroll: false, origin: "*mouse"});
2663     }
2664
2665     var lastPos = start;
2666     function extendTo(pos) {
2667       if (cmp(lastPos, pos) == 0) return;
2668       lastPos = pos;
2669
2670       if (type == "rect") {
2671         var ranges = [], tabSize = cm.options.tabSize;
2672         var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);
2673         var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
2674         var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);
2675         for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
2676              line <= end; line++) {
2677           var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);
2678           if (left == right)
2679             ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos)));
2680           else if (text.length > leftPos)
2681             ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize))));
2682         }
2683         if (!ranges.length) ranges.push(new Range(start, start));
2684         setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), sel_mouse);
2685       } else {
2686         var oldRange = ourRange;
2687         var anchor = oldRange.anchor, head = pos;
2688         if (type != "single") {
2689           if (type == "double")
2690             var range = findWordAt(doc, pos);
2691           else
2692             var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
2693           if (cmp(range.anchor, anchor) > 0) {
2694             head = range.head;
2695             anchor = minPos(oldRange.from(), range.anchor);
2696           } else {
2697             head = range.anchor;
2698             anchor = maxPos(oldRange.to(), range.head);
2699           }
2700         }
2701         var ranges = startSel.ranges.slice(0);
2702         ranges[ourIndex] = new Range(clipPos(doc, anchor), head);
2703         setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse);
2704       }
2705     }
2706
2707     var editorSize = display.wrapper.getBoundingClientRect();
2708     // Used to ensure timeout re-tries don't fire when another extend
2709     // happened in the meantime (clearTimeout isn't reliable -- at
2710     // least on Chrome, the timeouts still happen even when cleared,
2711     // if the clear happens after their scheduled firing time).
2712     var counter = 0;
2713
2714     function extend(e) {
2715       var curCount = ++counter;
2716       var cur = posFromMouse(cm, e, true, type == "rect");
2717       if (!cur) return;
2718       if (cmp(cur, lastPos) != 0) {
2719         ensureFocus(cm);
2720         extendTo(cur);
2721         var visible = visibleLines(display, doc);
2722         if (cur.line >= visible.to || cur.line < visible.from)
2723           setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
2724       } else {
2725         var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
2726         if (outside) setTimeout(operation(cm, function() {
2727           if (counter != curCount) return;
2728           display.scroller.scrollTop += outside;
2729           extend(e);
2730         }), 50);
2731       }
2732     }
2733
2734     function done(e) {
2735       counter = Infinity;
2736       e_preventDefault(e);
2737       focusInput(cm);
2738       off(document, "mousemove", move);
2739       off(document, "mouseup", up);
2740       doc.history.lastSelOrigin = null;
2741     }
2742
2743     var move = operation(cm, function(e) {
2744       if ((ie && !ie_upto9) ?  !e.buttons : !e_button(e)) done(e);
2745       else extend(e);
2746     });
2747     var up = operation(cm, done);
2748     on(document, "mousemove", move);
2749     on(document, "mouseup", up);
2750   }
2751
2752   // Determines whether an event happened in the gutter, and fires the
2753   // handlers for the corresponding event.
2754   function gutterEvent(cm, e, type, prevent, signalfn) {
2755     try { var mX = e.clientX, mY = e.clientY; }
2756     catch(e) { return false; }
2757     if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
2758     if (prevent) e_preventDefault(e);
2759
2760     var display = cm.display;
2761     var lineBox = display.lineDiv.getBoundingClientRect();
2762
2763     if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
2764     mY -= lineBox.top - display.viewOffset;
2765
2766     for (var i = 0; i < cm.options.gutters.length; ++i) {
2767       var g = display.gutters.childNodes[i];
2768       if (g && g.getBoundingClientRect().right >= mX) {
2769         var line = lineAtHeight(cm.doc, mY);
2770         var gutter = cm.options.gutters[i];
2771         signalfn(cm, type, cm, line, gutter, e);
2772         return e_defaultPrevented(e);
2773       }
2774     }
2775   }
2776
2777   function clickInGutter(cm, e) {
2778     return gutterEvent(cm, e, "gutterClick", true, signalLater);
2779   }
2780
2781   // Kludge to work around strange IE behavior where it'll sometimes
2782   // re-fire a series of drag-related events right after the drop (#1551)
2783   var lastDrop = 0;
2784
2785   function onDrop(e) {
2786     var cm = this;
2787     if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
2788       return;
2789     e_preventDefault(e);
2790     if (ie) lastDrop = +new Date;
2791     var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
2792     if (!pos || isReadOnly(cm)) return;
2793     // Might be a file drop, in which case we simply extract the text
2794     // and insert it.
2795     if (files && files.length && window.FileReader && window.File) {
2796       var n = files.length, text = Array(n), read = 0;
2797       var loadFile = function(file, i) {
2798         var reader = new FileReader;
2799         reader.onload = operation(cm, function() {
2800           text[i] = reader.result;
2801           if (++read == n) {
2802             pos = clipPos(cm.doc, pos);
2803             var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"};
2804             makeChange(cm.doc, change);
2805             setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
2806           }
2807         });
2808         reader.readAsText(file);
2809       };
2810       for (var i = 0; i < n; ++i) loadFile(files[i], i);
2811     } else { // Normal drop
2812       // Don't do a replace if the drop happened inside of the selected text.
2813       if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
2814         cm.state.draggingText(e);
2815         // Ensure the editor is re-focused
2816         setTimeout(bind(focusInput, cm), 20);
2817         return;
2818       }
2819       try {
2820         var text = e.dataTransfer.getData("Text");
2821         if (text) {
2822           var selected = cm.state.draggingText && cm.listSelections();
2823           setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
2824           if (selected) for (var i = 0; i < selected.length; ++i)
2825             replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
2826           cm.replaceSelection(text, "around", "paste");
2827           focusInput(cm);
2828         }
2829       }
2830       catch(e){}
2831     }
2832   }
2833
2834   function onDragStart(cm, e) {
2835     if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
2836     if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
2837
2838     e.dataTransfer.setData("Text", cm.getSelection());
2839
2840     // Use dummy image instead of default browsers image.
2841     // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
2842     if (e.dataTransfer.setDragImage && !safari) {
2843       var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
2844       img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
2845       if (presto) {
2846         img.width = img.height = 1;
2847         cm.display.wrapper.appendChild(img);
2848         // Force a relayout, or Opera won't use our image for some obscure reason
2849         img._top = img.offsetTop;
2850       }
2851       e.dataTransfer.setDragImage(img, 0, 0);
2852       if (presto) img.parentNode.removeChild(img);
2853     }
2854   }
2855
2856   // SCROLL EVENTS
2857
2858   // Sync the scrollable area and scrollbars, ensure the viewport
2859   // covers the visible area.
2860   function setScrollTop(cm, val) {
2861     if (Math.abs(cm.doc.scrollTop - val) < 2) return;
2862     cm.doc.scrollTop = val;
2863     if (!gecko) updateDisplay(cm, {top: val});
2864     if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
2865     if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
2866     if (gecko) updateDisplay(cm);
2867     startWorker(cm, 100);
2868   }
2869   // Sync scroller and scrollbar, ensure the gutter elements are
2870   // aligned.
2871   function setScrollLeft(cm, val, isScroller) {
2872     if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
2873     val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
2874     cm.doc.scrollLeft = val;
2875     alignHorizontally(cm);
2876     if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
2877     if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
2878   }
2879
2880   // Since the delta values reported on mouse wheel events are
2881   // unstandardized between browsers and even browser versions, and
2882   // generally horribly unpredictable, this code starts by measuring
2883   // the scroll effect that the first few mouse wheel events have,
2884   // and, from that, detects the way it can convert deltas to pixel
2885   // offsets afterwards.
2886   //
2887   // The reason we want to know the amount a wheel event will scroll
2888   // is that it gives us a chance to update the display before the
2889   // actual scrolling happens, reducing flickering.
2890
2891   var wheelSamples = 0, wheelPixelsPerUnit = null;
2892   // Fill in a browser-detected starting value on browsers where we
2893   // know one. These don't have to be accurate -- the result of them
2894   // being wrong would just be a slight flicker on the first wheel
2895   // scroll (if it is large enough).
2896   if (ie) wheelPixelsPerUnit = -.53;
2897   else if (gecko) wheelPixelsPerUnit = 15;
2898   else if (chrome) wheelPixelsPerUnit = -.7;
2899   else if (safari) wheelPixelsPerUnit = -1/3;
2900
2901   function onScrollWheel(cm, e) {
2902     var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
2903     if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
2904     if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
2905     else if (dy == null) dy = e.wheelDelta;
2906
2907     var display = cm.display, scroll = display.scroller;
2908     // Quit if there's nothing to scroll here
2909     if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
2910           dy && scroll.scrollHeight > scroll.clientHeight)) return;
2911
2912     // Webkit browsers on OS X abort momentum scrolls when the target
2913     // of the scroll event is removed from the scrollable element.
2914     // This hack (see related code in patchDisplay) makes sure the
2915     // element is kept around.
2916     if (dy && mac && webkit) {
2917       outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
2918         for (var i = 0; i < view.length; i++) {
2919           if (view[i].node == cur) {
2920             cm.display.currentWheelTarget = cur;
2921             break outer;
2922           }
2923         }
2924       }
2925     }
2926
2927     // On some browsers, horizontal scrolling will cause redraws to
2928     // happen before the gutter has been realigned, causing it to
2929     // wriggle around in a most unseemly way. When we have an
2930     // estimated pixels/delta value, we just handle horizontal
2931     // scrolling entirely here. It'll be slightly off from native, but
2932     // better than glitching out.
2933     if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
2934       if (dy)
2935         setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
2936       setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
2937       e_preventDefault(e);
2938       display.wheelStartX = null; // Abort measurement, if in progress
2939       return;
2940     }
2941
2942     // 'Project' the visible viewport to cover the area that is being
2943     // scrolled into view (if we know enough to estimate it).
2944     if (dy && wheelPixelsPerUnit != null) {
2945       var pixels = dy * wheelPixelsPerUnit;
2946       var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
2947       if (pixels < 0) top = Math.max(0, top + pixels - 50);
2948       else bot = Math.min(cm.doc.height, bot + pixels + 50);
2949       updateDisplay(cm, {top: top, bottom: bot});
2950     }
2951
2952     if (wheelSamples < 20) {
2953       if (display.wheelStartX == null) {
2954         display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
2955         display.wheelDX = dx; display.wheelDY = dy;
2956         setTimeout(function() {
2957           if (display.wheelStartX == null) return;
2958           var movedX = scroll.scrollLeft - display.wheelStartX;
2959           var movedY = scroll.scrollTop - display.wheelStartY;
2960           var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
2961             (movedX && display.wheelDX && movedX / display.wheelDX);
2962           display.wheelStartX = display.wheelStartY = null;
2963           if (!sample) return;
2964           wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
2965           ++wheelSamples;
2966         }, 200);
2967       } else {
2968         display.wheelDX += dx; display.wheelDY += dy;
2969       }
2970     }
2971   }
2972
2973   // KEY EVENTS
2974
2975   // Run a handler that was bound to a key.
2976   function doHandleBinding(cm, bound, dropShift) {
2977     if (typeof bound == "string") {
2978       bound = commands[bound];
2979       if (!bound) return false;
2980     }
2981     // Ensure previous input has been read, so that the handler sees a
2982     // consistent view of the document
2983     if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
2984     var prevShift = cm.display.shift, done = false;
2985     try {
2986       if (isReadOnly(cm)) cm.state.suppressEdits = true;
2987       if (dropShift) cm.display.shift = false;
2988       done = bound(cm) != Pass;
2989     } finally {
2990       cm.display.shift = prevShift;
2991       cm.state.suppressEdits = false;
2992     }
2993     return done;
2994   }
2995
2996   // Collect the currently active keymaps.
2997   function allKeyMaps(cm) {
2998     var maps = cm.state.keyMaps.slice(0);
2999     if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
3000     maps.push(cm.options.keyMap);
3001     return maps;
3002   }
3003
3004   var maybeTransition;
3005   // Handle a key from the keydown event.
3006   function handleKeyBinding(cm, e) {
3007     // Handle automatic keymap transitions
3008     var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
3009     clearTimeout(maybeTransition);
3010     if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
3011       if (getKeyMap(cm.options.keyMap) == startMap) {
3012         cm.options.keyMap = (next.call ? next.call(null, cm) : next);
3013         keyMapChanged(cm);
3014       }
3015     }, 50);
3016
3017     var name = keyName(e, true), handled = false;
3018     if (!name) return false;
3019     var keymaps = allKeyMaps(cm);
3020
3021     if (e.shiftKey) {
3022       // First try to resolve full name (including 'Shift-'). Failing
3023       // that, see if there is a cursor-motion command (starting with
3024       // 'go') bound to the keyname without 'Shift-'.
3025       handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
3026              || lookupKey(name, keymaps, function(b) {
3027                   if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
3028                     return doHandleBinding(cm, b);
3029                 });
3030     } else {
3031       handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
3032     }
3033
3034     if (handled) {
3035       e_preventDefault(e);
3036       restartBlink(cm);
3037       signalLater(cm, "keyHandled", cm, name, e);
3038     }
3039     return handled;
3040   }
3041
3042   // Handle a key from the keypress event
3043   function handleCharBinding(cm, e, ch) {
3044     var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
3045                             function(b) { return doHandleBinding(cm, b, true); });
3046     if (handled) {
3047       e_preventDefault(e);
3048       restartBlink(cm);
3049       signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
3050     }
3051     return handled;
3052   }
3053
3054   var lastStoppedKey = null;
3055   function onKeyDown(e) {
3056     var cm = this;
3057     ensureFocus(cm);
3058     if (signalDOMEvent(cm, e)) return;
3059     // IE does strange things with escape.
3060     if (ie_upto10 && e.keyCode == 27) e.returnValue = false;
3061     var code = e.keyCode;
3062     cm.display.shift = code == 16 || e.shiftKey;
3063     var handled = handleKeyBinding(cm, e);
3064     if (presto) {
3065       lastStoppedKey = handled ? code : null;
3066       // Opera has no cut event... we try to at least catch the key combo
3067       if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
3068         cm.replaceSelection("", null, "cut");
3069     }
3070
3071     // Turn mouse into crosshair when Alt is held on Mac.
3072     if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
3073       showCrossHair(cm);
3074   }
3075
3076   function showCrossHair(cm) {
3077     var lineDiv = cm.display.lineDiv;
3078     addClass(lineDiv, "CodeMirror-crosshair");
3079
3080     function up(e) {
3081       if (e.keyCode == 18 || !e.altKey) {
3082         rmClass(lineDiv, "CodeMirror-crosshair");
3083         off(document, "keyup", up);
3084         off(document, "mouseover", up);
3085       }
3086     }
3087     on(document, "keyup", up);
3088     on(document, "mouseover", up);
3089   }
3090
3091   function onKeyUp(e) {
3092     if (signalDOMEvent(this, e)) return;
3093     if (e.keyCode == 16) this.doc.sel.shift = false;
3094   }
3095
3096   function onKeyPress(e) {
3097     var cm = this;
3098     if (signalDOMEvent(cm, e)) return;
3099     var keyCode = e.keyCode, charCode = e.charCode;
3100     if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
3101     if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
3102     var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
3103     if (handleCharBinding(cm, e, ch)) return;
3104     if (ie && !ie_upto8) cm.display.inputHasSelection = null;
3105     fastPoll(cm);
3106   }
3107
3108   // FOCUS/BLUR EVENTS
3109
3110   function onFocus(cm) {
3111     if (cm.options.readOnly == "nocursor") return;
3112     if (!cm.state.focused) {
3113       signal(cm, "focus", cm);
3114       cm.state.focused = true;
3115       addClass(cm.display.wrapper, "CodeMirror-focused");
3116       // The prevInput test prevents this from firing when a context
3117       // menu is closed (since the resetInput would kill the
3118       // select-all detection hack)
3119       if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
3120         resetInput(cm);
3121         if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
3122       }
3123     }
3124     slowPoll(cm);
3125     restartBlink(cm);
3126   }
3127   function onBlur(cm) {
3128     if (cm.state.focused) {
3129       signal(cm, "blur", cm);
3130       cm.state.focused = false;
3131       rmClass(cm.display.wrapper, "CodeMirror-focused");
3132     }
3133     clearInterval(cm.display.blinker);
3134     setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150);
3135   }
3136
3137   // CONTEXT MENU HANDLING
3138
3139   // To make the context menu work, we need to briefly unhide the
3140   // textarea (making it as unobtrusive as possible) to let the
3141   // right-click take effect on it.
3142   function onContextMenu(cm, e)&nb