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