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