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