]> git.r.bdr.sh - rbdr/dotfiles/blob - atom/packages/pretty-json/node_modules/jsonminify/report/assets/scripts/vendor/codemirror/codemirror.js
2003aa74ba3aba26e5a49d2aa132805b329595ee
[rbdr/dotfiles] / atom / packages / pretty-json / node_modules / jsonminify / report / assets / scripts / vendor / codemirror / codemirror.js
1 // CodeMirror version 3.0
2 //
3 // CodeMirror is the only global var we claim
4 window.CodeMirror = (function() {
5 "use strict";
6
7 // BROWSER SNIFFING
8
9 // Crude, but necessary to handle a number of hard-to-feature-detect
10 // bugs and behavior differences.
11 var gecko = /gecko\/\d/i.test(navigator.userAgent);
12 var ie = /MSIE \d/.test(navigator.userAgent);
13 var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
14 var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
15 var webkit = /WebKit\//.test(navigator.userAgent);
16 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
17 var chrome = /Chrome\//.test(navigator.userAgent);
18 var opera = /Opera\//.test(navigator.userAgent);
19 var safari = /Apple Computer/.test(navigator.vendor);
20 var khtml = /KHTML\//.test(navigator.userAgent);
21 var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
22 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
23 var phantom = /PhantomJS/.test(navigator.userAgent);
24
25 var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
26 // This is woefully incomplete. Suggestions for alternative methods welcome.
27 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent);
28 var mac = ios || /Mac/.test(navigator.platform);
29
30 // Optimize some code when these features are not used
31 var sawReadOnlySpans = false, sawCollapsedSpans = false;
32
33 // CONSTRUCTOR
34
35 function CodeMirror(place, options) {
36 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
37
38 this.options = options = options || {};
39 // Determine effective options based on given values and defaults.
40 for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
41 options[opt] = defaults[opt];
42 setGuttersForLineNumbers(options);
43
44 var display = this.display = makeDisplay(place);
45 display.wrapper.CodeMirror = this;
46 updateGutters(this);
47 if (options.autofocus && !mobile) focusInput(this);
48
49 this.view = makeView(new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display))])]));
50 this.nextOpId = 0;
51 loadMode(this);
52 themeChanged(this);
53 if (options.lineWrapping)
54 this.display.wrapper.className += " CodeMirror-wrap";
55
56 // Initialize the content.
57 this.setValue(options.value || "");
58 // Override magic textarea content restore that IE sometimes does
59 // on our hidden textarea on reload
60 if (ie) setTimeout(bind(resetInput, this, true), 20);
61 this.view.history = makeHistory();
62
63 registerEventHandlers(this);
64 // IE throws unspecified error in certain cases, when
65 // trying to access activeElement before onload
66 var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
67 if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
68 else onBlur(this);
69
70 operation(this, function() {
71 for (var opt in optionHandlers)
72 if (optionHandlers.propertyIsEnumerable(opt))
73 optionHandlers[opt](this, options[opt], Init);
74 for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
75 })();
76 }
77
78 // DISPLAY CONSTRUCTOR
79
80 function makeDisplay(place) {
81 var d = {};
82 var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
83 input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
84 // Wraps and hides input textarea
85 d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
86 // The actual fake scrollbars.
87 d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
88 d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
89 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
90 // DIVs containing the selection and the actual code
91 d.lineDiv = elt("div");
92 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
93 // Blinky cursor, and element used to ensure cursor fits at the end of a line
94 d.cursor = elt("pre", "\u00a0", "CodeMirror-cursor");
95 // Secondary cursor, shown when on a 'jump' in bi-directional text
96 d.otherCursor = elt("pre", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
97 // Used to measure text size
98 d.measure = elt("div", null, "CodeMirror-measure");
99 // Wraps everything that needs to exist inside the vertically-padded coordinate system
100 d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
101 null, "position: relative; outline: none");
102 // Moved around its parent to cover visible view
103 d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
104 // Set to the height of the text, causes scrolling
105 d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
106 // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
107 d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px");
108 // Will contain the gutters, if any
109 d.gutters = elt("div", null, "CodeMirror-gutters");
110 d.lineGutter = null;
111 // Helper element to properly size the gutter backgrounds
112 var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%");
113 // Provides scrolling
114 d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll");
115 d.scroller.setAttribute("tabIndex", "-1");
116 // The element in which the editor lives.
117 d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
118 d.scrollbarFiller, d.scroller], "CodeMirror");
119 // Work around IE7 z-index bug
120 if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
121 if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
122
123 // Needed to hide big blue blinking cursor on Mobile Safari
124 if (ios) input.style.width = "0px";
125 if (!webkit) d.scroller.draggable = true;
126 // Needed to handle Tab key in KHTML
127 if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
128 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
129 else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
130
131 // Current visible range (may be bigger than the view window).
132 d.viewOffset = d.showingFrom = d.showingTo = d.lastSizeC = 0;
133
134 // Used to only resize the line number gutter when necessary (when
135 // the amount of lines crosses a boundary that makes its width change)
136 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
137 // See readInput and resetInput
138 d.prevInput = "";
139 // Set to true when a non-horizontal-scrolling widget is added. As
140 // an optimization, widget aligning is skipped when d is false.
141 d.alignWidgets = false;
142 // Flag that indicates whether we currently expect input to appear
143 // (after some event like 'keypress' or 'input') and are polling
144 // intensively.
145 d.pollingFast = false;
146 // Self-resetting timeout for the poller
147 d.poll = new Delayed();
148 // True when a drag from the editor is active
149 d.draggingText = false;
150
151 d.cachedCharWidth = d.cachedTextHeight = null;
152 d.measureLineCache = [];
153 d.measureLineCachePos = 0;
154
155 // Tracks when resetInput has punted to just putting a short
156 // string instead of the (large) selection.
157 d.inaccurateSelection = false;
158
159 // Used to adjust overwrite behaviour when a paste has been
160 // detected
161 d.pasteIncoming = false;
162
163 return d;
164 }
165
166 // VIEW CONSTRUCTOR
167
168 function makeView(doc) {
169 var selPos = {line: 0, ch: 0};
170 return {
171 doc: doc,
172 // frontier is the point up to which the content has been parsed,
173 frontier: 0, highlight: new Delayed(),
174 sel: {from: selPos, to: selPos, head: selPos, anchor: selPos, shift: false, extend: false},
175 scrollTop: 0, scrollLeft: 0,
176 overwrite: false, focused: false,
177 // Tracks the maximum line length so that
178 // the horizontal scrollbar can be kept
179 // static when scrolling.
180 maxLine: getLine(doc, 0),
181 maxLineLength: 0,
182 maxLineChanged: false,
183 suppressEdits: false,
184 goalColumn: null,
185 cantEdit: false,
186 keyMaps: []
187 };
188 }
189
190 // STATE UPDATES
191
192 // Used to get the editor into a consistent state again when options change.
193
194 function loadMode(cm) {
195 var doc = cm.view.doc;
196 cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode);
197 doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
198 cm.view.frontier = 0;
199 startWorker(cm, 100);
200 }
201
202 function wrappingChanged(cm) {
203 var doc = cm.view.doc, th = textHeight(cm.display);
204 if (cm.options.lineWrapping) {
205 cm.display.wrapper.className += " CodeMirror-wrap";
206 var perLine = cm.display.scroller.clientWidth / charWidth(cm.display) - 3;
207 doc.iter(0, doc.size, function(line) {
208 if (line.height == 0) return;
209 var guess = Math.ceil(line.text.length / perLine) || 1;
210 if (guess != 1) updateLineHeight(line, guess * th);
211 });
212 cm.display.sizer.style.minWidth = "";
213 } else {
214 cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
215 computeMaxLength(cm.view);
216 doc.iter(0, doc.size, function(line) {
217 if (line.height != 0) updateLineHeight(line, th);
218 });
219 }
220 regChange(cm, 0, doc.size);
221 clearCaches(cm);
222 setTimeout(function(){updateScrollbars(cm.display, cm.view.doc.height);}, 100);
223 }
224
225 function keyMapChanged(cm) {
226 var style = keyMap[cm.options.keyMap].style;
227 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
228 (style ? " cm-keymap-" + style : "");
229 }
230
231 function themeChanged(cm) {
232 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
233 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
234 clearCaches(cm);
235 }
236
237 function guttersChanged(cm) {
238 updateGutters(cm);
239 updateDisplay(cm, true);
240 }
241
242 function updateGutters(cm) {
243 var gutters = cm.display.gutters, specs = cm.options.gutters;
244 removeChildren(gutters);
245 for (var i = 0; i < specs.length; ++i) {
246 var gutterClass = specs[i];
247 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
248 if (gutterClass == "CodeMirror-linenumbers") {
249 cm.display.lineGutter = gElt;
250 gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
251 }
252 }
253 gutters.style.display = i ? "" : "none";
254 }
255
256 function lineLength(doc, line) {
257 if (line.height == 0) return 0;
258 var len = line.text.length, merged, cur = line;
259 while (merged = collapsedSpanAtStart(cur)) {
260 var found = merged.find();
261 cur = getLine(doc, found.from.line);
262 len += found.from.ch - found.to.ch;
263 }
264 cur = line;
265 while (merged = collapsedSpanAtEnd(cur)) {
266 var found = merged.find();
267 len -= cur.text.length - found.from.ch;
268 cur = getLine(doc, found.to.line);
269 len += cur.text.length - found.to.ch;
270 }
271 return len;
272 }
273
274 function computeMaxLength(view) {
275 view.maxLine = getLine(view.doc, 0);
276 view.maxLineLength = lineLength(view.doc, view.maxLine);
277 view.maxLineChanged = true;
278 view.doc.iter(1, view.doc.size, function(line) {
279 var len = lineLength(view.doc, line);
280 if (len > view.maxLineLength) {
281 view.maxLineLength = len;
282 view.maxLine = line;
283 }
284 });
285 }
286
287 // Make sure the gutters options contains the element
288 // "CodeMirror-linenumbers" when the lineNumbers option is true.
289 function setGuttersForLineNumbers(options) {
290 var found = false;
291 for (var i = 0; i < options.gutters.length; ++i) {
292 if (options.gutters[i] == "CodeMirror-linenumbers") {
293 if (options.lineNumbers) found = true;
294 else options.gutters.splice(i--, 1);
295 }
296 }
297 if (!found && options.lineNumbers)
298 options.gutters.push("CodeMirror-linenumbers");
299 }
300
301 // SCROLLBARS
302
303 // Re-synchronize the fake scrollbars with the actual size of the
304 // content. Optionally force a scrollTop.
305 function updateScrollbars(d /* display */, docHeight) {
306 var totalHeight = docHeight + 2 * paddingTop(d);
307 d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
308 var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
309 var needsH = d.scroller.scrollWidth > d.scroller.clientWidth;
310 var needsV = scrollHeight > d.scroller.clientHeight;
311 if (needsV) {
312 d.scrollbarV.style.display = "block";
313 d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
314 d.scrollbarV.firstChild.style.height =
315 (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
316 } else d.scrollbarV.style.display = "";
317 if (needsH) {
318 d.scrollbarH.style.display = "block";
319 d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
320 d.scrollbarH.firstChild.style.width =
321 (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
322 } else d.scrollbarH.style.display = "";
323 if (needsH && needsV) {
324 d.scrollbarFiller.style.display = "block";
325 d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
326 } else d.scrollbarFiller.style.display = "";
327
328 if (mac_geLion && scrollbarWidth(d.measure) === 0)
329 d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
330 }
331
332 function visibleLines(display, doc, viewPort) {
333 var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
334 if (typeof viewPort == "number") top = viewPort;
335 else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
336 top = Math.floor(top - paddingTop(display));
337 var bottom = Math.ceil(top + height);
338 return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
339 }
340
341 // LINE NUMBERS
342
343 function alignHorizontally(cm) {
344 var display = cm.display;
345 if (!display.alignWidgets && !display.gutters.firstChild) return;
346 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.view.scrollLeft;
347 var gutterW = display.gutters.offsetWidth, l = comp + "px";
348 for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
349 for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
350 }
351 display.gutters.style.left = (comp + gutterW) + "px";
352 }
353
354 function maybeUpdateLineNumberWidth(cm) {
355 if (!cm.options.lineNumbers) return false;
356 var doc = cm.view.doc, last = lineNumberFor(cm.options, doc.size - 1), display = cm.display;
357 if (last.length != display.lineNumChars) {
358 var test = display.measure.appendChild(elt("div", [elt("div", last)],
359 "CodeMirror-linenumber CodeMirror-gutter-elt"));
360 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
361 display.lineGutter.style.width = "";
362 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
363 display.lineNumWidth = display.lineNumInnerWidth + padding;
364 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
365 display.lineGutter.style.width = display.lineNumWidth + "px";
366 return true;
367 }
368 return false;
369 }
370
371 function lineNumberFor(options, i) {
372 return String(options.lineNumberFormatter(i + options.firstLineNumber));
373 }
374 function compensateForHScroll(display) {
375 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
376 }
377
378 // DISPLAY DRAWING
379
380 function updateDisplay(cm, changes, viewPort) {
381 var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo;
382 var updated = updateDisplayInner(cm, changes, viewPort);
383 if (updated) {
384 signalLater(cm, cm, "update", cm);
385 if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
386 signalLater(cm, cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
387 }
388 updateSelection(cm);
389 updateScrollbars(cm.display, cm.view.doc.height);
390
391 return updated;
392 }
393
394 // Uses a set of changes plus the current scroll position to
395 // determine which DOM updates have to be made, and makes the
396 // updates.
397 function updateDisplayInner(cm, changes, viewPort) {
398 var display = cm.display, doc = cm.view.doc;
399 if (!display.wrapper.clientWidth) {
400 display.showingFrom = display.showingTo = display.viewOffset = 0;
401 return;
402 }
403
404 // Compute the new visible window
405 // If scrollTop is specified, use that to determine which lines
406 // to render instead of the current scrollbar position.
407 var visible = visibleLines(display, doc, viewPort);
408 // Bail out if the visible area is already rendered and nothing changed.
409 if (changes !== true && changes.length == 0 &&
410 visible.from > display.showingFrom && visible.to < display.showingTo)
411 return;
412
413 if (changes && maybeUpdateLineNumberWidth(cm))
414 changes = true;
415 display.sizer.style.marginLeft = display.scrollbarH.style.left = display.gutters.offsetWidth + "px";
416
417 // When merged lines are present, the line that needs to be
418 // redrawn might not be the one that was changed.
419 if (changes !== true && sawCollapsedSpans)
420 for (var i = 0; i < changes.length; ++i) {
421 var ch = changes[i], merged;
422 while (merged = collapsedSpanAtStart(getLine(doc, ch.from))) {
423 var from = merged.find().from.line;
424 if (ch.diff) ch.diff -= ch.from - from;
425 ch.from = from;
426 }
427 }
428
429 // Used to determine which lines need their line numbers updated
430 var positionsChangedFrom = changes === true ? 0 : Infinity;
431 if (cm.options.lineNumbers && changes && changes !== true)
432 for (var i = 0; i < changes.length; ++i)
433 if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }
434
435 var from = Math.max(visible.from - cm.options.viewportMargin, 0);
436 var to = Math.min(doc.size, visible.to + cm.options.viewportMargin);
437 if (display.showingFrom < from && from - display.showingFrom < 20) from = display.showingFrom;
438 if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(doc.size, display.showingTo);
439 if (sawCollapsedSpans) {
440 from = lineNo(visualLine(doc, getLine(doc, from)));
441 while (to < doc.size && lineIsHidden(getLine(doc, to))) ++to;
442 }
443
444 // Create a range of theoretically intact lines, and punch holes
445 // in that using the change info.
446 var intact = changes === true ? [] :
447 computeIntact([{from: display.showingFrom, to: display.showingTo}], changes);
448 // Clip off the parts that won't be visible
449 var intactLines = 0;
450 for (var i = 0; i < intact.length; ++i) {
451 var range = intact[i];
452 if (range.from < from) range.from = from;
453 if (range.to > to) range.to = to;
454 if (range.from >= range.to) intact.splice(i--, 1);
455 else intactLines += range.to - range.from;
456 }
457 if (intactLines == to - from && from == display.showingFrom && to == display.showingTo)
458 return;
459 intact.sort(function(a, b) {return a.from - b.from;});
460
461 if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
462 patchDisplay(cm, from, to, intact, positionsChangedFrom);
463 display.lineDiv.style.display = "";
464
465 var different = from != display.showingFrom || to != display.showingTo ||
466 display.lastSizeC != display.wrapper.clientHeight;
467 // This is just a bogus formula that detects when the editor is
468 // resized or the font size changes.
469 if (different) display.lastSizeC = display.wrapper.clientHeight;
470 display.showingFrom = from; display.showingTo = to;
471 startWorker(cm, 100);
472
473 var prevBottom = display.lineDiv.offsetTop;
474 for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
475 if (ie_lt8) {
476 var bot = node.offsetTop + node.offsetHeight;
477 height = bot - prevBottom;
478 prevBottom = bot;
479 } else {
480 var box = node.getBoundingClientRect();
481 height = box.bottom - box.top;
482 }
483 var diff = node.lineObj.height - height;
484 if (height < 2) height = textHeight(display);
485 if (diff > .001 || diff < -.001)
486 updateLineHeight(node.lineObj, height);
487 }
488 display.viewOffset = heightAtLine(cm, getLine(doc, from));
489 // Position the mover div to align with the current virtual scroll position
490 display.mover.style.top = display.viewOffset + "px";
491 return true;
492 }
493
494 function computeIntact(intact, changes) {
495 for (var i = 0, l = changes.length || 0; i < l; ++i) {
496 var change = changes[i], intact2 = [], diff = change.diff || 0;
497 for (var j = 0, l2 = intact.length; j < l2; ++j) {
498 var range = intact[j];
499 if (change.to <= range.from && change.diff) {
500 intact2.push({from: range.from + diff, to: range.to + diff});
501 } else if (change.to <= range.from || change.from >= range.to) {
502 intact2.push(range);
503 } else {
504 if (change.from > range.from)
505 intact2.push({from: range.from, to: change.from});
506 if (change.to < range.to)
507 intact2.push({from: change.to + diff, to: range.to + diff});
508 }
509 }
510 intact = intact2;
511 }
512 return intact;
513 }
514
515 function getDimensions(cm) {
516 var d = cm.display, left = {}, width = {};
517 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
518 left[cm.options.gutters[i]] = n.offsetLeft;
519 width[cm.options.gutters[i]] = n.offsetWidth;
520 }
521 return {fixedPos: compensateForHScroll(d),
522 gutterTotalWidth: d.gutters.offsetWidth,
523 gutterLeft: left,
524 gutterWidth: width,
525 wrapperWidth: d.wrapper.clientWidth};
526 }
527
528 function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
529 var dims = getDimensions(cm);
530 var display = cm.display, lineNumbers = cm.options.lineNumbers;
531 // IE does bad things to nodes when .innerHTML = "" is used on a parent
532 // we still need widgets and markers intact to add back to the new content later
533 if (!intact.length && !ie && (!webkit || !cm.display.currentWheelTarget))
534 removeChildren(display.lineDiv);
535 var container = display.lineDiv, cur = container.firstChild;
536
537 function rm(node) {
538 var next = node.nextSibling;
539 if (webkit && mac && cm.display.currentWheelTarget == node) {
540 node.style.display = "none";
541 node.lineObj = null;
542 } else {
543 container.removeChild(node);
544 }
545 return next;
546 }
547
548 var nextIntact = intact.shift(), lineNo = from;
549 cm.view.doc.iter(from, to, function(line) {
550 if (nextIntact && nextIntact.to == lineNo) nextIntact = intact.shift();
551 if (lineIsHidden(line)) {
552 if (line.height != 0) updateLineHeight(line, 0);
553 } else if (nextIntact && nextIntact.from <= lineNo && nextIntact.to > lineNo) {
554 // This line is intact. Skip to the actual node. Update its
555 // line number if needed.
556 while (cur.lineObj != line) cur = rm(cur);
557 if (lineNumbers && updateNumbersFrom <= lineNo && cur.lineNumber)
558 setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineNo));
559 cur = cur.nextSibling;
560 } else {
561 // This line needs to be generated.
562 var lineNode = buildLineElement(cm, line, lineNo, dims);
563 container.insertBefore(lineNode, cur);
564 lineNode.lineObj = line;
565 }
566 ++lineNo;
567 });
568 while (cur) cur = rm(cur);
569 }
570
571 function buildLineElement(cm, line, lineNo, dims) {
572 var lineElement = lineContent(cm, line);
573 var markers = line.gutterMarkers, display = cm.display;
574
575 if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass &&
576 (!line.widgets || !line.widgets.length)) return lineElement;
577
578 // Lines with gutter elements or a background class need
579 // to be wrapped again, and have the extra elements added
580 // to the wrapper div
581
582 var wrap = elt("div", null, line.wrapClass, "position: relative");
583 if (cm.options.lineNumbers || markers) {
584 var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " +
585 dims.fixedPos + "px"));
586 wrap.alignable = [gutterWrap];
587 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
588 wrap.lineNumber = gutterWrap.appendChild(
589 elt("div", lineNumberFor(cm.options, lineNo),
590 "CodeMirror-linenumber CodeMirror-gutter-elt",
591 "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
592 + display.lineNumInnerWidth + "px"));
593 if (markers)
594 for (var k = 0; k < cm.options.gutters.length; ++k) {
595 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
596 if (found)
597 gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
598 dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
599 }
600 }
601 // Kludge to make sure the styled element lies behind the selection (by z-index)
602 if (line.bgClass)
603 wrap.appendChild(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground"));
604 wrap.appendChild(lineElement);
605 if (line.widgets)
606 for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
607 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
608 node.widget = widget;
609 if (widget.noHScroll) {
610 (wrap.alignable || (wrap.alignable = [])).push(node);
611 var width = dims.wrapperWidth;
612 node.style.left = dims.fixedPos + "px";
613 if (!widget.coverGutter) {
614 width -= dims.gutterTotalWidth;
615 node.style.paddingLeft = dims.gutterTotalWidth + "px";
616 }
617 node.style.width = width + "px";
618 }
619 if (widget.coverGutter) {
620 node.style.zIndex = 5;
621 node.style.position = "relative";
622 if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
623 }
624 if (widget.above)
625 wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
626 else
627 wrap.appendChild(node);
628 }
629
630 if (ie_lt8) wrap.style.zIndex = 2;
631 return wrap;
632 }
633
634 // SELECTION / CURSOR
635
636 function updateSelection(cm) {
637 var display = cm.display;
638 var collapsed = posEq(cm.view.sel.from, cm.view.sel.to);
639 if (collapsed || cm.options.showCursorWhenSelecting)
640 updateSelectionCursor(cm);
641 else
642 display.cursor.style.display = display.otherCursor.style.display = "none";
643 if (!collapsed)
644 updateSelectionRange(cm);
645 else
646 display.selectionDiv.style.display = "none";
647
648 // Move the hidden textarea near the cursor to prevent scrolling artifacts
649 var headPos = cursorCoords(cm, cm.view.sel.head, "div");
650 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
651 display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
652 headPos.top + lineOff.top - wrapOff.top)) + "px";
653 display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
654 headPos.left + lineOff.left - wrapOff.left)) + "px";
655 }
656
657 // No selection, plain cursor
658 function updateSelectionCursor(cm) {
659 var display = cm.display, pos = cursorCoords(cm, cm.view.sel.head, "div");
660 display.cursor.style.left = pos.left + "px";
661 display.cursor.style.top = pos.top + "px";
662 display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
663 display.cursor.style.display = "";
664
665 if (pos.other) {
666 display.otherCursor.style.display = "";
667 display.otherCursor.style.left = pos.other.left + "px";
668 display.otherCursor.style.top = pos.other.top + "px";
669 display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
670 } else { display.otherCursor.style.display = "none"; }
671 }
672
673 // Highlight selection
674 function updateSelectionRange(cm) {
675 var display = cm.display, doc = cm.view.doc, sel = cm.view.sel;
676 var fragment = document.createDocumentFragment();
677 var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
678
679 function add(left, top, width, bottom) {
680 if (top < 0) top = 0;
681 fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
682 "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
683 "px; height: " + (bottom - top) + "px"));
684 }
685
686 function drawForLine(line, fromArg, toArg, retTop) {
687 var lineObj = getLine(doc, line);
688 var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity;
689 function coords(ch) {
690 return charCoords(cm, {line: line, ch: ch}, "div", lineObj);
691 }
692
693 iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
694 var leftPos = coords(dir == "rtl" ? to - 1 : from);
695 var rightPos = coords(dir == "rtl" ? from : to - 1);
696 var left = leftPos.left, right = rightPos.right;
697 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
698 add(left, leftPos.top, null, leftPos.bottom);
699 left = pl;
700 if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
701 }
702 if (toArg == null && to == lineLen) right = clientWidth;
703 if (fromArg == null && from == 0) left = pl;
704 rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal);
705 if (left < pl + 1) left = pl;
706 add(left, rightPos.top, right - left, rightPos.bottom);
707 });
708 return rVal;
709 }
710
711 if (sel.from.line == sel.to.line) {
712 drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
713 } else {
714 var fromObj = getLine(doc, sel.from.line);
715 var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine;
716 while (merged = collapsedSpanAtEnd(cur)) {
717 var found = merged.find();
718 path.push(found.from.ch, found.to.line, found.to.ch);
719 if (found.to.line == sel.to.line) {
720 path.push(sel.to.ch);
721 singleLine = true;
722 break;
723 }
724 cur = getLine(doc, found.to.line);
725 }
726
727 // This is a single, merged line
728 if (singleLine) {
729 for (var i = 0; i < path.length; i += 3)
730 drawForLine(path[i], path[i+1], path[i+2]);
731 } else {
732 var middleTop, middleBot, toObj = getLine(doc, sel.to.line);
733 if (sel.from.ch)
734 // Draw the first line of selection.
735 middleTop = drawForLine(sel.from.line, sel.from.ch, null, false);
736 else
737 // Simply include it in the middle block.
738 middleTop = heightAtLine(cm, fromObj) - display.viewOffset;
739
740 if (!sel.to.ch)
741 middleBot = heightAtLine(cm, toObj) - display.viewOffset;
742 else
743 middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true);
744
745 if (middleTop < middleBot) add(pl, middleTop, null, middleBot);
746 }
747 }
748
749 removeChildrenAndAdd(display.selectionDiv, fragment);
750 display.selectionDiv.style.display = "";
751 }
752
753 // Cursor-blinking
754 function restartBlink(cm) {
755 var display = cm.display;
756 clearInterval(display.blinker);
757 var on = true;
758 display.cursor.style.visibility = display.otherCursor.style.visibility = "";
759 display.blinker = setInterval(function() {
760 if (!display.cursor.offsetHeight) return;
761 display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
762 }, cm.options.cursorBlinkRate);
763 }
764
765 // HIGHLIGHT WORKER
766
767 function startWorker(cm, time) {
768 if (cm.view.frontier < cm.display.showingTo)
769 cm.view.highlight.set(time, bind(highlightWorker, cm));
770 }
771
772 function highlightWorker(cm) {
773 var view = cm.view, doc = view.doc;
774 if (view.frontier >= cm.display.showingTo) return;
775 var end = +new Date + cm.options.workTime;
776 var state = copyState(view.mode, getStateBefore(cm, view.frontier));
777 var changed = [], prevChange;
778 doc.iter(view.frontier, Math.min(doc.size, cm.display.showingTo + 500), function(line) {
779 if (view.frontier >= cm.display.showingFrom) { // Visible
780 if (highlightLine(cm, line, state) && view.frontier >= cm.display.showingFrom) {
781 if (prevChange && prevChange.end == view.frontier) prevChange.end++;
782 else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1});
783 }
784 line.stateAfter = copyState(view.mode, state);
785 } else {
786 processLine(cm, line, state);
787 line.stateAfter = view.frontier % 5 == 0 ? copyState(view.mode, state) : null;
788 }
789 ++view.frontier;
790 if (+new Date > end) {
791 startWorker(cm, cm.options.workDelay);
792 return true;
793 }
794 });
795 if (changed.length)
796 operation(cm, function() {
797 for (var i = 0; i < changed.length; ++i)
798 regChange(this, changed[i].start, changed[i].end);
799 })();
800 }
801
802 // Finds the line to start with when starting a parse. Tries to
803 // find a line with a stateAfter, so that it can start with a
804 // valid state. If that fails, it returns the line with the
805 // smallest indentation, which tends to need the least context to
806 // parse correctly.
807 function findStartLine(cm, n) {
808 var minindent, minline, doc = cm.view.doc;
809 for (var search = n, lim = n - 100; search > lim; --search) {
810 if (search == 0) return 0;
811 var line = getLine(doc, search-1);
812 if (line.stateAfter) return search;
813 var indented = countColumn(line.text, null, cm.options.tabSize);
814 if (minline == null || minindent > indented) {
815 minline = search - 1;
816 minindent = indented;
817 }
818 }
819 return minline;
820 }
821
822 function getStateBefore(cm, n) {
823 var view = cm.view;
824 var pos = findStartLine(cm, n), state = pos && getLine(view.doc, pos-1).stateAfter;
825 if (!state) state = startState(view.mode);
826 else state = copyState(view.mode, state);
827 view.doc.iter(pos, n, function(line) {
828 processLine(cm, line, state);
829 var save = pos == n - 1 || pos % 5 == 0 || pos >= view.showingFrom && pos < view.showingTo;
830 line.stateAfter = save ? copyState(view.mode, state) : null;
831 ++pos;
832 });
833 return state;
834 }
835
836 // POSITION MEASUREMENT
837
838 function paddingTop(display) {return display.lineSpace.offsetTop;}
839 function paddingLeft(display) {
840 var e = removeChildrenAndAdd(display.measure, elt("pre")).appendChild(elt("span", "x"));
841 return e.offsetLeft;
842 }
843
844 function measureChar(cm, line, ch, data) {
845 var data = data || measureLine(cm, line), dir = -1;
846 for (var pos = ch;; pos += dir) {
847 var r = data[pos];
848 if (r) break;
849 if (dir < 0 && pos == 0) dir = 1;
850 }
851 return {left: pos < ch ? r.right : r.left,
852 right: pos > ch ? r.left : r.right,
853 top: r.top, bottom: r.bottom};
854 }
855
856 function measureLine(cm, line) {
857 // First look in the cache
858 var display = cm.display, cache = cm.display.measureLineCache;
859 for (var i = 0; i < cache.length; ++i) {
860 var memo = cache[i];
861 if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
862 display.scroller.clientWidth == memo.width)
863 return memo.measure;
864 }
865
866 var measure = measureLineInner(cm, line);
867 // Store result in the cache
868 var memo = {text: line.text, width: display.scroller.clientWidth,
869 markedSpans: line.markedSpans, measure: measure};
870 if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo;
871 else cache.push(memo);
872 return measure;
873 }
874
875 function measureLineInner(cm, line) {
876 var display = cm.display, measure = emptyArray(line.text.length);
877 var pre = lineContent(cm, line, measure);
878
879 // IE does not cache element positions of inline elements between
880 // calls to getBoundingClientRect. This makes the loop below,
881 // which gathers the positions of all the characters on the line,
882 // do an amount of layout work quadratic to the number of
883 // characters. When line wrapping is off, we try to improve things
884 // by first subdividing the line into a bunch of inline blocks, so
885 // that IE can reuse most of the layout information from caches
886 // for those blocks. This does interfere with line wrapping, so it
887 // doesn't work when wrapping is on, but in that case the
888 // situation is slightly better, since IE does cache line-wrapping
889 // information and only recomputes per-line.
890 if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
891 var fragment = document.createDocumentFragment();
892 var chunk = 10, n = pre.childNodes.length;
893 for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
894 var wrap = elt("div", null, null, "display: inline-block");
895 for (var j = 0; j < chunk && n; ++j) {
896 wrap.appendChild(pre.firstChild);
897 --n;
898 }
899 fragment.appendChild(wrap);
900 }
901 pre.appendChild(fragment);
902 }
903
904 removeChildrenAndAdd(display.measure, pre);
905
906 var outer = display.lineDiv.getBoundingClientRect();
907 var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
908 for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
909 var size = cur.getBoundingClientRect();
910 var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot);
911 for (var j = 0; j < vranges.length; j += 2) {
912 var rtop = vranges[j], rbot = vranges[j+1];
913 if (rtop > bot || rbot < top) continue;
914 if (rtop <= top && rbot >= bot ||
915 top <= rtop && bot >= rbot ||
916 Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
917 vranges[j] = Math.min(top, rtop);
918 vranges[j+1] = Math.max(bot, rbot);
919 break;
920 }
921 }
922 if (j == vranges.length) vranges.push(top, bot);
923 data[i] = {left: size.left - outer.left, right: size.right - outer.left, top: j};
924 }
925 for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
926 var vr = cur.top;
927 cur.top = vranges[vr]; cur.bottom = vranges[vr+1];
928 }
929 return data;
930 }
931
932 function clearCaches(cm) {
933 cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
934 cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
935 cm.view.maxLineChanged = true;
936 }
937
938 // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
939 function intoCoordSystem(cm, lineObj, rect, context) {
940 if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
941 var size = lineObj.widgets[i].node.offsetHeight;
942 rect.top += size; rect.bottom += size;
943 }
944 if (context == "line") return rect;
945 if (!context) context = "local";
946 var yOff = heightAtLine(cm, lineObj);
947 if (context != "local") yOff -= cm.display.viewOffset;
948 if (context == "page") {
949 var lOff = cm.display.lineSpace.getBoundingClientRect();
950 yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop);
951 var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft);
952 rect.left += xOff; rect.right += xOff;
953 }
954 rect.top += yOff; rect.bottom += yOff;
955 return rect;
956 }
957
958 function charCoords(cm, pos, context, lineObj) {
959 if (!lineObj) lineObj = getLine(cm.view.doc, pos.line);
960 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context);
961 }
962
963 function cursorCoords(cm, pos, context, lineObj, measurement) {
964 lineObj = lineObj || getLine(cm.view.doc, pos.line);
965 if (!measurement) measurement = measureLine(cm, lineObj);
966 function get(ch, right) {
967 var m = measureChar(cm, lineObj, ch, measurement);
968 if (right) m.left = m.right; else m.right = m.left;
969 return intoCoordSystem(cm, lineObj, m, context);
970 }
971 var order = getOrder(lineObj), ch = pos.ch;
972 if (!order) return get(ch);
973 var main, other, linedir = order[0].level;
974 for (var i = 0; i < order.length; ++i) {
975 var part = order[i], rtl = part.level % 2, nb, here;
976 if (part.from < ch && part.to > ch) return get(ch, rtl);
977 var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
978 if (left == ch) {
979 // Opera and IE return bogus offsets and widths for edges
980 // where the direction flips, but only for the side with the
981 // lower level. So we try to use the side with the higher
982 // level.
983 if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true);
984 else here = get(rtl && part.from != part.to ? ch - 1 : ch);
985 if (rtl == linedir) main = here; else other = here;
986 } else if (right == ch) {
987 var nb = i < order.length - 1 && order[i+1];
988 if (!rtl && nb && nb.from == nb.to) continue;
989 if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from);
990 else here = get(rtl ? ch : ch - 1, true);
991 if (rtl == linedir) main = here; else other = here;
992 }
993 }
994 if (linedir && !ch) other = get(order[0].to - 1);
995 if (!main) return other;
996 if (other) main.other = other;
997 return main;
998 }
999
1000 // Coords must be lineSpace-local
1001 function coordsChar(cm, x, y) {
1002 var doc = cm.view.doc;
1003 y += cm.display.viewOffset;
1004 if (y < 0) return {line: 0, ch: 0, outside: true};
1005 var lineNo = lineAtHeight(doc, y);
1006 if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc, doc.size - 1).text.length};
1007 if (x < 0) x = 0;
1008
1009 for (;;) {
1010 var lineObj = getLine(doc, lineNo);
1011 var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1012 var merged = collapsedSpanAtEnd(lineObj);
1013 if (merged && found.ch == lineRight(lineObj))
1014 lineNo = merged.find().to.line;
1015 else
1016 return found;
1017 }
1018 }
1019
1020 function coordsCharInner(cm, lineObj, lineNo, x, y) {
1021 var innerOff = y - heightAtLine(cm, lineObj);
1022 var wrongLine = false, cWidth = cm.display.wrapper.clientWidth;
1023 var measurement = measureLine(cm, lineObj);
1024
1025 function getX(ch) {
1026 var sp = cursorCoords(cm, {line: lineNo, ch: ch}, "line",
1027 lineObj, measurement);
1028 wrongLine = true;
1029 if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth);
1030 else if (innerOff < sp.top) return sp.left + cWidth;
1031 else wrongLine = false;
1032 return sp.left;
1033 }
1034
1035 var bidi = getOrder(lineObj), dist = lineObj.text.length;
1036 var from = lineLeft(lineObj), to = lineRight(lineObj);
1037 var fromX = paddingLeft(cm.display), toX = getX(to);
1038
1039 if (x > toX) return {line: lineNo, ch: to, outside: wrongLine};
1040 // Do a binary search between these bounds.
1041 for (;;) {
1042 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1043 var after = x - fromX < toX - x, ch = after ? from : to;
1044 while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1045 return {line: lineNo, ch: ch, after: after, outside: wrongLine};
1046 }
1047 var step = Math.ceil(dist / 2), middle = from + step;
1048 if (bidi) {
1049 middle = from;
1050 for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1051 }
1052 var middleX = getX(middle);
1053 if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;}
1054 else {from = middle; fromX = middleX; dist = step;}
1055 }
1056 }
1057
1058 var measureText;
1059 function textHeight(display) {
1060 if (display.cachedTextHeight != null) return display.cachedTextHeight;
1061 if (measureText == null) {
1062 measureText = elt("pre");
1063 // Measure a bunch of lines, for browsers that compute
1064 // fractional heights.
1065 for (var i = 0; i < 49; ++i) {
1066 measureText.appendChild(document.createTextNode("x"));
1067 measureText.appendChild(elt("br"));
1068 }
1069 measureText.appendChild(document.createTextNode("x"));
1070 }
1071 removeChildrenAndAdd(display.measure, measureText);
1072 var height = measureText.offsetHeight / 50;
1073 if (height > 3) display.cachedTextHeight = height;
1074 removeChildren(display.measure);
1075 return height || 1;
1076 }
1077
1078 function charWidth(display) {
1079 if (display.cachedCharWidth != null) return display.cachedCharWidth;
1080 var anchor = elt("span", "x");
1081 var pre = elt("pre", [anchor]);
1082 removeChildrenAndAdd(display.measure, pre);
1083 var width = anchor.offsetWidth;
1084 if (width > 2) display.cachedCharWidth = width;
1085 return width || 10;
1086 }
1087
1088 // OPERATIONS
1089
1090 // Operations are used to wrap changes in such a way that each
1091 // change won't have to update the cursor and display (which would
1092 // be awkward, slow, and error-prone), but instead updates are
1093 // batched and then all combined and executed at once.
1094
1095 function startOperation(cm) {
1096 if (cm.curOp) ++cm.curOp.depth;
1097 else cm.curOp = {
1098 // Nested operations delay update until the outermost one
1099 // finishes.
1100 depth: 1,
1101 // An array of ranges of lines that have to be updated. See
1102 // updateDisplay.
1103 changes: [],
1104 delayedCallbacks: [],
1105 updateInput: null,
1106 userSelChange: null,
1107 textChanged: null,
1108 selectionChanged: false,
1109 updateMaxLine: false,
1110 id: ++cm.nextOpId
1111 };
1112 }
1113
1114 function endOperation(cm) {
1115 var op = cm.curOp;
1116 if (--op.depth) return;
1117 cm.curOp = null;
1118 var view = cm.view, display = cm.display;
1119 if (op.updateMaxLine) computeMaxLength(view);
1120 if (view.maxLineChanged && !cm.options.lineWrapping) {
1121 var width = measureChar(cm, view.maxLine, view.maxLine.text.length).right;
1122 display.sizer.style.minWidth = (width + 3 + scrollerCutOff) + "px";
1123 view.maxLineChanged = false;
1124 }
1125 var newScrollPos, updated;
1126 if (op.selectionChanged) {
1127 var coords = cursorCoords(cm, view.sel.head);
1128 newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1129 }
1130 if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null)
1131 updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop);
1132 if (!updated && op.selectionChanged) updateSelection(cm);
1133 if (newScrollPos) scrollCursorIntoView(cm);
1134 if (op.selectionChanged) restartBlink(cm);
1135
1136 if (view.focused && op.updateInput)
1137 resetInput(cm, op.userSelChange);
1138
1139 if (op.textChanged)
1140 signal(cm, "change", cm, op.textChanged);
1141 if (op.selectionChanged) signal(cm, "cursorActivity", cm);
1142 for (var i = 0; i < op.delayedCallbacks.length; ++i) op.delayedCallbacks[i](cm);
1143 }
1144
1145 // Wraps a function in an operation. Returns the wrapped function.
1146 function operation(cm1, f) {
1147 return function() {
1148 var cm = cm1 || this;
1149 startOperation(cm);
1150 try {var result = f.apply(cm, arguments);}
1151 finally {endOperation(cm);}
1152 return result;
1153 };
1154 }
1155
1156 function regChange(cm, from, to, lendiff) {
1157 cm.curOp.changes.push({from: from, to: to, diff: lendiff});
1158 }
1159
1160 // INPUT HANDLING
1161
1162 function slowPoll(cm) {
1163 if (cm.view.pollingFast) return;
1164 cm.display.poll.set(cm.options.pollInterval, function() {
1165 readInput(cm);
1166 if (cm.view.focused) slowPoll(cm);
1167 });
1168 }
1169
1170 function fastPoll(cm) {
1171 var missed = false;
1172 cm.display.pollingFast = true;
1173 function p() {
1174 var changed = readInput(cm);
1175 if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
1176 else {cm.display.pollingFast = false; slowPoll(cm);}
1177 }
1178 cm.display.poll.set(20, p);
1179 }
1180
1181 // prevInput is a hack to work with IME. If we reset the textarea
1182 // on every change, that breaks IME. So we look for changes
1183 // compared to the previous content instead. (Modern browsers have
1184 // events that indicate IME taking place, but these are not widely
1185 // supported or compatible enough yet to rely on.)
1186 function readInput(cm) {
1187 var input = cm.display.input, prevInput = cm.display.prevInput, view = cm.view, sel = view.sel;
1188 if (!view.focused || hasSelection(input) || isReadOnly(cm)) return false;
1189 var text = input.value;
1190 if (text == prevInput && posEq(sel.from, sel.to)) return false;
1191 startOperation(cm);
1192 view.sel.shift = false;
1193 var same = 0, l = Math.min(prevInput.length, text.length);
1194 while (same < l && prevInput[same] == text[same]) ++same;
1195 var from = sel.from, to = sel.to;
1196 if (same < prevInput.length)
1197 from = {line: from.line, ch: from.ch - (prevInput.length - same)};
1198 else if (view.overwrite && posEq(from, to) && !cm.display.pasteIncoming)
1199 to = {line: to.line, ch: Math.min(getLine(cm.view.doc, to.line).text.length, to.ch + (text.length - same))};
1200 var updateInput = cm.curOp.updateInput;
1201 updateDoc(cm, from, to, splitLines(text.slice(same)), "end",
1202 cm.display.pasteIncoming ? "paste" : "input", {from: from, to: to});
1203 cm.curOp.updateInput = updateInput;
1204 if (text.length > 1000) input.value = cm.display.prevInput = "";
1205 else cm.display.prevInput = text;
1206 endOperation(cm);
1207 cm.display.pasteIncoming = false;
1208 return true;
1209 }
1210
1211 function resetInput(cm, user) {
1212 var view = cm.view, minimal, selected;
1213 if (!posEq(view.sel.from, view.sel.to)) {
1214 cm.display.prevInput = "";
1215 minimal = hasCopyEvent &&
1216 (view.sel.to.line - view.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1217 if (minimal) cm.display.input.value = "-";
1218 else cm.display.input.value = selected || cm.getSelection();
1219 if (view.focused) selectInput(cm.display.input);
1220 } else if (user) cm.display.prevInput = cm.display.input.value = "";
1221 cm.display.inaccurateSelection = minimal;
1222 }
1223
1224 function focusInput(cm) {
1225 if (cm.options.readOnly != "nocursor" && (ie || document.activeElement != cm.display.input))
1226 cm.display.input.focus();
1227 }
1228
1229 function isReadOnly(cm) {
1230 return cm.options.readOnly || cm.view.cantEdit;
1231 }
1232
1233 // EVENT HANDLERS
1234
1235 function registerEventHandlers(cm) {
1236 var d = cm.display;
1237 on(d.scroller, "mousedown", operation(cm, onMouseDown));
1238 on(d.scroller, "dblclick", operation(cm, e_preventDefault));
1239 on(d.lineSpace, "selectstart", function(e) {
1240 if (!mouseEventInWidget(d, e)) e_preventDefault(e);
1241 });
1242 // Gecko browsers fire contextmenu *after* opening the menu, at
1243 // which point we can't mess with it anymore. Context menu is
1244 // handled in onMouseDown for Gecko.
1245 if (!gecko) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1246
1247 on(d.scroller, "scroll", function() {
1248 setScrollTop(cm, d.scroller.scrollTop);
1249 setScrollLeft(cm, d.scroller.scrollLeft, true);
1250 signal(cm, "scroll", cm);
1251 });
1252 on(d.scrollbarV, "scroll", function() {
1253 setScrollTop(cm, d.scrollbarV.scrollTop);
1254 });
1255 on(d.scrollbarH, "scroll", function() {
1256 setScrollLeft(cm, d.scrollbarH.scrollLeft);
1257 });
1258
1259 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
1260 on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
1261
1262 function reFocus() { if (cm.view.focused) setTimeout(bind(focusInput, cm), 0); }
1263 on(d.scrollbarH, "mousedown", reFocus);
1264 on(d.scrollbarV, "mousedown", reFocus);
1265 // Prevent wrapper from ever scrolling
1266 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1267 on(window, "resize", function resizeHandler() {
1268 // Might be a text scaling operation, clear size caches.
1269 d.cachedCharWidth = d.cachedTextHeight = null;
1270 clearCaches(cm);
1271 if (d.wrapper.parentNode) updateDisplay(cm, true);
1272 else off(window, "resize", resizeHandler);
1273 });
1274
1275 on(d.input, "keyup", operation(cm, function(e) {
1276 if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1277 if (e_prop(e, "keyCode") == 16) cm.view.sel.shift = false;
1278 }));
1279 on(d.input, "input", bind(fastPoll, cm));
1280 on(d.input, "keydown", operation(cm, onKeyDown));
1281 on(d.input, "keypress", operation(cm, onKeyPress));
1282 on(d.input, "focus", bind(onFocus, cm));
1283 on(d.input, "blur", bind(onBlur, cm));
1284
1285 function drag_(e) {
1286 if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1287 e_stop(e);
1288 }
1289 if (cm.options.dragDrop) {
1290 on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
1291 on(d.scroller, "dragenter", drag_);
1292 on(d.scroller, "dragover", drag_);
1293 on(d.scroller, "drop", operation(cm, onDrop));
1294 }
1295 on(d.scroller, "paste", function(){focusInput(cm); fastPoll(cm);});
1296 on(d.input, "paste", function() {
1297 d.pasteIncoming = true;
1298 fastPoll(cm);
1299 });
1300
1301 function prepareCopy() {
1302 if (d.inaccurateSelection) {
1303 d.prevInput = "";
1304 d.inaccurateSelection = false;
1305 d.input.value = cm.getSelection();
1306 selectInput(d.input);
1307 }
1308 }
1309 on(d.input, "cut", prepareCopy);
1310 on(d.input, "copy", prepareCopy);
1311
1312 // Needed to handle Tab key in KHTML
1313 if (khtml) on(d.sizer, "mouseup", function() {
1314 if (document.activeElement == d.input) d.input.blur();
1315 focusInput(cm);
1316 });
1317 }
1318
1319 function mouseEventInWidget(display, e) {
1320 for (var n = e_target(e); n != display.wrapper; n = n.parentNode)
1321 if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) ||
1322 n.parentNode == display.sizer && n != display.mover) return true;
1323 }
1324
1325 function posFromMouse(cm, e, liberal) {
1326 var display = cm.display;
1327 if (!liberal) {
1328 var target = e_target(e);
1329 if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1330 target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1331 target == display.scrollbarFiller) return null;
1332 }
1333 var x, y, space = display.lineSpace.getBoundingClientRect();
1334 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1335 try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1336 return coordsChar(cm, x - space.left, y - space.top);
1337 }
1338
1339 var lastClick, lastDoubleClick;
1340 function onMouseDown(e) {
1341 var cm = this, display = cm.display, view = cm.view, sel = view.sel, doc = view.doc;
1342 sel.shift = e_prop(e, "shiftKey");
1343
1344 if (mouseEventInWidget(display, e)) {
1345 if (!webkit) {
1346 display.scroller.draggable = false;
1347 setTimeout(function(){display.scroller.draggable = true;}, 100);
1348 }
1349 return;
1350 }
1351 if (clickInGutter(cm, e)) return;
1352 var start = posFromMouse(cm, e);
1353
1354 switch (e_button(e)) {
1355 case 3:
1356 if (gecko) onContextMenu.call(cm, cm, e);
1357 return;
1358 case 2:
1359 if (start) extendSelection(cm, start);
1360 setTimeout(bind(focusInput, cm), 20);
1361 e_preventDefault(e);
1362 return;
1363 }
1364 // For button 1, if it was clicked inside the editor
1365 // (posFromMouse returning non-null), we have to adjust the
1366 // selection.
1367 if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
1368
1369 if (!view.focused) onFocus(cm);
1370
1371 var now = +new Date, type = "single";
1372 if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
1373 type = "triple";
1374 e_preventDefault(e);
1375 setTimeout(bind(focusInput, cm), 20);
1376 selectLine(cm, start.line);
1377 } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
1378 type = "double";
1379 lastDoubleClick = {time: now, pos: start};
1380 e_preventDefault(e);
1381 var word = findWordAt(getLine(doc, start.line).text, start);
1382 extendSelection(cm, word.from, word.to);
1383 } else { lastClick = {time: now, pos: start}; }
1384
1385 var last = start;
1386 if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
1387 !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
1388 var dragEnd = operation(cm, function(e2) {
1389 if (webkit) display.scroller.draggable = false;
1390 view.draggingText = false;
1391 off(document, "mouseup", dragEnd);
1392 off(display.scroller, "drop", dragEnd);
1393 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
1394 e_preventDefault(e2);
1395 extendSelection(cm, start);
1396 focusInput(cm);
1397 }
1398 });
1399 // Let the drag handler handle this.
1400 if (webkit) display.scroller.draggable = true;
1401 view.draggingText = dragEnd;
1402 // IE's approach to draggable
1403 if (display.scroller.dragDrop) display.scroller.dragDrop();
1404 on(document, "mouseup", dragEnd);
1405 on(display.scroller, "drop", dragEnd);
1406 return;
1407 }
1408 e_preventDefault(e);
1409 if (type == "single") extendSelection(cm, clipPos(doc, start));
1410
1411 var startstart = sel.from, startend = sel.to;
1412
1413 function doSelect(cur) {
1414 if (type == "single") {
1415 extendSelection(cm, clipPos(doc, start), cur);
1416 return;
1417 }
1418
1419 startstart = clipPos(doc, startstart);
1420 startend = clipPos(doc, startend);
1421 if (type == "double") {
1422 var word = findWordAt(getLine(doc, cur.line).text, cur);
1423 if (posLess(cur, startstart)) extendSelection(cm, word.from, startend);
1424 else extendSelection(cm, startstart, word.to);
1425 } else if (type == "triple") {
1426 if (posLess(cur, startstart)) extendSelection(cm, startend, clipPos(doc, {line: cur.line, ch: 0}));
1427 else extendSelection(cm, startstart, clipPos(doc, {line: cur.line + 1, ch: 0}));
1428 }
1429 }
1430
1431 var editorSize = display.wrapper.getBoundingClientRect();
1432 // Used to ensure timeout re-tries don't fire when another extend
1433 // happened in the meantime (clearTimeout isn't reliable -- at
1434 // least on Chrome, the timeouts still happen even when cleared,
1435 // if the clear happens after their scheduled firing time).
1436 var counter = 0;
1437
1438 function extend(e) {
1439 var curCount = ++counter;
1440 var cur = posFromMouse(cm, e, true);
1441 if (!cur) return;
1442 if (!posEq(cur, last)) {
1443 if (!view.focused) onFocus(cm);
1444 last = cur;
1445 doSelect(cur);
1446 var visible = visibleLines(display, doc);
1447 if (cur.line >= visible.to || cur.line < visible.from)
1448 setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
1449 } else {
1450 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
1451 if (outside) setTimeout(operation(cm, function() {
1452 if (counter != curCount) return;
1453 display.scroller.scrollTop += outside;
1454 extend(e);
1455 }), 50);
1456 }
1457 }
1458
1459 function done(e) {
1460 counter = Infinity;
1461 var cur = posFromMouse(cm, e);
1462 if (cur) doSelect(cur);
1463 e_preventDefault(e);
1464 focusInput(cm);
1465 off(document, "mousemove", move);
1466 off(document, "mouseup", up);
1467 }
1468
1469 var move = operation(cm, function(e) {
1470 if (!ie && !e_button(e)) done(e);
1471 else extend(e);
1472 });
1473 var up = operation(cm, done);
1474 on(document, "mousemove", move);
1475 on(document, "mouseup", up);
1476 }
1477
1478 function onDrop(e) {
1479 var cm = this;
1480 if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1481 e_preventDefault(e);
1482 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1483 if (!pos || isReadOnly(cm)) return;
1484 if (files && files.length && window.FileReader && window.File) {
1485 var n = files.length, text = Array(n), read = 0;
1486 var loadFile = function(file, i) {
1487 var reader = new FileReader;
1488 reader.onload = function() {
1489 text[i] = reader.result;
1490 if (++read == n) {
1491 pos = clipPos(cm.view.doc, pos);
1492 operation(cm, function() {
1493 var end = replaceRange(cm, text.join(""), pos, pos, "paste");
1494 setSelection(cm, pos, end);
1495 })();
1496 }
1497 };
1498 reader.readAsText(file);
1499 };
1500 for (var i = 0; i < n; ++i) loadFile(files[i], i);
1501 } else {
1502 // Don't do a replace if the drop happened inside of the selected text.
1503 if (cm.view.draggingText && !(posLess(pos, cm.view.sel.from) || posLess(cm.view.sel.to, pos))) {
1504 cm.view.draggingText(e);
1505 if (ie) setTimeout(bind(focusInput, cm), 50);
1506 return;
1507 }
1508 try {
1509 var text = e.dataTransfer.getData("Text");
1510 if (text) {
1511 var curFrom = cm.view.sel.from, curTo = cm.view.sel.to;
1512 setSelection(cm, pos, pos);
1513 if (cm.view.draggingText) replaceRange(cm, "", curFrom, curTo, "paste");
1514 cm.replaceSelection(text, null, "paste");
1515 focusInput(cm);
1516 onFocus(cm);
1517 }
1518 }
1519 catch(e){}
1520 }
1521 }
1522
1523 function clickInGutter(cm, e) {
1524 var display = cm.display;
1525 try { var mX = e.clientX, mY = e.clientY; }
1526 catch(e) { return false; }
1527
1528 if (mX >= Math.floor(display.gutters.getBoundingClientRect().right)) return false;
1529 e_preventDefault(e);
1530 if (!hasHandler(cm, "gutterClick")) return true;
1531
1532 var lineBox = display.lineDiv.getBoundingClientRect();
1533 if (mY > lineBox.bottom) return true;
1534 mY -= lineBox.top - display.viewOffset;
1535
1536 for (var i = 0; i < cm.options.gutters.length; ++i) {
1537 var g = display.gutters.childNodes[i];
1538 if (g && g.getBoundingClientRect().right >= mX) {
1539 var line = lineAtHeight(cm.view.doc, mY);
1540 var gutter = cm.options.gutters[i];
1541 signalLater(cm, cm, "gutterClick", cm, line, gutter, e);
1542 break;
1543 }
1544 }
1545 return true;
1546 }
1547
1548 function onDragStart(cm, e) {
1549 var txt = cm.getSelection();
1550 e.dataTransfer.setData("Text", txt);
1551
1552 // Use dummy image instead of default browsers image.
1553 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1554 if (e.dataTransfer.setDragImage && !safari)
1555 e.dataTransfer.setDragImage(elt('img'), 0, 0);
1556 }
1557
1558 function setScrollTop(cm, val) {
1559 if (Math.abs(cm.view.scrollTop - val) < 2) return;
1560 cm.view.scrollTop = val;
1561 if (!gecko) updateDisplay(cm, [], val);
1562 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1563 if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1564 if (gecko) updateDisplay(cm, []);
1565 }
1566 function setScrollLeft(cm, val, isScroller) {
1567 if (isScroller ? val == cm.view.scrollLeft : Math.abs(cm.view.scrollLeft - val) < 2) return;
1568 cm.view.scrollLeft = val;
1569 alignHorizontally(cm);
1570 if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
1571 if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
1572 }
1573
1574 // Since the delta values reported on mouse wheel events are
1575 // unstandardized between browsers and even browser versions, and
1576 // generally horribly unpredictable, this code starts by measuring
1577 // the scroll effect that the first few mouse wheel events have,
1578 // and, from that, detects the way it can convert deltas to pixel
1579 // offsets afterwards.
1580 //
1581 // The reason we want to know the amount a wheel event will scroll
1582 // is that it gives us a chance to update the display before the
1583 // actual scrolling happens, reducing flickering.
1584
1585 var wheelSamples = 0, wheelDX, wheelDY, wheelStartX, wheelStartY, wheelPixelsPerUnit = null;
1586 // Fill in a browser-detected starting value on browsers where we
1587 // know one. These don't have to be accurate -- the result of them
1588 // being wrong would just be a slight flicker on the first wheel
1589 // scroll (if it is large enough).
1590 if (ie) wheelPixelsPerUnit = -.53;
1591 else if (gecko) wheelPixelsPerUnit = 15;
1592 else if (chrome) wheelPixelsPerUnit = -.7;
1593 else if (safari) wheelPixelsPerUnit = -1/3;
1594
1595 function onScrollWheel(cm, e) {
1596 var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
1597 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
1598 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1599 else if (dy == null) dy = e.wheelDelta;
1600
1601 // Webkit browsers on OS X abort momentum scrolls when the target
1602 // of the scroll event is removed from the scrollable element.
1603 // This hack (see related code in patchDisplay) makes sure the
1604 // element is kept around.
1605 if (dy && mac && webkit) {
1606 for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
1607 if (cur.lineObj) {
1608 cm.display.currentWheelTarget = cur;
1609 break;
1610 }
1611 }
1612 }
1613
1614 var scroll = cm.display.scroller;
1615 // On some browsers, horizontal scrolling will cause redraws to
1616 // happen before the gutter has been realigned, causing it to
1617 // wriggle around in a most unseemly way. When we have an
1618 // estimated pixels/delta value, we just handle horizontal
1619 // scrolling entirely here. It'll be slightly off from native, but
1620 // better than glitching out.
1621 if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
1622 if (dy)
1623 setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
1624 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
1625 e_preventDefault(e);
1626 wheelStartX = null; // Abort measurement, if in progress
1627 return;
1628 }
1629
1630 if (dy && wheelPixelsPerUnit != null) {
1631 var pixels = dy * wheelPixelsPerUnit;
1632 var top = cm.view.scrollTop, bot = top + cm.display.wrapper.clientHeight;
1633 if (pixels < 0) top = Math.max(0, top + pixels - 50);
1634 else bot = Math.min(cm.view.doc.height, bot + pixels + 50);
1635 updateDisplay(cm, [], {top: top, bottom: bot});
1636 }
1637
1638 if (wheelSamples < 20) {
1639 if (wheelStartX == null) {
1640 wheelStartX = scroll.scrollLeft; wheelStartY = scroll.scrollTop;
1641 wheelDX = dx; wheelDY = dy;
1642 setTimeout(function() {
1643 if (wheelStartX == null) return;
1644 var movedX = scroll.scrollLeft - wheelStartX;
1645 var movedY = scroll.scrollTop - wheelStartY;
1646 var sample = (movedY && wheelDY && movedY / wheelDY) ||
1647 (movedX && wheelDX && movedX / wheelDX);
1648 wheelStartX = wheelStartY = null;
1649 if (!sample) return;
1650 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
1651 ++wheelSamples;
1652 }, 200);
1653 } else {
1654 wheelDX += dx; wheelDY += dy;
1655 }
1656 }
1657 }
1658
1659 function doHandleBinding(cm, bound, dropShift) {
1660 if (typeof bound == "string") {
1661 bound = commands[bound];
1662 if (!bound) return false;
1663 }
1664 // Ensure previous input has been read, so that the handler sees a
1665 // consistent view of the document
1666 if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
1667 var view = cm.view, prevShift = view.sel.shift;
1668 try {
1669 if (isReadOnly(cm)) view.suppressEdits = true;
1670 if (dropShift) view.sel.shift = false;
1671 bound(cm);
1672 } catch(e) {
1673 if (e != Pass) throw e;
1674 return false;
1675 } finally {
1676 view.sel.shift = prevShift;
1677 view.suppressEdits = false;
1678 }
1679 return true;
1680 }
1681
1682 function allKeyMaps(cm) {
1683 var maps = cm.view.keyMaps.slice(0);
1684 maps.push(cm.options.keyMap);
1685 if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys);
1686 return maps;
1687 }
1688
1689 var maybeTransition;
1690 function handleKeyBinding(cm, e) {
1691 // Handle auto keymap transitions
1692 var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
1693 clearTimeout(maybeTransition);
1694 if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
1695 if (getKeyMap(cm.options.keyMap) == startMap)
1696 cm.options.keyMap = (next.call ? next.call(null, cm) : next);
1697 }, 50);
1698
1699 var name = keyNames[e_prop(e, "keyCode")], handled = false;
1700 var flipCtrlCmd = mac && (opera || qtwebkit);
1701 if (name == null || e.altGraphKey) return false;
1702 if (e_prop(e, "altKey")) name = "Alt-" + name;
1703 if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name;
1704 if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name;
1705
1706 var stopped = false;
1707 function stop() { stopped = true; }
1708 var keymaps = allKeyMaps(cm);
1709
1710 if (e_prop(e, "shiftKey")) {
1711 handled = lookupKey("Shift-" + name, keymaps,
1712 function(b) {return doHandleBinding(cm, b, true);}, stop)
1713 || lookupKey(name, keymaps, function(b) {
1714 if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b);
1715 }, stop);
1716 } else {
1717 handled = lookupKey(name, keymaps,
1718 function(b) { return doHandleBinding(cm, b); }, stop);
1719 }
1720 if (stopped) handled = false;
1721 if (handled) {
1722 e_preventDefault(e);
1723 restartBlink(cm);
1724 if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
1725 }
1726 return handled;
1727 }
1728
1729 function handleCharBinding(cm, e, ch) {
1730 var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
1731 function(b) { return doHandleBinding(cm, b, true); });
1732 if (handled) {
1733 e_preventDefault(e);
1734 restartBlink(cm);
1735 }
1736 return handled;
1737 }
1738
1739 var lastStoppedKey = null;
1740 function onKeyDown(e) {
1741 var cm = this;
1742 if (!cm.view.focused) onFocus(cm);
1743 if (ie && e.keyCode == 27) { e.returnValue = false; }
1744 if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1745 var code = e_prop(e, "keyCode");
1746 // IE does strange things with escape.
1747 cm.view.sel.shift = code == 16 || e_prop(e, "shiftKey");
1748 // First give onKeyEvent option a chance to handle this.
1749 var handled = handleKeyBinding(cm, e);
1750 if (opera) {
1751 lastStoppedKey = handled ? code : null;
1752 // Opera has no cut event... we try to at least catch the key combo
1753 if (!handled && code == 88 && !hasCopyEvent && e_prop(e, mac ? "metaKey" : "ctrlKey"))
1754 cm.replaceSelection("");
1755 }
1756 }
1757
1758 function onKeyPress(e) {
1759 var cm = this;
1760 if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1761 var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
1762 if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
1763 if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
1764 var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
1765 if (this.options.electricChars && this.view.mode.electricChars &&
1766 this.options.smartIndent && !isReadOnly(this) &&
1767 this.view.mode.electricChars.indexOf(ch) > -1)
1768 setTimeout(operation(cm, function() {indentLine(cm, cm.view.sel.to.line, "smart");}), 75);
1769 if (handleCharBinding(cm, e, ch)) return;
1770 fastPoll(cm);
1771 }
1772
1773 function onFocus(cm) {
1774 if (cm.options.readOnly == "nocursor") return;
1775 if (!cm.view.focused) {
1776 signal(cm, "focus", cm);
1777 cm.view.focused = true;
1778 if (cm.display.scroller.className.search(/\bCodeMirror-focused\b/) == -1)
1779 cm.display.scroller.className += " CodeMirror-focused";
1780 resetInput(cm, true);
1781 }
1782 slowPoll(cm);
1783 restartBlink(cm);
1784 }
1785 function onBlur(cm) {
1786 if (cm.view.focused) {
1787 signal(cm, "blur", cm);
1788 cm.view.focused = false;
1789 cm.display.scroller.className = cm.display.scroller.className.replace(" CodeMirror-focused", "");
1790 }
1791 clearInterval(cm.display.blinker);
1792 setTimeout(function() {if (!cm.view.focused) cm.view.sel.shift = false;}, 150);
1793 }
1794
1795 var detectingSelectAll;
1796 function onContextMenu(cm, e) {
1797 var display = cm.display, sel = cm.view.sel;
1798 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
1799 if (!pos || opera) return; // Opera is difficult.
1800 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1801 operation(cm, setSelection)(cm, pos, pos);
1802
1803 var oldCSS = display.input.style.cssText;
1804 display.inputDiv.style.position = "absolute";
1805 display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1806 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
1807 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1808 focusInput(cm);
1809 resetInput(cm, true);
1810 // Adds "Select all" to context menu in FF
1811 if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
1812
1813 function rehide() {
1814 display.inputDiv.style.position = "relative";
1815 display.input.style.cssText = oldCSS;
1816 if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
1817 slowPoll(cm);
1818
1819 // Try to detect the user choosing select-all
1820 if (display.input.selectionStart != null) {
1821 clearTimeout(detectingSelectAll);
1822 var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0;
1823 display.prevInput = " ";
1824 display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
1825 detectingSelectAll = setTimeout(function poll(){
1826 if (display.prevInput == " " && display.input.selectionStart == 0)
1827 operation(cm, commands.selectAll)(cm);
1828 else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
1829 else resetInput(cm);
1830 }, 200);
1831 }
1832 }
1833
1834 if (gecko) {
1835 e_stop(e);
1836 on(window, "mouseup", function mouseup() {
1837 off(window, "mouseup", mouseup);
1838 setTimeout(rehide, 20);
1839 });
1840 } else {
1841 setTimeout(rehide, 50);
1842 }
1843 }
1844
1845 // UPDATING
1846
1847 // Replace the range from from to to by the strings in newText.
1848 // Afterwards, set the selection to selFrom, selTo.
1849 function updateDoc(cm, from, to, newText, selUpdate, origin) {
1850 // Possibly split or suppress the update based on the presence
1851 // of read-only spans in its range.
1852 var split = sawReadOnlySpans &&
1853 removeReadOnlyRanges(cm.view.doc, from, to);
1854 if (split) {
1855 for (var i = split.length - 1; i >= 1; --i)
1856 updateDocInner(cm, split[i].from, split[i].to, [""], origin);
1857 if (split.length)
1858 return updateDocInner(cm, split[0].from, split[0].to, newText, selUpdate, origin);
1859 } else {
1860 return updateDocInner(cm, from, to, newText, selUpdate, origin);
1861 }
1862 }
1863
1864 function updateDocInner(cm, from, to, newText, selUpdate, origin) {
1865 if (cm.view.suppressEdits) return;
1866
1867 var view = cm.view, doc = view.doc, old = [];
1868 doc.iter(from.line, to.line + 1, function(line) {
1869 old.push(newHL(line.text, line.markedSpans));
1870 });
1871 var startSelFrom = view.sel.from, startSelTo = view.sel.to;
1872 var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText);
1873 var retval = updateDocNoUndo(cm, from, to, lines, selUpdate, origin);
1874 if (view.history) addChange(cm, from.line, newText.length, old, origin,
1875 startSelFrom, startSelTo, view.sel.from, view.sel.to);
1876 return retval;
1877 }
1878
1879 function unredoHelper(cm, type) {
1880 var doc = cm.view.doc, hist = cm.view.history;
1881 var set = (type == "undo" ? hist.done : hist.undone).pop();
1882 if (!set) return;
1883 var anti = {events: [], fromBefore: set.fromAfter, toBefore: set.toAfter,
1884 fromAfter: set.fromBefore, toAfter: set.toBefore};
1885 for (var i = set.events.length - 1; i >= 0; i -= 1) {
1886 hist.dirtyCounter += type == "undo" ? -1 : 1;
1887 var change = set.events[i];
1888 var replaced = [], end = change.start + change.added;
1889 doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); });
1890 anti.events.push({start: change.start, added: change.old.length, old: replaced});
1891 var selPos = i ? null : {from: set.fromBefore, to: set.toBefore};
1892 updateDocNoUndo(cm, {line: change.start, ch: 0}, {line: end - 1, ch: getLine(doc, end-1).text.length},
1893 change.old, selPos, type);
1894 }
1895 (type == "undo" ? hist.undone : hist.done).push(anti);
1896 }
1897
1898 function updateDocNoUndo(cm, from, to, lines, selUpdate, origin) {
1899 var view = cm.view, doc = view.doc, display = cm.display;
1900 if (view.suppressEdits) return;
1901
1902 var nlines = to.line - from.line, firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
1903 var recomputeMaxLength = false, checkWidthStart = from.line;
1904 if (!cm.options.lineWrapping) {
1905 checkWidthStart = lineNo(visualLine(doc, firstLine));
1906 doc.iter(checkWidthStart, to.line + 1, function(line) {
1907 if (lineLength(doc, line) == view.maxLineLength) {
1908 recomputeMaxLength = true;
1909 return true;
1910 }
1911 });
1912 }
1913
1914 var lastHL = lst(lines), th = textHeight(display);
1915
1916 // First adjust the line structure
1917 if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") {
1918 // This is a whole-line replace. Treated specially to make
1919 // sure line objects move the way they are supposed to.
1920 var added = [];
1921 for (var i = 0, e = lines.length - 1; i < e; ++i)
1922 added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
1923 updateLine(cm, lastLine, lastLine.text, hlSpans(lastHL));
1924 if (nlines) doc.remove(from.line, nlines, cm);
1925 if (added.length) doc.insert(from.line, added);
1926 } else if (firstLine == lastLine) {
1927 if (lines.length == 1) {
1928 updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) +
1929 firstLine.text.slice(to.ch), hlSpans(lines[0]));
1930 } else {
1931 for (var added = [], i = 1, e = lines.length - 1; i < e; ++i)
1932 added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
1933 added.push(makeLine(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL), th));
1934 updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
1935 doc.insert(from.line + 1, added);
1936 }
1937 } else if (lines.length == 1) {
1938 updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) +
1939 lastLine.text.slice(to.ch), hlSpans(lines[0]));
1940 doc.remove(from.line + 1, nlines, cm);
1941 } else {
1942 var added = [];
1943 updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
1944 updateLine(cm, lastLine, hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL));
1945 for (var i = 1, e = lines.length - 1; i < e; ++i)
1946 added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
1947 if (nlines > 1) doc.remove(from.line + 1, nlines - 1, cm);
1948 doc.insert(from.line + 1, added);
1949 }
1950
1951 if (cm.options.lineWrapping) {
1952 var perLine = Math.max(5, display.scroller.clientWidth / charWidth(display) - 3);
1953 doc.iter(from.line, from.line + lines.length, function(line) {
1954 if (line.height == 0) return;
1955 var guess = (Math.ceil(line.text.length / perLine) || 1) * th;
1956 if (guess != line.height) updateLineHeight(line, guess);
1957 });
1958 } else {
1959 doc.iter(checkWidthStart, from.line + lines.length, function(line) {
1960 var len = lineLength(doc, line);
1961 if (len > view.maxLineLength) {
1962 view.maxLine = line;
1963 view.maxLineLength = len;
1964 view.maxLineChanged = true;
1965 recomputeMaxLength = false;
1966 }
1967 });
1968 if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
1969 }
1970
1971 // Adjust frontier, schedule worker
1972 view.frontier = Math.min(view.frontier, from.line);
1973 startWorker(cm, 400);
1974
1975 var lendiff = lines.length - nlines - 1;
1976 // Remember that these lines changed, for updating the display
1977 regChange(cm, from.line, to.line + 1, lendiff);
1978 if (hasHandler(cm, "change")) {
1979 // Normalize lines to contain only strings, since that's what
1980 // the change event handler expects
1981 for (var i = 0; i < lines.length; ++i)
1982 if (typeof lines[i] != "string") lines[i] = lines[i].text;
1983 var changeObj = {from: from, to: to, text: lines, origin: origin};
1984 if (cm.curOp.textChanged) {
1985 for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
1986 cur.next = changeObj;
1987 } else cm.curOp.textChanged = changeObj;
1988 }
1989
1990 // Update the selection
1991 var newSelFrom, newSelTo, end = {line: from.line + lines.length - 1,
1992 ch: hlText(lastHL).length + (lines.length == 1 ? from.ch : 0)};
1993 if (selUpdate && typeof selUpdate != "string") {
1994 if (selUpdate.from) { newSelFrom = selUpdate.from; newSelTo = selUpdate.to; }
1995 else newSelFrom = newSelTo = selUpdate;
1996 } else if (selUpdate == "end") {
1997 newSelFrom = newSelTo = end;
1998 } else if (selUpdate == "start") {
1999 newSelFrom = newSelTo = from;
2000 } else if (selUpdate == "around") {
2001 newSelFrom = from; newSelTo = end;
2002 } else {
2003 var adjustPos = function(pos) {
2004 if (posLess(pos, from)) return pos;
2005 if (!posLess(to, pos)) return end;
2006 var line = pos.line + lendiff;
2007 var ch = pos.ch;
2008 if (pos.line == to.line)
2009 ch += hlText(lastHL).length - (to.ch - (to.line == from.line ? from.ch : 0));
2010 return {line: line, ch: ch};
2011 };
2012 newSelFrom = adjustPos(view.sel.from);
2013 newSelTo = adjustPos(view.sel.to);
2014 }
2015 setSelection(cm, newSelFrom, newSelTo, null, true);
2016 return end;
2017 }
2018
2019 function replaceRange(cm, code, from, to, origin) {
2020 if (!to) to = from;
2021 if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
2022 return updateDoc(cm, from, to, splitLines(code), null, origin);
2023 }
2024
2025 // SELECTION
2026
2027 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2028 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2029 function copyPos(x) {return {line: x.line, ch: x.ch};}
2030
2031 function clipLine(doc, n) {return Math.max(0, Math.min(n, doc.size-1));}
2032 function clipPos(doc, pos) {
2033 if (pos.line < 0) return {line: 0, ch: 0};
2034 if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc, doc.size-1).text.length};
2035 var ch = pos.ch, linelen = getLine(doc, pos.line).text.length;
2036 if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
2037 else if (ch < 0) return {line: pos.line, ch: 0};
2038 else return pos;
2039 }
2040 function isLine(doc, l) {return l >= 0 && l < doc.size;}
2041
2042 // If shift is held, this will move the selection anchor. Otherwise,
2043 // it'll set the whole selection.
2044 function extendSelection(cm, pos, other, bias) {
2045 var sel = cm.view.sel;
2046 if (sel.shift || sel.extend) {
2047 var anchor = sel.anchor;
2048 if (other) {
2049 var posBefore = posLess(pos, anchor);
2050 if (posBefore != posLess(other, anchor)) {
2051 anchor = pos;
2052 pos = other;
2053 } else if (posBefore != posLess(pos, other)) {
2054 pos = other;
2055 }
2056 }
2057 setSelection(cm, anchor, pos, bias);
2058 } else {
2059 setSelection(cm, pos, other || pos, bias);
2060 }
2061 cm.curOp.userSelChange = true;
2062 }
2063
2064 // Update the selection. Last two args are only used by
2065 // updateDoc, since they have to be expressed in the line
2066 // numbers before the update.
2067 function setSelection(cm, anchor, head, bias, checkAtomic) {
2068 cm.view.goalColumn = null;
2069 var sel = cm.view.sel;
2070 // Skip over atomic spans.
2071 if (checkAtomic || !posEq(anchor, sel.anchor))
2072 anchor = skipAtomic(cm, anchor, bias, checkAtomic != "push");
2073 if (checkAtomic || !posEq(head, sel.head))
2074 head = skipAtomic(cm, head, bias, checkAtomic != "push");
2075
2076 if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
2077
2078 sel.anchor = anchor; sel.head = head;
2079 var inv = posLess(head, anchor);
2080 sel.from = inv ? head : anchor;
2081 sel.to = inv ? anchor : head;
2082
2083 cm.curOp.updateInput = true;
2084 cm.curOp.selectionChanged = true;
2085 }
2086
2087 function reCheckSelection(cm) {
2088 setSelection(cm, cm.view.sel.from, cm.view.sel.to, null, "push");
2089 }
2090
2091 function skipAtomic(cm, pos, bias, mayClear) {
2092 var doc = cm.view.doc, flipped = false, curPos = pos;
2093 var dir = bias || 1;
2094 cm.view.cantEdit = false;
2095 search: for (;;) {
2096 var line = getLine(doc, curPos.line), toClear;
2097 if (line.markedSpans) {
2098 for (var i = 0; i < line.markedSpans.length; ++i) {
2099 var sp = line.markedSpans[i], m = sp.marker;
2100 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2101 (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2102 if (mayClear && m.clearOnEnter) {
2103 (toClear || (toClear = [])).push(m);
2104 continue;
2105 } else if (!m.atomic) continue;
2106 var newPos = m.find()[dir < 0 ? "from" : "to"];
2107 if (posEq(newPos, curPos)) {
2108 newPos.ch += dir;
2109 if (newPos.ch < 0) {
2110 if (newPos.line) newPos = clipPos(doc, {line: newPos.line - 1});
2111 else newPos = null;
2112 } else if (newPos.ch > line.text.length) {
2113 if (newPos.line < doc.size - 1) newPos = {line: newPos.line + 1, ch: 0};
2114 else newPos = null;
2115 }
2116 if (!newPos) {
2117 if (flipped) {
2118 // Driven in a corner -- no valid cursor position found at all
2119 // -- try again *with* clearing, if we didn't already
2120 if (!mayClear) return skipAtomic(cm, pos, bias, true);
2121 // Otherwise, turn off editing until further notice, and return the start of the doc
2122 cm.view.cantEdit = true;
2123 return {line: 0, ch: 0};
2124 }
2125 flipped = true; newPos = pos; dir = -dir;
2126 }
2127 }
2128 curPos = newPos;
2129 continue search;
2130 }
2131 }
2132 if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear();
2133 }
2134 return curPos;
2135 }
2136 }
2137
2138 // SCROLLING
2139
2140 function scrollCursorIntoView(cm) {
2141 var view = cm.view;
2142 var coords = scrollPosIntoView(cm, view.sel.head);
2143 if (!view.focused) return;
2144 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
2145 if (coords.top + box.top < 0) doScroll = true;
2146 else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2147 if (doScroll != null && !phantom) {
2148 var hidden = display.cursor.style.display == "none";
2149 if (hidden) {
2150 display.cursor.style.display = "";
2151 display.cursor.style.left = coords.left + "px";
2152 display.cursor.style.top = (coords.top - display.viewOffset) + "px";
2153 }
2154 display.cursor.scrollIntoView(doScroll);
2155 if (hidden) display.cursor.style.display = "none";
2156 }
2157 }
2158
2159 function scrollPosIntoView(cm, pos) {
2160 for (;;) {
2161 var changed = false, coords = cursorCoords(cm, pos);
2162 var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
2163 var startTop = cm.view.scrollTop, startLeft = cm.view.scrollLeft;
2164 if (scrollPos.scrollTop != null) {
2165 setScrollTop(cm, scrollPos.scrollTop);
2166 if (Math.abs(cm.view.scrollTop - startTop) > 1) changed = true;
2167 }
2168 if (scrollPos.scrollLeft != null) {
2169 setScrollLeft(cm, scrollPos.scrollLeft);
2170 if (Math.abs(cm.view.scrollLeft - startLeft) > 1) changed = true;
2171 }
2172 if (!changed) return coords;
2173 }
2174 }
2175
2176 function scrollIntoView(cm, x1, y1, x2, y2) {
2177 var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
2178 if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
2179 if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
2180 }
2181
2182 function calculateScrollPos(cm, x1, y1, x2, y2) {
2183 var display = cm.display, pt = paddingTop(display);
2184 y1 += pt; y2 += pt;
2185 var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2186 var docBottom = cm.view.doc.height + 2 * pt;
2187 var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
2188 if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
2189 else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
2190
2191 var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2192 x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
2193 var gutterw = display.gutters.offsetWidth;
2194 var atLeft = x1 < gutterw + 10;
2195 if (x1 < screenleft + gutterw || atLeft) {
2196 if (atLeft) x1 = 0;
2197 result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
2198 } else if (x2 > screenw + screenleft - 3) {
2199 result.scrollLeft = x2 + 10 - screenw;
2200 }
2201 return result;
2202 }
2203
2204 // API UTILITIES
2205
2206 function indentLine(cm, n, how, aggressive) {
2207 var doc = cm.view.doc;
2208 if (!how) how = "add";
2209 if (how == "smart") {
2210 if (!cm.view.mode.indent) how = "prev";
2211 else var state = getStateBefore(cm, n);
2212 }
2213
2214 var tabSize = cm.options.tabSize;
2215 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
2216 var curSpaceString = line.text.match(/^\s*/)[0], indentation;
2217 if (how == "smart") {
2218 indentation = cm.view.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2219 if (indentation == Pass) {
2220 if (!aggressive) return;
2221 how = "prev";
2222 }
2223 }
2224 if (how == "prev") {
2225 if (n) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
2226 else indentation = 0;
2227 }
2228 else if (how == "add") indentation = curSpace + cm.options.indentUnit;
2229 else if (how == "subtract") indentation = curSpace - cm.options.indentUnit;
2230 indentation = Math.max(0, indentation);
2231
2232 var indentString = "", pos = 0;
2233 if (cm.options.indentWithTabs)
2234 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
2235 if (pos < indentation) indentString += spaceStr(indentation - pos);
2236
2237 if (indentString != curSpaceString)
2238 replaceRange(cm, indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}, "input");
2239 line.stateAfter = null;
2240 }
2241
2242 function changeLine(cm, handle, op) {
2243 var no = handle, line = handle, doc = cm.view.doc;
2244 if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
2245 else no = lineNo(handle);
2246 if (no == null) return null;
2247 if (op(line, no)) regChange(cm, no, no + 1);
2248 else return null;
2249 return line;
2250 }
2251
2252 function findPosH(cm, dir, unit, visually) {
2253 var doc = cm.view.doc, end = cm.view.sel.head, line = end.line, ch = end.ch;
2254 var lineObj = getLine(doc, line);
2255 function findNextLine() {
2256 var l = line + dir;
2257 if (l < 0 || l == doc.size) return false;
2258 line = l;
2259 return lineObj = getLine(doc, l);
2260 }
2261 function moveOnce(boundToLine) {
2262 var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
2263 if (next == null) {
2264 if (!boundToLine && findNextLine()) {
2265 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
2266 else ch = dir < 0 ? lineObj.text.length : 0;
2267 } else return false;
2268 } else ch = next;
2269 return true;
2270 }
2271 if (unit == "char") moveOnce();
2272 else if (unit == "column") moveOnce(true);
2273 else if (unit == "word") {
2274 var sawWord = false;
2275 for (;;) {
2276 if (dir < 0) if (!moveOnce()) break;
2277 if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
2278 else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
2279 if (dir > 0) if (!moveOnce()) break;
2280 }
2281 }
2282 return skipAtomic(cm, {line: line, ch: ch}, dir, true);
2283 }
2284
2285 function findWordAt(line, pos) {
2286 var start = pos.ch, end = pos.ch;
2287 if (line) {
2288 if (pos.after === false || end == line.length) --start; else ++end;
2289 var startChar = line.charAt(start);
2290 var check = isWordChar(startChar) ? isWordChar :
2291 /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} :
2292 function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2293 while (start > 0 && check(line.charAt(start - 1))) --start;
2294 while (end < line.length && check(line.charAt(end))) ++end;
2295 }
2296 return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
2297 }
2298
2299 function selectLine(cm, line) {
2300 extendSelection(cm, {line: line, ch: 0}, clipPos(cm.view.doc, {line: line + 1, ch: 0}));
2301 }
2302
2303 // PROTOTYPE
2304
2305 // The publicly visible API. Note that operation(null, f) means
2306 // 'wrap f in an operation, performed on its `this` parameter'
2307
2308 CodeMirror.prototype = {
2309 getValue: function(lineSep) {
2310 var text = [], doc = this.view.doc;
2311 doc.iter(0, doc.size, function(line) { text.push(line.text); });
2312 return text.join(lineSep || "\n");
2313 },
2314
2315 setValue: operation(null, function(code) {
2316 var doc = this.view.doc, top = {line: 0, ch: 0}, lastLen = getLine(doc, doc.size-1).text.length;
2317 updateDocInner(this, top, {line: doc.size - 1, ch: lastLen}, splitLines(code), top, top, "setValue");
2318 }),
2319
2320 getSelection: function(lineSep) { return this.getRange(this.view.sel.from, this.view.sel.to, lineSep); },
2321
2322 replaceSelection: operation(null, function(code, collapse, origin) {
2323 var sel = this.view.sel;
2324 updateDoc(this, sel.from, sel.to, splitLines(code), collapse || "around", origin);
2325 }),
2326
2327 focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2328
2329 setOption: function(option, value) {
2330 var options = this.options, old = options[option];
2331 if (options[option] == value && option != "mode") return;
2332 options[option] = value;
2333 if (optionHandlers.hasOwnProperty(option))
2334 operation(this, optionHandlers[option])(this, value, old);
2335 },
2336
2337 getOption: function(option) {return this.options[option];},
2338
2339 getMode: function() {return this.view.mode;},
2340
2341 addKeyMap: function(map) {
2342 this.view.keyMaps.push(map);
2343 },
2344
2345 removeKeyMap: function(map) {
2346 var maps = this.view.keyMaps;
2347 for (var i = 0; i < maps.length; ++i)
2348 if ((typeof map == "string" ? maps[i].name : maps[i]) == map) {
2349 maps.splice(i, 1);
2350 return true;
2351 }
2352 },
2353
2354 undo: operation(null, function() {unredoHelper(this, "undo");}),
2355 redo: operation(null, function() {unredoHelper(this, "redo");}),
2356
2357 indentLine: operation(null, function(n, dir, aggressive) {
2358 if (typeof dir != "string") {
2359 if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2360 else dir = dir ? "add" : "subtract";
2361 }
2362 if (isLine(this.view.doc, n)) indentLine(this, n, dir, aggressive);
2363 }),
2364
2365 indentSelection: operation(null, function(how) {
2366 var sel = this.view.sel;
2367 if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
2368 var e = sel.to.line - (sel.to.ch ? 0 : 1);
2369 for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
2370 }),
2371
2372 historySize: function() {
2373 var hist = this.view.history;
2374 return {undo: hist.done.length, redo: hist.undone.length};
2375 },
2376
2377 clearHistory: function() {this.view.history = makeHistory();},
2378
2379 markClean: function() {
2380 this.view.history.dirtyCounter = 0;
2381 this.view.history.lastOp = this.view.history.lastOrigin = null;
2382 },
2383
2384 isClean: function () {return this.view.history.dirtyCounter == 0;},
2385
2386 getHistory: function() {
2387 var hist = this.view.history;
2388 function cp(arr) {
2389 for (var i = 0, nw = [], nwelt; i < arr.length; ++i) {
2390 var set = arr[i];
2391 nw.push({events: nwelt = [], fromBefore: set.fromBefore, toBefore: set.toBefore,
2392 fromAfter: set.fromAfter, toAfter: set.toAfter});
2393 for (var j = 0, elt = set.events; j < elt.length; ++j) {
2394 var old = [], cur = elt[j];
2395 nwelt.push({start: cur.start, added: cur.added, old: old});
2396 for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k]));
2397 }
2398 }
2399 return nw;
2400 }
2401 return {done: cp(hist.done), undone: cp(hist.undone)};
2402 },
2403
2404 setHistory: function(histData) {
2405 var hist = this.view.history = makeHistory();
2406 hist.done = histData.done;
2407 hist.undone = histData.undone;
2408 },
2409
2410 // Fetch the parser token for a given character. Useful for hacks
2411 // that want to inspect the mode state (say, for completion).
2412 getTokenAt: function(pos) {
2413 var doc = this.view.doc;
2414 pos = clipPos(doc, pos);
2415 var state = getStateBefore(this, pos.line), mode = this.view.mode;
2416 var line = getLine(doc, pos.line);
2417 var stream = new StringStream(line.text, this.options.tabSize);
2418 while (stream.pos < pos.ch && !stream.eol()) {
2419 stream.start = stream.pos;
2420 var style = mode.token(stream, state);
2421 }
2422 return {start: stream.start,
2423 end: stream.pos,
2424 string: stream.current(),
2425 className: style || null, // Deprecated, use 'type' instead
2426 type: style || null,
2427 state: state};
2428 },
2429
2430 getStateAfter: function(line) {
2431 var doc = this.view.doc;
2432 line = clipLine(doc, line == null ? doc.size - 1: line);
2433 return getStateBefore(this, line + 1);
2434 },
2435
2436 cursorCoords: function(start, mode) {
2437 var pos, sel = this.view.sel;
2438 if (start == null) pos = sel.head;
2439 else if (typeof start == "object") pos = clipPos(this.view.doc, start);
2440 else pos = start ? sel.from : sel.to;
2441 return cursorCoords(this, pos, mode || "page");
2442 },
2443
2444 charCoords: function(pos, mode) {
2445 return charCoords(this, clipPos(this.view.doc, pos), mode || "page");
2446 },
2447
2448 coordsChar: function(coords) {
2449 var off = this.display.lineSpace.getBoundingClientRect();
2450 return coordsChar(this, coords.left - off.left, coords.top - off.top);
2451 },
2452
2453 defaultTextHeight: function() { return textHeight(this.display); },
2454
2455 markText: operation(null, function(from, to, options) {
2456 return markText(this, clipPos(this.view.doc, from), clipPos(this.view.doc, to),
2457 options, "range");
2458 }),
2459
2460 setBookmark: operation(null, function(pos, widget) {
2461 pos = clipPos(this.view.doc, pos);
2462 return markText(this, pos, pos, widget ? {replacedWith: widget} : {}, "bookmark");
2463 }),
2464
2465 findMarksAt: function(pos) {
2466 var doc = this.view.doc;
2467 pos = clipPos(doc, pos);
2468 var markers = [], spans = getLine(doc, pos.line).markedSpans;
2469 if (spans) for (var i = 0; i < spans.length; ++i) {
2470 var span = spans[i];
2471 if ((span.from == null || span.from <= pos.ch) &&
2472 (span.to == null || span.to >= pos.ch))
2473 markers.push(span.marker);
2474 }
2475 return markers;
2476 },
2477
2478 setGutterMarker: operation(null, function(line, gutterID, value) {
2479 return changeLine(this, line, function(line) {
2480 var markers = line.gutterMarkers || (line.gutterMarkers = {});
2481 markers[gutterID] = value;
2482 if (!value && isEmpty(markers)) line.gutterMarkers = null;
2483 return true;
2484 });
2485 }),
2486
2487 clearGutter: operation(null, function(gutterID) {
2488 var i = 0, cm = this, doc = cm.view.doc;
2489 doc.iter(0, doc.size, function(line) {
2490 if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
2491 line.gutterMarkers[gutterID] = null;
2492 regChange(cm, i, i + 1);
2493 if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
2494 }
2495 ++i;
2496 });
2497 }),
2498
2499 addLineClass: operation(null, function(handle, where, cls) {
2500 return changeLine(this, handle, function(line) {
2501 var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2502 if (!line[prop]) line[prop] = cls;
2503 else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false;
2504 else line[prop] += " " + cls;
2505 return true;
2506 });
2507 }),
2508
2509 removeLineClass: operation(null, function(handle, where, cls) {
2510 return changeLine(this, handle, function(line) {
2511 var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2512 var cur = line[prop];
2513 if (!cur) return false;
2514 else if (cls == null) line[prop] = null;
2515 else {
2516 var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), "");
2517 if (upd == cur) return false;
2518 line[prop] = upd || null;
2519 }
2520 return true;
2521 });
2522 }),
2523
2524 addLineWidget: operation(null, function(handle, node, options) {
2525 var widget = options || {};
2526 widget.node = node;
2527 if (widget.noHScroll) this.display.alignWidgets = true;
2528 changeLine(this, handle, function(line) {
2529 (line.widgets || (line.widgets = [])).push(widget);
2530 widget.line = line;
2531 return true;
2532 });
2533 return widget;
2534 }),
2535
2536 removeLineWidget: operation(null, function(widget) {
2537 var ws = widget.line.widgets, no = lineNo(widget.line);
2538 if (no == null) return;
2539 for (var i = 0; i < ws.length; ++i) if (ws[i] == widget) ws.splice(i--, 1);
2540 regChange(this, no, no + 1);
2541 }),
2542
2543 lineInfo: function(line) {
2544 if (typeof line == "number") {
2545 if (!isLine(this.view.doc, line)) return null;
2546 var n = line;
2547 line = getLine(this.view.doc, line);
2548 if (!line) return null;
2549 } else {
2550 var n = lineNo(line);
2551 if (n == null) return null;
2552 }
2553 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
2554 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
2555 widgets: line.widgets};
2556 },
2557
2558 getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
2559
2560 addWidget: function(pos, node, scroll, vert, horiz) {
2561 var display = this.display;
2562 pos = cursorCoords(this, clipPos(this.view.doc, pos));
2563 var top = pos.top, left = pos.left;
2564 node.style.position = "absolute";
2565 display.sizer.appendChild(node);
2566 if (vert == "over") top = pos.top;
2567 else if (vert == "near") {
2568 var vspace = Math.max(display.wrapper.clientHeight, this.view.doc.height),
2569 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
2570 if (pos.bottom + node.offsetHeight > vspace && pos.top > node.offsetHeight)
2571 top = pos.top - node.offsetHeight;
2572 if (left + node.offsetWidth > hspace)
2573 left = hspace - node.offsetWidth;
2574 }
2575 node.style.top = (top + paddingTop(display)) + "px";
2576 node.style.left = node.style.right = "";
2577 if (horiz == "right") {
2578 left = display.sizer.clientWidth - node.offsetWidth;
2579 node.style.right = "0px";
2580 } else {
2581 if (horiz == "left") left = 0;
2582 else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
2583 node.style.left = left + "px";
2584 }
2585 if (scroll)
2586 scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
2587 },
2588
2589 lineCount: function() {return this.view.doc.size;},
2590
2591 clipPos: function(pos) {return clipPos(this.view.doc, pos);},
2592
2593 getCursor: function(start) {
2594 var sel = this.view.sel, pos;
2595 if (start == null || start == "head") pos = sel.head;
2596 else if (start == "anchor") pos = sel.anchor;
2597 else if (start == "end" || start === false) pos = sel.to;
2598 else pos = sel.from;
2599 return copyPos(pos);
2600 },
2601
2602 somethingSelected: function() {return !posEq(this.view.sel.from, this.view.sel.to);},
2603
2604 setCursor: operation(null, function(line, ch, extend) {
2605 var pos = clipPos(this.view.doc, typeof line == "number" ? {line: line, ch: ch || 0} : line);
2606 if (extend) extendSelection(this, pos);
2607 else setSelection(this, pos, pos);
2608 }),
2609
2610 setSelection: operation(null, function(anchor, head) {
2611 var doc = this.view.doc;
2612 setSelection(this, clipPos(doc, anchor), clipPos(doc, head || anchor));
2613 }),
2614
2615 extendSelection: operation(null, function(from, to) {
2616 var doc = this.view.doc;
2617 extendSelection(this, clipPos(doc, from), to && clipPos(doc, to));
2618 }),
2619
2620 setExtending: function(val) {this.view.sel.extend = val;},
2621
2622 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
2623
2624 getLineHandle: function(line) {
2625 var doc = this.view.doc;
2626 if (isLine(doc, line)) return getLine(doc, line);
2627 },
2628
2629 getLineNumber: function(line) {return lineNo(line);},
2630
2631 setLine: operation(null, function(line, text) {
2632 if (isLine(this.view.doc, line))
2633 replaceRange(this, text, {line: line, ch: 0}, {line: line, ch: getLine(this.view.doc, line).text.length});
2634 }),
2635
2636 removeLine: operation(null, function(line) {
2637 if (isLine(this.view.doc, line))
2638 replaceRange(this, "", {line: line, ch: 0}, clipPos(this.view.doc, {line: line+1, ch: 0}));
2639 }),
2640
2641 replaceRange: operation(null, function(code, from, to) {
2642 var doc = this.view.doc;
2643 from = clipPos(doc, from);
2644 to = to ? clipPos(doc, to) : from;
2645 return replaceRange(this, code, from, to);
2646 }),
2647
2648 getRange: function(from, to, lineSep) {
2649 var doc = this.view.doc;
2650 from = clipPos(doc, from); to = clipPos(doc, to);
2651 var l1 = from.line, l2 = to.line;
2652 if (l1 == l2) return getLine(doc, l1).text.slice(from.ch, to.ch);
2653 var code = [getLine(doc, l1).text.slice(from.ch)];
2654 doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
2655 code.push(getLine(doc, l2).text.slice(0, to.ch));
2656 return code.join(lineSep || "\n");
2657 },
2658
2659 triggerOnKeyDown: operation(null, onKeyDown),
2660
2661 execCommand: function(cmd) {return commands[cmd](this);},
2662
2663 // Stuff used by commands, probably not much use to outside code.
2664 moveH: operation(null, function(dir, unit) {
2665 var sel = this.view.sel, pos = dir < 0 ? sel.from : sel.to;
2666 if (sel.shift || sel.extend || posEq(sel.from, sel.to)) pos = findPosH(this, dir, unit, true);
2667 extendSelection(this, pos, pos, dir);
2668 }),
2669
2670 deleteH: operation(null, function(dir, unit) {
2671 var sel = this.view.sel;
2672 if (!posEq(sel.from, sel.to)) replaceRange(this, "", sel.from, sel.to, "delete");
2673 else replaceRange(this, "", sel.from, findPosH(this, dir, unit, false), "delete");
2674 this.curOp.userSelChange = true;
2675 }),
2676
2677 moveV: operation(null, function(dir, unit) {
2678 var view = this.view, doc = view.doc, display = this.display;
2679 var cur = view.sel.head, pos = cursorCoords(this, cur, "div");
2680 var x = pos.left, y;
2681 if (view.goalColumn != null) x = view.goalColumn;
2682 if (unit == "page") {
2683 var pageSize = Math.min(display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2684 y = pos.top + dir * pageSize;
2685 } else if (unit == "line") {
2686 y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2687 }
2688 do {
2689 var target = coordsChar(this, x, y);
2690 y += dir * 5;
2691 } while (target.outside && (dir < 0 ? y > 0 : y < doc.height));
2692
2693 if (unit == "page") display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top;
2694 extendSelection(this, target, target, dir);
2695 view.goalColumn = x;
2696 }),
2697
2698 toggleOverwrite: function() {
2699 if (this.view.overwrite = !this.view.overwrite)
2700 this.display.cursor.className += " CodeMirror-overwrite";
2701 else
2702 this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
2703 },
2704
2705 posFromIndex: function(off) {
2706 var lineNo = 0, ch, doc = this.view.doc;
2707 doc.iter(0, doc.size, function(line) {
2708 var sz = line.text.length + 1;
2709 if (sz > off) { ch = off; return true; }
2710 off -= sz;
2711 ++lineNo;
2712 });
2713 return clipPos(doc, {line: lineNo, ch: ch});
2714 },
2715 indexFromPos: function (coords) {
2716 if (coords.line < 0 || coords.ch < 0) return 0;
2717 var index = coords.ch;
2718 this.view.doc.iter(0, coords.line, function (line) {
2719 index += line.text.length + 1;
2720 });
2721 return index;
2722 },
2723
2724 scrollTo: function(x, y) {
2725 if (x != null) this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = x;
2726 if (y != null) this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = y;
2727 updateDisplay(this, []);
2728 },
2729 getScrollInfo: function() {
2730 var scroller = this.display.scroller, co = scrollerCutOff;
2731 return {left: scroller.scrollLeft, top: scroller.scrollTop,
2732 height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
2733 clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
2734 },
2735
2736 scrollIntoView: function(pos) {
2737 if (typeof pos == "number") pos = {line: pos, ch: 0};
2738 pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head;
2739 scrollPosIntoView(this, pos);
2740 },
2741
2742 setSize: function(width, height) {
2743 function interpret(val) {
2744 return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
2745 }
2746 if (width != null) this.display.wrapper.style.width = interpret(width);
2747 if (height != null) this.display.wrapper.style.height = interpret(height);
2748 this.refresh();
2749 },
2750
2751 on: function(type, f) {on(this, type, f);},
2752 off: function(type, f) {off(this, type, f);},
2753
2754 operation: function(f){return operation(this, f)();},
2755
2756 refresh: function() {
2757 clearCaches(this);
2758 if (this.display.scroller.scrollHeight > this.view.scrollTop)
2759 this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = this.view.scrollTop;
2760 updateDisplay(this, true);
2761 },
2762
2763 getInputField: function(){return this.display.input;},
2764 getWrapperElement: function(){return this.display.wrapper;},
2765 getScrollerElement: function(){return this.display.scroller;},
2766 getGutterElement: function(){return this.display.gutters;}
2767 };
2768
2769 // OPTION DEFAULTS
2770
2771 var optionHandlers = CodeMirror.optionHandlers = {};
2772
2773 // The default configuration options.
2774 var defaults = CodeMirror.defaults = {};
2775
2776 function option(name, deflt, handle, notOnInit) {
2777 CodeMirror.defaults[name] = deflt;
2778 if (handle) optionHandlers[name] =
2779 notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
2780 }
2781
2782 var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
2783
2784 // These two are, on init, called from the constructor because they
2785 // have to be initialized before the editor can start at all.
2786 option("value", "", function(cm, val) {cm.setValue(val);}, true);
2787 option("mode", null, loadMode, true);
2788
2789 option("indentUnit", 2, loadMode, true);
2790 option("indentWithTabs", false);
2791 option("smartIndent", true);
2792 option("tabSize", 4, function(cm) {
2793 loadMode(cm);
2794 clearCaches(cm);
2795 updateDisplay(cm, true);
2796 }, true);
2797 option("electricChars", true);
2798
2799 option("theme", "default", function(cm) {
2800 themeChanged(cm);
2801 guttersChanged(cm);
2802 }, true);
2803 option("keyMap", "default", keyMapChanged);
2804 option("extraKeys", null);
2805
2806 option("onKeyEvent", null);
2807 option("onDragEvent", null);
2808
2809 option("lineWrapping", false, wrappingChanged, true);
2810 option("gutters", [], function(cm) {
2811 setGuttersForLineNumbers(cm.options);
2812 guttersChanged(cm);
2813 }, true);
2814 option("lineNumbers", false, function(cm) {
2815 setGuttersForLineNumbers(cm.options);
2816 guttersChanged(cm);
2817 }, true);
2818 option("firstLineNumber", 1, guttersChanged, true);
2819 option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
2820 option("showCursorWhenSelecting", false, updateSelection, true);
2821
2822 option("readOnly", false, function(cm, val) {
2823 if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
2824 else if (!val) resetInput(cm, true);
2825 });
2826 option("dragDrop", true);
2827
2828 option("cursorBlinkRate", 530);
2829 option("cursorHeight", 1);
2830 option("workTime", 100);
2831 option("workDelay", 100);
2832 option("flattenSpans", true);
2833 option("pollInterval", 100);
2834 option("undoDepth", 40);
2835 option("viewportMargin", 10, function(cm){cm.refresh();}, true);
2836
2837 option("tabindex", null, function(cm, val) {
2838 cm.display.input.tabIndex = val || "";
2839 });
2840 option("autofocus", null);
2841
2842 // MODE DEFINITION AND QUERYING
2843
2844 // Known modes, by name and by MIME
2845 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
2846
2847 CodeMirror.defineMode = function(name, mode) {
2848 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
2849 if (arguments.length > 2) {
2850 mode.dependencies = [];
2851 for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
2852 }
2853 modes[name] = mode;
2854 };
2855
2856 CodeMirror.defineMIME = function(mime, spec) {
2857 mimeModes[mime] = spec;
2858 };
2859
2860 CodeMirror.resolveMode = function(spec) {
2861 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
2862 spec = mimeModes[spec];
2863 else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
2864 return CodeMirror.resolveMode("application/xml");
2865 if (typeof spec == "string") return {name: spec};
2866 else return spec || {name: "null"};
2867 };
2868
2869 CodeMirror.getMode = function(options, spec) {
2870 var spec = CodeMirror.resolveMode(spec);
2871 var mfactory = modes[spec.name];
2872 if (!mfactory) return CodeMirror.getMode(options, "text/plain");
2873 var modeObj = mfactory(options, spec);
2874 if (modeExtensions.hasOwnProperty(spec.name)) {
2875 var exts = modeExtensions[spec.name];
2876 for (var prop in exts) {
2877 if (!exts.hasOwnProperty(prop)) continue;
2878 if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
2879 modeObj[prop] = exts[prop];
2880 }
2881 }
2882 modeObj.name = spec.name;
2883 return modeObj;
2884 };
2885
2886 CodeMirror.defineMode("null", function() {
2887 return {token: function(stream) {stream.skipToEnd();}};
2888 });
2889 CodeMirror.defineMIME("text/plain", "null");
2890
2891 var modeExtensions = CodeMirror.modeExtensions = {};
2892 CodeMirror.extendMode = function(mode, properties) {
2893 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
2894 for (var prop in properties) if (properties.hasOwnProperty(prop))
2895 exts[prop] = properties[prop];
2896 };
2897
2898 // EXTENSIONS
2899
2900 CodeMirror.defineExtension = function(name, func) {
2901 CodeMirror.prototype[name] = func;
2902 };
2903
2904 CodeMirror.defineOption = option;
2905
2906 var initHooks = [];
2907 CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
2908
2909 // MODE STATE HANDLING
2910
2911 // Utility functions for working with state. Exported because modes
2912 // sometimes need to do this.
2913 function copyState(mode, state) {
2914 if (state === true) return state;
2915 if (mode.copyState) return mode.copyState(state);
2916 var nstate = {};
2917 for (var n in state) {
2918 var val = state[n];
2919 if (val instanceof Array) val = val.concat([]);
2920 nstate[n] = val;
2921 }
2922 return nstate;
2923 }
2924 CodeMirror.copyState = copyState;
2925
2926 function startState(mode, a1, a2) {
2927 return mode.startState ? mode.startState(a1, a2) : true;
2928 }
2929 CodeMirror.startState = startState;
2930
2931 CodeMirror.innerMode = function(mode, state) {
2932 while (mode.innerMode) {
2933 var info = mode.innerMode(state);
2934 state = info.state;
2935 mode = info.mode;
2936 }
2937 return info || {mode: mode, state: state};
2938 };
2939
2940 // STANDARD COMMANDS
2941
2942 var commands = CodeMirror.commands = {
2943 selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
2944 killLine: function(cm) {
2945 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
2946 if (!sel && cm.getLine(from.line).length == from.ch)
2947 cm.replaceRange("", from, {line: from.line + 1, ch: 0}, "delete");
2948 else cm.replaceRange("", from, sel ? to : {line: from.line}, "delete");
2949 },
2950 deleteLine: function(cm) {
2951 var l = cm.getCursor().line;
2952 cm.replaceRange("", {line: l, ch: 0}, {line: l}, "delete");
2953 },
2954 undo: function(cm) {cm.undo();},
2955 redo: function(cm) {cm.redo();},
2956 goDocStart: function(cm) {cm.extendSelection({line: 0, ch: 0});},
2957 goDocEnd: function(cm) {cm.extendSelection({line: cm.lineCount() - 1});},
2958 goLineStart: function(cm) {
2959 cm.extendSelection(lineStart(cm, cm.getCursor().line));
2960 },
2961 goLineStartSmart: function(cm) {
2962 var cur = cm.getCursor(), start = lineStart(cm, cur.line);
2963 var line = cm.getLineHandle(start.line);
2964 var order = getOrder(line);
2965 if (!order || order[0].level == 0) {
2966 var firstNonWS = Math.max(0, line.text.search(/\S/));
2967 var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
2968 cm.extendSelection({line: start.line, ch: inWS ? 0 : firstNonWS});
2969 } else cm.extendSelection(start);
2970 },
2971 goLineEnd: function(cm) {
2972 cm.extendSelection(lineEnd(cm, cm.getCursor().line));
2973 },
2974 goLineUp: function(cm) {cm.moveV(-1, "line");},
2975 goLineDown: function(cm) {cm.moveV(1, "line");},
2976 goPageUp: function(cm) {cm.moveV(-1, "page");},
2977 goPageDown: function(cm) {cm.moveV(1, "page");},
2978 goCharLeft: function(cm) {cm.moveH(-1, "char");},
2979 goCharRight: function(cm) {cm.moveH(1, "char");},
2980 goColumnLeft: function(cm) {cm.moveH(-1, "column");},
2981 goColumnRight: function(cm) {cm.moveH(1, "column");},
2982 goWordLeft: function(cm) {cm.moveH(-1, "word");},
2983 goWordRight: function(cm) {cm.moveH(1, "word");},
2984 delCharBefore: function(cm) {cm.deleteH(-1, "char");},
2985 delCharAfter: function(cm) {cm.deleteH(1, "char");},
2986 delWordBefore: function(cm) {cm.deleteH(-1, "word");},
2987 delWordAfter: function(cm) {cm.deleteH(1, "word");},
2988 indentAuto: function(cm) {cm.indentSelection("smart");},
2989 indentMore: function(cm) {cm.indentSelection("add");},
2990 indentLess: function(cm) {cm.indentSelection("subtract");},
2991 insertTab: function(cm) {cm.replaceSelection("\t", "end", "input");},
2992 defaultTab: function(cm) {
2993 if (cm.somethingSelected()) cm.indentSelection("add");
2994 else cm.replaceSelection("\t", "end", "input");
2995 },
2996 transposeChars: function(cm) {
2997 var cur = cm.getCursor(), line = cm.getLine(cur.line);
2998 if (cur.ch > 0 && cur.ch < line.length - 1)
2999 cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
3000 {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
3001 },
3002 newlineAndIndent: function(cm) {
3003 operation(cm, function() {
3004 cm.replaceSelection("\n", "end", "input");
3005 cm.indentLine(cm.getCursor().line, null, true);
3006 })();
3007 },
3008 toggleOverwrite: function(cm) {cm.toggleOverwrite();}
3009 };
3010
3011 // STANDARD KEYMAPS
3012
3013 var keyMap = CodeMirror.keyMap = {};
3014 keyMap.basic = {
3015 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
3016 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
3017 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3018 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
3019 };
3020 // Note that the save and find-related commands aren't defined by
3021 // default. Unknown commands are simply ignored.
3022 keyMap.pcDefault = {
3023 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3024 "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3025 "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3026 "Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3027 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3028 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3029 fallthrough: "basic"
3030 };
3031 keyMap.macDefault = {
3032 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3033 "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
3034 "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore",
3035 "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find",
3036 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3037 "Cmd-[": "indentLess", "Cmd-]": "indentMore",
3038 fallthrough: ["basic", "emacsy"]
3039 };
3040 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
3041 keyMap.emacsy = {
3042 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
3043 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
3044 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
3045 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
3046 };
3047
3048 // KEYMAP DISPATCH
3049
3050 function getKeyMap(val) {
3051 if (typeof val == "string") return keyMap[val];
3052 else return val;
3053 }
3054
3055 function lookupKey(name, maps, handle, stop) {
3056 function lookup(map) {
3057 map = getKeyMap(map);
3058 var found = map[name];
3059 if (found === false) {
3060 if (stop) stop();
3061 return true;
3062 }
3063 if (found != null && handle(found)) return true;
3064 if (map.nofallthrough) {
3065 if (stop) stop();
3066 return true;
3067 }
3068 var fallthrough = map.fallthrough;
3069 if (fallthrough == null) return false;
3070 if (Object.prototype.toString.call(fallthrough) != "[object Array]")
3071 return lookup(fallthrough);
3072 for (var i = 0, e = fallthrough.length; i < e; ++i) {
3073 if (lookup(fallthrough[i])) return true;
3074 }
3075 return false;
3076 }
3077
3078 for (var i = 0; i < maps.length; ++i)
3079 if (lookup(maps[i])) return true;
3080 }
3081 function isModifierKey(event) {
3082 var name = keyNames[e_prop(event, "keyCode")];
3083 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
3084 }
3085 CodeMirror.isModifierKey = isModifierKey;
3086
3087 // FROMTEXTAREA
3088
3089 CodeMirror.fromTextArea = function(textarea, options) {
3090 if (!options) options = {};
3091 options.value = textarea.value;
3092 if (!options.tabindex && textarea.tabindex)
3093 options.tabindex = textarea.tabindex;
3094 // Set autofocus to true if this textarea is focused, or if it has
3095 // autofocus and no other element is focused.
3096 if (options.autofocus == null) {
3097 var hasFocus = document.body;
3098 // doc.activeElement occasionally throws on IE
3099 try { hasFocus = document.activeElement; } catch(e) {}
3100 options.autofocus = hasFocus == textarea ||
3101 textarea.getAttribute("autofocus") != null && hasFocus == document.body;
3102 }
3103
3104 function save() {textarea.value = cm.getValue();}
3105 if (textarea.form) {
3106 // Deplorable hack to make the submit method do the right thing.
3107 on(textarea.form, "submit", save);
3108 var form = textarea.form, realSubmit = form.submit;
3109 try {
3110 form.submit = function wrappedSubmit() {
3111 save();
3112 form.submit = realSubmit;
3113 form.submit();
3114 form.submit = wrappedSubmit;
3115 };
3116 } catch(e) {}
3117 }
3118
3119 textarea.style.display = "none";
3120 var cm = CodeMirror(function(node) {
3121 textarea.parentNode.insertBefore(node, textarea.nextSibling);
3122 }, options);
3123 cm.save = save;
3124 cm.getTextArea = function() { return textarea; };
3125 cm.toTextArea = function() {
3126 save();
3127 textarea.parentNode.removeChild(cm.getWrapperElement());
3128 textarea.style.display = "";
3129 if (textarea.form) {
3130 off(textarea.form, "submit", save);
3131 if (typeof textarea.form.submit == "function")
3132 textarea.form.submit = realSubmit;
3133 }
3134 };
3135 return cm;
3136 };
3137
3138 // STRING STREAM
3139
3140 // Fed to the mode parsers, provides helper functions to make
3141 // parsers more succinct.
3142
3143 // The character stream used by a mode's parser.
3144 function StringStream(string, tabSize) {
3145 this.pos = this.start = 0;
3146 this.string = string;
3147 this.tabSize = tabSize || 8;
3148 }
3149
3150 StringStream.prototype = {
3151 eol: function() {return this.pos >= this.string.length;},
3152 sol: function() {return this.pos == 0;},
3153 peek: function() {return this.string.charAt(this.pos) || undefined;},
3154 next: function() {
3155 if (this.pos < this.string.length)
3156 return this.string.charAt(this.pos++);
3157 },
3158 eat: function(match) {
3159 var ch = this.string.charAt(this.pos);
3160 if (typeof match == "string") var ok = ch == match;
3161 else var ok = ch && (match.test ? match.test(ch) : match(ch));
3162 if (ok) {++this.pos; return ch;}
3163 },
3164 eatWhile: function(match) {
3165 var start = this.pos;
3166 while (this.eat(match)){}
3167 return this.pos > start;
3168 },
3169 eatSpace: function() {
3170 var start = this.pos;
3171 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
3172 return this.pos > start;
3173 },
3174 skipToEnd: function() {this.pos = this.string.length;},
3175 skipTo: function(ch) {
3176 var found = this.string.indexOf(ch, this.pos);
3177 if (found > -1) {this.pos = found; return true;}
3178 },
3179 backUp: function(n) {this.pos -= n;},
3180 column: function() {return countColumn(this.string, this.start, this.tabSize);},
3181 indentation: function() {return countColumn(this.string, null, this.tabSize);},
3182 match: function(pattern, consume, caseInsensitive) {
3183 if (typeof pattern == "string") {
3184 var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
3185 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
3186 if (consume !== false) this.pos += pattern.length;
3187 return true;
3188 }
3189 } else {
3190 var match = this.string.slice(this.pos).match(pattern);
3191 if (match && match.index > 0) return null;
3192 if (match && consume !== false) this.pos += match[0].length;
3193 return match;
3194 }
3195 },
3196 current: function(){return this.string.slice(this.start, this.pos);}
3197 };
3198 CodeMirror.StringStream = StringStream;
3199
3200 // TEXTMARKERS
3201
3202 function TextMarker(cm, type) {
3203 this.lines = [];
3204 this.type = type;
3205 this.cm = cm;
3206 }
3207
3208 TextMarker.prototype.clear = function() {
3209 if (this.explicitlyCleared) return;
3210 startOperation(this.cm);
3211 var min = null, max = null;
3212 for (var i = 0; i < this.lines.length; ++i) {
3213 var line = this.lines[i];
3214 var span = getMarkedSpanFor(line.markedSpans, this);
3215 if (span.to != null) max = lineNo(line);
3216 line.markedSpans = removeMarkedSpan(line.markedSpans, span);
3217 if (span.from != null)
3218 min = lineNo(line);
3219 else if (this.collapsed && !lineIsHidden(line))
3220 updateLineHeight(line, textHeight(this.cm.display));
3221 }
3222 if (min != null) regChange(this.cm, min, max + 1);
3223 this.lines.length = 0;
3224 this.explicitlyCleared = true;
3225 if (this.collapsed && this.cm.view.cantEdit) {
3226 this.cm.view.cantEdit = false;
3227 reCheckSelection(this.cm);
3228 }
3229 endOperation(this.cm);
3230 signalLater(this.cm, this, "clear");
3231 };
3232
3233 TextMarker.prototype.find = function() {
3234 var from, to;
3235 for (var i = 0; i < this.lines.length; ++i) {
3236 var line = this.lines[i];
3237 var span = getMarkedSpanFor(line.markedSpans, this);
3238 if (span.from != null || span.to != null) {
3239 var found = lineNo(line);
3240 if (span.from != null) from = {line: found, ch: span.from};
3241 if (span.to != null) to = {line: found, ch: span.to};
3242 }
3243 }
3244 if (this.type == "bookmark") return from;
3245 return from && {from: from, to: to};
3246 };
3247
3248 function markText(cm, from, to, options, type) {
3249 var doc = cm.view.doc;
3250 var marker = new TextMarker(cm, type);
3251 if (type == "range" && !posLess(from, to)) return marker;
3252 if (options) for (var opt in options) if (options.hasOwnProperty(opt))
3253 marker[opt] = options[opt];
3254 if (marker.replacedWith) {
3255 marker.collapsed = true;
3256 marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
3257 }
3258 if (marker.collapsed) sawCollapsedSpans = true;
3259
3260 var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd;
3261 doc.iter(curLine, to.line + 1, function(line) {
3262 var span = {from: null, to: null, marker: marker};
3263 size += line.text.length;
3264 if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
3265 if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
3266 if (marker.collapsed) {
3267 if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
3268 if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
3269 else updateLineHeight(line, 0);
3270 }
3271 addMarkedSpan(line, span);
3272 if (marker.collapsed && curLine == from.line && lineIsHidden(line))
3273 updateLineHeight(line, 0);
3274 ++curLine;
3275 });
3276
3277 if (marker.readOnly) {
3278 sawReadOnlySpans = true;
3279 if (cm.view.history.done.length || cm.view.history.undone.length)
3280 cm.clearHistory();
3281 }
3282 if (marker.collapsed) {
3283 if (collapsedAtStart != collapsedAtEnd)
3284 throw new Error("Inserting collapsed marker overlapping an existing one");
3285 marker.size = size;
3286 marker.atomic = true;
3287 }
3288 if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed)
3289 regChange(cm, from.line, to.line + 1);
3290 if (marker.atomic) reCheckSelection(cm);
3291 return marker;
3292 }
3293
3294 // TEXTMARKER SPANS
3295
3296 function getMarkedSpanFor(spans, marker) {
3297 if (spans) for (var i = 0; i < spans.length; ++i) {
3298 var span = spans[i];
3299 if (span.marker == marker) return span;
3300 }
3301 }
3302 function removeMarkedSpan(spans, span) {
3303 for (var r, i = 0; i < spans.length; ++i)
3304 if (spans[i] != span) (r || (r = [])).push(spans[i]);
3305 return r;
3306 }
3307 function addMarkedSpan(line, span) {
3308 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
3309 span.marker.lines.push(line);
3310 }
3311
3312 function markedSpansBefore(old, startCh) {
3313 if (old) for (var i = 0, nw; i < old.length; ++i) {
3314 var span = old[i], marker = span.marker;
3315 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
3316 if (startsBefore || marker.type == "bookmark" && span.from == startCh) {
3317 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
3318 (nw || (nw = [])).push({from: span.from,
3319 to: endsAfter ? null : span.to,
3320 marker: marker});
3321 }
3322 }
3323 return nw;
3324 }
3325
3326 function markedSpansAfter(old, startCh, endCh) {
3327 if (old) for (var i = 0, nw; i < old.length; ++i) {
3328 var span = old[i], marker = span.marker;
3329 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
3330 if (endsAfter || marker.type == "bookmark" && span.from == endCh && span.from != startCh) {
3331 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
3332 (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
3333 to: span.to == null ? null : span.to - endCh,
3334 marker: marker});
3335 }
3336 }
3337 return nw;
3338 }
3339
3340 function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) {
3341 if (!oldFirst && !oldLast) return newText;
3342 // Get the spans that 'stick out' on both sides
3343 var first = markedSpansBefore(oldFirst, startCh);
3344 var last = markedSpansAfter(oldLast, startCh, endCh);
3345
3346 // Next, merge those two ends
3347 var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0);
3348 if (first) {
3349 // Fix up .to properties of first
3350 for (var i = 0; i < first.length; ++i) {
3351 var span = first[i];
3352 if (span.to == null) {
3353 var found = getMarkedSpanFor(last, span.marker);
3354 if (!found) span.to = startCh;
3355 else if (sameLine) span.to = found.to == null ? null : found.to + offset;
3356 }
3357 }
3358 }
3359 if (last) {
3360 // Fix up .from in last (or move them into first in case of sameLine)
3361 for (var i = 0; i < last.length; ++i) {
3362 var span = last[i];
3363 if (span.to != null) span.to += offset;
3364 if (span.from == null) {
3365 var found = getMarkedSpanFor(first, span.marker);
3366 if (!found) {
3367 span.from = offset;
3368 if (sameLine) (first || (first = [])).push(span);
3369 }
3370 } else {
3371 span.from += offset;
3372 if (sameLine) (first || (first = [])).push(span);
3373 }
3374 }
3375 }
3376
3377 var newMarkers = [newHL(newText[0], first)];
3378 if (!sameLine) {
3379 // Fill gap with whole-line-spans
3380 var gap = newText.length - 2, gapMarkers;
3381 if (gap > 0 && first)
3382 for (var i = 0; i < first.length; ++i)
3383 if (first[i].to == null)
3384 (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
3385 for (var i = 0; i < gap; ++i)
3386 newMarkers.push(newHL(newText[i+1], gapMarkers));
3387 newMarkers.push(newHL(lst(newText), last));
3388 }
3389 return newMarkers;
3390 }
3391
3392 function removeReadOnlyRanges(doc, from, to) {
3393 var markers = null;
3394 doc.iter(from.line, to.line + 1, function(line) {
3395 if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
3396 var mark = line.markedSpans[i].marker;
3397 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
3398 (markers || (markers = [])).push(mark);
3399 }
3400 });
3401 if (!markers) return null;
3402 var parts = [{from: from, to: to}];
3403 for (var i = 0; i < markers.length; ++i) {
3404 var m = markers[i].find();
3405 for (var j = 0; j < parts.length; ++j) {
3406 var p = parts[j];
3407 if (!posLess(m.from, p.to) || posLess(m.to, p.from)) continue;
3408 var newParts = [j, 1];
3409 if (posLess(p.from, m.from)) newParts.push({from: p.from, to: m.from});
3410 if (posLess(m.to, p.to)) newParts.push({from: m.to, to: p.to});
3411 parts.splice.apply(parts, newParts);
3412 j += newParts.length - 1;
3413 }
3414 }
3415 return parts;
3416 }
3417
3418 function collapsedSpanAt(line, ch) {
3419 var sps = sawCollapsedSpans && line.markedSpans, found;
3420 if (sps) for (var sp, i = 0; i < sps.length; ++i) {
3421 sp = sps[i];
3422 if (!sp.marker.collapsed) continue;
3423 if ((sp.from == null || sp.from < ch) &&
3424 (sp.to == null || sp.to > ch) &&
3425 (!found || found.width < sp.marker.width))
3426 found = sp.marker;
3427 }
3428 return found;
3429 }
3430 function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
3431 function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
3432
3433 function visualLine(doc, line) {
3434 var merged;
3435 while (merged = collapsedSpanAtStart(line))
3436 line = getLine(doc, merged.find().from.line);
3437 return line;
3438 }
3439
3440 function lineIsHidden(line) {
3441 var sps = sawCollapsedSpans && line.markedSpans;
3442 if (sps) for (var sp, i = 0; i < sps.length; ++i) {
3443 sp = sps[i];
3444 if (!sp.marker.collapsed) continue;
3445 if (sp.from == null) return true;
3446 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(line, sp))
3447 return true;
3448 }
3449 }
3450 window.lineIsHidden = lineIsHidden;
3451 function lineIsHiddenInner(line, span) {
3452 if (span.to == null || span.marker.inclusiveRight && span.to == line.text.length)
3453 return true;
3454 for (var sp, i = 0; i < line.markedSpans.length; ++i) {
3455 sp = line.markedSpans[i];
3456 if (sp.marker.collapsed && sp.from == span.to &&
3457 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
3458 lineIsHiddenInner(line, sp)) return true;
3459 }
3460 }
3461
3462 // hl stands for history-line, a data structure that can be either a
3463 // string (line without markers) or a {text, markedSpans} object.
3464 function hlText(val) { return typeof val == "string" ? val : val.text; }
3465 function hlSpans(val) {
3466 if (typeof val == "string") return null;
3467 var spans = val.markedSpans, out = null;
3468 for (var i = 0; i < spans.length; ++i) {
3469 if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
3470 else if (out) out.push(spans[i]);
3471 }
3472 return !out ? spans : out.length ? out : null;
3473 }
3474 function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; }
3475
3476 function detachMarkedSpans(line) {
3477 var spans = line.markedSpans;
3478 if (!spans) return;
3479 for (var i = 0; i < spans.length; ++i) {
3480 var lines = spans[i].marker.lines;
3481 var ix = indexOf(lines, line);
3482 lines.splice(ix, 1);
3483 }
3484 line.markedSpans = null;
3485 }
3486
3487 function attachMarkedSpans(line, spans) {
3488 if (!spans) return;
3489 for (var i = 0; i < spans.length; ++i)
3490 spans[i].marker.lines.push(line);
3491 line.markedSpans = spans;
3492 }
3493
3494 // LINE DATA STRUCTURE
3495
3496 // Line objects. These hold state related to a line, including
3497 // highlighting info (the styles array).
3498 function makeLine(text, markedSpans, height) {
3499 var line = {text: text, height: height};
3500 attachMarkedSpans(line, markedSpans);
3501 if (lineIsHidden(line)) line.height = 0;
3502 return line;
3503 }
3504
3505 function updateLine(cm, line, text, markedSpans) {
3506 line.text = text;
3507 line.stateAfter = line.styles = null;
3508 if (line.order != null) line.order = null;
3509 detachMarkedSpans(line);
3510 attachMarkedSpans(line, markedSpans);
3511 if (lineIsHidden(line)) line.height = 0;
3512 else if (!line.height) line.height = textHeight(cm.display);
3513 signalLater(cm, line, "change");
3514 }
3515
3516 function cleanUpLine(line) {
3517 line.parent = null;
3518 detachMarkedSpans(line);
3519 }
3520
3521 // Run the given mode's parser over a line, update the styles
3522 // array, which contains alternating fragments of text and CSS
3523 // classes.
3524 function highlightLine(cm, line, state) {
3525 var mode = cm.view.mode, flattenSpans = cm.options.flattenSpans;
3526 var changed = !line.styles, pos = 0, curText = "", curStyle = null;
3527 var stream = new StringStream(line.text, cm.options.tabSize), st = line.styles || (line.styles = []);
3528 if (line.text == "" && mode.blankLine) mode.blankLine(state);
3529 while (!stream.eol()) {
3530 var style = mode.token(stream, state), substr = stream.current();
3531 stream.start = stream.pos;
3532 if (!flattenSpans || curStyle != style) {
3533 if (curText) {
3534 changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1];
3535 st[pos++] = curText; st[pos++] = curStyle;
3536 }
3537 curText = substr; curStyle = style;
3538 } else curText = curText + substr;
3539 // Give up when line is ridiculously long
3540 if (stream.pos > 5000) break;
3541 }
3542 if (curText) {
3543 changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1];
3544 st[pos++] = curText; st[pos++] = curStyle;
3545 }
3546 if (stream.pos > 5000) { st[pos++] = line.text.slice(stream.pos); st[pos++] = null; }
3547 if (pos != st.length) { st.length = pos; changed = true; }
3548 return changed;
3549 }
3550
3551 // Lightweight form of highlight -- proceed over this line and
3552 // update state, but don't save a style array.
3553 function processLine(cm, line, state) {
3554 var mode = cm.view.mode;
3555 var stream = new StringStream(line.text, cm.options.tabSize);
3556 if (line.text == "" && mode.blankLine) mode.blankLine(state);
3557 while (!stream.eol() && stream.pos <= 5000) {
3558 mode.token(stream, state);
3559 stream.start = stream.pos;
3560 }
3561 }
3562
3563 var styleToClassCache = {};
3564 function styleToClass(style) {
3565 if (!style) return null;
3566 return styleToClassCache[style] ||
3567 (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
3568 }
3569
3570 function lineContent(cm, realLine, measure) {
3571 var merged, line = realLine, lineBefore, sawBefore, simple = true;
3572 while (merged = collapsedSpanAtStart(line)) {
3573 simple = false;
3574 line = getLine(cm.view.doc, merged.find().from.line);
3575 if (!lineBefore) lineBefore = line;
3576 }
3577
3578 var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure,
3579 measure: null, addedOne: false, cm: cm};
3580 if (line.textClass) builder.pre.className = line.textClass;
3581
3582 do {
3583 if (!line.styles)
3584 highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
3585 builder.measure = line == realLine && measure;
3586 builder.pos = 0;
3587 builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
3588 if (measure && sawBefore && line != realLine && !builder.addedOne) {
3589 measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
3590 builder.addedOne = true;
3591 }
3592 var next = insertLineContent(line, builder);
3593 sawBefore = line == lineBefore;
3594 if (next) {
3595 line = getLine(cm.view.doc, next.to.line);
3596 simple = false;
3597 }
3598 } while (next);
3599
3600 if (measure && !builder.addedOne)
3601 measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
3602 if (!builder.pre.firstChild && !lineIsHidden(realLine))
3603 builder.pre.appendChild(document.createTextNode("\u00a0"));
3604
3605 return builder.pre;
3606 }
3607
3608 var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
3609 function buildToken(builder, text, style, startStyle, endStyle) {
3610 if (!text) return;
3611 if (!tokenSpecialChars.test(text)) {
3612 builder.col += text.length;
3613 var content = document.createTextNode(text);
3614 } else {
3615 var content = document.createDocumentFragment(), pos = 0;
3616 while (true) {
3617 tokenSpecialChars.lastIndex = pos;
3618 var m = tokenSpecialChars.exec(text);
3619 var skipped = m ? m.index - pos : text.length - pos;
3620 if (skipped) {
3621 content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
3622 builder.col += skipped;
3623 }
3624 if (!m) break;
3625 pos += skipped + 1;
3626 if (m[0] == "\t") {
3627 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
3628 content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
3629 builder.col += tabWidth;
3630 } else {
3631 var token = elt("span", "\u2022", "cm-invalidchar");
3632 token.title = "\\u" + m[0].charCodeAt(0).toString(16);
3633 content.appendChild(token);
3634 builder.col += 1;
3635 }
3636 }
3637 }
3638 if (style || startStyle || endStyle || builder.measure) {
3639 var fullStyle = style || "";
3640 if (startStyle) fullStyle += startStyle;
3641 if (endStyle) fullStyle += endStyle;
3642 return builder.pre.appendChild(elt("span", [content], fullStyle));
3643 }
3644 builder.pre.appendChild(content);
3645 }
3646
3647 function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
3648 for (var i = 0; i < text.length; ++i) {
3649 if (i && i < text.length - 1 &&
3650 builder.cm.options.lineWrapping &&
3651 spanAffectsWrapping.test(text.slice(i - 1, i + 1)))
3652 builder.pre.appendChild(elt("wbr"));
3653 builder.measure[builder.pos++] =
3654 buildToken(builder, text.charAt(i), style,
3655 i == 0 && startStyle, i == text.length - 1 && endStyle);
3656 }
3657 if (text.length) builder.addedOne = true;
3658 }
3659
3660 function buildCollapsedSpan(builder, size, widget) {
3661 if (widget) {
3662 if (!builder.display) widget = widget.cloneNode(true);
3663 builder.pre.appendChild(widget);
3664 if (builder.measure && size) {
3665 builder.measure[builder.pos] = widget;
3666 builder.addedOne = true;
3667 }
3668 }
3669 builder.pos += size;
3670 }
3671
3672 // Outputs a number of spans to make up a line, taking highlighting
3673 // and marked text into account.
3674 function insertLineContent(line, builder) {
3675 var st = line.styles, spans = line.markedSpans;
3676 if (!spans) {
3677 for (var i = 0; i < st.length; i+=2)
3678 builder.addToken(builder, st[i], styleToClass(st[i+1]));
3679 return;
3680 }
3681
3682 var allText = line.text, len = allText.length;
3683 var pos = 0, i = 0, text = "", style;
3684 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed;
3685 for (;;) {
3686 if (nextChange == pos) { // Update current marker set
3687 spanStyle = spanEndStyle = spanStartStyle = "";
3688 collapsed = null; nextChange = Infinity;
3689 var foundBookmark = null;
3690 for (var j = 0; j < spans.length; ++j) {
3691 var sp = spans[j], m = sp.marker;
3692 if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
3693 if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
3694 if (m.className) spanStyle += " " + m.className;
3695 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
3696 if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
3697 if (m.collapsed && (!collapsed || collapsed.marker.width < m.width))
3698 collapsed = sp;
3699 } else if (sp.from > pos && nextChange > sp.from) {
3700 nextChange = sp.from;
3701 }
3702 if (m.type == "bookmark" && sp.from == pos && m.replacedWith)
3703 foundBookmark = m.replacedWith;
3704 }
3705 if (collapsed && (collapsed.from || 0) == pos) {
3706 buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
3707 collapsed.from != null && collapsed.marker.replacedWith);
3708 if (collapsed.to == null) return collapsed.marker.find();
3709 }
3710 if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark);
3711 }
3712 if (pos >= len) break;
3713
3714 var upto = Math.min(len, nextChange);
3715 while (true) {
3716 if (text) {
3717 var end = pos + text.length;
3718 if (!collapsed) {
3719 var tokenText = end > upto ? text.slice(0, upto - pos) : text;
3720 builder.addToken(builder, tokenText, style + spanStyle,
3721 spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "");
3722 }
3723 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
3724 pos = end;
3725 spanStartStyle = "";
3726 }
3727 text = st[i++]; style = styleToClass(st[i++]);
3728 }
3729 }
3730 }
3731
3732 // DOCUMENT DATA STRUCTURE
3733
3734 function LeafChunk(lines) {
3735 this.lines = lines;
3736 this.parent = null;
3737 for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
3738 lines[i].parent = this;
3739 height += lines[i].height;
3740 }
3741 this.height = height;
3742 }
3743
3744 LeafChunk.prototype = {
3745 chunkSize: function() { return this.lines.length; },
3746 remove: function(at, n, cm) {
3747 for (var i = at, e = at + n; i < e; ++i) {
3748 var line = this.lines[i];
3749 this.height -= line.height;
3750 cleanUpLine(line);
3751 signalLater(cm, line, "delete");
3752 }
3753 this.lines.splice(at, n);
3754 },
3755 collapse: function(lines) {
3756 lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
3757 },
3758 insertHeight: function(at, lines, height) {
3759 this.height += height;
3760 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
3761 for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
3762 },
3763 iterN: function(at, n, op) {
3764 for (var e = at + n; at < e; ++at)
3765 if (op(this.lines[at])) return true;
3766 }
3767 };
3768
3769 function BranchChunk(children) {
3770 this.children = children;
3771 var size = 0, height = 0;
3772 for (var i = 0, e = children.length; i < e; ++i) {
3773 var ch = children[i];
3774 size += ch.chunkSize(); height += ch.height;
3775 ch.parent = this;
3776 }
3777 this.size = size;
3778 this.height = height;
3779 this.parent = null;
3780 }
3781
3782 BranchChunk.prototype = {
3783 chunkSize: function() { return this.size; },
3784 remove: function(at, n, callbacks) {
3785 this.size -= n;
3786 for (var i = 0; i < this.children.length; ++i) {
3787 var child = this.children[i], sz = child.chunkSize();
3788 if (at < sz) {
3789 var rm = Math.min(n, sz - at), oldHeight = child.height;
3790 child.remove(at, rm, callbacks);
3791 this.height -= oldHeight - child.height;
3792 if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
3793 if ((n -= rm) == 0) break;
3794 at = 0;
3795 } else at -= sz;
3796 }
3797 if (this.size - n < 25) {
3798 var lines = [];
3799 this.collapse(lines);
3800 this.children = [new LeafChunk(lines)];
3801 this.children[0].parent = this;
3802 }
3803 },
3804 collapse: function(lines) {
3805 for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
3806 },
3807 insert: function(at, lines) {
3808 var height = 0;
3809 for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
3810 this.insertHeight(at, lines, height);
3811 },
3812 insertHeight: function(at, lines, height) {
3813 this.size += lines.length;
3814 this.height += height;
3815 for (var i = 0, e = this.children.length; i < e; ++i) {
3816 var child = this.children[i], sz = child.chunkSize();
3817 if (at <= sz) {
3818 child.insertHeight(at, lines, height);
3819 if (child.lines && child.lines.length > 50) {
3820 while (child.lines.length > 50) {
3821 var spilled = child.lines.splice(child.lines.length - 25, 25);
3822 var newleaf = new LeafChunk(spilled);
3823 child.height -= newleaf.height;
3824 this.children.splice(i + 1, 0, newleaf);
3825 newleaf.parent = this;
3826 }
3827 this.maybeSpill();
3828 }
3829 break;
3830 }
3831 at -= sz;
3832 }
3833 },
3834 maybeSpill: function() {
3835 if (this.children.length <= 10) return;
3836 var me = this;
3837 do {
3838 var spilled = me.children.splice(me.children.length - 5, 5);
3839 var sibling = new BranchChunk(spilled);
3840 if (!me.parent) { // Become the parent node
3841 var copy = new BranchChunk(me.children);
3842 copy.parent = me;
3843 me.children = [copy, sibling];
3844 me = copy;
3845 } else {
3846 me.size -= sibling.size;
3847 me.height -= sibling.height;
3848 var myIndex = indexOf(me.parent.children, me);
3849 me.parent.children.splice(myIndex + 1, 0, sibling);
3850 }
3851 sibling.parent = me.parent;
3852 } while (me.children.length > 10);
3853 me.parent.maybeSpill();
3854 },
3855 iter: function(from, to, op) { this.iterN(from, to - from, op); },
3856 iterN: function(at, n, op) {
3857 for (var i = 0, e = this.children.length; i < e; ++i) {
3858 var child = this.children[i], sz = child.chunkSize();
3859 if (at < sz) {
3860 var used = Math.min(n, sz - at);
3861 if (child.iterN(at, used, op)) return true;
3862 if ((n -= used) == 0) break;
3863 at = 0;
3864 } else at -= sz;
3865 }
3866 }
3867 };
3868
3869 // LINE UTILITIES
3870
3871 function getLine(chunk, n) {
3872 while (!chunk.lines) {
3873 for (var i = 0;; ++i) {
3874 var child = chunk.children[i], sz = child.chunkSize();
3875 if (n < sz) { chunk = child; break; }
3876 n -= sz;
3877 }
3878 }
3879 return chunk.lines[n];
3880 }
3881
3882 function updateLineHeight(line, height) {
3883 var diff = height - line.height;
3884 for (var n = line; n; n = n.parent) n.height += diff;
3885 }
3886
3887 function lineNo(line) {
3888 if (line.parent == null) return null;
3889 var cur = line.parent, no = indexOf(cur.lines, line);
3890 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
3891 for (var i = 0;; ++i) {
3892 if (chunk.children[i] == cur) break;
3893 no += chunk.children[i].chunkSize();
3894 }
3895 }
3896 return no;
3897 }
3898
3899 function lineAtHeight(chunk, h) {
3900 var n = 0;
3901 outer: do {
3902 for (var i = 0, e = chunk.children.length; i < e; ++i) {
3903 var child = chunk.children[i], ch = child.height;
3904 if (h < ch) { chunk = child; continue outer; }
3905 h -= ch;
3906 n += child.chunkSize();
3907 }
3908 return n;
3909 } while (!chunk.lines);
3910 for (var i = 0, e = chunk.lines.length; i < e; ++i) {
3911 var line = chunk.lines[i], lh = line.height;
3912 if (h < lh) break;
3913 h -= lh;
3914 }
3915 return n + i;
3916 }
3917
3918 function heightAtLine(cm, lineObj) {
3919 lineObj = visualLine(cm.view.doc, lineObj);
3920
3921 var h = 0, chunk = lineObj.parent;
3922 for (var i = 0; i < chunk.lines.length; ++i) {
3923 var line = chunk.lines[i];
3924 if (line == lineObj) break;
3925 else h += line.height;
3926 }
3927 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
3928 for (var i = 0; i < p.children.length; ++i) {
3929 var cur = p.children[i];
3930 if (cur == chunk) break;
3931 else h += cur.height;
3932 }
3933 }
3934 return h;
3935 }
3936
3937 function getOrder(line) {
3938 var order = line.order;
3939 if (order == null) order = line.order = bidiOrdering(line.text);
3940 return order;
3941 }
3942
3943 // HISTORY
3944
3945 function makeHistory() {
3946 return {
3947 // Arrays of history events. Doing something adds an event to
3948 // done and clears undo. Undoing moves events from done to
3949 // undone, redoing moves them in the other direction.
3950 done: [], undone: [],
3951 // Used to track when changes can be merged into a single undo
3952 // event
3953 lastTime: 0, lastOp: null, lastOrigin: null,
3954 // Used by the isClean() method
3955 dirtyCounter: 0
3956 };
3957 }
3958
3959 function addChange(cm, start, added, old, origin, fromBefore, toBefore, fromAfter, toAfter) {
3960 var history = cm.view.history;
3961 history.undone.length = 0;
3962 var time = +new Date, cur = lst(history.done);
3963
3964 if (cur &&
3965 (history.lastOp == cm.curOp.id ||
3966 history.lastOrigin == origin && (origin == "input" || origin == "delete") &&
3967 history.lastTime > time - 600)) {
3968 // Merge this change into the last event
3969 var last = lst(cur.events);
3970 if (last.start > start + old.length || last.start + last.added < start) {
3971 // Doesn't intersect with last sub-event, add new sub-event
3972 cur.events.push({start: start, added: added, old: old});
3973 } else {
3974 // Patch up the last sub-event
3975 var startBefore = Math.max(0, last.start - start),
3976 endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
3977 for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
3978 for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
3979 if (startBefore) last.start = start;
3980 last.added += added - (old.length - startBefore - endAfter);
3981 }
3982 cur.fromAfter = fromAfter; cur.toAfter = toAfter;
3983 } else {
3984 // Can not be merged, start a new event.
3985 cur = {events: [{start: start, added: added, old: old}],
3986 fromBefore: fromBefore, toBefore: toBefore, fromAfter: fromAfter, toAfter: toAfter};
3987 history.done.push(cur);
3988 while (history.done.length > cm.options.undoDepth)
3989 history.done.shift();
3990 if (history.dirtyCounter < 0)
3991 // The user has made a change after undoing past the last clean state.
3992 // We can never get back to a clean state now until markClean() is called.
3993 history.dirtyCounter = NaN;
3994 else
3995 history.dirtyCounter++;
3996 }
3997 history.lastTime = time;
3998 history.lastOp = cm.curOp.id;
3999 history.lastOrigin = origin;
4000 }
4001
4002 // EVENT OPERATORS
4003
4004 function stopMethod() {e_stop(this);}
4005 // Ensure an event has a stop method.
4006 function addStop(event) {
4007 if (!event.stop) event.stop = stopMethod;
4008 return event;
4009 }
4010
4011 function e_preventDefault(e) {
4012 if (e.preventDefault) e.preventDefault();
4013 else e.returnValue = false;
4014 }
4015 function e_stopPropagation(e) {
4016 if (e.stopPropagation) e.stopPropagation();
4017 else e.cancelBubble = true;
4018 }
4019 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
4020 CodeMirror.e_stop = e_stop;
4021 CodeMirror.e_preventDefault = e_preventDefault;
4022 CodeMirror.e_stopPropagation = e_stopPropagation;
4023
4024 function e_target(e) {return e.target || e.srcElement;}
4025 function e_button(e) {
4026 var b = e.which;
4027 if (b == null) {
4028 if (e.button & 1) b = 1;
4029 else if (e.button & 2) b = 3;
4030 else if (e.button & 4) b = 2;
4031 }
4032 if (mac && e.ctrlKey && b == 1) b = 3;
4033 return b;
4034 }
4035
4036 // Allow 3rd-party code to override event properties by adding an override
4037 // object to an event object.
4038 function e_prop(e, prop) {
4039 var overridden = e.override && e.override.hasOwnProperty(prop);
4040 return overridden ? e.override[prop] : e[prop];
4041 }
4042
4043 // EVENT HANDLING
4044
4045 function on(emitter, type, f) {
4046 if (emitter.addEventListener)
4047 emitter.addEventListener(type, f, false);
4048 else if (emitter.attachEvent)
4049 emitter.attachEvent("on" + type, f);
4050 else {
4051 var map = emitter._handlers || (emitter._handlers = {});
4052 var arr = map[type] || (map[type] = []);
4053 arr.push(f);
4054 }
4055 }
4056
4057 function off(emitter, type, f) {
4058 if (emitter.removeEventListener)
4059 emitter.removeEventListener(type, f, false);
4060 else if (emitter.detachEvent)
4061 emitter.detachEvent("on" + type, f);
4062 else {
4063 var arr = emitter._handlers && emitter._handlers[type];
4064 if (!arr) return;
4065 for (var i = 0; i < arr.length; ++i)
4066 if (arr[i] == f) { arr.splice(i, 1); break; }
4067 }
4068 }
4069
4070 function signal(emitter, type /*, values...*/) {
4071 var arr = emitter._handlers && emitter._handlers[type];
4072 if (!arr) return;
4073 var args = Array.prototype.slice.call(arguments, 2);
4074 for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
4075 }
4076
4077 function signalLater(cm, emitter, type /*, values...*/) {
4078 var arr = emitter._handlers && emitter._handlers[type];
4079 if (!arr) return;
4080 var args = Array.prototype.slice.call(arguments, 3), flist = cm.curOp && cm.curOp.delayedCallbacks;
4081 function bnd(f) {return function(){f.apply(null, args);};};
4082 for (var i = 0; i < arr.length; ++i)
4083 if (flist) flist.push(bnd(arr[i]));
4084 else arr[i].apply(null, args);
4085 }
4086
4087 function hasHandler(emitter, type) {
4088 var arr = emitter._handlers && emitter._handlers[type];
4089 return arr && arr.length > 0;
4090 }
4091
4092 CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
4093
4094 // MISC UTILITIES
4095
4096 // Number of pixels added to scroller and sizer to hide scrollbar
4097 var scrollerCutOff = 30;
4098
4099 // Returned or thrown by various protocols to signal 'I'm not
4100 // handling this'.
4101 var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
4102
4103 function Delayed() {this.id = null;}
4104 Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
4105
4106 // Counts the column offset in a string, taking tabs into account.
4107 // Used mostly to find indentation.
4108 function countColumn(string, end, tabSize) {
4109 if (end == null) {
4110 end = string.search(/[^\s\u00a0]/);
4111 if (end == -1) end = string.length;
4112 }
4113 for (var i = 0, n = 0; i < end; ++i) {
4114 if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
4115 else ++n;
4116 }
4117 return n;
4118 }
4119 CodeMirror.countColumn = countColumn;
4120
4121 var spaceStrs = [""];
4122 function spaceStr(n) {
4123 while (spaceStrs.length <= n)
4124 spaceStrs.push(lst(spaceStrs) + " ");
4125 return spaceStrs[n];
4126 }
4127
4128 function lst(arr) { return arr[arr.length-1]; }
4129
4130 function selectInput(node) {
4131 if (ios) { // Mobile Safari apparently has a bug where select() is broken.
4132 node.selectionStart = 0;
4133 node.selectionEnd = node.value.length;
4134 } else node.select();
4135 }
4136
4137 function indexOf(collection, elt) {
4138 if (collection.indexOf) return collection.indexOf(elt);
4139 for (var i = 0, e = collection.length; i < e; ++i)
4140 if (collection[i] == elt) return i;
4141 return -1;
4142 }
4143
4144 function emptyArray(size) {
4145 for (var a = [], i = 0; i < size; ++i) a.push(undefined);
4146 return a;
4147 }
4148
4149 function bind(f) {
4150 var args = Array.prototype.slice.call(arguments, 1);
4151 return function(){return f.apply(null, args);};
4152 }
4153
4154 var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
4155 function isWordChar(ch) {
4156 return /\w/.test(ch) || ch > "\x80" &&
4157 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
4158 }
4159
4160 function isEmpty(obj) {
4161 var c = 0;
4162 for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) ++c;
4163 return !c;
4164 }
4165
4166 var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/;
4167
4168 // DOM UTILITIES
4169
4170 function elt(tag, content, className, style) {
4171 var e = document.createElement(tag);
4172 if (className) e.className = className;
4173 if (style) e.style.cssText = style;
4174 if (typeof content == "string") setTextContent(e, content);
4175 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
4176 return e;
4177 }
4178
4179 function removeChildren(e) {
4180 e.innerHTML = "";
4181 return e;
4182 }
4183
4184 function removeChildrenAndAdd(parent, e) {
4185 return removeChildren(parent).appendChild(e);
4186 }
4187
4188 function setTextContent(e, str) {
4189 if (ie_lt9) {
4190 e.innerHTML = "";
4191 e.appendChild(document.createTextNode(str));
4192 } else e.textContent = str;
4193 }
4194
4195 // FEATURE DETECTION
4196
4197 // Detect drag-and-drop
4198 var dragAndDrop = function() {
4199 // There is *some* kind of drag-and-drop support in IE6-8, but I
4200 // couldn't get it to work yet.
4201 if (ie_lt9) return false;
4202 var div = elt('div');
4203 return "draggable" in div || "dragDrop" in div;
4204 }();
4205
4206 // For a reason I have yet to figure out, some browsers disallow
4207 // word wrapping between certain characters *only* if a new inline
4208 // element is started between them. This makes it hard to reliably
4209 // measure the position of things, since that requires inserting an
4210 // extra span. This terribly fragile set of regexps matches the
4211 // character combinations that suffer from this phenomenon on the
4212 // various browsers.
4213 var spanAffectsWrapping = /^$/; // Won't match any two-character string
4214 if (gecko) spanAffectsWrapping = /$'/;
4215 else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
4216 else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
4217
4218 var knownScrollbarWidth;
4219 function scrollbarWidth(measure) {
4220 if (knownScrollbarWidth != null) return knownScrollbarWidth;
4221 var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
4222 removeChildrenAndAdd(measure, test);
4223 if (test.offsetWidth)
4224 knownScrollbarWidth = test.offsetHeight - test.clientHeight;
4225 return knownScrollbarWidth || 0;
4226 }
4227
4228 var zwspSupported;
4229 function zeroWidthElement(measure) {
4230 if (zwspSupported == null) {
4231 var test = elt("span", "\u200b");
4232 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
4233 if (measure.firstChild.offsetHeight != 0)
4234 zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;
4235 }
4236 if (zwspSupported) return elt("span", "\u200b");
4237 else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
4238 }
4239
4240 // See if "".split is the broken IE version, if so, provide an
4241 // alternative way to split lines.
4242 var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
4243 var pos = 0, result = [], l = string.length;
4244 while (pos <= l) {
4245 var nl = string.indexOf("\n", pos);
4246 if (nl == -1) nl = string.length;
4247 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
4248 var rt = line.indexOf("\r");
4249 if (rt != -1) {
4250 result.push(line.slice(0, rt));
4251 pos += rt + 1;
4252 } else {
4253 result.push(line);
4254 pos = nl + 1;
4255 }
4256 }
4257 return result;
4258 } : function(string){return string.split(/\r\n?|\n/);};
4259 CodeMirror.splitLines = splitLines;
4260
4261 var hasSelection = window.getSelection ? function(te) {
4262 try { return te.selectionStart != te.selectionEnd; }
4263 catch(e) { return false; }
4264 } : function(te) {
4265 try {var range = te.ownerDocument.selection.createRange();}
4266 catch(e) {}
4267 if (!range || range.parentElement() != te) return false;
4268 return range.compareEndPoints("StartToEnd", range) != 0;
4269 };
4270
4271 var hasCopyEvent = (function() {
4272 var e = elt("div");
4273 if ("oncopy" in e) return true;
4274 e.setAttribute("oncopy", "return;");
4275 return typeof e.oncopy == 'function';
4276 })();
4277
4278 // KEY NAMING
4279
4280 var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
4281 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
4282 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
4283 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
4284 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
4285 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
4286 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
4287 CodeMirror.keyNames = keyNames;
4288 (function() {
4289 // Number keys
4290 for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
4291 // Alphabetic keys
4292 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
4293 // Function keys
4294 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
4295 })();
4296
4297 // BIDI HELPERS
4298
4299 function iterateBidiSections(order, from, to, f) {
4300 if (!order) return f(from, to, "ltr");
4301 for (var i = 0; i < order.length; ++i) {
4302 var part = order[i];
4303 if (part.from < to && part.to > from)
4304 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
4305 }
4306 }
4307
4308 function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
4309 function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
4310
4311 function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
4312 function lineRight(line) {
4313 var order = getOrder(line);
4314 if (!order) return line.text.length;
4315 return bidiRight(lst(order));
4316 }
4317
4318 function lineStart(cm, lineN) {
4319 var line = getLine(cm.view.doc, lineN);
4320 var visual = visualLine(cm.view.doc, line);
4321 if (visual != line) lineN = lineNo(visual);
4322 var order = getOrder(visual);
4323 var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
4324 return {line: lineN, ch: ch};
4325 }
4326 function lineEnd(cm, lineNo) {
4327 var merged, line;
4328 while (merged = collapsedSpanAtEnd(line = getLine(cm.view.doc, lineNo)))
4329 lineNo = merged.find().to.line;
4330 var order = getOrder(line);
4331 var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
4332 return {line: lineNo, ch: ch};
4333 }
4334
4335 // This is somewhat involved. It is needed in order to move
4336 // 'visually' through bi-directional text -- i.e., pressing left
4337 // should make the cursor go left, even when in RTL text. The
4338 // tricky part is the 'jumps', where RTL and LTR text touch each
4339 // other. This often requires the cursor offset to move more than
4340 // one unit, in order to visually move one unit.
4341 function moveVisually(line, start, dir, byUnit) {
4342 var bidi = getOrder(line);
4343 if (!bidi) return moveLogically(line, start, dir, byUnit);
4344 var moveOneUnit = byUnit ? function(pos, dir) {
4345 do pos += dir;
4346 while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
4347 return pos;
4348 } : function(pos, dir) { return pos + dir; };
4349 var linedir = bidi[0].level;
4350 for (var i = 0; i < bidi.length; ++i) {
4351 var part = bidi[i], sticky = part.level % 2 == linedir;
4352 if ((part.from < start && part.to > start) ||
4353 (sticky && (part.from == start || part.to == start))) break;
4354 }
4355 var target = moveOneUnit(start, part.level % 2 ? -dir : dir);
4356
4357 while (target != null) {
4358 if (part.level % 2 == linedir) {
4359 if (target < part.from || target > part.to) {
4360 part = bidi[i += dir];
4361 target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1));
4362 } else break;
4363 } else {
4364 if (target == bidiLeft(part)) {
4365 part = bidi[--i];
4366 target = part && bidiRight(part);
4367 } else if (target == bidiRight(part)) {
4368 part = bidi[++i];
4369 target = part && bidiLeft(part);
4370 } else break;
4371 }
4372 }
4373
4374 return target < 0 || target > line.text.length ? null : target;
4375 }
4376
4377 function moveLogically(line, start, dir, byUnit) {
4378 var target = start + dir;
4379 if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir;
4380 return target < 0 || target > line.text.length ? null : target;
4381 }
4382
4383 // Bidirectional ordering algorithm
4384 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
4385 // that this (partially) implements.
4386
4387 // One-char codes used for character types:
4388 // L (L): Left-to-Right
4389 // R (R): Right-to-Left
4390 // r (AL): Right-to-Left Arabic
4391 // 1 (EN): European Number
4392 // + (ES): European Number Separator
4393 // % (ET): European Number Terminator
4394 // n (AN): Arabic Number
4395 // , (CS): Common Number Separator
4396 // m (NSM): Non-Spacing Mark
4397 // b (BN): Boundary Neutral
4398 // s (B): Paragraph Separator
4399 // t (S): Segment Separator
4400 // w (WS): Whitespace
4401 // N (ON): Other Neutrals
4402
4403 // Returns null if characters are ordered as they appear
4404 // (left-to-right), or an array of sections ({from, to, level}
4405 // objects) in the order in which they occur visually.
4406 var bidiOrdering = (function() {
4407 // Character types for codepoints 0 to 0xff
4408 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
4409 // Character types for codepoints 0x600 to 0x6ff
4410 var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
4411 function charType(code) {
4412 if (code <= 0xff) return lowTypes.charAt(code);
4413 else if (0x590 <= code && code <= 0x5f4) return "R";
4414 else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);
4415 else if (0x700 <= code && code <= 0x8ac) return "r";
4416 else return "L";
4417 }
4418
4419 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
4420 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
4421
4422 return function charOrdering(str) {
4423 if (!bidiRE.test(str)) return false;
4424 var len = str.length, types = [], startType = null;
4425 for (var i = 0, type; i < len; ++i) {
4426 types.push(type = charType(str.charCodeAt(i)));
4427 if (startType == null) {
4428 if (type == "L") startType = "L";
4429 else if (type == "R" || type == "r") startType = "R";
4430 }
4431 }
4432 if (startType == null) startType = "L";
4433
4434 // W1. Examine each non-spacing mark (NSM) in the level run, and
4435 // change the type of the NSM to the type of the previous
4436 // character. If the NSM is at the start of the level run, it will
4437 // get the type of sor.
4438 for (var i = 0, prev = startType; i < len; ++i) {
4439 var type = types[i];
4440 if (type == "m") types[i] = prev;
4441 else prev = type;
4442 }
4443
4444 // W2. Search backwards from each instance of a European number
4445 // until the first strong type (R, L, AL, or sor) is found. If an
4446 // AL is found, change the type of the European number to Arabic
4447 // number.
4448 // W3. Change all ALs to R.
4449 for (var i = 0, cur = startType; i < len; ++i) {
4450 var type = types[i];
4451 if (type == "1" && cur == "r") types[i] = "n";
4452 else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
4453 }
4454
4455 // W4. A single European separator between two European numbers
4456 // changes to a European number. A single common separator between
4457 // two numbers of the same type changes to that type.
4458 for (var i = 1, prev = types[0]; i < len - 1; ++i) {
4459 var type = types[i];
4460 if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
4461 else if (type == "," && prev == types[i+1] &&
4462 (prev == "1" || prev == "n")) types[i] = prev;
4463 prev = type;
4464 }
4465
4466 // W5. A sequence of European terminators adjacent to European
4467 // numbers changes to all European numbers.
4468 // W6. Otherwise, separators and terminators change to Other
4469 // Neutral.
4470 for (var i = 0; i < len; ++i) {
4471 var type = types[i];
4472 if (type == ",") types[i] = "N";
4473 else if (type == "%") {
4474 for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
4475 var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N";
4476 for (var j = i; j < end; ++j) types[j] = replace;
4477 i = end - 1;
4478 }
4479 }
4480
4481 // W7. Search backwards from each instance of a European number
4482 // until the first strong type (R, L, or sor) is found. If an L is
4483 // found, then change the type of the European number to L.
4484 for (var i = 0, cur = startType; i < len; ++i) {
4485 var type = types[i];
4486 if (cur == "L" && type == "1") types[i] = "L";
4487 else if (isStrong.test(type)) cur = type;
4488 }
4489
4490 // N1. A sequence of neutrals takes the direction of the
4491 // surrounding strong text if the text on both sides has the same
4492 // direction. European and Arabic numbers act as if they were R in
4493 // terms of their influence on neutrals. Start-of-level-run (sor)
4494 // and end-of-level-run (eor) are used at level run boundaries.
4495 // N2. Any remaining neutrals take the embedding direction.
4496 for (var i = 0; i < len; ++i) {
4497 if (isNeutral.test(types[i])) {
4498 for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
4499 var before = (i ? types[i-1] : startType) == "L";
4500 var after = (end < len - 1 ? types[end] : startType) == "L";
4501 var replace = before || after ? "L" : "R";
4502 for (var j = i; j < end; ++j) types[j] = replace;
4503 i = end - 1;
4504 }
4505 }
4506
4507 // Here we depart from the documented algorithm, in order to avoid
4508 // building up an actual levels array. Since there are only three
4509 // levels (0, 1, 2) in an implementation that doesn't take
4510 // explicit embedding into account, we can build up the order on
4511 // the fly, without following the level-based algorithm.
4512 var order = [], m;
4513 for (var i = 0; i < len;) {
4514 if (countsAsLeft.test(types[i])) {
4515 var start = i;
4516 for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
4517 order.push({from: start, to: i, level: 0});
4518 } else {
4519 var pos = i, at = order.length;
4520 for (++i; i < len && types[i] != "L"; ++i) {}
4521 for (var j = pos; j < i;) {
4522 if (countsAsNum.test(types[j])) {
4523 if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});
4524 var nstart = j;
4525 for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
4526 order.splice(at, 0, {from: nstart, to: j, level: 2});
4527 pos = j;
4528 } else ++j;
4529 }
4530 if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});
4531 }
4532 }
4533 if (order[0].level == 1 && (m = str.match(/^\s+/))) {
4534 order[0].from = m[0].length;
4535 order.unshift({from: 0, to: m[0].length, level: 0});
4536 }
4537 if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
4538 lst(order).to -= m[0].length;
4539 order.push({from: len - m[0].length, to: len, level: 0});
4540 }
4541 if (order[0].level != lst(order).level)
4542 order.push({from: len, to: len, level: order[0].level});
4543
4544 return order;
4545 };
4546 })();
4547
4548 // THE END
4549
4550 CodeMirror.version = "3.0";
4551
4552 return CodeMirror;
4553 })();