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
})();