@samitouri / QOS-React-2 / commits / b2e51c6e01

Upgrade codemirror and enable line wrapping

Fixes #678.

Ben Alpert committed Dec 30, 2013 at 18:06 UTC b2e51c6e0172890033bb2c9cb11c50b8cc239290
2 files changed +1537 -739
docs/_js/live_editor.js
+1
@@ -21,6 +21,7 @@ var CodeMirrorEditor = React.createClass({
21 this.editor = CodeMirror.fromTextArea(this.refs.editor.getDOMNode(), {
22 mode: 'javascript',
23 lineNumbers: false,
24 + lineWrapping: true,
25 matchBrackets: true,
26 theme: 'solarized-light',
27 readOnly: this.props.readOnly
docs/js/codemirror.js
+1536 -739
@@ -7,9 +7,13 @@ window.CodeMirror = (function() {
7 // Crude, but necessary to handle a number of hard-to-feature-detect
8 // bugs and behavior differences.
9 var gecko = /gecko\/\d/i.test(navigator.userAgent);
10 + // IE11 currently doesn't count as 'ie', since it has almost none of
11 + // the same bugs as earlier versions. Use ie_gt10 to handle
12 + // incompatibilities in that version.
13 var ie = /MSIE \d/.test(navigator.userAgent);
14 var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
15 var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
16 + var ie_gt10 = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent);
17 var webkit = /WebKit\//.test(navigator.userAgent);
18 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
19 var chrome = /Chrome\//.test(navigator.userAgent);
@@ -24,10 +28,11 @@ window.CodeMirror = (function() {
28 // This is woefully incomplete. Suggestions for alternative methods welcome.
29 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
30 var mac = ios || /Mac/.test(navigator.platform);
27 - var windows = /windows/i.test(navigator.platform);
31 + var windows = /win/i.test(navigator.platform);
32
33 var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
34 if (opera_version) opera_version = Number(opera_version[1]);
35 + if (opera_version && opera_version >= 15) { opera = false; webkit = true; }
36 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
37 var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
38 var captureMiddleClick = gecko || (ie && !ie_lt9);
@@ -39,7 +44,7 @@ window.CodeMirror = (function() {
44
45 function CodeMirror(place, options) {
46 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
42 -
47 +
48 this.options = options = options || {};
49 // Determine effective options based on given values and defaults.
50 for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
@@ -60,7 +65,6 @@ window.CodeMirror = (function() {
65 draggingText: false,
66 highlight: new Delayed()};
67
63 - this.nextOpId = 0;
68 themeChanged(this);
69 if (options.lineWrapping)
70 this.display.wrapper.className += " CodeMirror-wrap";
@@ -92,18 +96,23 @@ window.CodeMirror = (function() {
96
97 function makeDisplay(place, docStart) {
98 var d = {};
95 - var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
99 +
100 + var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
101 if (webkit) input.style.width = "1000px";
102 else input.setAttribute("wrap", "off");
98 - input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
103 + // if border: 0; -- iOS fails to open keyboard (issue #1287)
104 + if (ios) input.style.border = "1px solid black";
105 + input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
106 +
107 // Wraps and hides input textarea
108 d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
109 // The actual fake scrollbars.
110 d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
111 d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
112 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
113 + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
114 // DIVs containing the selection and the actual code
106 - d.lineDiv = elt("div");
115 + d.lineDiv = elt("div", null, "CodeMirror-code");
116 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
117 // Blinky cursor, and element used to ensure cursor fits at the end of a line
118 d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
@@ -119,18 +128,16 @@ window.CodeMirror = (function() {
128 // Set to the height of the text, causes scrolling
129 d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
130 // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
122 - d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px");
131 + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
132 // Will contain the gutters, if any
133 d.gutters = elt("div", null, "CodeMirror-gutters");
134 d.lineGutter = null;
126 - // Helper element to properly size the gutter backgrounds
127 - var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%");
135 // Provides scrolling
129 - d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll");
136 + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
137 d.scroller.setAttribute("tabIndex", "-1");
138 // The element in which the editor lives.
139 d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
133 - d.scrollbarFiller, d.scroller], "CodeMirror");
140 + d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
141 // Work around IE7 z-index bug
142 if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
143 if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
@@ -161,8 +168,6 @@ window.CodeMirror = (function() {
168 d.pollingFast = false;
169 // Self-resetting timeout for the poller
170 d.poll = new Delayed();
164 - // True when a drag from the editor is active
165 - d.draggingText = false;
171
172 d.cachedCharWidth = d.cachedTextHeight = null;
173 d.measureLineCache = [];
@@ -180,7 +185,7 @@ window.CodeMirror = (function() {
185
186 // Used for measuring wheel scrolling granularity
187 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
183 -
188 +
189 return d;
190 }
191
@@ -211,7 +216,7 @@ window.CodeMirror = (function() {
216 estimateLineHeights(cm);
217 regChange(cm);
218 clearCaches(cm);
214 - setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100);
219 + setTimeout(function(){updateScrollbars(cm);}, 100);
220 }
221
222 function estimateHeight(cm) {
@@ -236,9 +241,11 @@ window.CodeMirror = (function() {
241 }
242
243 function keyMapChanged(cm) {
239 - var style = keyMap[cm.options.keyMap].style;
244 + var map = keyMap[cm.options.keyMap], style = map.style;
245 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
246 (style ? " cm-keymap-" + style : "");
247 + if (cm.state.disableInput && !map.disableInput) resetInput(cm, true);
248 + cm.state.disableInput = map.disableInput;
249 }
250
251 function themeChanged(cm) {
@@ -250,6 +257,7 @@ window.CodeMirror = (function() {
257 function guttersChanged(cm) {
258 updateGutters(cm);
259 regChange(cm);
260 + setTimeout(function(){alignHorizontally(cm);}, 20);
261 }
262
263 function updateGutters(cm) {
@@ -301,46 +309,59 @@ window.CodeMirror = (function() {
309 // Make sure the gutters options contains the element
310 // "CodeMirror-linenumbers" when the lineNumbers option is true.
311 function setGuttersForLineNumbers(options) {
304 - var found = false;
305 - for (var i = 0; i < options.gutters.length; ++i) {
306 - if (options.gutters[i] == "CodeMirror-linenumbers") {
307 - if (options.lineNumbers) found = true;
308 - else options.gutters.splice(i--, 1);
309 - }
312 + var found = indexOf(options.gutters, "CodeMirror-linenumbers");
313 + if (found == -1 && options.lineNumbers) {
314 + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]);
315 + } else if (found > -1 && !options.lineNumbers) {
316 + options.gutters = options.gutters.slice(0);
317 + options.gutters.splice(found, 1);
318 }
311 - if (!found && options.lineNumbers)
312 - options.gutters.push("CodeMirror-linenumbers");
319 }
320
321 // SCROLLBARS
322
323 // Re-synchronize the fake scrollbars with the actual size of the
324 // content. Optionally force a scrollTop.
319 - function updateScrollbars(d /* display */, docHeight) {
320 - var totalHeight = docHeight + 2 * paddingTop(d);
325 + function updateScrollbars(cm) {
326 + var d = cm.display, docHeight = cm.doc.height;
327 + var totalHeight = docHeight + paddingVert(d);
328 d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
329 + d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
330 var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
323 - var needsH = d.scroller.scrollWidth > d.scroller.clientWidth;
324 - var needsV = scrollHeight > d.scroller.clientHeight;
331 + var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
332 + var needsV = scrollHeight > (d.scroller.clientHeight + 1);
333 if (needsV) {
334 d.scrollbarV.style.display = "block";
335 d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
328 - d.scrollbarV.firstChild.style.height =
336 + d.scrollbarV.firstChild.style.height =
337 (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
330 - } else d.scrollbarV.style.display = "";
338 + } else {
339 + d.scrollbarV.style.display = "";
340 + d.scrollbarV.firstChild.style.height = "0";
341 + }
342 if (needsH) {
343 d.scrollbarH.style.display = "block";
344 d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
345 d.scrollbarH.firstChild.style.width =
346 (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
336 - } else d.scrollbarH.style.display = "";
347 + } else {
348 + d.scrollbarH.style.display = "";
349 + d.scrollbarH.firstChild.style.width = "0";
350 + }
351 if (needsH && needsV) {
352 d.scrollbarFiller.style.display = "block";
353 d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
354 } else d.scrollbarFiller.style.display = "";
355 + if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
356 + d.gutterFiller.style.display = "block";
357 + d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
358 + d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
359 + } else d.gutterFiller.style.display = "";
360
342 - if (mac_geLion && scrollbarWidth(d.measure) === 0)
361 + if (mac_geLion && scrollbarWidth(d.measure) === 0) {
362 d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
363 + d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none";
364 + }
365 }
366
367 function visibleLines(display, doc, viewPort) {
@@ -387,42 +408,57 @@ window.CodeMirror = (function() {
408 return String(options.lineNumberFormatter(i + options.firstLineNumber));
409 }
410 function compensateForHScroll(display) {
390 - return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
411 + return getRect(display.scroller).left - getRect(display.sizer).left;
412 }
413
414 // DISPLAY DRAWING
415
395 - function updateDisplay(cm, changes, viewPort) {
396 - var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo;
397 - var updated = updateDisplayInner(cm, changes, viewPort);
416 + function updateDisplay(cm, changes, viewPort, forced) {
417 + var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
418 + var visible = visibleLines(cm.display, cm.doc, viewPort);
419 + for (var first = true;; first = false) {
420 + var oldWidth = cm.display.scroller.clientWidth;
421 + if (!updateDisplayInner(cm, changes, visible, forced)) break;
422 + updated = true;
423 + changes = [];
424 + updateSelection(cm);
425 + updateScrollbars(cm);
426 + if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) {
427 + forced = true;
428 + continue;
429 + }
430 + forced = false;
431 +
432 + // Clip forced viewport to actual scrollable area
433 + if (viewPort)
434 + viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
435 + typeof viewPort == "number" ? viewPort : viewPort.top);
436 + visible = visibleLines(cm.display, cm.doc, viewPort);
437 + if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
438 + break;
439 + }
440 +
441 if (updated) {
442 signalLater(cm, "update", cm);
443 if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
444 signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
445 }
403 - updateSelection(cm);
404 - updateScrollbars(cm.display, cm.doc.height);
405 -
446 return updated;
447 }
448
449 // Uses a set of changes plus the current scroll position to
450 // determine which DOM updates have to be made, and makes the
451 // updates.
412 - function updateDisplayInner(cm, changes, viewPort) {
452 + function updateDisplayInner(cm, changes, visible, forced) {
453 var display = cm.display, doc = cm.doc;
414 - if (!display.wrapper.clientWidth) {
454 + if (!display.wrapper.offsetWidth) {
455 display.showingFrom = display.showingTo = doc.first;
456 display.viewOffset = 0;
457 return;
458 }
459
420 - // Compute the new visible window
421 - // If scrollTop is specified, use that to determine which lines
422 - // to render instead of the current scrollbar position.
423 - var visible = visibleLines(display, doc, viewPort);
460 // Bail out if the visible area is already rendered and nothing changed.
425 - if (changes.length == 0 &&
461 + if (!forced && changes.length == 0 &&
462 visible.from > display.showingFrom && visible.to < display.showingTo)
463 return;
464
@@ -431,23 +467,11 @@ window.CodeMirror = (function() {
467 var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
468 display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
469
434 - // When merged lines are present, the line that needs to be
435 - // redrawn might not be the one that was changed.
436 - if (sawCollapsedSpans)
437 - for (var i = 0; i < changes.length; ++i) {
438 - var ch = changes[i], merged;
439 - while (merged = collapsedSpanAtStart(getLine(doc, ch.from))) {
440 - var from = merged.find().from.line;
441 - if (ch.diff) ch.diff -= ch.from - from;
442 - ch.from = from;
443 - }
444 - }
445 -
470 // Used to determine which lines need their line numbers updated
471 var positionsChangedFrom = Infinity;
472 if (cm.options.lineNumbers)
473 for (var i = 0; i < changes.length; ++i)
450 - if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }
474 + if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; }
475
476 var end = doc.first + doc.size;
477 var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
@@ -461,7 +485,22 @@ window.CodeMirror = (function() {
485
486 // Create a range of theoretically intact lines, and punch holes
487 // in that using the change info.
464 - var intact = computeIntact([{from: display.showingFrom, to: display.showingTo}], changes);
488 + var intact = [{from: Math.max(display.showingFrom, doc.first),
489 + to: Math.min(display.showingTo, end)}];
490 + if (intact[0].from >= intact[0].to) intact = [];
491 + else intact = computeIntact(intact, changes);
492 + // When merged lines are present, we might have to reduce the
493 + // intact ranges because changes in continued fragments of the
494 + // intact lines do require the lines to be redrawn.
495 + if (sawCollapsedSpans)
496 + for (var i = 0; i < intact.length; ++i) {
497 + var range = intact[i], merged;
498 + while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
499 + var newTo = merged.find().from.line;
500 + if (newTo > range.from) range.to = newTo;
501 + else { intact.splice(i--, 1); break; }
502 + }
503 + }
504
505 // Clip off the parts that won't be visible
506 var intactLines = 0;
@@ -472,26 +511,40 @@ window.CodeMirror = (function() {
511 if (range.from >= range.to) intact.splice(i--, 1);
512 else intactLines += range.to - range.from;
513 }
475 - if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
514 + if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
515 updateViewOffset(cm);
516 return;
517 }
518 intact.sort(function(a, b) {return a.from - b.from;});
519
481 - var focused = document.activeElement;
520 + // Avoid crashing on IE's "unspecified error" when in iframes
521 + try {
522 + var focused = document.activeElement;
523 + } catch(e) {}
524 if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
525 patchDisplay(cm, from, to, intact, positionsChangedFrom);
526 display.lineDiv.style.display = "";
485 - if (document.activeElement != focused && focused.offsetHeight) focused.focus();
527 + if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
528
529 var different = from != display.showingFrom || to != display.showingTo ||
530 display.lastSizeC != display.wrapper.clientHeight;
531 // This is just a bogus formula that detects when the editor is
532 // resized or the font size changes.
491 - if (different) display.lastSizeC = display.wrapper.clientHeight;
533 + if (different) {
534 + display.lastSizeC = display.wrapper.clientHeight;
535 + startWorker(cm, 400);
536 + }
537 display.showingFrom = from; display.showingTo = to;
493 - startWorker(cm, 100);
538
539 + display.gutters.style.height = "";
540 + updateHeightsInViewport(cm);
541 + updateViewOffset(cm);
542 +
543 + return true;
544 + }
545 +
546 + function updateHeightsInViewport(cm) {
547 + var display = cm.display;
548 var prevBottom = display.lineDiv.offsetTop;
549 for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
550 if (ie_lt8) {
@@ -499,7 +552,7 @@ window.CodeMirror = (function() {
552 height = bot - prevBottom;
553 prevBottom = bot;
554 } else {
502 - var box = node.getBoundingClientRect();
555 + var box = getRect(node);
556 height = box.bottom - box.top;
557 }
558 var diff = node.lineObj.height - height;
@@ -511,11 +564,6 @@ window.CodeMirror = (function() {
564 widgets[i].height = widgets[i].node.offsetHeight;
565 }
566 }
514 - updateViewOffset(cm);
515 -
516 - if (visibleLines(display, doc, viewPort).to >= to)
517 - updateDisplayInner(cm, [], viewPort);
518 - return true;
567 }
568
569 function updateViewOffset(cm) {
@@ -581,8 +629,9 @@ window.CodeMirror = (function() {
629 if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
630 if (lineIsHidden(cm.doc, line)) {
631 if (line.height != 0) updateLineHeight(line, 0);
584 - if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i)
585 - if (line.widgets[i].showIfHidden) {
632 + if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
633 + var w = line.widgets[i];
634 + if (w.showIfHidden) {
635 var prev = cur.previousSibling;
636 if (/pre/i.test(prev.nodeName)) {
637 var wrap = elt("div", null, null, "position: relative");
@@ -590,9 +639,11 @@ window.CodeMirror = (function() {
639 wrap.appendChild(prev);
640 prev = wrap;
641 }
593 - var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget"));
594 - positionLineWidget(line.widgets[i], wnode, prev, dims);
642 + var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
643 + if (!w.handleMouseEvents) wnode.ignoreEvents = true;
644 + positionLineWidget(w, wnode, prev, dims);
645 }
646 + }
647 } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
648 // This line is intact. Skip to the actual node. Update its
649 // line number if needed.
@@ -623,37 +674,38 @@ window.CodeMirror = (function() {
674 }
675
676 function buildLineElement(cm, line, lineNo, dims, reuse) {
626 - var lineElement = lineContent(cm, line);
677 + var built = buildLineContent(cm, line), lineElement = built.pre;
678 var markers = line.gutterMarkers, display = cm.display, wrap;
679
629 - if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets)
680 + var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass;
681 + if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets)
682 return lineElement;
683
684 // Lines with gutter elements, widgets or a background class need
685 // to be wrapped again, and have the extra elements added to the
686 // wrapper div
635 -
687 +
688 if (reuse) {
689 reuse.alignable = null;
638 - var isOk = true, widgetsSeen = 0;
690 + var isOk = true, widgetsSeen = 0, insertBefore = null;
691 for (var n = reuse.firstChild, next; n; n = next) {
692 next = n.nextSibling;
693 if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
694 reuse.removeChild(n);
695 } else {
644 - for (var i = 0, first = true; i < line.widgets.length; ++i) {
645 - var widget = line.widgets[i], isFirst = false;
646 - if (!widget.above) { isFirst = first; first = false; }
696 + for (var i = 0; i < line.widgets.length; ++i) {
697 + var widget = line.widgets[i];
698 if (widget.node == n.firstChild) {
699 + if (!widget.above && !insertBefore) insertBefore = n;
700 positionLineWidget(widget, n, reuse, dims);
701 ++widgetsSeen;
650 - if (isFirst) reuse.insertBefore(lineElement, n);
702 break;
703 }
704 }
705 if (i == line.widgets.length) { isOk = false; break; }
706 }
707 }
708 + reuse.insertBefore(lineElement, insertBefore);
709 if (isOk && widgetsSeen == line.widgets.length) {
710 wrap = reuse;
711 reuse.className = line.wrapClass || "";
@@ -664,12 +716,12 @@ window.CodeMirror = (function() {
716 wrap.appendChild(lineElement);
717 }
718 // Kludge to make sure the styled element lies behind the selection (by z-index)
667 - if (line.bgClass)
668 - wrap.insertBefore(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground"), wrap.firstChild);
719 + if (bgClass)
720 + wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild);
721 if (cm.options.lineNumbers || markers) {
670 - var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
722 + var gutterWrap = wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
723 (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
672 - wrap.firstChild);
724 + lineElement);
725 if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
726 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
727 wrap.lineNumber = gutterWrap.appendChild(
@@ -688,6 +740,7 @@ window.CodeMirror = (function() {
740 if (ie_lt8) wrap.style.zIndex = 2;
741 if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
742 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
743 + if (!widget.handleMouseEvents) node.ignoreEvents = true;
744 positionLineWidget(widget, node, wrap, dims);
745 if (widget.above)
746 wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
@@ -731,12 +784,14 @@ window.CodeMirror = (function() {
784 display.selectionDiv.style.display = "none";
785
786 // Move the hidden textarea near the cursor to prevent scrolling artifacts
734 - var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
735 - var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
736 - display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
737 - headPos.top + lineOff.top - wrapOff.top)) + "px";
738 - display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
739 - headPos.left + lineOff.left - wrapOff.left)) + "px";
787 + if (cm.options.moveInputWithCursor) {
788 + var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
789 + var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
790 + display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
791 + headPos.top + lineOff.top - wrapOff.top)) + "px";
792 + display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
793 + headPos.left + lineOff.left - wrapOff.left)) + "px";
794 + }
795 }
796
797 // No selection, plain cursor
@@ -768,67 +823,59 @@ window.CodeMirror = (function() {
823 "px; height: " + (bottom - top) + "px"));
824 }
825
771 - function drawForLine(line, fromArg, toArg, retTop) {
826 + function drawForLine(line, fromArg, toArg) {
827 var lineObj = getLine(doc, line);
773 - var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity;
774 - function coords(ch) {
775 - return charCoords(cm, Pos(line, ch), "div", lineObj);
828 + var lineLen = lineObj.text.length;
829 + var start, end;
830 + function coords(ch, bias) {
831 + return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
832 }
833
834 iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
779 - var leftPos = coords(dir == "rtl" ? to - 1 : from);
780 - var rightPos = coords(dir == "rtl" ? from : to - 1);
781 - var left = leftPos.left, right = rightPos.right;
835 + var leftPos = coords(from, "left"), rightPos, left, right;
836 + if (from == to) {
837 + rightPos = leftPos;
838 + left = right = leftPos.left;
839 + } else {
840 + rightPos = coords(to - 1, "right");
841 + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
842 + left = leftPos.left;
843 + right = rightPos.right;
844 + }
845 + if (fromArg == null && from == 0) left = pl;
846 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
847 add(left, leftPos.top, null, leftPos.bottom);
848 left = pl;
849 if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
850 }
851 if (toArg == null && to == lineLen) right = clientWidth;
788 - if (fromArg == null && from == 0) left = pl;
789 - rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal);
852 + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
853 + start = leftPos;
854 + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
855 + end = rightPos;
856 if (left < pl + 1) left = pl;
857 add(left, rightPos.top, right - left, rightPos.bottom);
858 });
793 - return rVal;
859 + return {start: start, end: end};
860 }
861
862 if (sel.from.line == sel.to.line) {
863 drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
864 } else {
799 - var fromObj = getLine(doc, sel.from.line);
800 - var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine;
801 - while (merged = collapsedSpanAtEnd(cur)) {
802 - var found = merged.find();
803 - path.push(found.from.ch, found.to.line, found.to.ch);
804 - if (found.to.line == sel.to.line) {
805 - path.push(sel.to.ch);
806 - singleLine = true;
807 - break;
865 + var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
866 + var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
867 + var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
868 + var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
869 + if (singleVLine) {
870 + if (leftEnd.top < rightStart.top - 2) {
871 + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
872 + add(pl, rightStart.top, rightStart.left, rightStart.bottom);
873 + } else {
874 + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
875 }
809 - cur = getLine(doc, found.to.line);
810 - }
811 -
812 - // This is a single, merged line
813 - if (singleLine) {
814 - for (var i = 0; i < path.length; i += 3)
815 - drawForLine(path[i], path[i+1], path[i+2]);
816 - } else {
817 - var middleTop, middleBot, toObj = getLine(doc, sel.to.line);
818 - if (sel.from.ch)
819 - // Draw the first line of selection.
820 - middleTop = drawForLine(sel.from.line, sel.from.ch, null, false);
821 - else
822 - // Simply include it in the middle block.
823 - middleTop = heightAtLine(cm, fromObj) - display.viewOffset;
824 -
825 - if (!sel.to.ch)
826 - middleBot = heightAtLine(cm, toObj) - display.viewOffset;
827 - else
828 - middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true);
829 -
830 - if (middleTop < middleBot) add(pl, middleTop, null, middleBot);
876 }
877 + if (leftEnd.bottom < rightStart.top)
878 + add(pl, leftEnd.bottom, null, rightStart.top);
879 }
880
881 removeChildrenAndAdd(display.selectionDiv, fragment);
@@ -837,14 +884,15 @@ window.CodeMirror = (function() {
884
885 // Cursor-blinking
886 function restartBlink(cm) {
887 + if (!cm.state.focused) return;
888 var display = cm.display;
889 clearInterval(display.blinker);
890 var on = true;
891 display.cursor.style.visibility = display.otherCursor.style.visibility = "";
844 - display.blinker = setInterval(function() {
845 - if (!display.cursor.offsetHeight) return;
846 - display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
847 - }, cm.options.cursorBlinkRate);
892 + if (cm.options.cursorBlinkRate > 0)
893 + display.blinker = setInterval(function() {
894 + display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
895 + }, cm.options.cursorBlinkRate);
896 }
897
898 // HIGHLIGHT WORKER
@@ -864,7 +912,7 @@ window.CodeMirror = (function() {
912 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
913 if (doc.frontier >= cm.display.showingFrom) { // Visible
914 var oldStyles = line.styles;
867 - line.styles = highlightLine(cm, line, state);
915 + line.styles = highlightLine(cm, line, state, true);
916 var ischange = !oldStyles || oldStyles.length != line.styles.length;
917 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
918 if (ischange) {
@@ -873,7 +921,7 @@ window.CodeMirror = (function() {
921 }
922 line.stateAfter = copyState(doc.mode, state);
923 } else {
876 - processLine(cm, line, state);
924 + processLine(cm, line.text, state);
925 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
926 }
927 ++doc.frontier;
@@ -894,12 +942,13 @@ window.CodeMirror = (function() {
942 // valid state. If that fails, it returns the line with the
943 // smallest indentation, which tends to need the least context to
944 // parse correctly.
897 - function findStartLine(cm, n) {
945 + function findStartLine(cm, n, precise) {
946 var minindent, minline, doc = cm.doc;
899 - for (var search = n, lim = n - 100; search > lim; --search) {
947 + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
948 + for (var search = n; search > lim; --search) {
949 if (search <= doc.first) return doc.first;
950 var line = getLine(doc, search - 1);
902 - if (line.stateAfter) return search;
951 + if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
952 var indented = countColumn(line.text, null, cm.options.tabSize);
953 if (minline == null || minindent > indented) {
954 minline = search - 1;
@@ -909,67 +958,91 @@ window.CodeMirror = (function() {
958 return minline;
959 }
960
912 - function getStateBefore(cm, n) {
961 + function getStateBefore(cm, n, precise) {
962 var doc = cm.doc, display = cm.display;
914 - if (!doc.mode.startState) return true;
915 - var pos = findStartLine(cm, n), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
963 + if (!doc.mode.startState) return true;
964 + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
965 if (!state) state = startState(doc.mode);
966 else state = copyState(doc.mode, state);
967 doc.iter(pos, n, function(line) {
919 - processLine(cm, line, state);
968 + processLine(cm, line.text, state);
969 var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
970 line.stateAfter = save ? copyState(doc.mode, state) : null;
971 ++pos;
972 });
973 + if (precise) doc.frontier = pos;
974 return state;
975 }
976
977 // POSITION MEASUREMENT
928 -
978 +
979 function paddingTop(display) {return display.lineSpace.offsetTop;}
980 + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
981 function paddingLeft(display) {
931 - var e = removeChildrenAndAdd(display.measure, elt("pre")).appendChild(elt("span", "x"));
982 + var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
983 return e.offsetLeft;
984 }
985
935 - function measureChar(cm, line, ch, data) {
986 + function measureChar(cm, line, ch, data, bias) {
987 var dir = -1;
988 data = data || measureLine(cm, line);
938 -
989 + if (data.crude) {
990 + var left = data.left + ch * data.width;
991 + return {left: left, right: left + data.width, top: data.top, bottom: data.bottom};
992 + }
993 +
994 for (var pos = ch;; pos += dir) {
995 var r = data[pos];
996 if (r) break;
997 if (dir < 0 && pos == 0) dir = 1;
998 }
999 + bias = pos > ch ? "left" : pos < ch ? "right" : bias;
1000 + if (bias == "left" && r.leftSide) r = r.leftSide;
1001 + else if (bias == "right" && r.rightSide) r = r.rightSide;
1002 return {left: pos < ch ? r.right : r.left,
1003 right: pos > ch ? r.left : r.right,
946 - top: r.top, bottom: r.bottom};
1004 + top: r.top,
1005 + bottom: r.bottom};
1006 }
1007
949 - function measureLine(cm, line) {
950 - // First look in the cache
951 - var display = cm.display, cache = cm.display.measureLineCache;
1008 + function findCachedMeasurement(cm, line) {
1009 + var cache = cm.display.measureLineCache;
1010 for (var i = 0; i < cache.length; ++i) {
1011 var memo = cache[i];
1012 if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
955 - display.scroller.clientWidth == memo.width &&
956 - memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
957 - return memo.measure;
1013 + cm.display.scroller.clientWidth == memo.width &&
1014 + memo.classes == line.textClass + "|" + line.wrapClass)
1015 + return memo;
1016 }
959 -
1017 + }
1018 +
1019 + function clearCachedMeasurement(cm, line) {
1020 + var exists = findCachedMeasurement(cm, line);
1021 + if (exists) exists.text = exists.measure = exists.markedSpans = null;
1022 + }
1023 +
1024 + function measureLine(cm, line) {
1025 + // First look in the cache
1026 + var cached = findCachedMeasurement(cm, line);
1027 + if (cached) return cached.measure;
1028 +
1029 + // Failing that, recompute and store result in cache
1030 var measure = measureLineInner(cm, line);
961 - // Store result in the cache
962 - var memo = {text: line.text, width: display.scroller.clientWidth,
1031 + var cache = cm.display.measureLineCache;
1032 + var memo = {text: line.text, width: cm.display.scroller.clientWidth,
1033 markedSpans: line.markedSpans, measure: measure,
964 - classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
965 - if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo;
1034 + classes: line.textClass + "|" + line.wrapClass};
1035 + if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
1036 else cache.push(memo);
1037 return measure;
1038 }
1039
1040 function measureLineInner(cm, line) {
1041 + if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom)
1042 + return crudelyMeasureLine(cm, line);
1043 +
1044 var display = cm.display, measure = emptyArray(line.text.length);
972 - var pre = lineContent(cm, line, measure);
1045 + var pre = buildLineContent(cm, line, measure, true).pre;
1046
1047 // IE does not cache element positions of inline elements between
1048 // calls to getBoundingClientRect. This makes the loop below,
@@ -998,50 +1071,97 @@ window.CodeMirror = (function() {
1071
1072 removeChildrenAndAdd(display.measure, pre);
1073
1001 - var outer = display.lineDiv.getBoundingClientRect();
1074 + var outer = getRect(display.lineDiv);
1075 var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
1076 // Work around an IE7/8 bug where it will sometimes have randomly
1077 // replaced our pre with a clone at this point.
1078 if (ie_lt9 && display.measure.first != pre)
1079 removeChildrenAndAdd(display.measure, pre);
1080
1008 - for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1009 - var size = cur.getBoundingClientRect();
1010 - var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot);
1011 - for (var j = 0; j < vranges.length; j += 2) {
1012 - var rtop = vranges[j], rbot = vranges[j+1];
1081 + function measureRect(rect) {
1082 + var top = rect.top - outer.top, bot = rect.bottom - outer.top;
1083 + if (bot > maxBot) bot = maxBot;
1084 + if (top < 0) top = 0;
1085 + for (var i = vranges.length - 2; i >= 0; i -= 2) {
1086 + var rtop = vranges[i], rbot = vranges[i+1];
1087 if (rtop > bot || rbot < top) continue;
1088 if (rtop <= top && rbot >= bot ||
1089 top <= rtop && bot >= rbot ||
1090 Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
1017 - vranges[j] = Math.min(top, rtop);
1018 - vranges[j+1] = Math.max(bot, rbot);
1091 + vranges[i] = Math.min(top, rtop);
1092 + vranges[i+1] = Math.max(bot, rbot);
1093 break;
1094 }
1095 }
1022 - if (j == vranges.length) vranges.push(top, bot);
1023 - data[i] = {left: size.left - outer.left, right: size.right - outer.left, top: j};
1096 + if (i < 0) { i = vranges.length; vranges.push(top, bot); }
1097 + return {left: rect.left - outer.left,
1098 + right: rect.right - outer.left,
1099 + top: i, bottom: null};
1100 + }
1101 + function finishRect(rect) {
1102 + rect.bottom = vranges[rect.top+1];
1103 + rect.top = vranges[rect.top];
1104 + }
1105 +
1106 + for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1107 + var node = cur, rect = null;
1108 + // A widget might wrap, needs special care
1109 + if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
1110 + if (cur.firstChild.nodeType == 1) node = cur.firstChild;
1111 + var rects = node.getClientRects();
1112 + if (rects.length > 1) {
1113 + rect = data[i] = measureRect(rects[0]);
1114 + rect.rightSide = measureRect(rects[rects.length - 1]);
1115 + }
1116 + }
1117 + if (!rect) rect = data[i] = measureRect(getRect(node));
1118 + if (cur.measureRight) rect.right = getRect(cur.measureRight).left - outer.left;
1119 + if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));
1120 }
1121 + removeChildren(cm.display.measure);
1122 for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
1026 - var vr = cur.top;
1027 - cur.top = vranges[vr]; cur.bottom = vranges[vr+1];
1123 + finishRect(cur);
1124 + if (cur.leftSide) finishRect(cur.leftSide);
1125 + if (cur.rightSide) finishRect(cur.rightSide);
1126 }
1029 - if (!cm.options.lineWrapping) {
1030 - var last = pre.lastChild;
1031 - if (last.nodeType == 3) last = pre.appendChild(elt("span", "\u200b"));
1032 - data.width = last.getBoundingClientRect().right - outer.left;
1127 + return data;
1128 + }
1129 +
1130 + function crudelyMeasureLine(cm, line) {
1131 + var copy = new Line(line.text.slice(0, 100), null);
1132 + if (line.textClass) copy.textClass = line.textClass;
1133 + var measure = measureLineInner(cm, copy);
1134 + var left = measureChar(cm, copy, 0, measure, "left");
1135 + var right = measureChar(cm, copy, 99, measure, "right");
1136 + return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100};
1137 + }
1138 +
1139 + function measureLineWidth(cm, line) {
1140 + var hasBadSpan = false;
1141 + if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
1142 + var sp = line.markedSpans[i];
1143 + if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
1144 }
1145 + var cached = !hasBadSpan && findCachedMeasurement(cm, line);
1146 + if (cached || line.text.length >= cm.options.crudeMeasuringFrom)
1147 + return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right;
1148
1035 - return data;
1149 + var pre = buildLineContent(cm, line, null, true).pre;
1150 + var end = pre.appendChild(zeroWidthElement(cm.display.measure));
1151 + removeChildrenAndAdd(cm.display.measure, pre);
1152 + return getRect(end).right - getRect(cm.display.lineDiv).left;
1153 }
1154
1155 function clearCaches(cm) {
1156 cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
1157 cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
1041 - cm.display.maxLineChanged = true;
1158 + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
1159 cm.display.lineNumChars = null;
1160 }
1161
1162 + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
1163 + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
1164 +
1165 // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
1166 function intoCoordSystem(cm, lineObj, rect, context) {
1167 if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
@@ -1051,67 +1171,87 @@ window.CodeMirror = (function() {
1171 if (context == "line") return rect;
1172 if (!context) context = "local";
1173 var yOff = heightAtLine(cm, lineObj);
1054 - if (context != "local") yOff -= cm.display.viewOffset;
1055 - if (context == "page") {
1056 - var lOff = cm.display.lineSpace.getBoundingClientRect();
1057 - yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop);
1058 - var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft);
1174 + if (context == "local") yOff += paddingTop(cm.display);
1175 + else yOff -= cm.display.viewOffset;
1176 + if (context == "page" || context == "window") {
1177 + var lOff = getRect(cm.display.lineSpace);
1178 + yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
1179 + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
1180 rect.left += xOff; rect.right += xOff;
1181 }
1182 rect.top += yOff; rect.bottom += yOff;
1183 return rect;
1184 }
1185
1065 - function charCoords(cm, pos, context, lineObj) {
1186 + // Context may be "window", "page", "div", or "local"/null
1187 + // Result is in "div" coords
1188 + function fromCoordSystem(cm, coords, context) {
1189 + if (context == "div") return coords;
1190 + var left = coords.left, top = coords.top;
1191 + // First move into "page" coordinate system
1192 + if (context == "page") {
1193 + left -= pageScrollX();
1194 + top -= pageScrollY();
1195 + } else if (context == "local" || !context) {
1196 + var localBox = getRect(cm.display.sizer);
1197 + left += localBox.left;
1198 + top += localBox.top;
1199 + }
1200 +
1201 + var lineSpaceBox = getRect(cm.display.lineSpace);
1202 + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
1203 + }
1204 +
1205 + function charCoords(cm, pos, context, lineObj, bias) {
1206 if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1067 - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context);
1207 + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
1208 }
1209
1210 function cursorCoords(cm, pos, context, lineObj, measurement) {
1211 lineObj = lineObj || getLine(cm.doc, pos.line);
1212 if (!measurement) measurement = measureLine(cm, lineObj);
1213 function get(ch, right) {
1074 - var m = measureChar(cm, lineObj, ch, measurement);
1214 + var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
1215 if (right) m.left = m.right; else m.right = m.left;
1216 return intoCoordSystem(cm, lineObj, m, context);
1217 }
1078 - var order = getOrder(lineObj), ch = pos.ch;
1079 - if (!order) return get(ch);
1080 - var main, other, linedir = order[0].level;
1081 - for (var i = 0; i < order.length; ++i) {
1082 - var part = order[i], rtl = part.level % 2, nb, here;
1083 - if (part.from < ch && part.to > ch) return get(ch, rtl);
1084 - var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
1085 - if (left == ch) {
1086 - // Opera and IE return bogus offsets and widths for edges
1087 - // where the direction flips, but only for the side with the
1088 - // lower level. So we try to use the side with the higher
1089 - // level.
1090 - if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true);
1091 - else here = get(rtl && part.from != part.to ? ch - 1 : ch);
1092 - if (rtl == linedir) main = here; else other = here;
1093 - } else if (right == ch) {
1094 - var nb = i < order.length - 1 && order[i+1];
1095 - if (!rtl && nb && nb.from == nb.to) continue;
1096 - if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from);
1097 - else here = get(rtl ? ch : ch - 1, true);
1098 - if (rtl == linedir) main = here; else other = here;
1218 + function getBidi(ch, partPos) {
1219 + var part = order[partPos], right = part.level % 2;
1220 + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
1221 + part = order[--partPos];
1222 + ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
1223 + right = true;
1224 + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
1225 + part = order[++partPos];
1226 + ch = bidiLeft(part) - part.level % 2;
1227 + right = false;
1228 }
1229 + if (right && ch == part.to && ch > part.from) return get(ch - 1);
1230 + return get(ch, right);
1231 }
1101 - if (linedir && !ch) other = get(order[0].to - 1);
1102 - if (!main) return other;
1103 - if (other) main.other = other;
1104 - return main;
1232 + var order = getOrder(lineObj), ch = pos.ch;
1233 + if (!order) return get(ch);
1234 + var partPos = getBidiPartAt(order, ch);
1235 + var val = getBidi(ch, partPos);
1236 + if (bidiOther != null) val.other = getBidi(ch, bidiOther);
1237 + return val;
1238 + }
1239 +
1240 + function PosWithInfo(line, ch, outside, xRel) {
1241 + var pos = new Pos(line, ch);
1242 + pos.xRel = xRel;
1243 + if (outside) pos.outside = true;
1244 + return pos;
1245 }
1246
1247 // Coords must be lineSpace-local
1248 function coordsChar(cm, x, y) {
1249 var doc = cm.doc;
1250 y += cm.display.viewOffset;
1111 - if (y < 0) return {line: doc.first, ch: 0, outside: true};
1251 + if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
1252 var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1253 if (lineNo > last)
1114 - return Pos(doc.size - 1, getLine(doc, last).text.length);
1254 + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
1255 if (x < 0) x = 0;
1256
1257 for (;;) {
@@ -1119,7 +1259,7 @@ window.CodeMirror = (function() {
1259 var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1260 var merged = collapsedSpanAtEnd(lineObj);
1261 var mergedPos = merged && merged.find();
1122 - if (merged && found.ch >= mergedPos.from.ch)
1262 + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
1263 lineNo = mergedPos.to.line;
1264 else
1265 return found;
@@ -1128,30 +1268,33 @@ window.CodeMirror = (function() {
1268
1269 function coordsCharInner(cm, lineObj, lineNo, x, y) {
1270 var innerOff = y - heightAtLine(cm, lineObj);
1131 - var wrongLine = false, cWidth = cm.display.wrapper.clientWidth;
1271 + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
1272 var measurement = measureLine(cm, lineObj);
1273
1274 function getX(ch) {
1275 var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
1276 lineObj, measurement);
1277 wrongLine = true;
1138 - if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth);
1139 - else if (innerOff < sp.top) return sp.left + cWidth;
1278 + if (innerOff > sp.bottom) return sp.left - adjust;
1279 + else if (innerOff < sp.top) return sp.left + adjust;
1280 else wrongLine = false;
1281 return sp.left;
1282 }
1283
1284 var bidi = getOrder(lineObj), dist = lineObj.text.length;
1285 var from = lineLeft(lineObj), to = lineRight(lineObj);
1146 - var fromX = getX(from), toX = getX(to);
1286 + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1287
1148 - if (x > toX) return {line: lineNo, ch: to, outside: wrongLine};
1288 + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
1289 // Do a binary search between these bounds.
1290 for (;;) {
1291 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1152 - var after = x - fromX < toX - x, ch = after ? from : to;
1292 + var ch = x < fromX || x - fromX <= toX - x ? from : to;
1293 + var xDiff = x - (ch == from ? fromX : toX);
1294 while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1154 - return {line: lineNo, ch: ch, after: after, outside: wrongLine};
1295 + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
1296 + xDiff < 0 ? -1 : xDiff ? 1 : 0);
1297 + return pos;
1298 }
1299 var step = Math.ceil(dist / 2), middle = from + step;
1300 if (bidi) {
@@ -1159,8 +1302,8 @@ window.CodeMirror = (function() {
1302 for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1303 }
1304 var middleX = getX(middle);
1162 - if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;}
1163 - else {from = middle; fromX = middleX; dist = step;}
1305 + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
1306 + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
1307 }
1308 }
1309
@@ -1201,18 +1344,21 @@ window.CodeMirror = (function() {
1344 // be awkward, slow, and error-prone), but instead updates are
1345 // batched and then all combined and executed at once.
1346
1347 + var nextOpId = 0;
1348 function startOperation(cm) {
1349 cm.curOp = {
1350 // An array of ranges of lines that have to be updated. See
1351 // updateDisplay.
1352 changes: [],
1353 + forceUpdate: false,
1354 updateInput: null,
1355 userSelChange: null,
1356 textChanged: null,
1357 selectionChanged: false,
1358 + cursorActivity: false,
1359 updateMaxLine: false,
1360 updateScrollPos: false,
1215 - id: ++cm.nextOpId
1361 + id: ++nextOpId
1362 };
1363 if (!delayedCallbackDepth++) delayedCallbacks = [];
1364 }
@@ -1222,8 +1368,8 @@ window.CodeMirror = (function() {
1368 cm.curOp = null;
1369
1370 if (op.updateMaxLine) computeMaxLength(cm);
1225 - if (display.maxLineChanged && !cm.options.lineWrapping) {
1226 - var width = measureLine(cm, display.maxLine).width;
1371 + if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
1372 + var width = measureLineWidth(cm, display.maxLine);
1373 display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1374 display.maxLineChanged = false;
1375 var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
@@ -1233,17 +1379,24 @@ window.CodeMirror = (function() {
1379 var newScrollPos, updated;
1380 if (op.updateScrollPos) {
1381 newScrollPos = op.updateScrollPos;
1236 - } else if (op.selectionChanged) {
1382 + } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
1383 var coords = cursorCoords(cm, doc.sel.head);
1384 newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1385 }
1240 - if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null)
1241 - updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop);
1386 + if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) {
1387 + updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate);
1388 + if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
1389 + }
1390 if (!updated && op.selectionChanged) updateSelection(cm);
1391 if (op.updateScrollPos) {
1244 - display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
1245 - display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
1392 + var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, newScrollPos.scrollTop));
1393 + var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, newScrollPos.scrollLeft));
1394 + display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
1395 + display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
1396 alignHorizontally(cm);
1397 + if (op.scrollToPos)
1398 + scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from),
1399 + clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin);
1400 } else if (newScrollPos) {
1401 scrollCursorIntoView(cm);
1402 }
@@ -1265,8 +1418,8 @@ window.CodeMirror = (function() {
1418 }
1419 if (op.textChanged)
1420 signal(cm, "change", cm, op.textChanged);
1268 - if (op.selectionChanged) signal(cm, "cursorActivity", cm);
1269 - if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](cm);
1421 + if (op.cursorActivity) signal(cm, "cursorActivity", cm);
1422 + if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
1423 }
1424
1425 // Wraps a function in an operation. Returns the wrapped function.
@@ -1330,25 +1483,47 @@ window.CodeMirror = (function() {
1483 // supported or compatible enough yet to rely on.)
1484 function readInput(cm) {
1485 var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1333 - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false;
1486 + if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
1487 + if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
1488 + input.value = input.value.substring(0, input.value.length - 1);
1489 + cm.state.fakedLastChar = false;
1490 + }
1491 var text = input.value;
1492 if (text == prevInput && posEq(sel.from, sel.to)) return false;
1493 + if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
1494 + resetInput(cm, true);
1495 + return false;
1496 + }
1497 +
1498 var withOp = !cm.curOp;
1499 if (withOp) startOperation(cm);
1500 sel.shift = false;
1501 var same = 0, l = Math.min(prevInput.length, text.length);
1340 - while (same < l && prevInput[same] == text[same]) ++same;
1502 + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1503 var from = sel.from, to = sel.to;
1504 + var inserted = text.slice(same);
1505 if (same < prevInput.length)
1506 from = Pos(from.line, from.ch - (prevInput.length - same));
1507 else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1345 - to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
1508 + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + inserted.length));
1509 +
1510 var updateInput = cm.curOp.updateInput;
1347 - makeChange(cm.doc, {from: from, to: to, text: splitLines(text.slice(same)),
1348 - origin: cm.state.pasteIncoming ? "paste" : "+input"}, "end");
1349 -
1511 + var changeEvent = {from: from, to: to, text: splitLines(inserted),
1512 + origin: cm.state.pasteIncoming ? "paste" : "+input"};
1513 + makeChange(cm.doc, changeEvent, "end");
1514 cm.curOp.updateInput = updateInput;
1351 - if (text.length > 1000) input.value = cm.display.prevInput = "";
1515 + signalLater(cm, "inputRead", cm, changeEvent);
1516 + if (!cm.state.pasteIncoming && cm.options.electricChars &&
1517 + cm.options.smartIndent && sel.head.ch < 100) {
1518 + var electric = cm.getModeAt(sel.head).electricChars;
1519 + if (electric) for (var i = 0; i < electric.length; i++)
1520 + if (inserted.indexOf(electric.charAt(i)) > -1) {
1521 + indentLine(cm, sel.head.line, "smart");
1522 + break;
1523 + }
1524 + }
1525 +
1526 + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
1527 else cm.display.prevInput = text;
1528 if (withOp) endOperation(cm);
1529 cm.state.pasteIncoming = false;
@@ -1361,10 +1536,14 @@ window.CodeMirror = (function() {
1536 cm.display.prevInput = "";
1537 minimal = hasCopyEvent &&
1538 (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1364 - if (minimal) cm.display.input.value = "-";
1365 - else cm.display.input.value = selected || cm.getSelection();
1539 + var content = minimal ? "-" : selected || cm.getSelection();
1540 + cm.display.input.value = content;
1541 if (cm.state.focused) selectInput(cm.display.input);
1367 - } else if (user) cm.display.prevInput = cm.display.input.value = "";
1542 + if (ie && !ie_lt9) cm.display.inputHasSelection = content;
1543 + } else if (user) {
1544 + cm.display.prevInput = cm.display.input.value = "";
1545 + if (ie && !ie_lt9) cm.display.inputHasSelection = null;
1546 + }
1547 cm.display.inaccurateSelection = minimal;
1548 }
1549
@@ -1382,7 +1561,17 @@ window.CodeMirror = (function() {
1561 function registerEventHandlers(cm) {
1562 var d = cm.display;
1563 on(d.scroller, "mousedown", operation(cm, onMouseDown));
1385 - on(d.scroller, "dblclick", operation(cm, e_preventDefault));
1564 + if (ie)
1565 + on(d.scroller, "dblclick", operation(cm, function(e) {
1566 + if (signalDOMEvent(cm, e)) return;
1567 + var pos = posFromMouse(cm, e);
1568 + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
1569 + e_preventDefault(e);
1570 + var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
1571 + extendSelection(cm.doc, word.from, word.to);
1572 + }));
1573 + else
1574 + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
1575 on(d.lineSpace, "selectstart", function(e) {
1576 if (!eventInWidget(d, e)) e_preventDefault(e);
1577 });
@@ -1392,15 +1581,17 @@ window.CodeMirror = (function() {
1581 if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1582
1583 on(d.scroller, "scroll", function() {
1395 - setScrollTop(cm, d.scroller.scrollTop);
1396 - setScrollLeft(cm, d.scroller.scrollLeft, true);
1397 - signal(cm, "scroll", cm);
1584 + if (d.scroller.clientHeight) {
1585 + setScrollTop(cm, d.scroller.scrollTop);
1586 + setScrollLeft(cm, d.scroller.scrollLeft, true);
1587 + signal(cm, "scroll", cm);
1588 + }
1589 });
1590 on(d.scrollbarV, "scroll", function() {
1400 - setScrollTop(cm, d.scrollbarV.scrollTop);
1591 + if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
1592 });
1593 on(d.scrollbarH, "scroll", function() {
1403 - setScrollLeft(cm, d.scrollbarH.scrollLeft);
1594 + if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
1595 });
1596
1597 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
@@ -1412,11 +1603,15 @@ window.CodeMirror = (function() {
1603 // Prevent wrapper from ever scrolling
1604 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1605
1606 + var resizeTimer;
1607 function onResize() {
1416 - // Might be a text scaling operation, clear size caches.
1417 - d.cachedCharWidth = d.cachedTextHeight = null;
1418 - clearCaches(cm);
1419 - runInOp(cm, bind(regChange, cm));
1608 + if (resizeTimer == null) resizeTimer = setTimeout(function() {
1609 + resizeTimer = null;
1610 + // Might be a text scaling operation, clear size caches.
1611 + d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
1612 + clearCaches(cm);
1613 + runInOp(cm, bind(regChange, cm));
1614 + }, 100);
1615 }
1616 on(window, "resize", onResize);
1617 // Above handler holds on to the editor and its data structures.
@@ -1426,21 +1621,24 @@ window.CodeMirror = (function() {
1621 for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
1622 if (p) setTimeout(unregister, 5000);
1623 else off(window, "resize", onResize);
1429 - }
1624 + }
1625 setTimeout(unregister, 5000);
1626
1627 on(d.input, "keyup", operation(cm, function(e) {
1433 - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1434 - if (e_prop(e, "keyCode") == 16) cm.doc.sel.shift = false;
1628 + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1629 + if (e.keyCode == 16) cm.doc.sel.shift = false;
1630 }));
1436 - on(d.input, "input", bind(fastPoll, cm));
1631 + on(d.input, "input", function() {
1632 + if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
1633 + fastPoll(cm);
1634 + });
1635 on(d.input, "keydown", operation(cm, onKeyDown));
1636 on(d.input, "keypress", operation(cm, onKeyPress));
1637 on(d.input, "focus", bind(onFocus, cm));
1638 on(d.input, "blur", bind(onBlur, cm));
1639
1640 function drag_(e) {
1443 - if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1641 + if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1642 e_stop(e);
1643 }
1644 if (cm.options.dragDrop) {
@@ -1449,12 +1647,22 @@ window.CodeMirror = (function() {
1647 on(d.scroller, "dragover", drag_);
1648 on(d.scroller, "drop", operation(cm, onDrop));
1649 }
1452 - on(d.scroller, "paste", function(e){
1650 + on(d.scroller, "paste", function(e) {
1651 if (eventInWidget(d, e)) return;
1454 - focusInput(cm);
1652 + focusInput(cm);
1653 fastPoll(cm);
1654 });
1655 on(d.input, "paste", function() {
1656 + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
1657 + // Add a char to the end of textarea before paste occur so that
1658 + // selection doesn't span to the end of textarea.
1659 + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
1660 + var start = d.input.selectionStart, end = d.input.selectionEnd;
1661 + d.input.value += "$";
1662 + d.input.selectionStart = start;
1663 + d.input.selectionEnd = end;
1664 + cm.state.fakedLastChar = true;
1665 + }
1666 cm.state.pasteIncoming = true;
1667 fastPoll(cm);
1668 });
@@ -1479,9 +1687,7 @@ window.CodeMirror = (function() {
1687
1688 function eventInWidget(display, e) {
1689 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
1482 - if (!n) return true;
1483 - if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) ||
1484 - n.parentNode == display.sizer && n != display.mover) return true;
1690 + if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
1691 }
1692 }
1693
@@ -1491,9 +1697,9 @@ window.CodeMirror = (function() {
1697 var target = e_target(e);
1698 if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1699 target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1494 - target == display.scrollbarFiller) return null;
1700 + target == display.scrollbarFiller || target == display.gutterFiller) return null;
1701 }
1496 - var x, y, space = display.lineSpace.getBoundingClientRect();
1702 + var x, y, space = getRect(display.lineSpace);
1703 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1704 try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1705 return coordsChar(cm, x - space.left, y - space.top);
@@ -1501,8 +1707,9 @@ window.CodeMirror = (function() {
1707
1708 var lastClick, lastDoubleClick;
1709 function onMouseDown(e) {
1710 + if (signalDOMEvent(this, e)) return;
1711 var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1505 - sel.shift = e_prop(e, "shiftKey");
1712 + sel.shift = e.shiftKey;
1713
1714 if (eventInWidget(display, e)) {
1715 if (!webkit) {
@@ -1519,6 +1726,7 @@ window.CodeMirror = (function() {
1726 if (captureMiddleClick) onContextMenu.call(cm, cm, e);
1727 return;
1728 case 2:
1729 + if (webkit) cm.state.lastMiddleDown = +new Date;
1730 if (start) extendSelection(cm.doc, start);
1731 setTimeout(bind(focusInput, cm), 20);
1732 e_preventDefault(e);
@@ -1571,9 +1779,12 @@ window.CodeMirror = (function() {
1779 e_preventDefault(e);
1780 if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1781
1574 - var startstart = sel.from, startend = sel.to;
1782 + var startstart = sel.from, startend = sel.to, lastPos = start;
1783
1784 function doSelect(cur) {
1785 + if (posEq(lastPos, cur)) return;
1786 + lastPos = cur;
1787 +
1788 if (type == "single") {
1789 extendSelection(cm.doc, clipPos(doc, start), cur);
1790 return;
@@ -1591,7 +1802,7 @@ window.CodeMirror = (function() {
1802 }
1803 }
1804
1594 - var editorSize = display.wrapper.getBoundingClientRect();
1805 + var editorSize = getRect(display.wrapper);
1806 // Used to ensure timeout re-tries don't fire when another extend
1807 // happened in the meantime (clearTimeout isn't reliable -- at
1808 // least on Chrome, the timeouts still happen even when cleared,
@@ -1621,8 +1832,6 @@ window.CodeMirror = (function() {
1832
1833 function done(e) {
1834 counter = Infinity;
1624 - var cur = posFromMouse(cm, e);
1625 - if (cur) doSelect(cur);
1835 e_preventDefault(e);
1836 focusInput(cm);
1837 off(document, "mousemove", move);
@@ -1638,11 +1847,48 @@ window.CodeMirror = (function() {
1847 on(document, "mouseup", up);
1848 }
1849
1850 + function gutterEvent(cm, e, type, prevent, signalfn) {
1851 + try { var mX = e.clientX, mY = e.clientY; }
1852 + catch(e) { return false; }
1853 + if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false;
1854 + if (prevent) e_preventDefault(e);
1855 +
1856 + var display = cm.display;
1857 + var lineBox = getRect(display.lineDiv);
1858 +
1859 + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
1860 + mY -= lineBox.top - display.viewOffset;
1861 +
1862 + for (var i = 0; i < cm.options.gutters.length; ++i) {
1863 + var g = display.gutters.childNodes[i];
1864 + if (g && getRect(g).right >= mX) {
1865 + var line = lineAtHeight(cm.doc, mY);
1866 + var gutter = cm.options.gutters[i];
1867 + signalfn(cm, type, cm, line, gutter, e);
1868 + return e_defaultPrevented(e);
1869 + }
1870 + }
1871 + }
1872 +
1873 + function contextMenuInGutter(cm, e) {
1874 + if (!hasHandler(cm, "gutterContextMenu")) return false;
1875 + return gutterEvent(cm, e, "gutterContextMenu", false, signal);
1876 + }
1877 +
1878 + function clickInGutter(cm, e) {
1879 + return gutterEvent(cm, e, "gutterClick", true, signalLater);
1880 + }
1881 +
1882 + // Kludge to work around strange IE behavior where it'll sometimes
1883 + // re-fire a series of drag-related events right after the drop (#1551)
1884 + var lastDrop = 0;
1885 +
1886 function onDrop(e) {
1887 var cm = this;
1643 - if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
1888 + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
1889 return;
1890 e_preventDefault(e);
1891 + if (ie) lastDrop = +new Date;
1892 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1893 if (!pos || isReadOnly(cm)) return;
1894 if (files && files.length && window.FileReader && window.File) {
@@ -1653,7 +1899,7 @@ window.CodeMirror = (function() {
1899 text[i] = reader.result;
1900 if (++read == n) {
1901 pos = clipPos(cm.doc, pos);
1656 - replaceRange(cm.doc, text.join(""), pos, "around", "paste");
1902 + makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
1903 }
1904 };
1905 reader.readAsText(file);
@@ -1675,41 +1921,16 @@ window.CodeMirror = (function() {
1921 if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
1922 cm.replaceSelection(text, null, "paste");
1923 focusInput(cm);
1678 - onFocus(cm);
1924 }
1925 }
1926 catch(e){}
1927 }
1928 }
1929
1685 - function clickInGutter(cm, e) {
1686 - var display = cm.display;
1687 - try { var mX = e.clientX, mY = e.clientY; }
1688 - catch(e) { return false; }
1689 -
1690 - if (mX >= Math.floor(display.gutters.getBoundingClientRect().right)) return false;
1691 - e_preventDefault(e);
1692 - if (!hasHandler(cm, "gutterClick")) return true;
1693 -
1694 - var lineBox = display.lineDiv.getBoundingClientRect();
1695 - if (mY > lineBox.bottom) return true;
1696 - mY -= lineBox.top - display.viewOffset;
1697 -
1698 - for (var i = 0; i < cm.options.gutters.length; ++i) {
1699 - var g = display.gutters.childNodes[i];
1700 - if (g && g.getBoundingClientRect().right >= mX) {
1701 - var line = lineAtHeight(cm.doc, mY);
1702 - var gutter = cm.options.gutters[i];
1703 - signalLater(cm, "gutterClick", cm, line, gutter, e);
1704 - break;
1705 - }
1706 - }
1707 - return true;
1708 - }
1709 -
1930 function onDragStart(cm, e) {
1711 - if (eventInWidget(cm.display, e)) return;
1712 -
1931 + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
1932 + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
1933 +
1934 var txt = cm.getSelection();
1935 e.dataTransfer.setData("Text", txt);
1936
@@ -1717,6 +1938,7 @@ window.CodeMirror = (function() {
1938 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1939 if (e.dataTransfer.setDragImage && !safari) {
1940 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
1941 + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
1942 if (opera) {
1943 img.width = img.height = 1;
1944 cm.display.wrapper.appendChild(img);
@@ -1735,6 +1957,7 @@ window.CodeMirror = (function() {
1957 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1958 if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1959 if (gecko) updateDisplay(cm, []);
1960 + startWorker(cm, 100);
1961 }
1962 function setScrollLeft(cm, val, isScroller) {
1963 if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
@@ -1772,6 +1995,11 @@ window.CodeMirror = (function() {
1995 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1996 else if (dy == null) dy = e.wheelDelta;
1997
1998 + var display = cm.display, scroll = display.scroller;
1999 + // Quit if there's nothing to scroll here
2000 + if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
2001 + dy && scroll.scrollHeight > scroll.clientHeight)) return;
2002 +
2003 // Webkit browsers on OS X abort momentum scrolls when the target
2004 // of the scroll event is removed from the scrollable element.
2005 // This hack (see related code in patchDisplay) makes sure the
@@ -1785,7 +2013,6 @@ window.CodeMirror = (function() {
2013 }
2014 }
2015
1788 - var display = cm.display, scroll = display.scroller;
2016 // On some browsers, horizontal scrolling will cause redraws to
2017 // happen before the gutter has been realigned, causing it to
2018 // wriggle around in a most unseemly way. When we have an
@@ -1838,22 +2065,22 @@ window.CodeMirror = (function() {
2065 // Ensure previous input has been read, so that the handler sees a
2066 // consistent view of the document
2067 if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
1841 - var doc = cm.doc, prevShift = doc.sel.shift;
2068 + var doc = cm.doc, prevShift = doc.sel.shift, done = false;
2069 try {
2070 if (isReadOnly(cm)) cm.state.suppressEdits = true;
2071 if (dropShift) doc.sel.shift = false;
1845 - bound(cm);
2072 + done = bound(cm) != Pass;
2073 } finally {
2074 doc.sel.shift = prevShift;
2075 cm.state.suppressEdits = false;
2076 }
1850 - return true;
2077 + return done;
2078 }
2079
2080 function allKeyMaps(cm) {
2081 var maps = cm.state.keyMaps.slice(0);
2082 + if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
2083 maps.push(cm.options.keyMap);
1856 - if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys);
2084 return maps;
2085 }
2086
@@ -1863,31 +2090,34 @@ window.CodeMirror = (function() {
2090 var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
2091 clearTimeout(maybeTransition);
2092 if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
1866 - if (getKeyMap(cm.options.keyMap) == startMap)
2093 + if (getKeyMap(cm.options.keyMap) == startMap) {
2094 cm.options.keyMap = (next.call ? next.call(null, cm) : next);
2095 + keyMapChanged(cm);
2096 + }
2097 }, 50);
2098
2099 var name = keyName(e, true), handled = false;
2100 if (!name) return false;
2101 var keymaps = allKeyMaps(cm);
2102
1874 - if (e_prop(e, "shiftKey")) {
2103 + if (e.shiftKey) {
2104 // First try to resolve full name (including 'Shift-'). Failing
2105 // that, see if there is a cursor-motion command (starting with
2106 // 'go') bound to the keyname without 'Shift-'.
2107 handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
2108 || lookupKey(name, keymaps, function(b) {
1880 - if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b);
2109 + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
2110 + return doHandleBinding(cm, b);
2111 });
2112 } else {
2113 handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
2114 }
1885 - if (handled == "stop") handled = false;
2115
2116 if (handled) {
2117 e_preventDefault(e);
2118 restartBlink(cm);
2119 if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
2120 + signalLater(cm, "keyHandled", cm, name, e);
2121 }
2122 return handled;
2123 }
@@ -1898,6 +2128,7 @@ window.CodeMirror = (function() {
2128 if (handled) {
2129 e_preventDefault(e);
2130 restartBlink(cm);
2131 + signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
2132 }
2133 return handled;
2134 }
@@ -1906,33 +2137,30 @@ window.CodeMirror = (function() {
2137 function onKeyDown(e) {
2138 var cm = this;
2139 if (!cm.state.focused) onFocus(cm);
1909 - if (ie && e.keyCode == 27) { e.returnValue = false; }
1910 - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1911 - var code = e_prop(e, "keyCode");
2140 + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2141 + if (ie && e.keyCode == 27) e.returnValue = false;
2142 + var code = e.keyCode;
2143 // IE does strange things with escape.
1913 - cm.doc.sel.shift = code == 16 || e_prop(e, "shiftKey");
2144 + cm.doc.sel.shift = code == 16 || e.shiftKey;
2145 // First give onKeyEvent option a chance to handle this.
2146 var handled = handleKeyBinding(cm, e);
2147 if (opera) {
2148 lastStoppedKey = handled ? code : null;
2149 // Opera has no cut event... we try to at least catch the key combo
1919 - if (!handled && code == 88 && !hasCopyEvent && e_prop(e, mac ? "metaKey" : "ctrlKey"))
2150 + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
2151 cm.replaceSelection("");
2152 }
2153 }
2154
2155 function onKeyPress(e) {
2156 var cm = this;
1926 - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1927 - var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
2157 + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2158 + var keyCode = e.keyCode, charCode = e.charCode;
2159 if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
2160 if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
2161 var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
1931 - if (this.options.electricChars && this.doc.mode.electricChars &&
1932 - this.options.smartIndent && !isReadOnly(this) &&
1933 - this.doc.mode.electricChars.indexOf(ch) > -1)
1934 - setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
2162 if (handleCharBinding(cm, e, ch)) return;
2163 + if (ie && !ie_lt9) cm.display.inputHasSelection = null;
2164 fastPoll(cm);
2165 }
2166
@@ -1943,7 +2171,10 @@ window.CodeMirror = (function() {
2171 cm.state.focused = true;
2172 if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
2173 cm.display.wrapper.className += " CodeMirror-focused";
1946 - resetInput(cm, true);
2174 + if (!cm.curOp) {
2175 + resetInput(cm, true);
2176 + if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
2177 + }
2178 }
2179 slowPoll(cm);
2180 restartBlink(cm);
@@ -1960,38 +2191,48 @@ window.CodeMirror = (function() {
2191
2192 var detectingSelectAll;
2193 function onContextMenu(cm, e) {
2194 + if (signalDOMEvent(cm, e, "contextmenu")) return;
2195 var display = cm.display, sel = cm.doc.sel;
1964 - if (eventInWidget(display, e)) return;
2196 + if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
2197
2198 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
2199 if (!pos || opera) return; // Opera is difficult.
1968 - if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
2200 +
2201 + // Reset the current text selection only if the click is done outside of the selection
2202 + // and 'resetSelectionOnContextMenu' option is true.
2203 + var reset = cm.options.resetSelectionOnContextMenu;
2204 + if (reset && (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)))
2205 operation(cm, setSelection)(cm.doc, pos, pos);
2206
2207 var oldCSS = display.input.style.cssText;
2208 display.inputDiv.style.position = "absolute";
2209 display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1974 - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
2210 + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: transparent; outline: none;" +
2211 "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
2212 focusInput(cm);
2213 resetInput(cm, true);
2214 // Adds "Select all" to context menu in FF
2215 if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
2216
2217 + function prepareSelectAllHack() {
2218 + if (display.input.selectionStart != null) {
2219 + var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value);
2220 + display.prevInput = "\u200b";
2221 + display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
2222 + }
2223 + }
2224 function rehide() {
2225 display.inputDiv.style.position = "relative";
2226 display.input.style.cssText = oldCSS;
2227 if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
2228 slowPoll(cm);
2229
1987 - // Try to detect the user choosing select-all
1988 - if (display.input.selectionStart != null && (!ie || ie_lt9)) {
2230 + // Try to detect the user choosing select-all
2231 + if (display.input.selectionStart != null) {
2232 + if (!ie || ie_lt9) prepareSelectAllHack();
2233 clearTimeout(detectingSelectAll);
1990 - var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0;
1991 - display.prevInput = " ";
1992 - display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
1993 - var poll = function(){
1994 - if (display.prevInput == " " && display.input.selectionStart == 0)
2234 + var i = 0, poll = function(){
2235 + if (display.prevInput == "\u200b" && display.input.selectionStart == 0)
2236 operation(cm, commands.selectAll)(cm);
2237 else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
2238 else resetInput(cm);
@@ -2000,6 +2241,7 @@ window.CodeMirror = (function() {
2241 }
2242 }
2243
2244 + if (ie && !ie_lt9) prepareSelectAllHack();
2245 if (captureMiddleClick) {
2246 e_stop(e);
2247 var mouseup = function() {
@@ -2014,16 +2256,36 @@ window.CodeMirror = (function() {
2256
2257 // UPDATING
2258
2017 - function changeEnd(change) {
2259 + var changeEnd = CodeMirror.changeEnd = function(change) {
2260 + if (!change.text) return change.to;
2261 return Pos(change.from.line + change.text.length - 1,
2262 lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
2263 + };
2264 +
2265 + // Make sure a position will be valid after the given change.
2266 + function clipPostChange(doc, change, pos) {
2267 + if (!posLess(change.from, pos)) return clipPos(doc, pos);
2268 + var diff = (change.text.length - 1) - (change.to.line - change.from.line);
2269 + if (pos.line > change.to.line + diff) {
2270 + var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
2271 + if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
2272 + return clipToLen(pos, getLine(doc, preLine).text.length);
2273 + }
2274 + if (pos.line == change.to.line + diff)
2275 + return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
2276 + getLine(doc, change.to.line).text.length - change.to.ch);
2277 + var inside = pos.line - change.from.line;
2278 + return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
2279 }
2280
2281 // Hint can be null|"end"|"start"|"around"|{anchor,head}
2023 - function computeSelAfterChange(sel, change, hint) {
2024 - if (hint && typeof hint == "object") return hint; // Assumed to be {from, to} object
2282 + function computeSelAfterChange(doc, change, hint) {
2283 + if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
2284 + return {anchor: clipPostChange(doc, change, hint.anchor),
2285 + head: clipPostChange(doc, change, hint.head)};
2286 +
2287 if (hint == "start") return {anchor: change.from, head: change.from};
2026 -
2288 +
2289 var end = changeEnd(change);
2290 if (hint == "around") return {anchor: change.from, head: end};
2291 if (hint == "end") return {anchor: end, head: end};
@@ -2037,7 +2299,29 @@ window.CodeMirror = (function() {
2299 if (pos.line == change.to.line) ch += end.ch - change.to.ch;
2300 return Pos(line, ch);
2301 };
2040 - return {anchor: adjustPos(sel.anchor), head: adjustPos(sel.head)};
2302 + return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
2303 + }
2304 +
2305 + function filterChange(doc, change, update) {
2306 + var obj = {
2307 + canceled: false,
2308 + from: change.from,
2309 + to: change.to,
2310 + text: change.text,
2311 + origin: change.origin,
2312 + cancel: function() { this.canceled = true; }
2313 + };
2314 + if (update) obj.update = function(from, to, text, origin) {
2315 + if (from) this.from = clipPos(doc, from);
2316 + if (to) this.to = clipPos(doc, to);
2317 + if (text) this.text = text;
2318 + if (origin !== undefined) this.origin = origin;
2319 + };
2320 + signal(doc, "beforeChange", doc, obj);
2321 + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
2322 +
2323 + if (obj.canceled) return null;
2324 + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
2325 }
2326
2327 // Replace the range from from to to by the strings in replacement.
@@ -2048,6 +2332,11 @@ window.CodeMirror = (function() {
2332 if (doc.cm.state.suppressEdits) return;
2333 }
2334
2335 + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
2336 + change = filterChange(doc, change, true);
2337 + if (!change) return;
2338 + }
2339 +
2340 // Possibly split or suppress the update based on the presence
2341 // of read-only spans in its range.
2342 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
@@ -2062,7 +2351,8 @@ window.CodeMirror = (function() {
2351 }
2352
2353 function makeChangeNoReadonly(doc, change, selUpdate) {
2065 - var selAfter = computeSelAfterChange(doc.sel, change, selUpdate);
2354 + if (change.text.length == 1 && change.text[0] == "" && posEq(change.from, change.to)) return;
2355 + var selAfter = computeSelAfterChange(doc, change, selUpdate);
2356 addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
2357
2358 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
@@ -2078,21 +2368,31 @@ window.CodeMirror = (function() {
2368 }
2369
2370 function makeChangeFromHistory(doc, type) {
2371 + if (doc.cm && doc.cm.state.suppressEdits) return;
2372 +
2373 var hist = doc.history;
2374 var event = (type == "undo" ? hist.done : hist.undone).pop();
2375 if (!event) return;
2084 - hist.dirtyCounter += type == "undo" ? -1 : 1;
2376
2377 var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
2087 - anchorAfter: event.anchorBefore, headAfter: event.headBefore};
2378 + anchorAfter: event.anchorBefore, headAfter: event.headBefore,
2379 + generation: hist.generation};
2380 (type == "undo" ? hist.undone : hist.done).push(anti);
2381 + hist.generation = event.generation || ++hist.maxGeneration;
2382 +
2383 + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
2384
2385 for (var i = event.changes.length - 1; i >= 0; --i) {
2386 var change = event.changes[i];
2387 change.origin = type;
2388 + if (filter && !filterChange(doc, change, false)) {
2389 + (type == "undo" ? hist.done : hist.undone).length = 0;
2390 + return;
2391 + }
2392 +
2393 anti.changes.push(historyChangeFromChange(doc, change));
2394
2095 - var after = i ? computeSelAfterChange(doc.sel, change, null)
2395 + var after = i ? computeSelAfterChange(doc, change, null)
2396 : {anchor: event.anchorBefore, head: event.headBefore};
2397 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
2398 var rebased = [];
@@ -2138,7 +2438,9 @@ window.CodeMirror = (function() {
2438 text: [change.text[0]], origin: change.origin};
2439 }
2440
2141 - if (!selAfter) selAfter = computeSelAfterChange(doc.sel, change, null);
2441 + change.removed = getBetween(doc, change.from, change.to);
2442 +
2443 + if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
2444 if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
2445 else updateDoc(doc, change, spans, selAfter);
2446 }
@@ -2157,6 +2459,9 @@ window.CodeMirror = (function() {
2459 });
2460 }
2461
2462 + if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
2463 + cm.curOp.cursorActivity = true;
2464 +
2465 updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
2466
2467 if (!cm.options.lineWrapping) {
@@ -2179,8 +2484,12 @@ window.CodeMirror = (function() {
2484 var lendiff = change.text.length - (to.line - from.line) - 1;
2485 // Remember that these lines changed, for updating the display
2486 regChange(cm, from.line, to.line + 1, lendiff);
2487 +
2488 if (hasHandler(cm, "change")) {
2183 - var changeObj = {from: from, to: to, text: change.text, origin: change.origin};
2489 + var changeObj = {from: from, to: to,
2490 + text: change.text,
2491 + removed: change.removed,
2492 + origin: change.origin};
2493 if (cm.curOp.textChanged) {
2494 for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
2495 cur.next = changeObj;
@@ -2205,6 +2514,7 @@ window.CodeMirror = (function() {
2514
2515 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2516 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2517 + function cmp(a, b) {return a.line - b.line || a.ch - b.ch;}
2518 function copyPos(x) {return Pos(x.line, x.ch);}
2519
2520 // SELECTION
@@ -2214,7 +2524,10 @@ window.CodeMirror = (function() {
2524 if (pos.line < doc.first) return Pos(doc.first, 0);
2525 var last = doc.first + doc.size - 1;
2526 if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
2217 - var ch = pos.ch, linelen = getLine(doc, pos.line).text.length;
2527 + return clipToLen(pos, getLine(doc, pos.line).text.length);
2528 + }
2529 + function clipToLen(pos, linelen) {
2530 + var ch = pos.ch;
2531 if (ch == null || ch > linelen) return Pos(pos.line, linelen);
2532 else if (ch < 0) return Pos(pos.line, 0);
2533 else return pos;
@@ -2242,12 +2555,27 @@ window.CodeMirror = (function() {
2555 if (doc.cm) doc.cm.curOp.userSelChange = true;
2556 }
2557
2558 + function filterSelectionChange(doc, anchor, head) {
2559 + var obj = {anchor: anchor, head: head};
2560 + signal(doc, "beforeSelectionChange", doc, obj);
2561 + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
2562 + obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
2563 + return obj;
2564 + }
2565 +
2566 // Update the selection. Last two args are only used by
2567 // updateDoc, since they have to be expressed in the line
2568 // numbers before the update.
2569 function setSelection(doc, anchor, head, bias, checkAtomic) {
2570 + if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
2571 + var filtered = filterSelectionChange(doc, anchor, head);
2572 + head = filtered.head;
2573 + anchor = filtered.anchor;
2574 + }
2575 +
2576 var sel = doc.sel;
2577 sel.goalColumn = null;
2578 + if (bias == null) bias = posLess(head, sel.head) ? -1 : 1;
2579 // Skip over atomic spans.
2580 if (checkAtomic || !posEq(anchor, sel.anchor))
2581 anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
@@ -2262,7 +2590,10 @@ window.CodeMirror = (function() {
2590 sel.to = inv ? anchor : head;
2591
2592 if (doc.cm)
2265 - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
2593 + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
2594 + doc.cm.curOp.cursorActivity = true;
2595 +
2596 + signalLater(doc, "cursorActivity", doc);
2597 }
2598
2599 function reCheckSelection(cm) {
@@ -2274,16 +2605,20 @@ window.CodeMirror = (function() {
2605 var dir = bias || 1;
2606 doc.cantEdit = false;
2607 search: for (;;) {
2277 - var line = getLine(doc, curPos.line), toClear;
2608 + var line = getLine(doc, curPos.line);
2609 if (line.markedSpans) {
2610 for (var i = 0; i < line.markedSpans.length; ++i) {
2611 var sp = line.markedSpans[i], m = sp.marker;
2612 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2613 (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2283 - if (mayClear && m.clearOnEnter) {
2284 - (toClear || (toClear = [])).push(m);
2285 - continue;
2286 - } else if (!m.atomic) continue;
2614 + if (mayClear) {
2615 + signal(m, "beforeCursorEnter");
2616 + if (m.explicitlyCleared) {
2617 + if (!line.markedSpans) break;
2618 + else {--i; continue;}
2619 + }
2620 + }
2621 + if (!m.atomic) continue;
2622 var newPos = m.find()[dir < 0 ? "from" : "to"];
2623 if (posEq(newPos, curPos)) {
2624 newPos.ch += dir;
@@ -2310,7 +2645,6 @@ window.CodeMirror = (function() {
2645 continue search;
2646 }
2647 }
2313 - if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear();
2648 }
2649 return curPos;
2650 }
@@ -2319,27 +2653,31 @@ window.CodeMirror = (function() {
2653 // SCROLLING
2654
2655 function scrollCursorIntoView(cm) {
2322 - var coords = scrollPosIntoView(cm, cm.doc.sel.head);
2656 + var coords = scrollPosIntoView(cm, cm.doc.sel.head, null, cm.options.cursorScrollMargin);
2657 if (!cm.state.focused) return;
2324 - var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
2658 + var display = cm.display, box = getRect(display.sizer), doScroll = null;
2659 if (coords.top + box.top < 0) doScroll = true;
2660 else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2661 if (doScroll != null && !phantom) {
2328 - var hidden = display.cursor.style.display == "none";
2329 - if (hidden) {
2330 - display.cursor.style.display = "";
2331 - display.cursor.style.left = coords.left + "px";
2332 - display.cursor.style.top = (coords.top - display.viewOffset) + "px";
2333 - }
2334 - display.cursor.scrollIntoView(doScroll);
2335 - if (hidden) display.cursor.style.display = "none";
2662 + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
2663 + (coords.top - display.viewOffset) + "px; height: " +
2664 + (coords.bottom - coords.top + scrollerCutOff) + "px; left: " +
2665 + coords.left + "px; width: 2px;");
2666 + cm.display.lineSpace.appendChild(scrollNode);
2667 + scrollNode.scrollIntoView(doScroll);
2668 + cm.display.lineSpace.removeChild(scrollNode);
2669 }
2670 }
2671
2339 - function scrollPosIntoView(cm, pos) {
2672 + function scrollPosIntoView(cm, pos, end, margin) {
2673 + if (margin == null) margin = 0;
2674 for (;;) {
2675 var changed = false, coords = cursorCoords(cm, pos);
2342 - var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
2676 + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
2677 + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left),
2678 + Math.min(coords.top, endCoords.top) - margin,
2679 + Math.max(coords.left, endCoords.left),
2680 + Math.max(coords.bottom, endCoords.bottom) + margin);
2681 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
2682 if (scrollPos.scrollTop != null) {
2683 setScrollTop(cm, scrollPos.scrollTop);
@@ -2360,13 +2698,17 @@ window.CodeMirror = (function() {
2698 }
2699
2700 function calculateScrollPos(cm, x1, y1, x2, y2) {
2363 - var display = cm.display, pt = paddingTop(display);
2364 - y1 += pt; y2 += pt;
2701 + var display = cm.display, snapMargin = textHeight(cm.display);
2702 + if (y1 < 0) y1 = 0;
2703 var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2366 - var docBottom = cm.doc.height + 2 * pt;
2367 - var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
2368 - if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
2369 - else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
2704 + var docBottom = cm.doc.height + paddingVert(display);
2705 + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
2706 + if (y1 < screentop) {
2707 + result.scrollTop = atTop ? 0 : y1;
2708 + } else if (y2 > screentop + screen) {
2709 + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
2710 + if (newTop != screentop) result.scrollTop = newTop;
2711 + }
2712
2713 var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2714 x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
@@ -2381,11 +2723,23 @@ window.CodeMirror = (function() {
2723 return result;
2724 }
2725
2726 + function updateScrollPos(cm, left, top) {
2727 + cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
2728 + scrollTop: top == null ? cm.doc.scrollTop : top};
2729 + }
2730 +
2731 + function addToScrollPos(cm, left, top) {
2732 + var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});
2733 + var scroll = cm.display.scroller;
2734 + pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));
2735 + pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));
2736 + }
2737 +
2738 // API UTILITIES
2739
2740 function indentLine(cm, n, how, aggressive) {
2741 var doc = cm.doc;
2388 - if (!how) how = "add";
2742 + if (how == null) how = "add";
2743 if (how == "smart") {
2744 if (!cm.doc.mode.indent) how = "prev";
2745 else var state = getStateBefore(cm, n);
@@ -2394,7 +2748,10 @@ window.CodeMirror = (function() {
2748 var tabSize = cm.options.tabSize;
2749 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
2750 var curSpaceString = line.text.match(/^\s*/)[0], indentation;
2397 - if (how == "smart") {
2751 + if (!aggressive && !/\S/.test(line.text)) {
2752 + indentation = 0;
2753 + how = "not";
2754 + } else if (how == "smart") {
2755 indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2756 if (indentation == Pass) {
2757 if (!aggressive) return;
@@ -2408,6 +2765,8 @@ window.CodeMirror = (function() {
2765 indentation = curSpace + cm.options.indentUnit;
2766 } else if (how == "subtract") {
2767 indentation = curSpace - cm.options.indentUnit;
2768 + } else if (typeof how == "number") {
2769 + indentation = curSpace + how;
2770 }
2771 indentation = Math.max(0, indentation);
2772
@@ -2418,6 +2777,8 @@ window.CodeMirror = (function() {
2777
2778 if (indentString != curSpaceString)
2779 replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
2780 + else if (doc.sel.head.line == n && doc.sel.head.ch < curSpaceString.length)
2781 + setSelection(doc, Pos(n, curSpaceString.length), Pos(n, curSpaceString.length), 1);
2782 line.stateAfter = null;
2783 }
2784
@@ -2431,12 +2792,13 @@ window.CodeMirror = (function() {
2792 return line;
2793 }
2794
2434 - function findPosH(doc, dir, unit, visually) {
2435 - var end = doc.sel.head, line = end.line, ch = end.ch;
2795 + function findPosH(doc, pos, dir, unit, visually) {
2796 + var line = pos.line, ch = pos.ch, origDir = dir;
2797 var lineObj = getLine(doc, line);
2798 + var possible = true;
2799 function findNextLine() {
2800 var l = line + dir;
2439 - if (l < doc.first || l >= doc.first + doc.size) return false;
2801 + if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
2802 line = l;
2803 return lineObj = getLine(doc, l);
2804 }
@@ -2446,32 +2808,60 @@ window.CodeMirror = (function() {
2808 if (!boundToLine && findNextLine()) {
2809 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
2810 else ch = dir < 0 ? lineObj.text.length : 0;
2449 - } else return false;
2811 + } else return (possible = false);
2812 } else ch = next;
2813 return true;
2814 }
2815 +
2816 if (unit == "char") moveOnce();
2817 else if (unit == "column") moveOnce(true);
2455 - else if (unit == "word") {
2456 - var sawWord = false;
2457 - for (;;) {
2458 - if (dir < 0) if (!moveOnce()) break;
2459 - if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
2460 - else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
2461 - if (dir > 0) if (!moveOnce()) break;
2818 + else if (unit == "word" || unit == "group") {
2819 + var sawType = null, group = unit == "group";
2820 + for (var first = true;; first = false) {
2821 + if (dir < 0 && !moveOnce(!first)) break;
2822 + var cur = lineObj.text.charAt(ch) || "\n";
2823 + var type = isWordChar(cur) ? "w"
2824 + : !group ? null
2825 + : /\s/.test(cur) ? null
2826 + : "p";
2827 + if (sawType && sawType != type) {
2828 + if (dir < 0) {dir = 1; moveOnce();}
2829 + break;
2830 + }
2831 + if (type) sawType = type;
2832 + if (dir > 0 && !moveOnce(!first)) break;
2833 }
2834 }
2464 - return skipAtomic(doc, Pos(line, ch), dir, true);
2835 + var result = skipAtomic(doc, Pos(line, ch), origDir, true);
2836 + if (!possible) result.hitSide = true;
2837 + return result;
2838 + }
2839 +
2840 + function findPosV(cm, pos, dir, unit) {
2841 + var doc = cm.doc, x = pos.left, y;
2842 + if (unit == "page") {
2843 + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2844 + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
2845 + } else if (unit == "line") {
2846 + y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2847 + }
2848 + for (;;) {
2849 + var target = coordsChar(cm, x, y);
2850 + if (!target.outside) break;
2851 + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
2852 + y += dir * 5;
2853 + }
2854 + return target;
2855 }
2856
2857 function findWordAt(line, pos) {
2858 var start = pos.ch, end = pos.ch;
2859 if (line) {
2470 - if (pos.after === false || end == line.length) --start; else ++end;
2860 + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
2861 var startChar = line.charAt(start);
2472 - var check = isWordChar(startChar) ? isWordChar :
2473 - /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} :
2474 - function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2862 + var check = isWordChar(startChar) ? isWordChar
2863 + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
2864 + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2865 while (start > 0 && check(line.charAt(start - 1))) --start;
2866 while (end < line.length && check(line.charAt(end))) ++end;
2867 }
@@ -2488,7 +2878,8 @@ window.CodeMirror = (function() {
2878 // 'wrap f in an operation, performed on its `this` parameter'
2879
2880 CodeMirror.prototype = {
2491 - focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2881 + constructor: CodeMirror,
2882 + focus: function(){window.focus(); focusInput(this); fastPoll(this);},
2883
2884 setOption: function(option, value) {
2885 var options = this.options, old = options[option];
@@ -2501,13 +2892,13 @@ window.CodeMirror = (function() {
2892 getOption: function(option) {return this.options[option];},
2893 getDoc: function() {return this.doc;},
2894
2504 - addKeyMap: function(map) {
2505 - this.state.keyMaps.push(map);
2895 + addKeyMap: function(map, bottom) {
2896 + this.state.keyMaps[bottom ? "push" : "unshift"](map);
2897 },
2898 removeKeyMap: function(map) {
2899 var maps = this.state.keyMaps;
2900 for (var i = 0; i < maps.length; ++i)
2510 - if ((typeof map == "string" ? maps[i].name : maps[i]) == map) {
2901 + if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
2902 maps.splice(i, 1);
2903 return true;
2904 }
@@ -2523,7 +2914,8 @@ window.CodeMirror = (function() {
2914 removeOverlay: operation(null, function(spec) {
2915 var overlays = this.state.overlays;
2916 for (var i = 0; i < overlays.length; ++i) {
2526 - if (overlays[i].modeSpec == spec) {
2917 + var cur = overlays[i].modeSpec;
2918 + if (cur == spec || typeof spec == "string" && cur.name == spec) {
2919 overlays.splice(i, 1);
2920 this.state.modeGen++;
2921 regChange(this);
@@ -2533,7 +2925,7 @@ window.CodeMirror = (function() {
2925 }),
2926
2927 indentLine: operation(null, function(n, dir, aggressive) {
2536 - if (typeof dir != "string") {
2928 + if (typeof dir != "string" && typeof dir != "number") {
2929 if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2930 else dir = dir ? "add" : "subtract";
2931 }
@@ -2541,17 +2933,17 @@ window.CodeMirror = (function() {
2933 }),
2934 indentSelection: operation(null, function(how) {
2935 var sel = this.doc.sel;
2544 - if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
2936 + if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how, true);
2937 var e = sel.to.line - (sel.to.ch ? 0 : 1);
2938 for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
2939 }),
2940
2941 // Fetch the parser token for a given character. Useful for hacks
2942 // that want to inspect the mode state (say, for completion).
2551 - getTokenAt: function(pos) {
2943 + getTokenAt: function(pos, precise) {
2944 var doc = this.doc;
2945 pos = clipPos(doc, pos);
2554 - var state = getStateBefore(this, pos.line), mode = this.doc.mode;
2946 + var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
2947 var line = getLine(doc, pos.line);
2948 var stream = new StringStream(line.text, this.options.tabSize);
2949 while (stream.pos < pos.ch && !stream.eol()) {
@@ -2566,10 +2958,57 @@ window.CodeMirror = (function() {
2958 state: state};
2959 },
2960
2569 - getStateAfter: function(line) {
2961 + getTokenTypeAt: function(pos) {
2962 + pos = clipPos(this.doc, pos);
2963 + var styles = getLineStyles(this, getLine(this.doc, pos.line));
2964 + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
2965 + if (ch == 0) return styles[2];
2966 + for (;;) {
2967 + var mid = (before + after) >> 1;
2968 + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
2969 + else if (styles[mid * 2 + 1] < ch) before = mid + 1;
2970 + else return styles[mid * 2 + 2];
2971 + }
2972 + },
2973 +
2974 + getModeAt: function(pos) {
2975 + var mode = this.doc.mode;
2976 + if (!mode.innerMode) return mode;
2977 + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
2978 + },
2979 +
2980 + getHelper: function(pos, type) {
2981 + return this.getHelpers(pos, type)[0];
2982 + },
2983 +
2984 + getHelpers: function(pos, type) {
2985 + var found = [];
2986 + if (!helpers.hasOwnProperty(type)) return helpers;
2987 + var help = helpers[type], mode = this.getModeAt(pos);
2988 + if (typeof mode[type] == "string") {
2989 + if (help[mode[type]]) found.push(help[mode[type]]);
2990 + } else if (mode[type]) {
2991 + for (var i = 0; i < mode[type].length; i++) {
2992 + var val = help[mode[type][i]];
2993 + if (val) found.push(val);
2994 + }
2995 + } else if (mode.helperType && help[mode.helperType]) {
2996 + found.push(help[mode.helperType]);
2997 + } else if (help[mode.name]) {
2998 + found.push(help[mode.name]);
2999 + }
3000 + for (var i = 0; i < help._global.length; i++) {
3001 + var cur = help._global[i];
3002 + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)
3003 + found.push(cur.val);
3004 + }
3005 + return found;
3006 + },
3007 +
3008 + getStateAfter: function(line, precise) {
3009 var doc = this.doc;
3010 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
2572 - return getStateBefore(this, line + 1);
3011 + return getStateBefore(this, line + 1, precise);
3012 },
3013
3014 cursorCoords: function(start, mode) {
@@ -2584,14 +3023,26 @@ window.CodeMirror = (function() {
3023 return charCoords(this, clipPos(this.doc, pos), mode || "page");
3024 },
3025
2587 - coordsChar: function(coords) {
2588 - var off = this.display.lineSpace.getBoundingClientRect();
2589 - var scrollY = window.pageYOffset || (document.documentElement || document.body).scrollTop;
2590 - var scrollX = window.pageXOffset || (document.documentElement || document.body).scrollLeft;
2591 - return coordsChar(this, coords.left - off.left - scrollX, coords.top - off.top - scrollY);
3026 + coordsChar: function(coords, mode) {
3027 + coords = fromCoordSystem(this, coords, mode || "page");
3028 + return coordsChar(this, coords.left, coords.top);
3029 + },
3030 +
3031 + lineAtHeight: function(height, mode) {
3032 + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
3033 + return lineAtHeight(this.doc, height + this.display.viewOffset);
3034 + },
3035 + heightAtLine: function(line, mode) {
3036 + var end = false, last = this.doc.first + this.doc.size - 1;
3037 + if (line < this.doc.first) line = this.doc.first;
3038 + else if (line > last) { line = last; end = true; }
3039 + var lineObj = getLine(this.doc, line);
3040 + return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
3041 + (end ? lineObj.height : 0);
3042 },
3043
3044 defaultTextHeight: function() { return textHeight(this.display); },
3045 + defaultCharWidth: function() { return charWidth(this.display); },
3046
3047 setGutterMarker: operation(null, function(line, gutterID, value) {
3048 return changeLine(this, line, function(line) {
@@ -2618,7 +3069,7 @@ window.CodeMirror = (function() {
3069 return changeLine(this, handle, function(line) {
3070 var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
3071 if (!line[prop]) line[prop] = cls;
2621 - else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false;
3072 + else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
3073 else line[prop] += " " + cls;
3074 return true;
3075 });
@@ -2631,9 +3082,10 @@ window.CodeMirror = (function() {
3082 if (!cur) return false;
3083 else if (cls == null) line[prop] = null;
3084 else {
2634 - var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), "");
2635 - if (upd == cur) return false;
2636 - line[prop] = upd || null;
3085 + var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
3086 + if (!found) return false;
3087 + var end = found.index + found[0].length;
3088 + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
3089 }
3090 return true;
3091 });
@@ -2681,7 +3133,7 @@ window.CodeMirror = (function() {
3133 if (left + node.offsetWidth > hspace)
3134 left = hspace - node.offsetWidth;
3135 }
2684 - node.style.top = (top + paddingTop(display)) + "px";
3136 + node.style.top = top + "px";
3137 node.style.left = node.style.right = "";
3138 if (horiz == "right") {
3139 left = display.sizer.clientWidth - node.offsetWidth;
@@ -2699,51 +3151,71 @@ window.CodeMirror = (function() {
3151
3152 execCommand: function(cmd) {return commands[cmd](this);},
3153
2702 - // Stuff used by commands, probably not much use to outside code.
3154 + findPosH: function(from, amount, unit, visually) {
3155 + var dir = 1;
3156 + if (amount < 0) { dir = -1; amount = -amount; }
3157 + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
3158 + cur = findPosH(this.doc, cur, dir, unit, visually);
3159 + if (cur.hitSide) break;
3160 + }
3161 + return cur;
3162 + },
3163 +
3164 moveH: operation(null, function(dir, unit) {
2704 - var sel = this.doc.sel, pos = dir < 0 ? sel.from : sel.to;
3165 + var sel = this.doc.sel, pos;
3166 if (sel.shift || sel.extend || posEq(sel.from, sel.to))
2706 - pos = findPosH(this.doc, dir, unit, this.options.rtlMoveVisually);
3167 + pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
3168 + else
3169 + pos = dir < 0 ? sel.from : sel.to;
3170 extendSelection(this.doc, pos, pos, dir);
3171 }),
3172
3173 deleteH: operation(null, function(dir, unit) {
3174 var sel = this.doc.sel;
3175 if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
2713 - else replaceRange(this.doc, "", sel.from, findPosH(this.doc, dir, unit, false), "+delete");
3176 + else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
3177 this.curOp.userSelChange = true;
3178 }),
3179
2717 - moveV: operation(null, function(dir, unit) {
2718 - var doc = this.doc, display = this.display;
2719 - var cur = doc.sel.head, pos = cursorCoords(this, cur, "div");
2720 - var x = pos.left, y;
2721 - if (doc.sel.goalColumn != null) x = doc.sel.goalColumn;
2722 - if (unit == "page") {
2723 - var pageSize = Math.min(display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2724 - y = pos.top + dir * pageSize;
2725 - } else if (unit == "line") {
2726 - y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
3180 + findPosV: function(from, amount, unit, goalColumn) {
3181 + var dir = 1, x = goalColumn;
3182 + if (amount < 0) { dir = -1; amount = -amount; }
3183 + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
3184 + var coords = cursorCoords(this, cur, "div");
3185 + if (x == null) x = coords.left;
3186 + else coords.left = x;
3187 + cur = findPosV(this, coords, dir, unit);
3188 + if (cur.hitSide) break;
3189 }
2728 - do {
2729 - var target = coordsChar(this, x, y);
2730 - y += dir * 5;
2731 - } while (target.outside && (dir < 0 ? y > 0 : y < doc.height));
3190 + return cur;
3191 + },
3192
2733 - if (unit == "page") display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top;
3193 + moveV: operation(null, function(dir, unit) {
3194 + var sel = this.doc.sel, target, goal;
3195 + if (sel.shift || sel.extend || posEq(sel.from, sel.to)) {
3196 + var pos = cursorCoords(this, sel.head, "div");
3197 + if (sel.goalColumn != null) pos.left = sel.goalColumn;
3198 + target = findPosV(this, pos, dir, unit);
3199 + if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
3200 + goal = pos.left;
3201 + } else {
3202 + target = dir < 0 ? sel.from : sel.to;
3203 + }
3204 extendSelection(this.doc, target, target, dir);
2735 - doc.sel.goalColumn = x;
3205 + if (goal != null) sel.goalColumn = goal;
3206 }),
3207
2738 - toggleOverwrite: function() {
3208 + toggleOverwrite: function(value) {
3209 + if (value != null && value == this.state.overwrite) return;
3210 if (this.state.overwrite = !this.state.overwrite)
3211 this.display.cursor.className += " CodeMirror-overwrite";
3212 else
3213 this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
3214 },
3215 + hasFocus: function() { return this.state.focused; },
3216
3217 scrollTo: operation(null, function(x, y) {
2746 - this.curOp.updateScrollPos = {scrollLeft: x, scrollTop: y};
3218 + updateScrollPos(this, x, y);
3219 }),
3220 getScrollInfo: function() {
3221 var scroller = this.display.scroller, co = scrollerCutOff;
@@ -2752,34 +3224,45 @@ window.CodeMirror = (function() {
3224 clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
3225 },
3226
2755 - scrollIntoView: function(pos) {
2756 - if (typeof pos == "number") pos = Pos(pos, 0);
2757 - if (!pos || pos.line != null) {
2758 - pos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
2759 - scrollPosIntoView(this, pos);
2760 - } else {
2761 - scrollIntoView(this, pos.left, pos.top, pos.right, pos.bottom);
3227 + scrollIntoView: operation(null, function(range, margin) {
3228 + if (range == null) range = {from: this.doc.sel.head, to: null};
3229 + else if (typeof range == "number") range = {from: Pos(range, 0), to: null};
3230 + else if (range.from == null) range = {from: range, to: null};
3231 + if (!range.to) range.to = range.from;
3232 + if (!margin) margin = 0;
3233 +
3234 + var coords = range;
3235 + if (range.from.line != null) {
3236 + this.curOp.scrollToPos = {from: range.from, to: range.to, margin: margin};
3237 + coords = {from: cursorCoords(this, range.from),
3238 + to: cursorCoords(this, range.to)};
3239 }
2763 - },
3240 + var sPos = calculateScrollPos(this, Math.min(coords.from.left, coords.to.left),
3241 + Math.min(coords.from.top, coords.to.top) - margin,
3242 + Math.max(coords.from.right, coords.to.right),
3243 + Math.max(coords.from.bottom, coords.to.bottom) + margin);
3244 + updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
3245 + }),
3246
2765 - setSize: function(width, height) {
3247 + setSize: operation(null, function(width, height) {
3248 function interpret(val) {
3249 return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
3250 }
3251 if (width != null) this.display.wrapper.style.width = interpret(width);
3252 if (height != null) this.display.wrapper.style.height = interpret(height);
2771 - this.refresh();
2772 - },
2773 -
2774 - on: function(type, f) {on(this, type, f);},
2775 - off: function(type, f) {off(this, type, f);},
3253 + if (this.options.lineWrapping)
3254 + this.display.measureLineCache.length = this.display.measureLineCachePos = 0;
3255 + this.curOp.forceUpdate = true;
3256 + }),
3257
3258 operation: function(f){return runInOp(this, f);},
3259
3260 refresh: operation(null, function() {
3261 + var badHeight = this.display.cachedTextHeight == null;
3262 clearCaches(this);
2781 - this.curOp.updateScrollPos = {scrollTop: this.doc.scrollTop, scrollLeft: this.doc.scrollLeft};
3263 + updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);
3264 regChange(this);
3265 + if (badHeight) estimateLineHeights(this);
3266 }),
3267
3268 swapDoc: operation(null, function(doc) {
@@ -2787,7 +3270,9 @@ window.CodeMirror = (function() {
3270 old.cm = null;
3271 attachDoc(this, doc);
3272 clearCaches(this);
2790 - this.curOp.updateScrollPos = {scrollTop: doc.scrollTop, scrollLeft: doc.scrollLeft};
3273 + resetInput(this, true);
3274 + updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
3275 + signalLater(this, "swapDoc", this, old);
3276 return old;
3277 }),
3278
@@ -2796,6 +3281,7 @@ window.CodeMirror = (function() {
3281 getScrollerElement: function(){return this.display.scroller;},
3282 getGutterElement: function(){return this.display.gutters;}
3283 };
3284 + eventMixin(CodeMirror);
3285
3286 // OPTION DEFAULTS
3287
@@ -2830,8 +3316,14 @@ window.CodeMirror = (function() {
3316 clearCaches(cm);
3317 regChange(cm);
3318 }, true);
3319 + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) {
3320 + cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
3321 + cm.refresh();
3322 + }, true);
3323 + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true);
3324 option("electricChars", true);
3325 option("rtlMoveVisually", !windows);
3326 + option("wholeLineUpdateBefore", true);
3327
3328 option("theme", "default", function(cm) {
3329 themeChanged(cm);
@@ -2852,6 +3344,7 @@ window.CodeMirror = (function() {
3344 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
3345 cm.refresh();
3346 }, true);
3347 + option("coverGutterNextToScrollbar", false, updateScrollbars, true);
3348 option("lineNumbers", false, function(cm) {
3349 setGuttersForLineNumbers(cm.options);
3350 guttersChanged(cm);
@@ -2859,21 +3352,36 @@ window.CodeMirror = (function() {
3352 option("firstLineNumber", 1, guttersChanged, true);
3353 option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
3354 option("showCursorWhenSelecting", false, updateSelection, true);
2862 -
3355 +
3356 + option("resetSelectionOnContextMenu", true);
3357 +
3358 option("readOnly", false, function(cm, val) {
2864 - if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
2865 - else if (!val) resetInput(cm, true);
3359 + if (val == "nocursor") {
3360 + onBlur(cm);
3361 + cm.display.input.blur();
3362 + cm.display.disabled = true;
3363 + } else {
3364 + cm.display.disabled = false;
3365 + if (!val) resetInput(cm, true);
3366 + }
3367 });
3368 option("dragDrop", true);
3369
3370 option("cursorBlinkRate", 530);
3371 + option("cursorScrollMargin", 0);
3372 option("cursorHeight", 1);
3373 option("workTime", 100);
3374 option("workDelay", 100);
3375 option("flattenSpans", true);
3376 option("pollInterval", 100);
3377 option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
3378 + option("historyEventDelay", 500);
3379 option("viewportMargin", 10, function(cm){cm.refresh();}, true);
3380 + option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
3381 + option("crudeMeasuringFrom", 10000);
3382 + option("moveInputWithCursor", true, function(cm, val) {
3383 + if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
3384 + });
3385
3386 option("tabindex", null, function(cm, val) {
3387 cm.display.input.tabIndex = val || "";
@@ -2899,16 +3407,21 @@ window.CodeMirror = (function() {
3407 };
3408
3409 CodeMirror.resolveMode = function(spec) {
2902 - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
3410 + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
3411 spec = mimeModes[spec];
2904 - else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
3412 + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
3413 + var found = mimeModes[spec.name];
3414 + spec = createObj(found, spec);
3415 + spec.name = found.name;
3416 + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
3417 return CodeMirror.resolveMode("application/xml");
3418 + }
3419 if (typeof spec == "string") return {name: spec};
3420 else return spec || {name: "null"};
3421 };
3422
3423 CodeMirror.getMode = function(options, spec) {
2911 - spec = CodeMirror.resolveMode(spec);
3424 + var spec = CodeMirror.resolveMode(spec);
3425 var mfactory = modes[spec.name];
3426 if (!mfactory) return CodeMirror.getMode(options, "text/plain");
3427 var modeObj = mfactory(options, spec);
@@ -2921,6 +3434,10 @@ window.CodeMirror = (function() {
3434 }
3435 }
3436 modeObj.name = spec.name;
3437 + if (spec.helperType) modeObj.helperType = spec.helperType;
3438 + if (spec.modeProps) for (var prop in spec.modeProps)
3439 + modeObj[prop] = spec.modeProps[prop];
3440 +
3441 return modeObj;
3442 };
3443
@@ -2940,12 +3457,28 @@ window.CodeMirror = (function() {
3457 CodeMirror.defineExtension = function(name, func) {
3458 CodeMirror.prototype[name] = func;
3459 };
2943 -
3460 + CodeMirror.defineDocExtension = function(name, func) {
3461 + Doc.prototype[name] = func;
3462 + };
3463 CodeMirror.defineOption = option;
3464
3465 var initHooks = [];
3466 CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
3467
3468 + var helpers = CodeMirror.helpers = {};
3469 + CodeMirror.registerHelper = function(type, name, value) {
3470 + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []};
3471 + helpers[type][name] = value;
3472 + };
3473 + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
3474 + CodeMirror.registerHelper(type, name, value);
3475 + helpers[type]._global.push({pred: predicate, val: value});
3476 + };
3477 +
3478 + // UTILITIES
3479 +
3480 + CodeMirror.isWordChar = isWordChar;
3481 +
3482 // MODE STATE HANDLING
3483
3484 // Utility functions for working with state. Exported because modes
@@ -2971,6 +3504,7 @@ window.CodeMirror = (function() {
3504 CodeMirror.innerMode = function(mode, state) {
3505 while (mode.innerMode) {
3506 var info = mode.innerMode(state);
3507 + if (!info || info.mode == mode) break;
3508 state = info.state;
3509 mode = info.mode;
3510 }
@@ -2991,6 +3525,10 @@ window.CodeMirror = (function() {
3525 var l = cm.getCursor().line;
3526 cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
3527 },
3528 + delLineLeft: function(cm) {
3529 + var cur = cm.getCursor();
3530 + cm.replaceRange("", Pos(cur.line, 0), cur, "+delete");
3531 + },
3532 undo: function(cm) {cm.undo();},
3533 redo: function(cm) {cm.redo();},
3534 goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
@@ -3011,6 +3549,14 @@ window.CodeMirror = (function() {
3549 goLineEnd: function(cm) {
3550 cm.extendSelection(lineEnd(cm, cm.getCursor().line));
3551 },
3552 + goLineRight: function(cm) {
3553 + var top = cm.charCoords(cm.getCursor(), "div").top + 5;
3554 + cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"));
3555 + },
3556 + goLineLeft: function(cm) {
3557 + var top = cm.charCoords(cm.getCursor(), "div").top + 5;
3558 + cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div"));
3559 + },
3560 goLineUp: function(cm) {cm.moveV(-1, "line");},
3561 goLineDown: function(cm) {cm.moveV(1, "line");},
3562 goPageUp: function(cm) {cm.moveV(-1, "page");},
@@ -3020,11 +3566,15 @@ window.CodeMirror = (function() {
3566 goColumnLeft: function(cm) {cm.moveH(-1, "column");},
3567 goColumnRight: function(cm) {cm.moveH(1, "column");},
3568 goWordLeft: function(cm) {cm.moveH(-1, "word");},
3569 + goGroupRight: function(cm) {cm.moveH(1, "group");},
3570 + goGroupLeft: function(cm) {cm.moveH(-1, "group");},
3571 goWordRight: function(cm) {cm.moveH(1, "word");},
3572 delCharBefore: function(cm) {cm.deleteH(-1, "char");},
3573 delCharAfter: function(cm) {cm.deleteH(1, "char");},
3574 delWordBefore: function(cm) {cm.deleteH(-1, "word");},
3575 delWordAfter: function(cm) {cm.deleteH(1, "word");},
3576 + delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
3577 + delGroupAfter: function(cm) {cm.deleteH(1, "group");},
3578 indentAuto: function(cm) {cm.indentSelection("smart");},
3579 indentMore: function(cm) {cm.indentSelection("add");},
3580 indentLess: function(cm) {cm.indentSelection("subtract");},
@@ -3054,7 +3604,8 @@ window.CodeMirror = (function() {
3604 keyMap.basic = {
3605 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
3606 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
3057 - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3607 + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
3608 + "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3609 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
3610 };
3611 // Note that the save and find-related commands aren't defined by
@@ -3062,19 +3613,19 @@ window.CodeMirror = (function() {
3613 keyMap.pcDefault = {
3614 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3615 "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3065 - "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3066 - "Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3616 + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3617 + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3618 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3619 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3620 fallthrough: "basic"
3621 };
3622 keyMap.macDefault = {
3623 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3073 - "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
3074 - "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore",
3075 - "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find",
3624 + "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
3625 + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
3626 + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
3627 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3077 - "Cmd-[": "indentLess", "Cmd-]": "indentMore",
3628 + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
3629 fallthrough: ["basic", "emacsy"]
3630 };
3631 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
@@ -3097,11 +3648,7 @@ window.CodeMirror = (function() {
3648 map = getKeyMap(map);
3649 var found = map[name];
3650 if (found === false) return "stop";
3100 - if (found != null) {
3101 - try { var done = handle(found); }
3102 - catch (e) { if (e != Pass) throw e; }
3103 - if (done) return true;
3104 - }
3651 + if (found != null && handle(found)) return true;
3652 if (map.nofallthrough) return "stop";
3653
3654 var fallthrough = map.fallthrough;
@@ -3117,20 +3664,21 @@ window.CodeMirror = (function() {
3664
3665 for (var i = 0; i < maps.length; ++i) {
3666 var done = lookup(maps[i]);
3120 - if (done) return done;
3667 + if (done) return done != "stop";
3668 }
3669 }
3670 function isModifierKey(event) {
3124 - var name = keyNames[e_prop(event, "keyCode")];
3671 + var name = keyNames[event.keyCode];
3672 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
3673 }
3674 function keyName(event, noShift) {
3128 - var name = keyNames[e_prop(event, "keyCode")];
3675 + if (opera && event.keyCode == 34 && event["char"]) return false;
3676 + var name = keyNames[event.keyCode];
3677 if (name == null || event.altGraphKey) return false;
3130 - if (e_prop(event, "altKey")) name = "Alt-" + name;
3131 - if (e_prop(event, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name;
3132 - if (e_prop(event, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name;
3133 - if (!noShift && e_prop(event, "shiftKey")) name = "Shift-" + name;
3678 + if (event.altKey) name = "Alt-" + name;
3679 + if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
3680 + if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
3681 + if (!noShift && event.shiftKey) name = "Shift-" + name;
3682 return name;
3683 }
3684 CodeMirror.lookupKey = lookupKey;
@@ -3144,6 +3692,8 @@ window.CodeMirror = (function() {
3692 options.value = textarea.value;
3693 if (!options.tabindex && textarea.tabindex)
3694 options.tabindex = textarea.tabindex;
3695 + if (!options.placeholder && textarea.placeholder)
3696 + options.placeholder = textarea.placeholder;
3697 // Set autofocus to true if this textarea is focused, or if it has
3698 // autofocus and no other element is focused.
3699 if (options.autofocus == null) {
@@ -3156,17 +3706,19 @@ window.CodeMirror = (function() {
3706
3707 function save() {textarea.value = cm.getValue();}
3708 if (textarea.form) {
3159 - // Deplorable hack to make the submit method do the right thing.
3709 on(textarea.form, "submit", save);
3161 - var form = textarea.form, realSubmit = form.submit;
3162 - try {
3163 - var wrappedSubmit = form.submit = function() {
3164 - save();
3165 - form.submit = realSubmit;
3166 - form.submit();
3167 - form.submit = wrappedSubmit;
3168 - };
3169 - } catch(e) {}
3710 + // Deplorable hack to make the submit method do the right thing.
3711 + if (!options.leaveSubmitMethodAlone) {
3712 + var form = textarea.form, realSubmit = form.submit;
3713 + try {
3714 + var wrappedSubmit = form.submit = function() {
3715 + save();
3716 + form.submit = realSubmit;
3717 + form.submit();
3718 + form.submit = wrappedSubmit;
3719 + };
3720 + } catch(e) {}
3721 + }
3722 }
3723
3724 textarea.style.display = "none";
@@ -3198,11 +3750,13 @@ window.CodeMirror = (function() {
3750 this.pos = this.start = 0;
3751 this.string = string;
3752 this.tabSize = tabSize || 8;
3753 + this.lastColumnPos = this.lastColumnValue = 0;
3754 + this.lineStart = 0;
3755 }
3756
3757 StringStream.prototype = {
3758 eol: function() {return this.pos >= this.string.length;},
3205 - sol: function() {return this.pos == 0;},
3759 + sol: function() {return this.pos == this.lineStart;},
3760 peek: function() {return this.string.charAt(this.pos) || undefined;},
3761 next: function() {
3762 if (this.pos < this.string.length)
@@ -3230,12 +3784,22 @@ window.CodeMirror = (function() {
3784 if (found > -1) {this.pos = found; return true;}
3785 },
3786 backUp: function(n) {this.pos -= n;},
3233 - column: function() {return countColumn(this.string, this.start, this.tabSize);},
3234 - indentation: function() {return countColumn(this.string, null, this.tabSize);},
3787 + column: function() {
3788 + if (this.lastColumnPos < this.start) {
3789 + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
3790 + this.lastColumnPos = this.start;
3791 + }
3792 + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
3793 + },
3794 + indentation: function() {
3795 + return countColumn(this.string, null, this.tabSize) -
3796 + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
3797 + },
3798 match: function(pattern, consume, caseInsensitive) {
3799 if (typeof pattern == "string") {
3800 var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
3238 - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
3801 + var substr = this.string.substr(this.pos, pattern.length);
3802 + if (cased(substr) == cased(pattern)) {
3803 if (consume !== false) this.pos += pattern.length;
3804 return true;
3805 }
@@ -3246,7 +3810,12 @@ window.CodeMirror = (function() {
3810 return match;
3811 }
3812 },
3249 - current: function(){return this.string.slice(this.start, this.pos);}
3813 + current: function(){return this.string.slice(this.start, this.pos);},
3814 + hideFirstChars: function(n, inner) {
3815 + this.lineStart += n;
3816 + try { return inner(); }
3817 + finally { this.lineStart -= n; }
3818 + }
3819 };
3820 CodeMirror.StringStream = StringStream;
3821
@@ -3258,11 +3827,16 @@ window.CodeMirror = (function() {
3827 this.doc = doc;
3828 }
3829 CodeMirror.TextMarker = TextMarker;
3830 + eventMixin(TextMarker);
3831
3832 TextMarker.prototype.clear = function() {
3833 if (this.explicitlyCleared) return;
3834 var cm = this.doc.cm, withOp = cm && !cm.curOp;
3835 if (withOp) startOperation(cm);
3836 + if (hasHandler(this, "clear")) {
3837 + var found = this.find();
3838 + if (found) signalLater(this, "clear", found.from, found.to);
3839 + }
3840 var min = null, max = null;
3841 for (var i = 0; i < this.lines.length; ++i) {
3842 var line = this.lines[i];
@@ -3286,15 +3860,14 @@ window.CodeMirror = (function() {
3860 if (min != null && cm) regChange(cm, min, max + 1);
3861 this.lines.length = 0;
3862 this.explicitlyCleared = true;
3289 - if (this.collapsed && this.doc.cantEdit) {
3863 + if (this.atomic && this.doc.cantEdit) {
3864 this.doc.cantEdit = false;
3865 if (cm) reCheckSelection(cm);
3866 }
3867 if (withOp) endOperation(cm);
3294 - signalLater(this, "clear");
3868 };
3869
3297 - TextMarker.prototype.find = function() {
3870 + TextMarker.prototype.find = function(bothSides) {
3871 var from, to;
3872 for (var i = 0; i < this.lines.length; ++i) {
3873 var line = this.lines[i];
@@ -3305,20 +3878,25 @@ window.CodeMirror = (function() {
3878 if (span.to != null) to = Pos(found, span.to);
3879 }
3880 }
3308 - if (this.type == "bookmark") return from;
3881 + if (this.type == "bookmark" && !bothSides) return from;
3882 return from && {from: from, to: to};
3883 };
3884
3312 - TextMarker.prototype.getOptions = function(copyWidget) {
3313 - var repl = this.replacedWith;
3314 - return {className: this.className,
3315 - inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight,
3316 - atomic: this.atomic,
3317 - collapsed: this.collapsed,
3318 - clearOnEnter: this.clearOnEnter,
3319 - replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl,
3320 - readOnly: this.readOnly,
3321 - startStyle: this.startStyle, endStyle: this.endStyle};
3885 + TextMarker.prototype.changed = function() {
3886 + var pos = this.find(), cm = this.doc.cm;
3887 + if (!pos || !cm) return;
3888 + if (this.type != "bookmark") pos = pos.from;
3889 + var line = getLine(this.doc, pos.line);
3890 + clearCachedMeasurement(cm, line);
3891 + if (pos.line >= cm.display.showingFrom && pos.line < cm.display.showingTo) {
3892 + for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) {
3893 + if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight);
3894 + break;
3895 + }
3896 + runInOp(cm, function() {
3897 + cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true;
3898 + });
3899 + }
3900 };
3901
3902 TextMarker.prototype.attachLine = function(line) {
@@ -3337,32 +3915,40 @@ window.CodeMirror = (function() {
3915 }
3916 };
3917
3918 + var nextMarkerId = 0;
3919 +
3920 function markText(doc, from, to, options, type) {
3921 if (options && options.shared) return markTextShared(doc, from, to, options, type);
3922 if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
3923
3924 var marker = new TextMarker(doc, type);
3345 - if (type == "range" && !posLess(from, to)) return marker;
3925 if (options) copyObj(options, marker);
3926 + if (posLess(to, from) || posEq(from, to) && marker.clearWhenEmpty !== false)
3927 + return marker;
3928 if (marker.replacedWith) {
3929 marker.collapsed = true;
3930 marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
3931 + if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;
3932 }
3351 - if (marker.collapsed) sawCollapsedSpans = true;
3933 + if (marker.collapsed) {
3934 + if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
3935 + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
3936 + throw new Error("Inserting collapsed marker partially overlapping an existing one");
3937 + sawCollapsedSpans = true;
3938 + }
3939 +
3940 + if (marker.addToHistory)
3941 + addToHistory(doc, {from: from, to: to, origin: "markText"},
3942 + {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
3943
3353 - var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
3944 + var curLine = from.line, cm = doc.cm, updateMaxLine;
3945 doc.iter(curLine, to.line + 1, function(line) {
3946 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
3947 updateMaxLine = true;
3948 var span = {from: null, to: null, marker: marker};
3358 - size += line.text.length;
3359 - if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
3360 - if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
3361 - if (marker.collapsed) {
3362 - if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
3363 - if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
3364 - else updateLineHeight(line, 0);
3365 - }
3949 + if (curLine == from.line) span.from = from.ch;
3950 + if (curLine == to.line) span.to = to.ch;
3951 + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
3952 addMarkedSpan(line, span);
3953 ++curLine;
3954 });
@@ -3370,20 +3956,20 @@ window.CodeMirror = (function() {
3956 if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
3957 });
3958
3959 + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
3960 +
3961 if (marker.readOnly) {
3962 sawReadOnlySpans = true;
3963 if (doc.history.done.length || doc.history.undone.length)
3964 doc.clearHistory();
3965 }
3966 if (marker.collapsed) {
3379 - if (collapsedAtStart != collapsedAtEnd)
3380 - throw new Error("Inserting collapsed marker overlapping an existing one");
3381 - marker.size = size;
3967 + marker.id = ++nextMarkerId;
3968 marker.atomic = true;
3969 }
3970 if (cm) {
3971 if (updateMaxLine) cm.curOp.updateMaxLine = true;
3386 - if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed)
3972 + if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed)
3973 regChange(cm, from.line, to.line + 1);
3974 if (marker.atomic) reCheckSelection(cm);
3975 }
@@ -3401,6 +3987,7 @@ window.CodeMirror = (function() {
3987 }
3988 }
3989 CodeMirror.SharedTextMarker = SharedTextMarker;
3990 + eventMixin(SharedTextMarker);
3991
3992 SharedTextMarker.prototype.clear = function() {
3993 if (this.explicitlyCleared) return;
@@ -3412,17 +3999,14 @@ window.CodeMirror = (function() {
3999 SharedTextMarker.prototype.find = function() {
4000 return this.primary.find();
4001 };
3415 - SharedTextMarker.prototype.getOptions = function(copyWidget) {
3416 - var inner = this.primary.getOptions(copyWidget);
3417 - inner.shared = true;
3418 - return inner;
3419 - };
4002
4003 function markTextShared(doc, from, to, options, type) {
4004 options = copyObj(options);
4005 options.shared = false;
4006 var markers = [markText(doc, from, to, options, type)], primary = markers[0];
4007 + var widget = options.replacedWith;
4008 linkedDocs(doc, function(doc) {
4009 + if (widget) options.replacedWith = widget.cloneNode(true);
4010 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
4011 for (var i = 0; i < doc.linked.length; ++i)
4012 if (doc.linked[i].isParent) return;
@@ -3453,7 +4037,7 @@ window.CodeMirror = (function() {
4037 if (old) for (var i = 0, nw; i < old.length; ++i) {
4038 var span = old[i], marker = span.marker;
4039 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
3456 - if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) {
4040 + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
4041 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
4042 (nw || (nw = [])).push({from: span.from,
4043 to: endsAfter ? null : span.to,
@@ -3467,7 +4051,7 @@ window.CodeMirror = (function() {
4051 if (old) for (var i = 0, nw; i < old.length; ++i) {
4052 var span = old[i], marker = span.marker;
4053 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
3470 - if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {
4054 + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
4055 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
4056 (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
4057 to: span.to == null ? null : span.to - endCh,
@@ -3517,6 +4101,9 @@ window.CodeMirror = (function() {
4101 }
4102 }
4103 }
4104 + // Make sure we didn't create any zero-length spans
4105 + if (first) first = clearEmptySpans(first);
4106 + if (last && last != first) last = clearEmptySpans(last);
4107
4108 var newMarkers = [first];
4109 if (!sameLine) {
@@ -3533,6 +4120,16 @@ window.CodeMirror = (function() {
4120 return newMarkers;
4121 }
4122
4123 + function clearEmptySpans(spans) {
4124 + for (var i = 0; i < spans.length; ++i) {
4125 + var span = spans[i];
4126 + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
4127 + spans.splice(i--, 1);
4128 + }
4129 + if (!spans.length) return null;
4130 + return spans;
4131 + }
4132 +
4133 function mergeOldSpans(doc, change) {
4134 var old = getOldSpans(doc, change);
4135 var stretched = stretchSpansOverChange(doc, change);
@@ -3583,20 +4180,48 @@ window.CodeMirror = (function() {
4180 return parts;
4181 }
4182
3586 - function collapsedSpanAt(line, ch) {
4183 + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
4184 + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }
4185 +
4186 + function compareCollapsedMarkers(a, b) {
4187 + var lenDiff = a.lines.length - b.lines.length;
4188 + if (lenDiff != 0) return lenDiff;
4189 + var aPos = a.find(), bPos = b.find();
4190 + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);
4191 + if (fromCmp) return -fromCmp;
4192 + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);
4193 + if (toCmp) return toCmp;
4194 + return b.id - a.id;
4195 + }
4196 +
4197 + function collapsedSpanAtSide(line, start) {
4198 var sps = sawCollapsedSpans && line.markedSpans, found;
4199 if (sps) for (var sp, i = 0; i < sps.length; ++i) {
4200 sp = sps[i];
3590 - if (!sp.marker.collapsed) continue;
3591 - if ((sp.from == null || sp.from < ch) &&
3592 - (sp.to == null || sp.to > ch) &&
3593 - (!found || found.width < sp.marker.width))
4201 + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
4202 + (!found || compareCollapsedMarkers(found, sp.marker) < 0))
4203 found = sp.marker;
4204 }
4205 return found;
4206 }
3598 - function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
3599 - function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
4207 + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); }
4208 + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); }
4209 +
4210 + function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
4211 + var line = getLine(doc, lineNo);
4212 + var sps = sawCollapsedSpans && line.markedSpans;
4213 + if (sps) for (var i = 0; i < sps.length; ++i) {
4214 + var sp = sps[i];
4215 + if (!sp.marker.collapsed) continue;
4216 + var found = sp.marker.find(true);
4217 + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
4218 + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
4219 + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
4220 + if (fromCmp <= 0 && (cmp(found.to, from) || extraRight(sp.marker) - extraLeft(marker)) > 0 ||
4221 + fromCmp >= 0 && (cmp(found.from, to) || extraLeft(sp.marker) - extraRight(marker)) < 0)
4222 + return true;
4223 + }
4224 + }
4225
4226 function visualLine(doc, line) {
4227 var merged;
@@ -3611,6 +4236,7 @@ window.CodeMirror = (function() {
4236 sp = sps[i];
4237 if (!sp.marker.collapsed) continue;
4238 if (sp.from == null) return true;
4239 + if (sp.marker.replacedWith) continue;
4240 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
4241 return true;
4242 }
@@ -3624,7 +4250,8 @@ window.CodeMirror = (function() {
4250 return true;
4251 for (var sp, i = 0; i < line.markedSpans.length; ++i) {
4252 sp = line.markedSpans[i];
3627 - if (sp.marker.collapsed && sp.from == span.to &&
4253 + if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to &&
4254 + (sp.to == null || sp.to != span.from) &&
4255 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
4256 lineIsHiddenInner(doc, line, sp)) return true;
4257 }
@@ -3648,11 +4275,12 @@ window.CodeMirror = (function() {
4275 // LINE WIDGETS
4276
4277 var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
3651 - for (var opt in options) if (options.hasOwnProperty(opt))
4278 + if (options) for (var opt in options) if (options.hasOwnProperty(opt))
4279 this[opt] = options[opt];
4280 this.cm = cm;
4281 this.node = node;
4282 };
4283 + eventMixin(LineWidget);
4284 function widgetOperation(f) {
4285 return function() {
4286 var withOp = !this.cm.curOp;
@@ -3667,7 +4295,9 @@ window.CodeMirror = (function() {
4295 if (no == null || !ws) return;
4296 for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
4297 if (!ws.length) this.line.widgets = null;
4298 + var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop;
4299 updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
4300 + if (aboveVisible) addToScrollPos(this.cm, 0, -this.height);
4301 regChange(this.cm, no, no + 1);
4302 });
4303 LineWidget.prototype.changed = widgetOperation(function() {
@@ -3691,14 +4321,14 @@ window.CodeMirror = (function() {
4321 var widget = new LineWidget(cm, node, options);
4322 if (widget.noHScroll) cm.display.alignWidgets = true;
4323 changeLine(cm, handle, function(line) {
3694 - (line.widgets || (line.widgets = [])).push(widget);
4324 + var widgets = line.widgets || (line.widgets = []);
4325 + if (widget.insertAt == null) widgets.push(widget);
4326 + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
4327 widget.line = line;
4328 if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
3697 - var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop;
4329 + var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop;
4330 updateLineHeight(line, line.height + widgetHeight(widget));
3699 - if (aboveVisible)
3700 - cm.curOp.updateScrollPos = {scrollTop: cm.doc.scrollTop + widget.height,
3701 - scrollLeft: cm.doc.scrollLeft};
4331 + if (aboveVisible) addToScrollPos(cm, 0, widget.height);
4332 }
4333 return true;
4334 });
@@ -3709,12 +4339,13 @@ window.CodeMirror = (function() {
4339
4340 // Line objects. These hold state related to a line, including
4341 // highlighting info (the styles array).
3712 - function makeLine(text, markedSpans, estimateHeight) {
3713 - var line = {text: text};
3714 - attachMarkedSpans(line, markedSpans);
3715 - line.height = estimateHeight ? estimateHeight(line) : 1;
3716 - return line;
3717 - }
4342 + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
4343 + this.text = text;
4344 + attachMarkedSpans(this, markedSpans);
4345 + this.height = estimateHeight ? estimateHeight(this) : 1;
4346 + };
4347 + eventMixin(Line);
4348 + Line.prototype.lineNo = function() { return lineNo(this); };
4349
4350 function updateLine(line, text, markedSpans, estimateHeight) {
4351 line.text = text;
@@ -3725,7 +4356,6 @@ window.CodeMirror = (function() {
4356 attachMarkedSpans(line, markedSpans);
4357 var estHeight = estimateHeight ? estimateHeight(line) : 1;
4358 if (estHeight != line.height) updateLineHeight(line, estHeight);
3728 - signalLater(line, "change");
4359 }
4360
4361 function cleanUpLine(line) {
@@ -3736,55 +4366,60 @@ window.CodeMirror = (function() {
4366 // Run the given mode's parser over a line, update the styles
4367 // array, which contains alternating fragments of text and CSS
4368 // classes.
3739 - function runMode(cm, text, mode, state, f) {
3740 - var flattenSpans = cm.options.flattenSpans;
3741 - var curText = "", curStyle = null;
3742 - var stream = new StringStream(text, cm.options.tabSize);
4369 + function runMode(cm, text, mode, state, f, forceToEnd) {
4370 + var flattenSpans = mode.flattenSpans;
4371 + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
4372 + var curStart = 0, curStyle = null;
4373 + var stream = new StringStream(text, cm.options.tabSize), style;
4374 if (text == "" && mode.blankLine) mode.blankLine(state);
4375 while (!stream.eol()) {
3745 - var style = mode.token(stream, state);
3746 - if (stream.pos > 5000) {
4376 + if (stream.pos > cm.options.maxHighlightLength) {
4377 flattenSpans = false;
3748 - // Webkit seems to refuse to render text nodes longer than 57444 characters
3749 - stream.pos = Math.min(text.length, stream.start + 50000);
4378 + if (forceToEnd) processLine(cm, text, state, stream.pos);
4379 + stream.pos = text.length;
4380 style = null;
4381 + } else {
4382 + style = mode.token(stream, state);
4383 }
3752 - var substr = stream.current();
3753 - stream.start = stream.pos;
4384 if (!flattenSpans || curStyle != style) {
3755 - if (curText) f(curText, curStyle);
3756 - curText = substr; curStyle = style;
3757 - } else curText = curText + substr;
4385 + if (curStart < stream.start) f(stream.start, curStyle);
4386 + curStart = stream.start; curStyle = style;
4387 + }
4388 + stream.start = stream.pos;
4389 + }
4390 + while (curStart < stream.pos) {
4391 + // Webkit seems to refuse to render text nodes longer than 57444 characters
4392 + var pos = Math.min(stream.pos, curStart + 50000);
4393 + f(pos, curStyle);
4394 + curStart = pos;
4395 }
3759 - if (curText) f(curText, curStyle);
4396 }
4397
3762 - function highlightLine(cm, line, state) {
4398 + function highlightLine(cm, line, state, forceToEnd) {
4399 // A styles array always starts with a number identifying the
4400 // mode/overlays that it is based on (for easy invalidation).
4401 var st = [cm.state.modeGen];
4402 // Compute the base array of styles
3767 - runMode(cm, line.text, cm.doc.mode, state, function(txt, style) {st.push(txt, style);});
4403 + runMode(cm, line.text, cm.doc.mode, state, function(end, style) {
4404 + st.push(end, style);
4405 + }, forceToEnd);
4406
4407 // Run overlays, adjust style array.
4408 for (var o = 0; o < cm.state.overlays.length; ++o) {
3771 - var overlay = cm.state.overlays[o], i = 1;
3772 - runMode(cm, line.text, overlay.mode, true, function(txt, style) {
3773 - var start = i, len = txt.length;
4409 + var overlay = cm.state.overlays[o], i = 1, at = 0;
4410 + runMode(cm, line.text, overlay.mode, true, function(end, style) {
4411 + var start = i;
4412 // Ensure there's a token end at the current position, and that i points at it
3775 - while (len) {
3776 - var cur = st[i], len_ = cur.length;
3777 - if (len_ <= len) {
3778 - len -= len_;
3779 - } else {
3780 - st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len));
3781 - len = 0;
3782 - }
4413 + while (at < end) {
4414 + var i_end = st[i];
4415 + if (i_end > end)
4416 + st.splice(i, 1, end, st[i+1], i_end);
4417 i += 2;
4418 + at = Math.min(end, i_end);
4419 }
4420 if (!style) return;
4421 if (overlay.opaque) {
3787 - st.splice(start, i - start, txt, style);
4422 + st.splice(start, i - start, end, style);
4423 i = start + 2;
4424 } else {
4425 for (; start < i; start += 2) {
@@ -3806,70 +4441,102 @@ window.CodeMirror = (function() {
4441
4442 // Lightweight form of highlight -- proceed over this line and
4443 // update state, but don't save a style array.
3809 - function processLine(cm, line, state) {
4444 + function processLine(cm, text, state, startAt) {
4445 var mode = cm.doc.mode;
3811 - var stream = new StringStream(line.text, cm.options.tabSize);
3812 - if (line.text == "" && mode.blankLine) mode.blankLine(state);
3813 - while (!stream.eol() && stream.pos <= 5000) {
4446 + var stream = new StringStream(text, cm.options.tabSize);
4447 + stream.start = stream.pos = startAt || 0;
4448 + if (text == "" && mode.blankLine) mode.blankLine(state);
4449 + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
4450 mode.token(stream, state);
4451 stream.start = stream.pos;
4452 }
4453 }
4454
4455 var styleToClassCache = {};
3820 - function styleToClass(style) {
4456 + function interpretTokenStyle(style, builder) {
4457 if (!style) return null;
4458 + for (;;) {
4459 + var lineClass = style.match(/(?:^|\s)line-(background-)?(\S+)/);
4460 + if (!lineClass) break;
4461 + style = style.slice(0, lineClass.index) + style.slice(lineClass.index + lineClass[0].length);
4462 + var prop = lineClass[1] ? "bgClass" : "textClass";
4463 + if (builder[prop] == null)
4464 + builder[prop] = lineClass[2];
4465 + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop]))
4466 + builder[prop] += " " + lineClass[2];
4467 + }
4468 return styleToClassCache[style] ||
4469 (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
4470 }
4471
3826 - function lineContent(cm, realLine, measure) {
3827 - var merged, line = realLine, lineBefore, sawBefore, simple = true;
3828 - while (merged = collapsedSpanAtStart(line)) {
3829 - simple = false;
4472 + function buildLineContent(cm, realLine, measure, copyWidgets) {
4473 + var merged, line = realLine, empty = true;
4474 + while (merged = collapsedSpanAtStart(line))
4475 line = getLine(cm.doc, merged.find().from.line);
3831 - if (!lineBefore) lineBefore = line;
3832 - }
4476
3834 - var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure,
3835 - measure: null, addedOne: false, cm: cm};
3836 - if (line.textClass) builder.pre.className = line.textClass;
4477 + var builder = {pre: elt("pre"), col: 0, pos: 0,
4478 + measure: null, measuredSomething: false, cm: cm,
4479 + copyWidgets: copyWidgets};
4480
4481 do {
4482 + if (line.text) empty = false;
4483 builder.measure = line == realLine && measure;
4484 builder.pos = 0;
4485 builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
3842 - if (measure && sawBefore && line != realLine && !builder.addedOne) {
3843 - measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
3844 - builder.addedOne = true;
3845 - }
4486 + if ((ie || webkit) && cm.getOption("lineWrapping"))
4487 + builder.addToken = buildTokenSplitSpaces(builder.addToken);
4488 var next = insertLineContent(line, builder, getLineStyles(cm, line));
3847 - sawBefore = line == lineBefore;
3848 - if (next) {
3849 - line = getLine(cm.doc, next.to.line);
3850 - simple = false;
4489 + if (measure && line == realLine && !builder.measuredSomething) {
4490 + measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
4491 + builder.measuredSomething = true;
4492 }
4493 + if (next) line = getLine(cm.doc, next.to.line);
4494 } while (next);
4495
3854 - if (measure && !builder.addedOne)
3855 - measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
4496 + if (measure && !builder.measuredSomething && !measure[0])
4497 + measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
4498 if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
4499 builder.pre.appendChild(document.createTextNode("\u00a0"));
4500
3859 - return builder.pre;
4501 + var order;
4502 + // Work around problem with the reported dimensions of single-char
4503 + // direction spans on IE (issue #1129). See also the comment in
4504 + // cursorCoords.
4505 + if (measure && (ie || ie_gt10) && (order = getOrder(line))) {
4506 + var l = order.length - 1;
4507 + if (order[l].from == order[l].to) --l;
4508 + var last = order[l], prev = order[l - 1];
4509 + if (last.from + 1 == last.to && prev && last.level < prev.level) {
4510 + var span = measure[builder.pos - 1];
4511 + if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure),
4512 + span.nextSibling);
4513 + }
4514 + }
4515 +
4516 + var textClass = builder.textClass ? builder.textClass + " " + (realLine.textClass || "") : realLine.textClass;
4517 + if (textClass) builder.pre.className = textClass;
4518 +
4519 + signal(cm, "renderLine", cm, realLine, builder.pre);
4520 + return builder;
4521 + }
4522 +
4523 + function defaultSpecialCharPlaceholder(ch) {
4524 + var token = elt("span", "\u2022", "cm-invalidchar");
4525 + token.title = "\\u" + ch.charCodeAt(0).toString(16);
4526 + return token;
4527 }
4528
3862 - var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
3863 - function buildToken(builder, text, style, startStyle, endStyle) {
4529 + function buildToken(builder, text, style, startStyle, endStyle, title) {
4530 if (!text) return;
3865 - if (!tokenSpecialChars.test(text)) {
4531 + var special = builder.cm.options.specialChars;
4532 + if (!special.test(text)) {
4533 builder.col += text.length;
4534 var content = document.createTextNode(text);
4535 } else {
4536 var content = document.createDocumentFragment(), pos = 0;
4537 while (true) {
3871 - tokenSpecialChars.lastIndex = pos;
3872 - var m = tokenSpecialChars.exec(text);
4538 + special.lastIndex = pos;
4539 + var m = special.exec(text);
4540 var skipped = m ? m.index - pos : text.length - pos;
4541 if (skipped) {
4542 content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
@@ -3882,8 +4549,7 @@ window.CodeMirror = (function() {
4549 content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
4550 builder.col += tabWidth;
4551 } else {
3885 - var token = elt("span", "\u2022", "cm-invalidchar");
3886 - token.title = "\\u" + m[0].charCodeAt(0).toString(16);
4552 + var token = builder.cm.options.specialCharPlaceholder(m[0]);
4553 content.appendChild(token);
4554 builder.col += 1;
4555 }
@@ -3893,31 +4559,69 @@ window.CodeMirror = (function() {
4559 var fullStyle = style || "";
4560 if (startStyle) fullStyle += startStyle;
4561 if (endStyle) fullStyle += endStyle;
3896 - return builder.pre.appendChild(elt("span", [content], fullStyle));
4562 + var token = elt("span", [content], fullStyle);
4563 + if (title) token.title = title;
4564 + return builder.pre.appendChild(token);
4565 }
4566 builder.pre.appendChild(content);
4567 }
4568
4569 function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
4570 + var wrapping = builder.cm.options.lineWrapping;
4571 for (var i = 0; i < text.length; ++i) {
3903 - if (i && i < text.length &&
3904 - builder.cm.options.lineWrapping &&
3905 - spanAffectsWrapping.test(text.slice(i - 1, i + 1)))
4572 + var ch = text.charAt(i), start = i == 0;
4573 + if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
4574 + ch = text.slice(i, i + 2);
4575 + ++i;
4576 + } else if (i && wrapping && spanAffectsWrapping(text, i)) {
4577 builder.pre.appendChild(elt("wbr"));
3907 - builder.measure[builder.pos++] =
3908 - buildToken(builder, text.charAt(i), style,
3909 - i == 0 && startStyle, i == text.length - 1 && endStyle);
3910 - }
3911 - if (text.length) builder.addedOne = true;
4578 + }
4579 + var old = builder.measure[builder.pos];
4580 + var span = builder.measure[builder.pos] =
4581 + buildToken(builder, ch, style,
4582 + start && startStyle, i == text.length - 1 && endStyle);
4583 + if (old) span.leftSide = old.leftSide || old;
4584 + // In IE single-space nodes wrap differently than spaces
4585 + // embedded in larger text nodes, except when set to
4586 + // white-space: normal (issue #1268).
4587 + if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) &&
4588 + i < text.length - 1 && !/\s/.test(text.charAt(i + 1)))
4589 + span.style.whiteSpace = "normal";
4590 + builder.pos += ch.length;
4591 + }
4592 + if (text.length) builder.measuredSomething = true;
4593 + }
4594 +
4595 + function buildTokenSplitSpaces(inner) {
4596 + function split(old) {
4597 + var out = " ";
4598 + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
4599 + out += " ";
4600 + return out;
4601 + }
4602 + return function(builder, text, style, startStyle, endStyle, title) {
4603 + return inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
4604 + };
4605 }
4606
3914 - function buildCollapsedSpan(builder, size, widget) {
4607 + function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
4608 + var widget = !ignoreWidget && marker.replacedWith;
4609 if (widget) {
3916 - if (!builder.display) widget = widget.cloneNode(true);
4610 + if (builder.copyWidgets) widget = widget.cloneNode(true);
4611 builder.pre.appendChild(widget);
3918 - if (builder.measure && size) {
3919 - builder.measure[builder.pos] = widget;
3920 - builder.addedOne = true;
4612 + if (builder.measure) {
4613 + if (size) {
4614 + builder.measure[builder.pos] = widget;
4615 + } else {
4616 + var elt = zeroWidthElement(builder.cm.display.measure);
4617 + if (marker.type == "bookmark" && !marker.insertLeft)
4618 + builder.measure[builder.pos] = builder.pre.appendChild(elt);
4619 + else if (builder.measure[builder.pos])
4620 + return;
4621 + else
4622 + builder.measure[builder.pos] = builder.pre.insertBefore(elt, widget);
4623 + }
4624 + builder.measuredSomething = true;
4625 }
4626 }
4627 builder.pos += size;
@@ -3926,21 +4630,20 @@ window.CodeMirror = (function() {
4630 // Outputs a number of spans to make up a line, taking highlighting
4631 // and marked text into account.
4632 function insertLineContent(line, builder, styles) {
3929 - var spans = line.markedSpans;
4633 + var spans = line.markedSpans, allText = line.text, at = 0;
4634 if (!spans) {
4635 for (var i = 1; i < styles.length; i+=2)
3932 - builder.addToken(builder, styles[i], styleToClass(styles[i+1]));
4636 + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder));
4637 return;
4638 }
4639
3936 - var allText = line.text, len = allText.length;
3937 - var pos = 0, i = 1, text = "", style;
3938 - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed;
4640 + var len = allText.length, pos = 0, i = 1, text = "", style;
4641 + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;
4642 for (;;) {
4643 if (nextChange == pos) { // Update current marker set
3941 - spanStyle = spanEndStyle = spanStartStyle = "";
4644 + spanStyle = spanEndStyle = spanStartStyle = title = "";
4645 collapsed = null; nextChange = Infinity;
3943 - var foundBookmark = null;
4646 + var foundBookmarks = [];
4647 for (var j = 0; j < spans.length; ++j) {
4648 var sp = spans[j], m = sp.marker;
4649 if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
@@ -3948,20 +4651,21 @@ window.CodeMirror = (function() {
4651 if (m.className) spanStyle += " " + m.className;
4652 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
4653 if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
3951 - if (m.collapsed && (!collapsed || collapsed.marker.width < m.width))
4654 + if (m.title && !title) title = m.title;
4655 + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
4656 collapsed = sp;
4657 } else if (sp.from > pos && nextChange > sp.from) {
4658 nextChange = sp.from;
4659 }
3956 - if (m.type == "bookmark" && sp.from == pos && m.replacedWith)
3957 - foundBookmark = m.replacedWith;
4660 + if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmarks.push(m);
4661 }
4662 if (collapsed && (collapsed.from || 0) == pos) {
4663 buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
3961 - collapsed.from != null && collapsed.marker.replacedWith);
4664 + collapsed.marker, collapsed.from == null);
4665 if (collapsed.to == null) return collapsed.marker.find();
4666 }
3964 - if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark);
4667 + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
4668 + buildCollapsedSpan(builder, 0, foundBookmarks[j]);
4669 }
4670 if (pos >= len) break;
4671
@@ -3972,13 +4676,14 @@ window.CodeMirror = (function() {
4676 if (!collapsed) {
4677 var tokenText = end > upto ? text.slice(0, upto - pos) : text;
4678 builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
3975 - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "");
4679 + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title);
4680 }
4681 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
4682 pos = end;
4683 spanStartStyle = "";
4684 }
3981 - text = styles[i++]; style = styleToClass(styles[i++]);
4685 + text = allText.slice(at, at = styles[i++]);
4686 + style = interpretTokenStyle(styles[i++], builder);
4687 }
4688 }
4689 }
@@ -3987,44 +4692,48 @@ window.CodeMirror = (function() {
4692
4693 function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {
4694 function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
4695 + function update(line, text, spans) {
4696 + updateLine(line, text, spans, estimateHeight);
4697 + signalLater(line, "change", line, change);
4698 + }
4699
4700 var from = change.from, to = change.to, text = change.text;
4701 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
4702 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
4703
4704 // First adjust the line structure
3996 - if (from.ch == 0 && to.ch == 0 && lastText == "") {
4705 + if (from.ch == 0 && to.ch == 0 && lastText == "" &&
4706 + (!doc.cm || doc.cm.options.wholeLineUpdateBefore)) {
4707 // This is a whole-line replace. Treated specially to make
4708 // sure line objects move the way they are supposed to.
4709 for (var i = 0, e = text.length - 1, added = []; i < e; ++i)
4000 - added.push(makeLine(text[i], spansFor(i), estimateHeight));
4001 - updateLine(lastLine, lastLine.text, lastSpans, estimateHeight);
4710 + added.push(new Line(text[i], spansFor(i), estimateHeight));
4711 + update(lastLine, lastLine.text, lastSpans);
4712 if (nlines) doc.remove(from.line, nlines);
4713 if (added.length) doc.insert(from.line, added);
4714 } else if (firstLine == lastLine) {
4715 if (text.length == 1) {
4006 - updateLine(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch),
4007 - lastSpans, estimateHeight);
4716 + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
4717 } else {
4718 for (var added = [], i = 1, e = text.length - 1; i < e; ++i)
4010 - added.push(makeLine(text[i], spansFor(i), estimateHeight));
4011 - added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
4012 - updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0), estimateHeight);
4719 + added.push(new Line(text[i], spansFor(i), estimateHeight));
4720 + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
4721 + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
4722 doc.insert(from.line + 1, added);
4723 }
4724 } else if (text.length == 1) {
4016 - updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch),
4017 - spansFor(0), estimateHeight);
4725 + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
4726 doc.remove(from.line + 1, nlines);
4727 } else {
4020 - updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0), estimateHeight);
4021 - updateLine(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans, estimateHeight);
4728 + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
4729 + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
4730 for (var i = 1, e = text.length - 1, added = []; i < e; ++i)
4023 - added.push(makeLine(text[i], spansFor(i), estimateHeight));
4731 + added.push(new Line(text[i], spansFor(i), estimateHeight));
4732 if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
4733 doc.insert(from.line + 1, added);
4734 }
4735
4736 + signalLater(doc, "change", doc, change);
4737 setSelection(doc, selAfter.anchor, selAfter.head, null, true);
4738 }
4739
@@ -4161,12 +4870,13 @@ window.CodeMirror = (function() {
4870 var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
4871 if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
4872 if (firstLine == null) firstLine = 0;
4164 -
4165 - BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]);
4873 +
4874 + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
4875 this.first = firstLine;
4876 this.scrollTop = this.scrollLeft = 0;
4877 this.cantEdit = false;
4878 this.history = makeHistory();
4879 + this.cleanGeneration = 1;
4880 this.frontier = firstLine;
4881 var start = Pos(firstLine, 0);
4882 this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
@@ -4178,8 +4888,9 @@ window.CodeMirror = (function() {
4888 };
4889
4890 Doc.prototype = createObj(BranchChunk.prototype, {
4891 + constructor: Doc,
4892 iter: function(from, to, op) {
4182 - if (op) this.iterN(from - this.first, to - (from - this.first), op);
4893 + if (op) this.iterN(from - this.first, to - from, op);
4894 else this.iterN(this.first, this.first + this.size, from);
4895 },
4896
@@ -4218,13 +4929,18 @@ window.CodeMirror = (function() {
4929 replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
4930 },
4931 removeLine: function(line) {
4221 - if (isLine(this, line))
4222 - replaceRange(this, "", Pos(line, 0), clipPos(this, Pos(line + 1, 0)));
4932 + if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line)));
4933 + else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0)));
4934 },
4935
4936 getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
4937 getLineNumber: function(line) {return lineNo(line);},
4938
4939 + getLineHandleVisualStart: function(line) {
4940 + if (typeof line == "number") line = getLine(this, line);
4941 + return visualLine(this, line);
4942 + },
4943 +
4944 lineCount: function() {return this.size;},
4945 firstLine: function() {return this.first;},
4946 lastLine: function() {return this.first + this.size - 1;},
@@ -4246,11 +4962,11 @@ window.CodeMirror = (function() {
4962 if (extend) extendSelection(this, pos);
4963 else setSelection(this, pos, pos);
4964 }),
4249 - setSelection: docOperation(function(anchor, head) {
4250 - setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor));
4965 + setSelection: docOperation(function(anchor, head, bias) {
4966 + setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), bias);
4967 }),
4252 - extendSelection: docOperation(function(from, to) {
4253 - extendSelection(this, clipPos(this, from), to && clipPos(this, to));
4968 + extendSelection: docOperation(function(from, to, bias) {
4969 + extendSelection(this, clipPos(this, from), to && clipPos(this, to), bias);
4970 }),
4971
4972 getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},
@@ -4266,20 +4982,26 @@ window.CodeMirror = (function() {
4982 var hist = this.history;
4983 return {undo: hist.done.length, redo: hist.undone.length};
4984 },
4269 - clearHistory: function() {this.history = makeHistory();},
4985 + clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);},
4986
4987 markClean: function() {
4272 - this.history.dirtyCounter = 0;
4273 - this.history.lastOp = this.history.lastOrigin = null;
4988 + this.cleanGeneration = this.changeGeneration(true);
4989 + },
4990 + changeGeneration: function(forceSplit) {
4991 + if (forceSplit)
4992 + this.history.lastOp = this.history.lastOrigin = null;
4993 + return this.history.generation;
4994 + },
4995 + isClean: function (gen) {
4996 + return this.history.generation == (gen || this.cleanGeneration);
4997 },
4275 - isClean: function () {return this.history.dirtyCounter == 0;},
4276 -
4998 +
4999 getHistory: function() {
5000 return {done: copyHistoryArray(this.history.done),
5001 undone: copyHistoryArray(this.history.undone)};
5002 },
5003 setHistory: function(histData) {
4282 - var hist = this.history = makeHistory();
5004 + var hist = this.history = makeHistory(this.history.maxGeneration);
5005 hist.done = histData.done.slice(0);
5006 hist.undone = histData.undone.slice(0);
5007 },
@@ -4289,7 +5011,8 @@ window.CodeMirror = (function() {
5011 },
5012 setBookmark: function(pos, options) {
5013 var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
4292 - insertLeft: options && options.insertLeft};
5014 + insertLeft: options && options.insertLeft,
5015 + clearWhenEmpty: false};
5016 pos = clipPos(this, pos);
5017 return markText(this, pos, pos, realOpts, "bookmark");
5018 },
@@ -4304,6 +5027,15 @@ window.CodeMirror = (function() {
5027 }
5028 return markers;
5029 },
5030 + getAllMarks: function() {
5031 + var markers = [];
5032 + this.iter(function(line) {
5033 + var sps = line.markedSpans;
5034 + if (sps) for (var i = 0; i < sps.length; ++i)
5035 + if (sps[i].from != null) markers.push(sps[i].marker);
5036 + });
5037 + return markers;
5038 + },
5039
5040 posFromIndex: function(off) {
5041 var ch, lineNo = this.first;
@@ -4375,14 +5107,13 @@ window.CodeMirror = (function() {
5107 Doc.prototype.eachLine = Doc.prototype.iter;
5108
5109 // The Doc methods that should be available on CodeMirror instances
4378 - var toDelegate = ("setValue getValue getSelection replaceSelection eachLine undo redo historySize clearHistory markClean isClean " +
4379 - "getHistory setHistory markText setBookmark findMarksAt lineCount firstLine lastLine clipPos getCursor " +
4380 - "somethingSelected setCursor setSelection extendSelection setExtending getLine setLine removeLine getMode " +
4381 - "replaceRange getRange getLineHandle getLineNumber posFromIndex indexFromPos linkedDoc unlinkDoc iterLinkedDocs").split(" ");
4382 - for (var i = 0; i < toDelegate.length; ++i) (function(method) {
4383 - var target = Doc.prototype[method];
4384 - CodeMirror.prototype[method] = function() {return target.apply(this.doc, arguments);};
4385 - })(toDelegate[i]);
5110 + var dontDelegate = "iter insert remove copy getEditor".split(" ");
5111 + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
5112 + CodeMirror.prototype[prop] = (function(method) {
5113 + return function() {return method.apply(this.doc, arguments);};
5114 + })(Doc.prototype[prop]);
5115 +
5116 + eventMixin(Doc);
5117
5118 function linkedDocs(doc, f, sharedHistOnly) {
5119 function propagate(doc, skip, sharedHist) {
@@ -4503,7 +5234,7 @@ window.CodeMirror = (function() {
5234
5235 // HISTORY
5236
4506 - function makeHistory() {
5237 + function makeHistory(startGen) {
5238 return {
5239 // Arrays of history events. Doing something adds an event to
5240 // done and clears undo. Undoing moves events from done to
@@ -4513,7 +5244,7 @@ window.CodeMirror = (function() {
5244 // event
5245 lastTime: 0, lastOp: null, lastOrigin: null,
5246 // Used by the isClean() method
4516 - dirtyCounter: 0
5247 + generation: startGen || 1, maxGeneration: startGen || 1
5248 };
5249 }
5250
@@ -4527,7 +5258,8 @@ window.CodeMirror = (function() {
5258 }
5259
5260 function historyChangeFromChange(doc, change) {
4530 - var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
5261 + var from = { line: change.from.line, ch: change.from.ch };
5262 + var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
5263 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
5264 linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
5265 return histChange;
@@ -4541,7 +5273,8 @@ window.CodeMirror = (function() {
5273 if (cur &&
5274 (hist.lastOp == opId ||
5275 hist.lastOrigin == change.origin && change.origin &&
4544 - ((change.origin.charAt(0) == "+" && hist.lastTime > time - 600) || change.origin.charAt(0) == "*"))) {
5276 + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) ||
5277 + change.origin.charAt(0) == "*"))) {
5278 // Merge this change into the last event
5279 var last = lst(cur.changes);
5280 if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
@@ -4556,18 +5289,14 @@ window.CodeMirror = (function() {
5289 } else {
5290 // Can not be merged, start a new event.
5291 cur = {changes: [historyChangeFromChange(doc, change)],
5292 + generation: hist.generation,
5293 anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
5294 anchorAfter: selAfter.anchor, headAfter: selAfter.head};
5295 hist.done.push(cur);
5296 while (hist.done.length > hist.undoDepth)
5297 hist.done.shift();
4564 - if (hist.dirtyCounter < 0)
4565 - // The user has made a change after undoing past the last clean state.
4566 - // We can never get back to a clean state now until markClean() is called.
4567 - hist.dirtyCounter = NaN;
4568 - else
4569 - hist.dirtyCounter++;
5298 }
5299 + hist.generation = ++hist.maxGeneration;
5300 hist.lastTime = time;
5301 hist.lastOp = opId;
5302 hist.lastOrigin = change.origin;
@@ -4681,6 +5410,9 @@ window.CodeMirror = (function() {
5410 if (e.stopPropagation) e.stopPropagation();
5411 else e.cancelBubble = true;
5412 }
5413 + function e_defaultPrevented(e) {
5414 + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
5415 + }
5416 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
5417 CodeMirror.e_stop = e_stop;
5418 CodeMirror.e_preventDefault = e_preventDefault;
@@ -4698,13 +5430,6 @@ window.CodeMirror = (function() {
5430 return b;
5431 }
5432
4701 - // Allow 3rd-party code to override event properties by adding an override
4702 - // object to an event object.
4703 - function e_prop(e, prop) {
4704 - var overridden = e.override && e.override.hasOwnProperty(prop);
4705 - return overridden ? e.override[prop] : e[prop];
4706 - }
4707 -
5433 // EVENT HANDLING
5434
5435 function on(emitter, type, f) {
@@ -4744,10 +5469,26 @@ window.CodeMirror = (function() {
5469 var arr = emitter._handlers && emitter._handlers[type];
5470 if (!arr) return;
5471 var args = Array.prototype.slice.call(arguments, 2);
5472 + if (!delayedCallbacks) {
5473 + ++delayedCallbackDepth;
5474 + delayedCallbacks = [];
5475 + setTimeout(fireDelayed, 0);
5476 + }
5477 function bnd(f) {return function(){f.apply(null, args);};};
5478 for (var i = 0; i < arr.length; ++i)
4749 - if (delayedCallbacks) delayedCallbacks.push(bnd(arr[i]));
4750 - else arr[i].apply(null, args);
5479 + delayedCallbacks.push(bnd(arr[i]));
5480 + }
5481 +
5482 + function signalDOMEvent(cm, e, override) {
5483 + signal(cm, override || e.type, cm, e);
5484 + return e_defaultPrevented(e) || e.codemirrorIgnore;
5485 + }
5486 +
5487 + function fireDelayed() {
5488 + --delayedCallbackDepth;
5489 + var delayed = delayedCallbacks;
5490 + delayedCallbacks = null;
5491 + for (var i = 0; i < delayed.length; ++i) delayed[i]();
5492 }
5493
5494 function hasHandler(emitter, type) {
@@ -4757,6 +5498,11 @@ window.CodeMirror = (function() {
5498
5499 CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
5500
5501 + function eventMixin(ctor) {
5502 + ctor.prototype.on = function(type, f) {on(this, type, f);};
5503 + ctor.prototype.off = function(type, f) {off(this, type, f);};
5504 + }
5505 +
5506 // MISC UTILITIES
5507
5508 // Number of pixels added to scroller and sizer to hide scrollbar
@@ -4771,12 +5517,12 @@ window.CodeMirror = (function() {
5517
5518 // Counts the column offset in a string, taking tabs into account.
5519 // Used mostly to find indentation.
4774 - function countColumn(string, end, tabSize) {
5520 + function countColumn(string, end, tabSize, startIndex, startValue) {
5521 if (end == null) {
5522 end = string.search(/[^\s\u00a0]/);
5523 if (end == -1) end = string.length;
5524 }
4779 - for (var i = 0, n = 0; i < end; ++i) {
5525 + for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) {
5526 if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
5527 else ++n;
5528 }
@@ -4797,7 +5543,11 @@ window.CodeMirror = (function() {
5543 if (ios) { // Mobile Safari apparently has a bug where select() is broken.
5544 node.selectionStart = 0;
5545 node.selectionEnd = node.value.length;
4800 - } else node.select();
5546 + } else {
5547 + // Suppress mysterious IE10 errors
5548 + try { node.select(); }
5549 + catch(_e) {}
5550 + }
5551 }
5552
5553 function indexOf(collection, elt) {
@@ -4808,9 +5558,9 @@ window.CodeMirror = (function() {
5558 }
5559
5560 function createObj(base, props) {
4811 - if (!base) return;
4812 - createObj.prototype = base;
4813 - var inst = new createObj();
5561 + function Obj() {}
5562 + Obj.prototype = base;
5563 + var inst = new Obj();
5564 if (props) copyObj(props, inst);
5565 return inst;
5566 }
@@ -4831,7 +5581,7 @@ window.CodeMirror = (function() {
5581 return function(){return f.apply(null, args);};
5582 }
5583
4834 - var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
5584 + var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
5585 function isWordChar(ch) {
5586 return /\w/.test(ch) || ch > "\x80" &&
5587 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
@@ -4842,7 +5592,7 @@ window.CodeMirror = (function() {
5592 return true;
5593 }
5594
4845 - 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]/;
5595 + 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\u1DC0-\u1DFF\u20D0-\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20-\uFE2F]/;
5596
5597 // DOM UTILITIES
5598
@@ -4856,9 +5606,8 @@ window.CodeMirror = (function() {
5606 }
5607
5608 function removeChildren(e) {
4859 - // IE will break all parent-child relations in subnodes when setting innerHTML
4860 - if (!ie) e.innerHTML = "";
4861 - else while (e.firstChild) e.removeChild(e.firstChild);
5609 + for (var count = e.childNodes.length; count > 0; --count)
5610 + e.removeChild(e.firstChild);
5611 return e;
5612 }
5613
@@ -4873,6 +5622,11 @@ window.CodeMirror = (function() {
5622 } else e.textContent = str;
5623 }
5624
5625 + function getRect(node) {
5626 + return node.getBoundingClientRect();
5627 + }
5628 + CodeMirror.replaceGetRect = function(f) { getRect = f; };
5629 +
5630 // FEATURE DETECTION
5631
5632 // Detect drag-and-drop
@@ -4888,13 +5642,31 @@ window.CodeMirror = (function() {
5642 // word wrapping between certain characters *only* if a new inline
5643 // element is started between them. This makes it hard to reliably
5644 // measure the position of things, since that requires inserting an
4891 - // extra span. This terribly fragile set of regexps matches the
5645 + // extra span. This terribly fragile set of tests matches the
5646 // character combinations that suffer from this phenomenon on the
5647 // various browsers.
4894 - var spanAffectsWrapping = /^$/; // Won't match any two-character string
4895 - if (gecko) spanAffectsWrapping = /$'/;
4896 - else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
4897 - else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
5648 + function spanAffectsWrapping() { return false; }
5649 + if (gecko) // Only for "$'"
5650 + spanAffectsWrapping = function(str, i) {
5651 + return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39;
5652 + };
5653 + else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent))
5654 + spanAffectsWrapping = function(str, i) {
5655 + return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1));
5656 + };
5657 + else if (webkit && /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent))
5658 + spanAffectsWrapping = function(str, i) {
5659 + var code = str.charCodeAt(i - 1);
5660 + return code >= 8208 && code <= 8212;
5661 + };
5662 + else if (webkit)
5663 + spanAffectsWrapping = function(str, i) {
5664 + if (i > 1 && str.charCodeAt(i - 1) == 45) {
5665 + if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true;
5666 + if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false;
5667 + }
5668 + return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1));
5669 + };
5670
5671 var knownScrollbarWidth;
5672 function scrollbarWidth(measure) {
@@ -4979,11 +5751,15 @@ window.CodeMirror = (function() {
5751
5752 function iterateBidiSections(order, from, to, f) {
5753 if (!order) return f(from, to, "ltr");
5754 + var found = false;
5755 for (var i = 0; i < order.length; ++i) {
5756 var part = order[i];
4984 - if (part.from < to && part.to > from || from == to && part.to == from)
5757 + if (part.from < to && part.to > from || from == to && part.to == from) {
5758 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
5759 + found = true;
5760 + }
5761 }
5762 + if (!found) f(from, to, "ltr");
5763 }
5764
5765 function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
@@ -5013,6 +5789,40 @@ window.CodeMirror = (function() {
5789 return Pos(lineN, ch);
5790 }
5791
5792 + function compareBidiLevel(order, a, b) {
5793 + var linedir = order[0].level;
5794 + if (a == linedir) return true;
5795 + if (b == linedir) return false;
5796 + return a < b;
5797 + }
5798 + var bidiOther;
5799 + function getBidiPartAt(order, pos) {
5800 + bidiOther = null;
5801 + for (var i = 0, found; i < order.length; ++i) {
5802 + var cur = order[i];
5803 + if (cur.from < pos && cur.to > pos) return i;
5804 + if ((cur.from == pos || cur.to == pos)) {
5805 + if (found == null) {
5806 + found = i;
5807 + } else if (compareBidiLevel(order, cur.level, order[found].level)) {
5808 + if (cur.from != cur.to) bidiOther = found;
5809 + return i;
5810 + } else {
5811 + if (cur.from != cur.to) bidiOther = i;
5812 + return found;
5813 + }
5814 + }
5815 + }
5816 + return found;
5817 + }
5818 +
5819 + function moveInLine(line, pos, dir, byUnit) {
5820 + if (!byUnit) return pos + dir;
5821 + do pos += dir;
5822 + while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
5823 + return pos;
5824 + }
5825 +
5826 // This is somewhat involved. It is needed in order to move
5827 // 'visually' through bi-directional text -- i.e., pressing left
5828 // should make the cursor go left, even when in RTL text. The
@@ -5022,37 +5832,24 @@ window.CodeMirror = (function() {
5832 function moveVisually(line, start, dir, byUnit) {
5833 var bidi = getOrder(line);
5834 if (!bidi) return moveLogically(line, start, dir, byUnit);
5025 - var moveOneUnit = byUnit ? function(pos, dir) {
5026 - do pos += dir;
5027 - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
5028 - return pos;
5029 - } : function(pos, dir) { return pos + dir; };
5030 - var linedir = bidi[0].level;
5031 - for (var i = 0; i < bidi.length; ++i) {
5032 - var part = bidi[i], sticky = part.level % 2 == linedir;
5033 - if ((part.from < start && part.to > start) ||
5034 - (sticky && (part.from == start || part.to == start))) break;
5035 - }
5036 - var target = moveOneUnit(start, part.level % 2 ? -dir : dir);
5037 -
5038 - while (target != null) {
5039 - if (part.level % 2 == linedir) {
5040 - if (target < part.from || target > part.to) {
5041 - part = bidi[i += dir];
5042 - target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1));
5043 - } else break;
5835 + var pos = getBidiPartAt(bidi, start), part = bidi[pos];
5836 + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
5837 +
5838 + for (;;) {
5839 + if (target > part.from && target < part.to) return target;
5840 + if (target == part.from || target == part.to) {
5841 + if (getBidiPartAt(bidi, target) == pos) return target;
5842 + part = bidi[pos += dir];
5843 + return (dir > 0) == part.level % 2 ? part.to : part.from;
5844 } else {
5045 - if (target == bidiLeft(part)) {
5046 - part = bidi[--i];
5047 - target = part && bidiRight(part);
5048 - } else if (target == bidiRight(part)) {
5049 - part = bidi[++i];
5050 - target = part && bidiLeft(part);
5051 - } else break;
5845 + part = bidi[pos += dir];
5846 + if (!part) return null;
5847 + if ((dir > 0) == part.level % 2)
5848 + target = moveInLine(line, part.to, -1, byUnit);
5849 + else
5850 + target = moveInLine(line, part.from, 1, byUnit);
5851 }
5852 }
5054 -
5055 - return target < 0 || target > line.text.length ? null : target;
5853 }
5854
5855 function moveLogically(line, start, dir, byUnit) {
@@ -5149,7 +5946,7 @@ window.CodeMirror = (function() {
5946 if (type == ",") types[i] = "N";
5947 else if (type == "%") {
5948 for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
5152 - var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N";
5949 + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N";
5950 for (var j = i; j < end; ++j) types[j] = replace;
5951 i = end - 1;
5952 }
@@ -5174,7 +5971,7 @@ window.CodeMirror = (function() {
5971 if (isNeutral.test(types[i])) {
5972 for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
5973 var before = (i ? types[i-1] : outerType) == "L";
5177 - var after = (end < len - 1 ? types[end] : outerType) == "L";
5974 + var after = (end < len ? types[end] : outerType) == "L";
5975 var replace = before || after ? "L" : "R";
5976 for (var j = i; j < end; ++j) types[j] = replace;
5977 i = end - 1;
@@ -5224,7 +6021,7 @@ window.CodeMirror = (function() {
6021
6022 // THE END
6023
5227 - CodeMirror.version = "3.02 +";
6024 + CodeMirror.version = "3.20.1";
6025
6026 return CodeMirror;
6027 })();