1 // CodeMirror is the only global var we claim
2 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 var ie = /MSIE \d/.test(navigator.userAgent);
11 var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
12 var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
13 var webkit = /WebKit\//.test(navigator.userAgent);
14 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
15 var chrome = /Chrome\//.test(navigator.userAgent);
16 var opera = /Opera\//.test(navigator.userAgent);
17 var safari = /Apple Computer/.test(navigator.vendor);
18 var khtml = /KHTML\//.test(navigator.userAgent);
19 var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
20 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
21 var phantom = /PhantomJS/.test(navigator.userAgent);
23 var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
24 // This is woefully incomplete. Suggestions for alternative methods welcome.
25 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
26 var mac = ios || /Mac/.test(navigator.platform);
27 var windows = /windows/i.test(navigator.platform);
29 var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
30 if (opera_version) opera_version = Number(opera_version[1]);
31 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
32 var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
33 var captureMiddleClick = gecko || (ie && !ie_lt9);
35 // Optimize some code when these features are not used
36 var sawReadOnlySpans = false, sawCollapsedSpans = false;
40 function CodeMirror(place, options) {
41 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
43 this.options = options = options || {};
44 // Determine effective options based on given values and defaults.
45 for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
46 options[opt] = defaults[opt];
47 setGuttersForLineNumbers(options);
49 var docStart = typeof options.value == "string" ? 0 : options.value.first;
50 var display = this.display = makeDisplay(place, docStart);
51 display.wrapper.CodeMirror = this;
53 if (options.autofocus && !mobile) focusInput(this);
55 this.state = {keyMaps: [],
58 overwrite: false, focused: false,
59 suppressEdits: false, pasteIncoming: false,
61 highlight: new Delayed()};
64 if (options.lineWrapping)
65 this.display.wrapper.className += " CodeMirror-wrap";
67 var doc = options.value;
68 if (typeof doc == "string") doc = new Doc(options.value, options.mode);
69 operation(this, attachDoc)(this, doc);
71 // Override magic textarea content restore that IE sometimes does
72 // on our hidden textarea on reload
73 if (ie) setTimeout(bind(resetInput, this, true), 20);
75 registerEventHandlers(this);
76 // IE throws unspecified error in certain cases, when
77 // trying to access activeElement before onload
78 var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
79 if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
82 operation(this, function() {
83 for (var opt in optionHandlers)
84 if (optionHandlers.propertyIsEnumerable(opt))
85 optionHandlers[opt](this, options[opt], Init);
86 for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
90 // DISPLAY CONSTRUCTOR
92 function makeDisplay(place, docStart) {
95 var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
96 if (webkit) input.style.width = "1000px";
97 else input.setAttribute("wrap", "off");
98 // if border: 0; -- iOS fails to open keyboard (issue #1287)
99 if (ios) input.style.border = "1px solid black";
100 input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
102 // Wraps and hides input textarea
103 d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
104 // The actual fake scrollbars.
105 d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
106 d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
107 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
108 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
109 // DIVs containing the selection and the actual code
110 d.lineDiv = elt("div", null, "CodeMirror-code");
111 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
112 // Blinky cursor, and element used to ensure cursor fits at the end of a line
113 d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
114 // Secondary cursor, shown when on a 'jump' in bi-directional text
115 d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
116 // Used to measure text size
117 d.measure = elt("div", null, "CodeMirror-measure");
118 // Wraps everything that needs to exist inside the vertically-padded coordinate system
119 d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
120 null, "position: relative; outline: none");
121 // Moved around its parent to cover visible view
122 d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
123 // Set to the height of the text, causes scrolling
124 d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
125 // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
126 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
127 // Will contain the gutters, if any
128 d.gutters = elt("div", null, "CodeMirror-gutters");
130 // Provides scrolling
131 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
132 d.scroller.setAttribute("tabIndex", "-1");
133 // The element in which the editor lives.
134 d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
135 d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
136 // Work around IE7 z-index bug
137 if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
138 if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
140 // Needed to hide big blue blinking cursor on Mobile Safari
141 if (ios) input.style.width = "0px";
142 if (!webkit) d.scroller.draggable = true;
143 // Needed to handle Tab key in KHTML
144 if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
145 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
146 else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
148 // Current visible range (may be bigger than the view window).
149 d.viewOffset = d.lastSizeC = 0;
150 d.showingFrom = d.showingTo = docStart;
152 // Used to only resize the line number gutter when necessary (when
153 // the amount of lines crosses a boundary that makes its width change)
154 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
155 // See readInput and resetInput
157 // Set to true when a non-horizontal-scrolling widget is added. As
158 // an optimization, widget aligning is skipped when d is false.
159 d.alignWidgets = false;
160 // Flag that indicates whether we currently expect input to appear
161 // (after some event like 'keypress' or 'input') and are polling
163 d.pollingFast = false;
164 // Self-resetting timeout for the poller
165 d.poll = new Delayed();
167 d.cachedCharWidth = d.cachedTextHeight = null;
168 d.measureLineCache = [];
169 d.measureLineCachePos = 0;
171 // Tracks when resetInput has punted to just putting a short
172 // string instead of the (large) selection.
173 d.inaccurateSelection = false;
175 // Tracks the maximum line length so that the horizontal scrollbar
176 // can be kept static when scrolling.
179 d.maxLineChanged = false;
181 // Used for measuring wheel scrolling granularity
182 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
189 // Used to get the editor into a consistent state again when options change.
191 function loadMode(cm) {
192 cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
193 cm.doc.iter(function(line) {
194 if (line.stateAfter) line.stateAfter = null;
195 if (line.styles) line.styles = null;
197 cm.doc.frontier = cm.doc.first;
198 startWorker(cm, 100);
200 if (cm.curOp) regChange(cm);
203 function wrappingChanged(cm) {
204 if (cm.options.lineWrapping) {
205 cm.display.wrapper.className += " CodeMirror-wrap";
206 cm.display.sizer.style.minWidth = "";
208 cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
209 computeMaxLength(cm);
211 estimateLineHeights(cm);
214 setTimeout(function(){updateScrollbars(cm);}, 100);
217 function estimateHeight(cm) {
218 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
219 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
220 return function(line) {
221 if (lineIsHidden(cm.doc, line))
224 return (Math.ceil(line.text.length / perLine) || 1) * th;
230 function estimateLineHeights(cm) {
231 var doc = cm.doc, est = estimateHeight(cm);
232 doc.iter(function(line) {
233 var estHeight = est(line);
234 if (estHeight != line.height) updateLineHeight(line, estHeight);
238 function keyMapChanged(cm) {
239 var map = keyMap[cm.options.keyMap], style = map.style;
240 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
241 (style ? " cm-keymap-" + style : "");
242 cm.state.disableInput = map.disableInput;
245 function themeChanged(cm) {
246 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
247 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
251 function guttersChanged(cm) {
254 setTimeout(function(){alignHorizontally(cm);}, 20);
257 function updateGutters(cm) {
258 var gutters = cm.display.gutters, specs = cm.options.gutters;
259 removeChildren(gutters);
260 for (var i = 0; i < specs.length; ++i) {
261 var gutterClass = specs[i];
262 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
263 if (gutterClass == "CodeMirror-linenumbers") {
264 cm.display.lineGutter = gElt;
265 gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
268 gutters.style.display = i ? "" : "none";
271 function lineLength(doc, line) {
272 if (line.height == 0) return 0;
273 var len = line.text.length, merged, cur = line;
274 while (merged = collapsedSpanAtStart(cur)) {
275 var found = merged.find();
276 cur = getLine(doc, found.from.line);
277 len += found.from.ch - found.to.ch;
280 while (merged = collapsedSpanAtEnd(cur)) {
281 var found = merged.find();
282 len -= cur.text.length - found.from.ch;
283 cur = getLine(doc, found.to.line);
284 len += cur.text.length - found.to.ch;
289 function computeMaxLength(cm) {
290 var d = cm.display, doc = cm.doc;
291 d.maxLine = getLine(doc, doc.first);
292 d.maxLineLength = lineLength(doc, d.maxLine);
293 d.maxLineChanged = true;
294 doc.iter(function(line) {
295 var len = lineLength(doc, line);
296 if (len > d.maxLineLength) {
297 d.maxLineLength = len;
303 // Make sure the gutters options contains the element
304 // "CodeMirror-linenumbers" when the lineNumbers option is true.
305 function setGuttersForLineNumbers(options) {
307 for (var i = 0; i < options.gutters.length; ++i) {
308 if (options.gutters[i] == "CodeMirror-linenumbers") {
309 if (options.lineNumbers) found = true;
310 else options.gutters.splice(i--, 1);
313 if (!found && options.lineNumbers)
314 options.gutters.push("CodeMirror-linenumbers");
319 // Re-synchronize the fake scrollbars with the actual size of the
320 // content. Optionally force a scrollTop.
321 function updateScrollbars(cm) {
322 var d = cm.display, docHeight = cm.doc.height;
323 var totalHeight = docHeight + paddingVert(d);
324 d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
325 d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
326 var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
327 var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
328 var needsV = scrollHeight > (d.scroller.clientHeight + 1);
330 d.scrollbarV.style.display = "block";
331 d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
332 d.scrollbarV.firstChild.style.height =
333 (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
334 } else d.scrollbarV.style.display = "";
336 d.scrollbarH.style.display = "block";
337 d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
338 d.scrollbarH.firstChild.style.width =
339 (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
340 } else d.scrollbarH.style.display = "";
341 if (needsH && needsV) {
342 d.scrollbarFiller.style.display = "block";
343 d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
344 } else d.scrollbarFiller.style.display = "";
345 if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
346 d.gutterFiller.style.display = "block";
347 d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
348 d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
349 } else d.gutterFiller.style.display = "";
351 if (mac_geLion && scrollbarWidth(d.measure) === 0)
352 d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
355 function visibleLines(display, doc, viewPort) {
356 var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
357 if (typeof viewPort == "number") top = viewPort;
358 else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
359 top = Math.floor(top - paddingTop(display));
360 var bottom = Math.ceil(top + height);
361 return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
366 function alignHorizontally(cm) {
367 var display = cm.display;
368 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
369 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
370 var gutterW = display.gutters.offsetWidth, l = comp + "px";
371 for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
372 for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
374 if (cm.options.fixedGutter)
375 display.gutters.style.left = (comp + gutterW) + "px";
378 function maybeUpdateLineNumberWidth(cm) {
379 if (!cm.options.lineNumbers) return false;
380 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
381 if (last.length != display.lineNumChars) {
382 var test = display.measure.appendChild(elt("div", [elt("div", last)],
383 "CodeMirror-linenumber CodeMirror-gutter-elt"));
384 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
385 display.lineGutter.style.width = "";
386 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
387 display.lineNumWidth = display.lineNumInnerWidth + padding;
388 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
389 display.lineGutter.style.width = display.lineNumWidth + "px";
395 function lineNumberFor(options, i) {
396 return String(options.lineNumberFormatter(i + options.firstLineNumber));
398 function compensateForHScroll(display) {
399 return getRect(display.scroller).left - getRect(display.sizer).left;
404 function updateDisplay(cm, changes, viewPort) {
405 var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
406 var visible = visibleLines(cm.display, cm.doc, viewPort);
408 if (!updateDisplayInner(cm, changes, visible)) break;
411 updateScrollbars(cm);
413 // Clip forced viewport to actual scrollable area
415 viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
416 typeof viewPort == "number" ? viewPort : viewPort.top);
417 visible = visibleLines(cm.display, cm.doc, viewPort);
418 if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
424 signalLater(cm, "update", cm);
425 if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
426 signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
431 // Uses a set of changes plus the current scroll position to
432 // determine which DOM updates have to be made, and makes the
434 function updateDisplayInner(cm, changes, visible) {
435 var display = cm.display, doc = cm.doc;
436 if (!display.wrapper.clientWidth) {
437 display.showingFrom = display.showingTo = doc.first;
438 display.viewOffset = 0;
442 // Bail out if the visible area is already rendered and nothing changed.
443 if (changes.length == 0 &&
444 visible.from > display.showingFrom && visible.to < display.showingTo)
447 if (maybeUpdateLineNumberWidth(cm))
448 changes = [{from: doc.first, to: doc.first + doc.size}];
449 var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
450 display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
452 // Used to determine which lines need their line numbers updated
453 var positionsChangedFrom = Infinity;
454 if (cm.options.lineNumbers)
455 for (var i = 0; i < changes.length; ++i)
456 if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }
458 var end = doc.first + doc.size;
459 var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
460 var to = Math.min(end, visible.to + cm.options.viewportMargin);
461 if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
462 if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
463 if (sawCollapsedSpans) {
464 from = lineNo(visualLine(doc, getLine(doc, from)));
465 while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
468 // Create a range of theoretically intact lines, and punch holes
469 // in that using the change info.
470 var intact = [{from: Math.max(display.showingFrom, doc.first),
471 to: Math.min(display.showingTo, end)}];
472 if (intact[0].from >= intact[0].to) intact = [];
473 else intact = computeIntact(intact, changes);
474 // When merged lines are present, we might have to reduce the
475 // intact ranges because changes in continued fragments of the
476 // intact lines do require the lines to be redrawn.
477 if (sawCollapsedSpans)
478 for (var i = 0; i < intact.length; ++i) {
479 var range = intact[i], merged;
480 while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
481 var newTo = merged.find().from.line;
482 if (newTo > range.from) range.to = newTo;
483 else { intact.splice(i--, 1); break; }
487 // Clip off the parts that won't be visible
489 for (var i = 0; i < intact.length; ++i) {
490 var range = intact[i];
491 if (range.from < from) range.from = from;
492 if (range.to > to) range.to = to;
493 if (range.from >= range.to) intact.splice(i--, 1);
494 else intactLines += range.to - range.from;
496 if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
497 updateViewOffset(cm);
500 intact.sort(function(a, b) {return a.from - b.from;});
502 // Avoid crashing on IE's "unspecified error" when in iframes
504 var focused = document.activeElement;
506 if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
507 patchDisplay(cm, from, to, intact, positionsChangedFrom);
508 display.lineDiv.style.display = "";
509 if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
511 var different = from != display.showingFrom || to != display.showingTo ||
512 display.lastSizeC != display.wrapper.clientHeight;
513 // This is just a bogus formula that detects when the editor is
514 // resized or the font size changes.
516 display.lastSizeC = display.wrapper.clientHeight;
517 startWorker(cm, 400);
519 display.showingFrom = from; display.showingTo = to;
521 var prevBottom = display.lineDiv.offsetTop;
522 for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
524 var bot = node.offsetTop + node.offsetHeight;
525 height = bot - prevBottom;
528 var box = getRect(node);
529 height = box.bottom - box.top;
531 var diff = node.lineObj.height - height;
532 if (height < 2) height = textHeight(display);
533 if (diff > .001 || diff < -.001) {
534 updateLineHeight(node.lineObj, height);
535 var widgets = node.lineObj.widgets;
536 if (widgets) for (var i = 0; i < widgets.length; ++i)
537 widgets[i].height = widgets[i].node.offsetHeight;
540 updateViewOffset(cm);
545 function updateViewOffset(cm) {
546 var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
547 // Position the mover div to align with the current virtual scroll position
548 cm.display.mover.style.top = off + "px";
551 function computeIntact(intact, changes) {
552 for (var i = 0, l = changes.length || 0; i < l; ++i) {
553 var change = changes[i], intact2 = [], diff = change.diff || 0;
554 for (var j = 0, l2 = intact.length; j < l2; ++j) {
555 var range = intact[j];
556 if (change.to <= range.from && change.diff) {
557 intact2.push({from: range.from + diff, to: range.to + diff});
558 } else if (change.to <= range.from || change.from >= range.to) {
561 if (change.from > range.from)
562 intact2.push({from: range.from, to: change.from});
563 if (change.to < range.to)
564 intact2.push({from: change.to + diff, to: range.to + diff});
572 function getDimensions(cm) {
573 var d = cm.display, left = {}, width = {};
574 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
575 left[cm.options.gutters[i]] = n.offsetLeft;
576 width[cm.options.gutters[i]] = n.offsetWidth;
578 return {fixedPos: compensateForHScroll(d),
579 gutterTotalWidth: d.gutters.offsetWidth,
582 wrapperWidth: d.wrapper.clientWidth};
585 function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
586 var dims = getDimensions(cm);
587 var display = cm.display, lineNumbers = cm.options.lineNumbers;
588 if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
589 removeChildren(display.lineDiv);
590 var container = display.lineDiv, cur = container.firstChild;
593 var next = node.nextSibling;
594 if (webkit && mac && cm.display.currentWheelTarget == node) {
595 node.style.display = "none";
598 node.parentNode.removeChild(node);
603 var nextIntact = intact.shift(), lineN = from;
604 cm.doc.iter(from, to, function(line) {
605 if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
606 if (lineIsHidden(cm.doc, line)) {
607 if (line.height != 0) updateLineHeight(line, 0);
608 if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
609 var w = line.widgets[i];
610 if (w.showIfHidden) {
611 var prev = cur.previousSibling;
612 if (/pre/i.test(prev.nodeName)) {
613 var wrap = elt("div", null, null, "position: relative");
614 prev.parentNode.replaceChild(wrap, prev);
615 wrap.appendChild(prev);
618 var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
619 if (!w.handleMouseEvents) wnode.ignoreEvents = true;
620 positionLineWidget(w, wnode, prev, dims);
623 } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
624 // This line is intact. Skip to the actual node. Update its
625 // line number if needed.
626 while (cur.lineObj != line) cur = rm(cur);
627 if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
628 setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
629 cur = cur.nextSibling;
631 // For lines with widgets, make an attempt to find and reuse
632 // the existing element, so that widgets aren't needlessly
633 // removed and re-inserted into the dom
634 if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
635 if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
636 // This line needs to be generated.
637 var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
638 if (lineNode != reuse) {
639 container.insertBefore(lineNode, cur);
641 while (cur != reuse) cur = rm(cur);
642 cur = cur.nextSibling;
645 lineNode.lineObj = line;
649 while (cur) cur = rm(cur);
652 function buildLineElement(cm, line, lineNo, dims, reuse) {
653 var lineElement = lineContent(cm, line);
654 var markers = line.gutterMarkers, display = cm.display, wrap;
656 if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets)
659 // Lines with gutter elements, widgets or a background class need
660 // to be wrapped again, and have the extra elements added to the
664 reuse.alignable = null;
665 var isOk = true, widgetsSeen = 0, insertBefore = null;
666 for (var n = reuse.firstChild, next; n; n = next) {
667 next = n.nextSibling;
668 if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
669 reuse.removeChild(n);
671 for (var i = 0; i < line.widgets.length; ++i) {
672 var widget = line.widgets[i];
673 if (widget.node == n.firstChild) {
674 if (!widget.above && !insertBefore) insertBefore = n;
675 positionLineWidget(widget, n, reuse, dims);
680 if (i == line.widgets.length) { isOk = false; break; }
683 reuse.insertBefore(lineElement, insertBefore);
684 if (isOk && widgetsSeen == line.widgets.length) {
686 reuse.className = line.wrapClass || "";
690 wrap = elt("div", null, line.wrapClass, "position: relative");
691 wrap.appendChild(lineElement);
693 // Kludge to make sure the styled element lies behind the selection (by z-index)
695 wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild);
696 if (cm.options.lineNumbers || markers) {
697 var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
698 (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
700 if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
701 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
702 wrap.lineNumber = gutterWrap.appendChild(
703 elt("div", lineNumberFor(cm.options, lineNo),
704 "CodeMirror-linenumber CodeMirror-gutter-elt",
705 "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
706 + display.lineNumInnerWidth + "px"));
708 for (var k = 0; k < cm.options.gutters.length; ++k) {
709 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
711 gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
712 dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
715 if (ie_lt8) wrap.style.zIndex = 2;
716 if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
717 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
718 if (!widget.handleMouseEvents) node.ignoreEvents = true;
719 positionLineWidget(widget, node, wrap, dims);
721 wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
723 wrap.appendChild(node);
724 signalLater(widget, "redraw");
729 function positionLineWidget(widget, node, wrap, dims) {
730 if (widget.noHScroll) {
731 (wrap.alignable || (wrap.alignable = [])).push(node);
732 var width = dims.wrapperWidth;
733 node.style.left = dims.fixedPos + "px";
734 if (!widget.coverGutter) {
735 width -= dims.gutterTotalWidth;
736 node.style.paddingLeft = dims.gutterTotalWidth + "px";
738 node.style.width = width + "px";
740 if (widget.coverGutter) {
741 node.style.zIndex = 5;
742 node.style.position = "relative";
743 if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
747 // SELECTION / CURSOR
749 function updateSelection(cm) {
750 var display = cm.display;
751 var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
752 if (collapsed || cm.options.showCursorWhenSelecting)
753 updateSelectionCursor(cm);
755 display.cursor.style.display = display.otherCursor.style.display = "none";
757 updateSelectionRange(cm);
759 display.selectionDiv.style.display = "none";
761 // Move the hidden textarea near the cursor to prevent scrolling artifacts
762 if (cm.options.moveInputWithCursor) {
763 var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
764 var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
765 display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
766 headPos.top + lineOff.top - wrapOff.top)) + "px";
767 display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
768 headPos.left + lineOff.left - wrapOff.left)) + "px";
772 // No selection, plain cursor
773 function updateSelectionCursor(cm) {
774 var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
775 display.cursor.style.left = pos.left + "px";
776 display.cursor.style.top = pos.top + "px";
777 display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
778 display.cursor.style.display = "";
781 display.otherCursor.style.display = "";
782 display.otherCursor.style.left = pos.other.left + "px";
783 display.otherCursor.style.top = pos.other.top + "px";
784 display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
785 } else { display.otherCursor.style.display = "none"; }
788 // Highlight selection
789 function updateSelectionRange(cm) {
790 var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
791 var fragment = document.createDocumentFragment();
792 var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
794 function add(left, top, width, bottom) {
795 if (top < 0) top = 0;
796 fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
797 "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
798 "px; height: " + (bottom - top) + "px"));
801 function drawForLine(line, fromArg, toArg) {
802 var lineObj = getLine(doc, line);
803 var lineLen = lineObj.text.length;
805 function coords(ch, bias) {
806 return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
809 iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
810 var leftPos = coords(from, "left"), rightPos, left, right;
813 left = right = leftPos.left;
815 rightPos = coords(to - 1, "right");
816 if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
818 right = rightPos.right;
820 if (fromArg == null && from == 0) left = pl;
821 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
822 add(left, leftPos.top, null, leftPos.bottom);
824 if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
826 if (toArg == null && to == lineLen) right = clientWidth;
827 if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
829 if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
831 if (left < pl + 1) left = pl;
832 add(left, rightPos.top, right - left, rightPos.bottom);
834 return {start: start, end: end};
837 if (sel.from.line == sel.to.line) {
838 drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
840 var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
841 var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
842 var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
843 var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
845 if (leftEnd.top < rightStart.top - 2) {
846 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
847 add(pl, rightStart.top, rightStart.left, rightStart.bottom);
849 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
852 if (leftEnd.bottom < rightStart.top)
853 add(pl, leftEnd.bottom, null, rightStart.top);
856 removeChildrenAndAdd(display.selectionDiv, fragment);
857 display.selectionDiv.style.display = "";
861 function restartBlink(cm) {
862 if (!cm.state.focused) return;
863 var display = cm.display;
864 clearInterval(display.blinker);
866 display.cursor.style.visibility = display.otherCursor.style.visibility = "";
867 display.blinker = setInterval(function() {
868 display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
869 }, cm.options.cursorBlinkRate);
874 function startWorker(cm, time) {
875 if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
876 cm.state.highlight.set(time, bind(highlightWorker, cm));
879 function highlightWorker(cm) {
881 if (doc.frontier < doc.first) doc.frontier = doc.first;
882 if (doc.frontier >= cm.display.showingTo) return;
883 var end = +new Date + cm.options.workTime;
884 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
885 var changed = [], prevChange;
886 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
887 if (doc.frontier >= cm.display.showingFrom) { // Visible
888 var oldStyles = line.styles;
889 line.styles = highlightLine(cm, line, state);
890 var ischange = !oldStyles || oldStyles.length != line.styles.length;
891 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
893 if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
894 else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
896 line.stateAfter = copyState(doc.mode, state);
898 processLine(cm, line, state);
899 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
902 if (+new Date > end) {
903 startWorker(cm, cm.options.workDelay);
908 operation(cm, function() {
909 for (var i = 0; i < changed.length; ++i)
910 regChange(this, changed[i].start, changed[i].end);
914 // Finds the line to start with when starting a parse. Tries to
915 // find a line with a stateAfter, so that it can start with a
916 // valid state. If that fails, it returns the line with the
917 // smallest indentation, which tends to need the least context to
919 function findStartLine(cm, n, precise) {
920 var minindent, minline, doc = cm.doc;
921 for (var search = n, lim = n - 100; search > lim; --search) {
922 if (search <= doc.first) return doc.first;
923 var line = getLine(doc, search - 1);
924 if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
925 var indented = countColumn(line.text, null, cm.options.tabSize);
926 if (minline == null || minindent > indented) {
927 minline = search - 1;
928 minindent = indented;
934 function getStateBefore(cm, n, precise) {
935 var doc = cm.doc, display = cm.display;
936 if (!doc.mode.startState) return true;
937 var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
938 if (!state) state = startState(doc.mode);
939 else state = copyState(doc.mode, state);
940 doc.iter(pos, n, function(line) {
941 processLine(cm, line, state);
942 var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
943 line.stateAfter = save ? copyState(doc.mode, state) : null;
949 // POSITION MEASUREMENT
951 function paddingTop(display) {return display.lineSpace.offsetTop;}
952 function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
953 function paddingLeft(display) {
954 var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
958 function measureChar(cm, line, ch, data, bias) {
960 data = data || measureLine(cm, line);
962 for (var pos = ch;; pos += dir) {
965 if (dir < 0 && pos == 0) dir = 1;
967 bias = pos > ch ? "left" : pos < ch ? "right" : bias;
968 if (bias == "left" && r.leftSide) r = r.leftSide;
969 else if (bias == "right" && r.rightSide) r = r.rightSide;
970 return {left: pos < ch ? r.right : r.left,
971 right: pos > ch ? r.left : r.right,
976 function findCachedMeasurement(cm, line) {
977 var cache = cm.display.measureLineCache;
978 for (var i = 0; i < cache.length; ++i) {
980 if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
981 cm.display.scroller.clientWidth == memo.width &&
982 memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
987 function clearCachedMeasurement(cm, line) {
988 var exists = findCachedMeasurement(cm, line);
989 if (exists) exists.text = exists.measure = exists.markedSpans = null;
992 function measureLine(cm, line) {
993 // First look in the cache
994 var cached = findCachedMeasurement(cm, line);
995 if (cached) return cached.measure;
997 // Failing that, recompute and store result in cache
998 var measure = measureLineInner(cm, line);
999 var cache = cm.display.measureLineCache;
1000 var memo = {text: line.text, width: cm.display.scroller.clientWidth,
1001 markedSpans: line.markedSpans, measure: measure,
1002 classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
1003 if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
1004 else cache.push(memo);
1008 function measureLineInner(cm, line) {
1009 var display = cm.display, measure = emptyArray(line.text.length);
1010 var pre = lineContent(cm, line, measure);
1012 // IE does not cache element positions of inline elements between
1013 // calls to getBoundingClientRect. This makes the loop below,
1014 // which gathers the positions of all the characters on the line,
1015 // do an amount of layout work quadratic to the number of
1016 // characters. When line wrapping is off, we try to improve things
1017 // by first subdividing the line into a bunch of inline blocks, so
1018 // that IE can reuse most of the layout information from caches
1019 // for those blocks. This does interfere with line wrapping, so it
1020 // doesn't work when wrapping is on, but in that case the
1021 // situation is slightly better, since IE does cache line-wrapping
1022 // information and only recomputes per-line.
1023 if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
1024 var fragment = document.createDocumentFragment();
1025 var chunk = 10, n = pre.childNodes.length;
1026 for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
1027 var wrap = elt("div", null, null, "display: inline-block");
1028 for (var j = 0; j < chunk && n; ++j) {
1029 wrap.appendChild(pre.firstChild);
1032 fragment.appendChild(wrap);
1034 pre.appendChild(fragment);
1037 removeChildrenAndAdd(display.measure, pre);
1039 var outer = getRect(display.lineDiv);
1040 var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
1041 // Work around an IE7/8 bug where it will sometimes have randomly
1042 // replaced our pre with a clone at this point.
1043 if (ie_lt9 && display.measure.first != pre)
1044 removeChildrenAndAdd(display.measure, pre);
1046 function measureRect(rect) {
1047 var top = rect.top - outer.top, bot = rect.bottom - outer.top;
1048 if (bot > maxBot) bot = maxBot;
1049 if (top < 0) top = 0;
1050 for (var i = vranges.length - 2; i >= 0; i -= 2) {
1051 var rtop = vranges[i], rbot = vranges[i+1];
1052 if (rtop > bot || rbot < top) continue;
1053 if (rtop <= top && rbot >= bot ||
1054 top <= rtop && bot >= rbot ||
1055 Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
1056 vranges[i] = Math.min(top, rtop);
1057 vranges[i+1] = Math.max(bot, rbot);
1061 if (i < 0) { i = vranges.length; vranges.push(top, bot); }
1062 return {left: rect.left - outer.left,
1063 right: rect.right - outer.left,
1064 top: i, bottom: null};
1066 function finishRect(rect) {
1067 rect.bottom = vranges[rect.top+1];
1068 rect.top = vranges[rect.top];
1071 for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1072 var node = cur, rect = null;
1073 // A widget might wrap, needs special care
1074 if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
1075 if (cur.firstChild.nodeType == 1) node = cur.firstChild;
1076 var rects = node.getClientRects();
1077 if (rects.length > 1) {
1078 rect = data[i] = measureRect(rects[0]);
1079 rect.rightSide = measureRect(rects[rects.length - 1]);
1082 if (!rect) rect = data[i] = measureRect(getRect(node));
1083 if (cur.measureRight) rect.right = getRect(cur.measureRight).left;
1084 if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));
1086 for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
1088 if (cur.leftSide) finishRect(cur.leftSide);
1089 if (cur.rightSide) finishRect(cur.rightSide);
1094 function measureLineWidth(cm, line) {
1095 var hasBadSpan = false;
1096 if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
1097 var sp = line.markedSpans[i];
1098 if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
1100 var cached = !hasBadSpan && findCachedMeasurement(cm, line);
1101 if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right;
1103 var pre = lineContent(cm, line);
1104 var end = pre.appendChild(zeroWidthElement(cm.display.measure));
1105 removeChildrenAndAdd(cm.display.measure, pre);
1106 return getRect(end).right - getRect(cm.display.lineDiv).left;
1109 function clearCaches(cm) {
1110 cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
1111 cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
1112 if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
1113 cm.display.lineNumChars = null;
1116 function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
1117 function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
1119 // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
1120 function intoCoordSystem(cm, lineObj, rect, context) {
1121 if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
1122 var size = widgetHeight(lineObj.widgets[i]);
1123 rect.top += size; rect.bottom += size;
1125 if (context == "line") return rect;
1126 if (!context) context = "local";
1127 var yOff = heightAtLine(cm, lineObj);
1128 if (context == "local") yOff += paddingTop(cm.display);
1129 else yOff -= cm.display.viewOffset;
1130 if (context == "page" || context == "window") {
1131 var lOff = getRect(cm.display.lineSpace);
1132 yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
1133 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
1134 rect.left += xOff; rect.right += xOff;
1136 rect.top += yOff; rect.bottom += yOff;
1140 // Context may be "window", "page", "div", or "local"/null
1141 // Result is in "div" coords
1142 function fromCoordSystem(cm, coords, context) {
1143 if (context == "div") return coords;
1144 var left = coords.left, top = coords.top;
1145 // First move into "page" coordinate system
1146 if (context == "page") {
1147 left -= pageScrollX();
1148 top -= pageScrollY();
1149 } else if (context == "local" || !context) {
1150 var localBox = getRect(cm.display.sizer);
1151 left += localBox.left;
1152 top += localBox.top;
1155 var lineSpaceBox = getRect(cm.display.lineSpace);
1156 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
1159 function charCoords(cm, pos, context, lineObj, bias) {
1160 if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1161 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
1164 function cursorCoords(cm, pos, context, lineObj, measurement) {
1165 lineObj = lineObj || getLine(cm.doc, pos.line);
1166 if (!measurement) measurement = measureLine(cm, lineObj);
1167 function get(ch, right) {
1168 var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
1169 if (right) m.left = m.right; else m.right = m.left;
1170 return intoCoordSystem(cm, lineObj, m, context);
1172 function getBidi(ch, partPos) {
1173 var part = order[partPos], right = part.level % 2;
1174 if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
1175 part = order[--partPos];
1176 ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
1178 } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
1179 part = order[++partPos];
1180 ch = bidiLeft(part) - part.level % 2;
1183 if (right && ch == part.to && ch > part.from) return get(ch - 1);
1184 return get(ch, right);
1186 var order = getOrder(lineObj), ch = pos.ch;
1187 if (!order) return get(ch);
1188 var partPos = getBidiPartAt(order, ch);
1189 var val = getBidi(ch, partPos);
1190 if (bidiOther != null) val.other = getBidi(ch, bidiOther);
1194 function PosWithInfo(line, ch, outside, xRel) {
1195 var pos = new Pos(line, ch);
1197 if (outside) pos.outside = true;
1201 // Coords must be lineSpace-local
1202 function coordsChar(cm, x, y) {
1204 y += cm.display.viewOffset;
1205 if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
1206 var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1208 return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
1212 var lineObj = getLine(doc, lineNo);
1213 var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1214 var merged = collapsedSpanAtEnd(lineObj);
1215 var mergedPos = merged && merged.find();
1216 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
1217 lineNo = mergedPos.to.line;
1223 function coordsCharInner(cm, lineObj, lineNo, x, y) {
1224 var innerOff = y - heightAtLine(cm, lineObj);
1225 var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
1226 var measurement = measureLine(cm, lineObj);
1229 var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
1230 lineObj, measurement);
1232 if (innerOff > sp.bottom) return sp.left - adjust;
1233 else if (innerOff < sp.top) return sp.left + adjust;
1234 else wrongLine = false;
1238 var bidi = getOrder(lineObj), dist = lineObj.text.length;
1239 var from = lineLeft(lineObj), to = lineRight(lineObj);
1240 var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1242 if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
1243 // Do a binary search between these bounds.
1245 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1246 var ch = x < fromX || x - fromX <= toX - x ? from : to;
1247 var xDiff = x - (ch == from ? fromX : toX);
1248 while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1249 var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
1250 xDiff < 0 ? -1 : xDiff ? 1 : 0);
1253 var step = Math.ceil(dist / 2), middle = from + step;
1256 for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1258 var middleX = getX(middle);
1259 if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
1260 else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
1265 function textHeight(display) {
1266 if (display.cachedTextHeight != null) return display.cachedTextHeight;
1267 if (measureText == null) {
1268 measureText = elt("pre");
1269 // Measure a bunch of lines, for browsers that compute
1270 // fractional heights.
1271 for (var i = 0; i < 49; ++i) {
1272 measureText.appendChild(document.createTextNode("x"));
1273 measureText.appendChild(elt("br"));
1275 measureText.appendChild(document.createTextNode("x"));
1277 removeChildrenAndAdd(display.measure, measureText);
1278 var height = measureText.offsetHeight / 50;
1279 if (height > 3) display.cachedTextHeight = height;
1280 removeChildren(display.measure);
1284 function charWidth(display) {
1285 if (display.cachedCharWidth != null) return display.cachedCharWidth;
1286 var anchor = elt("span", "x");
1287 var pre = elt("pre", [anchor]);
1288 removeChildrenAndAdd(display.measure, pre);
1289 var width = anchor.offsetWidth;
1290 if (width > 2) display.cachedCharWidth = width;
1296 // Operations are used to wrap changes in such a way that each
1297 // change won't have to update the cursor and display (which would
1298 // be awkward, slow, and error-prone), but instead updates are
1299 // batched and then all combined and executed at once.
1302 function startOperation(cm) {
1304 // An array of ranges of lines that have to be updated. See
1308 userSelChange: null,
1310 selectionChanged: false,
1311 cursorActivity: false,
1312 updateMaxLine: false,
1313 updateScrollPos: false,
1316 if (!delayedCallbackDepth++) delayedCallbacks = [];
1319 function endOperation(cm) {
1320 var op = cm.curOp, doc = cm.doc, display = cm.display;
1323 if (op.updateMaxLine) computeMaxLength(cm);
1324 if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
1325 var width = measureLineWidth(cm, display.maxLine);
1326 display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1327 display.maxLineChanged = false;
1328 var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
1329 if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
1330 setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
1332 var newScrollPos, updated;
1333 if (op.updateScrollPos) {
1334 newScrollPos = op.updateScrollPos;
1335 } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
1336 var coords = cursorCoords(cm, doc.sel.head);
1337 newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1339 if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null) {
1340 updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop);
1341 if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
1343 if (!updated && op.selectionChanged) updateSelection(cm);
1344 if (op.updateScrollPos) {
1345 display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
1346 display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
1347 alignHorizontally(cm);
1349 scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin);
1350 } else if (newScrollPos) {
1351 scrollCursorIntoView(cm);
1353 if (op.selectionChanged) restartBlink(cm);
1355 if (cm.state.focused && op.updateInput)
1356 resetInput(cm, op.userSelChange);
1358 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
1359 if (hidden) for (var i = 0; i < hidden.length; ++i)
1360 if (!hidden[i].lines.length) signal(hidden[i], "hide");
1361 if (unhidden) for (var i = 0; i < unhidden.length; ++i)
1362 if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
1365 if (!--delayedCallbackDepth) {
1366 delayed = delayedCallbacks;
1367 delayedCallbacks = null;
1370 signal(cm, "change", cm, op.textChanged);
1371 if (op.cursorActivity) signal(cm, "cursorActivity", cm);
1372 if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
1375 // Wraps a function in an operation. Returns the wrapped function.
1376 function operation(cm1, f) {
1378 var cm = cm1 || this, withOp = !cm.curOp;
1379 if (withOp) startOperation(cm);
1380 try { var result = f.apply(cm, arguments); }
1381 finally { if (withOp) endOperation(cm); }
1385 function docOperation(f) {
1387 var withOp = this.cm && !this.cm.curOp, result;
1388 if (withOp) startOperation(this.cm);
1389 try { result = f.apply(this, arguments); }
1390 finally { if (withOp) endOperation(this.cm); }
1394 function runInOp(cm, f) {
1395 var withOp = !cm.curOp, result;
1396 if (withOp) startOperation(cm);
1397 try { result = f(); }
1398 finally { if (withOp) endOperation(cm); }
1402 function regChange(cm, from, to, lendiff) {
1403 if (from == null) from = cm.doc.first;
1404 if (to == null) to = cm.doc.first + cm.doc.size;
1405 cm.curOp.changes.push({from: from, to: to, diff: lendiff});
1410 function slowPoll(cm) {
1411 if (cm.display.pollingFast) return;
1412 cm.display.poll.set(cm.options.pollInterval, function() {
1414 if (cm.state.focused) slowPoll(cm);
1418 function fastPoll(cm) {
1420 cm.display.pollingFast = true;
1422 var changed = readInput(cm);
1423 if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
1424 else {cm.display.pollingFast = false; slowPoll(cm);}
1426 cm.display.poll.set(20, p);
1429 // prevInput is a hack to work with IME. If we reset the textarea
1430 // on every change, that breaks IME. So we look for changes
1431 // compared to the previous content instead. (Modern browsers have
1432 // events that indicate IME taking place, but these are not widely
1433 // supported or compatible enough yet to rely on.)
1434 function readInput(cm) {
1435 var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1436 if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
1437 var text = input.value;
1438 if (text == prevInput && posEq(sel.from, sel.to)) return false;
1439 if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
1440 resetInput(cm, true);
1444 var withOp = !cm.curOp;
1445 if (withOp) startOperation(cm);
1447 var same = 0, l = Math.min(prevInput.length, text.length);
1448 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1449 var from = sel.from, to = sel.to;
1450 if (same < prevInput.length)
1451 from = Pos(from.line, from.ch - (prevInput.length - same));
1452 else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1453 to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
1455 var updateInput = cm.curOp.updateInput;
1456 var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),
1457 origin: cm.state.pasteIncoming ? "paste" : "+input"};
1458 makeChange(cm.doc, changeEvent, "end");
1459 cm.curOp.updateInput = updateInput;
1460 signalLater(cm, "inputRead", cm, changeEvent);
1462 if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
1463 else cm.display.prevInput = text;
1464 if (withOp) endOperation(cm);
1465 cm.state.pasteIncoming = false;
1469 function resetInput(cm, user) {
1470 var minimal, selected, doc = cm.doc;
1471 if (!posEq(doc.sel.from, doc.sel.to)) {
1472 cm.display.prevInput = "";
1473 minimal = hasCopyEvent &&
1474 (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1475 var content = minimal ? "-" : selected || cm.getSelection();
1476 cm.display.input.value = content;
1477 if (cm.state.focused) selectInput(cm.display.input);
1478 if (ie && !ie_lt9) cm.display.inputHasSelection = content;
1480 cm.display.prevInput = cm.display.input.value = "";
1481 if (ie && !ie_lt9) cm.display.inputHasSelection = null;
1483 cm.display.inaccurateSelection = minimal;
1486 function focusInput(cm) {
1487 if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
1488 cm.display.input.focus();
1491 function isReadOnly(cm) {
1492 return cm.options.readOnly || cm.doc.cantEdit;
1497 function registerEventHandlers(cm) {
1499 on(d.scroller, "mousedown", operation(cm, onMouseDown));
1501 on(d.scroller, "dblclick", operation(cm, function(e) {
1502 if (signalDOMEvent(cm, e)) return;
1503 var pos = posFromMouse(cm, e);
1504 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
1505 e_preventDefault(e);
1506 var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
1507 extendSelection(cm.doc, word.from, word.to);
1510 on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
1511 on(d.lineSpace, "selectstart", function(e) {
1512 if (!eventInWidget(d, e)) e_preventDefault(e);
1514 // Gecko browsers fire contextmenu *after* opening the menu, at
1515 // which point we can't mess with it anymore. Context menu is
1516 // handled in onMouseDown for Gecko.
1517 if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1519 on(d.scroller, "scroll", function() {
1520 if (d.scroller.clientHeight) {
1521 setScrollTop(cm, d.scroller.scrollTop);
1522 setScrollLeft(cm, d.scroller.scrollLeft, true);
1523 signal(cm, "scroll", cm);
1526 on(d.scrollbarV, "scroll", function() {
1527 if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
1529 on(d.scrollbarH, "scroll", function() {
1530 if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
1533 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
1534 on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
1536 function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
1537 on(d.scrollbarH, "mousedown", reFocus);
1538 on(d.scrollbarV, "mousedown", reFocus);
1539 // Prevent wrapper from ever scrolling
1540 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1543 function onResize() {
1544 if (resizeTimer == null) resizeTimer = setTimeout(function() {
1546 // Might be a text scaling operation, clear size caches.
1547 d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
1549 runInOp(cm, bind(regChange, cm));
1552 on(window, "resize", onResize);
1553 // Above handler holds on to the editor and its data structures.
1554 // Here we poll to unregister it when the editor is no longer in
1555 // the document, so that it can be garbage-collected.
1556 function unregister() {
1557 for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
1558 if (p) setTimeout(unregister, 5000);
1559 else off(window, "resize", onResize);
1561 setTimeout(unregister, 5000);
1563 on(d.input, "keyup", operation(cm, function(e) {
1564 if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1565 if (e.keyCode == 16) cm.doc.sel.shift = false;
1567 on(d.input, "input", bind(fastPoll, cm));
1568 on(d.input, "keydown", operation(cm, onKeyDown));
1569 on(d.input, "keypress", operation(cm, onKeyPress));
1570 on(d.input, "focus", bind(onFocus, cm));
1571 on(d.input, "blur", bind(onBlur, cm));
1574 if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1577 if (cm.options.dragDrop) {
1578 on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
1579 on(d.scroller, "dragenter", drag_);
1580 on(d.scroller, "dragover", drag_);
1581 on(d.scroller, "drop", operation(cm, onDrop));
1583 on(d.scroller, "paste", function(e){
1584 if (eventInWidget(d, e)) return;
1588 on(d.input, "paste", function() {
1589 cm.state.pasteIncoming = true;
1593 function prepareCopy() {
1594 if (d.inaccurateSelection) {
1596 d.inaccurateSelection = false;
1597 d.input.value = cm.getSelection();
1598 selectInput(d.input);
1601 on(d.input, "cut", prepareCopy);
1602 on(d.input, "copy", prepareCopy);
1604 // Needed to handle Tab key in KHTML
1605 if (khtml) on(d.sizer, "mouseup", function() {
1606 if (document.activeElement == d.input) d.input.blur();
1611 function eventInWidget(display, e) {
1612 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
1613 if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
1617 function posFromMouse(cm, e, liberal) {
1618 var display = cm.display;
1620 var target = e_target(e);
1621 if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1622 target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1623 target == display.scrollbarFiller || target == display.gutterFiller) return null;
1625 var x, y, space = getRect(display.lineSpace);
1626 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1627 try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1628 return coordsChar(cm, x - space.left, y - space.top);
1631 var lastClick, lastDoubleClick;
1632 function onMouseDown(e) {
1633 if (signalDOMEvent(this, e)) return;
1634 var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1635 sel.shift = e.shiftKey;
1637 if (eventInWidget(display, e)) {
1639 display.scroller.draggable = false;
1640 setTimeout(function(){display.scroller.draggable = true;}, 100);
1644 if (clickInGutter(cm, e)) return;
1645 var start = posFromMouse(cm, e);
1647 switch (e_button(e)) {
1649 if (captureMiddleClick) onContextMenu.call(cm, cm, e);
1652 if (start) extendSelection(cm.doc, start);
1653 setTimeout(bind(focusInput, cm), 20);
1654 e_preventDefault(e);
1657 // For button 1, if it was clicked inside the editor
1658 // (posFromMouse returning non-null), we have to adjust the
1660 if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
1662 if (!cm.state.focused) onFocus(cm);
1664 var now = +new Date, type = "single";
1665 if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
1667 e_preventDefault(e);
1668 setTimeout(bind(focusInput, cm), 20);
1669 selectLine(cm, start.line);
1670 } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
1672 lastDoubleClick = {time: now, pos: start};
1673 e_preventDefault(e);
1674 var word = findWordAt(getLine(doc, start.line).text, start);
1675 extendSelection(cm.doc, word.from, word.to);
1676 } else { lastClick = {time: now, pos: start}; }
1679 if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
1680 !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
1681 var dragEnd = operation(cm, function(e2) {
1682 if (webkit) display.scroller.draggable = false;
1683 cm.state.draggingText = false;
1684 off(document, "mouseup", dragEnd);
1685 off(display.scroller, "drop", dragEnd);
1686 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
1687 e_preventDefault(e2);
1688 extendSelection(cm.doc, start);
1692 // Let the drag handler handle this.
1693 if (webkit) display.scroller.draggable = true;
1694 cm.state.draggingText = dragEnd;
1695 // IE's approach to draggable
1696 if (display.scroller.dragDrop) display.scroller.dragDrop();
1697 on(document, "mouseup", dragEnd);
1698 on(display.scroller, "drop", dragEnd);
1701 e_preventDefault(e);
1702 if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1704 var startstart = sel.from, startend = sel.to, lastPos = start;
1706 function doSelect(cur) {
1707 if (posEq(lastPos, cur)) return;
1710 if (type == "single") {
1711 extendSelection(cm.doc, clipPos(doc, start), cur);
1715 startstart = clipPos(doc, startstart);
1716 startend = clipPos(doc, startend);
1717 if (type == "double") {
1718 var word = findWordAt(getLine(doc, cur.line).text, cur);
1719 if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
1720 else extendSelection(cm.doc, startstart, word.to);
1721 } else if (type == "triple") {
1722 if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
1723 else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
1727 var editorSize = getRect(display.wrapper);
1728 // Used to ensure timeout re-tries don't fire when another extend
1729 // happened in the meantime (clearTimeout isn't reliable -- at
1730 // least on Chrome, the timeouts still happen even when cleared,
1731 // if the clear happens after their scheduled firing time).
1734 function extend(e) {
1735 var curCount = ++counter;
1736 var cur = posFromMouse(cm, e, true);
1738 if (!posEq(cur, last)) {
1739 if (!cm.state.focused) onFocus(cm);
1742 var visible = visibleLines(display, doc);
1743 if (cur.line >= visible.to || cur.line < visible.from)
1744 setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
1746 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
1747 if (outside) setTimeout(operation(cm, function() {
1748 if (counter != curCount) return;
1749 display.scroller.scrollTop += outside;
1757 e_preventDefault(e);
1759 off(document, "mousemove", move);
1760 off(document, "mouseup", up);
1763 var move = operation(cm, function(e) {
1764 if (!ie && !e_button(e)) done(e);
1767 var up = operation(cm, done);
1768 on(document, "mousemove", move);
1769 on(document, "mouseup", up);
1772 function clickInGutter(cm, e) {
1773 var display = cm.display;
1774 try { var mX = e.clientX, mY = e.clientY; }
1775 catch(e) { return false; }
1777 if (mX >= Math.floor(getRect(display.gutters).right)) return false;
1778 e_preventDefault(e);
1779 if (!hasHandler(cm, "gutterClick")) return true;
1781 var lineBox = getRect(display.lineDiv);
1782 if (mY > lineBox.bottom) return true;
1783 mY -= lineBox.top - display.viewOffset;
1785 for (var i = 0; i < cm.options.gutters.length; ++i) {
1786 var g = display.gutters.childNodes[i];
1787 if (g && getRect(g).right >= mX) {
1788 var line = lineAtHeight(cm.doc, mY);
1789 var gutter = cm.options.gutters[i];
1790 signalLater(cm, "gutterClick", cm, line, gutter, e);
1797 // Kludge to work around strange IE behavior where it'll sometimes
1798 // re-fire a series of drag-related events right after the drop (#1551)
1801 function onDrop(e) {
1803 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
1805 e_preventDefault(e);
1806 if (ie) lastDrop = +new Date;
1807 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1808 if (!pos || isReadOnly(cm)) return;
1809 if (files && files.length && window.FileReader && window.File) {
1810 var n = files.length, text = Array(n), read = 0;
1811 var loadFile = function(file, i) {
1812 var reader = new FileReader;
1813 reader.onload = function() {
1814 text[i] = reader.result;
1816 pos = clipPos(cm.doc, pos);
1817 makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
1820 reader.readAsText(file);
1822 for (var i = 0; i < n; ++i) loadFile(files[i], i);
1824 // Don't do a replace if the drop happened inside of the selected text.
1825 if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
1826 cm.state.draggingText(e);
1827 // Ensure the editor is re-focused
1828 setTimeout(bind(focusInput, cm), 20);
1832 var text = e.dataTransfer.getData("Text");
1834 var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
1835 setSelection(cm.doc, pos, pos);
1836 if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
1837 cm.replaceSelection(text, null, "paste");
1846 function onDragStart(cm, e) {
1847 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
1848 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
1850 var txt = cm.getSelection();
1851 e.dataTransfer.setData("Text", txt);
1853 // Use dummy image instead of default browsers image.
1854 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1855 if (e.dataTransfer.setDragImage && !safari) {
1856 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
1858 img.width = img.height = 1;
1859 cm.display.wrapper.appendChild(img);
1860 // Force a relayout, or Opera won't use our image for some obscure reason
1861 img._top = img.offsetTop;
1863 e.dataTransfer.setDragImage(img, 0, 0);
1864 if (opera) img.parentNode.removeChild(img);
1868 function setScrollTop(cm, val) {
1869 if (Math.abs(cm.doc.scrollTop - val) < 2) return;
1870 cm.doc.scrollTop = val;
1871 if (!gecko) updateDisplay(cm, [], val);
1872 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1873 if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1874 if (gecko) updateDisplay(cm, []);
1875 startWorker(cm, 100);
1877 function setScrollLeft(cm, val, isScroller) {
1878 if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
1879 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
1880 cm.doc.scrollLeft = val;
1881 alignHorizontally(cm);
1882 if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
1883 if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
1886 // Since the delta values reported on mouse wheel events are
1887 // unstandardized between browsers and even browser versions, and
1888 // generally horribly unpredictable, this code starts by measuring
1889 // the scroll effect that the first few mouse wheel events have,
1890 // and, from that, detects the way it can convert deltas to pixel
1891 // offsets afterwards.
1893 // The reason we want to know the amount a wheel event will scroll
1894 // is that it gives us a chance to update the display before the
1895 // actual scrolling happens, reducing flickering.
1897 var wheelSamples = 0, wheelPixelsPerUnit = null;
1898 // Fill in a browser-detected starting value on browsers where we
1899 // know one. These don't have to be accurate -- the result of them
1900 // being wrong would just be a slight flicker on the first wheel
1901 // scroll (if it is large enough).
1902 if (ie) wheelPixelsPerUnit = -.53;
1903 else if (gecko) wheelPixelsPerUnit = 15;
1904 else if (chrome) wheelPixelsPerUnit = -.7;
1905 else if (safari) wheelPixelsPerUnit = -1/3;
1907 function onScrollWheel(cm, e) {
1908 var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
1909 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
1910 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1911 else if (dy == null) dy = e.wheelDelta;
1913 var display = cm.display, scroll = display.scroller;
1914 // Quit if there's nothing to scroll here
1915 if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
1916 dy && scroll.scrollHeight > scroll.clientHeight)) return;
1918 // Webkit browsers on OS X abort momentum scrolls when the target
1919 // of the scroll event is removed from the scrollable element.
1920 // This hack (see related code in patchDisplay) makes sure the
1921 // element is kept around.
1922 if (dy && mac && webkit) {
1923 for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
1925 cm.display.currentWheelTarget = cur;
1931 // On some browsers, horizontal scrolling will cause redraws to
1932 // happen before the gutter has been realigned, causing it to
1933 // wriggle around in a most unseemly way. When we have an
1934 // estimated pixels/delta value, we just handle horizontal
1935 // scrolling entirely here. It'll be slightly off from native, but
1936 // better than glitching out.
1937 if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
1939 setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
1940 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
1941 e_preventDefault(e);
1942 display.wheelStartX = null; // Abort measurement, if in progress
1946 if (dy && wheelPixelsPerUnit != null) {
1947 var pixels = dy * wheelPixelsPerUnit;
1948 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
1949 if (pixels < 0) top = Math.max(0, top + pixels - 50);
1950 else bot = Math.min(cm.doc.height, bot + pixels + 50);
1951 updateDisplay(cm, [], {top: top, bottom: bot});
1954 if (wheelSamples < 20) {
1955 if (display.wheelStartX == null) {
1956 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
1957 display.wheelDX = dx; display.wheelDY = dy;
1958 setTimeout(function() {
1959 if (display.wheelStartX == null) return;
1960 var movedX = scroll.scrollLeft - display.wheelStartX;
1961 var movedY = scroll.scrollTop - display.wheelStartY;
1962 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
1963 (movedX && display.wheelDX && movedX / display.wheelDX);
1964 display.wheelStartX = display.wheelStartY = null;
1965 if (!sample) return;
1966 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
1970 display.wheelDX += dx; display.wheelDY += dy;
1975 function doHandleBinding(cm, bound, dropShift) {
1976 if (typeof bound == "string") {
1977 bound = commands[bound];
1978 if (!bound) return false;
1980 // Ensure previous input has been read, so that the handler sees a
1981 // consistent view of the document
1982 if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
1983 var doc = cm.doc, prevShift = doc.sel.shift, done = false;
1985 if (isReadOnly(cm)) cm.state.suppressEdits = true;
1986 if (dropShift) doc.sel.shift = false;
1987 done = bound(cm) != Pass;
1989 doc.sel.shift = prevShift;
1990 cm.state.suppressEdits = false;
1995 function allKeyMaps(cm) {
1996 var maps = cm.state.keyMaps.slice(0);
1997 if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
1998 maps.push(cm.options.keyMap);
2002 var maybeTransition;
2003 function handleKeyBinding(cm, e) {
2004 // Handle auto keymap transitions
2005 var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
2006 clearTimeout(maybeTransition);
2007 if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
2008 if (getKeyMap(cm.options.keyMap) == startMap) {
2009 cm.options.keyMap = (next.call ? next.call(null, cm) : next);
2014 var name = keyName(e, true), handled = false;
2015 if (!name) return false;
2016 var keymaps = allKeyMaps(cm);
2019 // First try to resolve full name (including 'Shift-'). Failing
2020 // that, see if there is a cursor-motion command (starting with
2021 // 'go') bound to the keyname without 'Shift-'.
2022 handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
2023 || lookupKey(name, keymaps, function(b) {
2024 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
2025 return doHandleBinding(cm, b);
2028 handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
2032 e_preventDefault(e);
2034 if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
2035 signalLater(cm, "keyHandled", cm, name, e);
2040 function handleCharBinding(cm, e, ch) {
2041 var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
2042 function(b) { return doHandleBinding(cm, b, true); });
2044 e_preventDefault(e);
2046 signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
2051 var lastStoppedKey = null;
2052 function onKeyDown(e) {
2054 if (!cm.state.focused) onFocus(cm);
2055 if (ie && e.keyCode == 27) { e.returnValue = false; }
2056 if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2057 var code = e.keyCode;
2058 // IE does strange things with escape.
2059 cm.doc.sel.shift = code == 16 || e.shiftKey;
2060 // First give onKeyEvent option a chance to handle this.
2061 var handled = handleKeyBinding(cm, e);
2063 lastStoppedKey = handled ? code : null;
2064 // Opera has no cut event... we try to at least catch the key combo
2065 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
2066 cm.replaceSelection("");
2070 function onKeyPress(e) {
2072 if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
2073 var keyCode = e.keyCode, charCode = e.charCode;
2074 if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
2075 if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
2076 var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
2077 if (this.options.electricChars && this.doc.mode.electricChars &&
2078 this.options.smartIndent && !isReadOnly(this) &&
2079 this.doc.mode.electricChars.indexOf(ch) > -1)
2080 setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
2081 if (handleCharBinding(cm, e, ch)) return;
2082 if (ie && !ie_lt9) cm.display.inputHasSelection = null;
2086 function onFocus(cm) {
2087 if (cm.options.readOnly == "nocursor") return;
2088 if (!cm.state.focused) {
2089 signal(cm, "focus", cm);
2090 cm.state.focused = true;
2091 if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
2092 cm.display.wrapper.className += " CodeMirror-focused";
2093 resetInput(cm, true);
2098 function onBlur(cm) {
2099 if (cm.state.focused) {
2100 signal(cm, "blur", cm);
2101 cm.state.focused = false;
2102 cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
2104 clearInterval(cm.display.blinker);
2105 setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
2108 var detectingSelectAll;
2109 function onContextMenu(cm, e) {
2110 var display = cm.display, sel = cm.doc.sel;
2111 if (eventInWidget(display, e)) return;
2113 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
2114 if (!pos || opera) return; // Opera is difficult.
2115 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
2116 operation(cm, setSelection)(cm.doc, pos, pos);
2118 var oldCSS = display.input.style.cssText;
2119 display.inputDiv.style.position = "absolute";
2120 display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
2121 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
2122 "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
2124 resetInput(cm, true);
2125 // Adds "Select all" to context menu in FF
2126 if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
2128 function prepareSelectAllHack() {
2129 if (display.input.selectionStart != null) {
2130 var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value);
2131 display.prevInput = " ";
2132 display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
2136 display.inputDiv.style.position = "relative";
2137 display.input.style.cssText = oldCSS;
2138 if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
2141 // Try to detect the user choosing select-all
2142 if (display.input.selectionStart != null) {
2143 if (!ie || ie_lt9) prepareSelectAllHack();
2144 clearTimeout(detectingSelectAll);
2145 var i = 0, poll = function(){
2146 if (display.prevInput == " " && display.input.selectionStart == 0)
2147 operation(cm, commands.selectAll)(cm);
2148 else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
2149 else resetInput(cm);
2151 detectingSelectAll = setTimeout(poll, 200);
2155 if (ie && !ie_lt9) prepareSelectAllHack();
2156 if (captureMiddleClick) {
2158 var mouseup = function() {
2159 off(window, "mouseup", mouseup);
2160 setTimeout(rehide, 20);
2162 on(window, "mouseup", mouseup);
2164 setTimeout(rehide, 50);
2170 var changeEnd = CodeMirror.changeEnd = function(change) {
2171 if (!change.text) return change.to;
2172 return Pos(change.from.line + change.text.length - 1,
2173 lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
2176 // Make sure a position will be valid after the given change.
2177 function clipPostChange(doc, change, pos) {
2178 if (!posLess(change.from, pos)) return clipPos(doc, pos);
2179 var diff = (change.text.length - 1) - (change.to.line - change.from.line);
2180 if (pos.line > change.to.line + diff) {
2181 var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
2182 if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
2183 return clipToLen(pos, getLine(doc, preLine).text.length);
2185 if (pos.line == change.to.line + diff)
2186 return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
2187 getLine(doc, change.to.line).text.length - change.to.ch);
2188 var inside = pos.line - change.from.line;
2189 return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
2192 // Hint can be null|"end"|"start"|"around"|{anchor,head}
2193 function computeSelAfterChange(doc, change, hint) {
2194 if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
2195 return {anchor: clipPostChange(doc, change, hint.anchor),
2196 head: clipPostChange(doc, change, hint.head)};
2198 if (hint == "start") return {anchor: change.from, head: change.from};
2200 var end = changeEnd(change);
2201 if (hint == "around") return {anchor: change.from, head: end};
2202 if (hint == "end") return {anchor: end, head: end};
2204 // hint is null, leave the selection alone as much as possible
2205 var adjustPos = function(pos) {
2206 if (posLess(pos, change.from)) return pos;
2207 if (!posLess(change.to, pos)) return end;
2209 var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
2210 if (pos.line == change.to.line) ch += end.ch - change.to.ch;
2211 return Pos(line, ch);
2213 return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
2216 function filterChange(doc, change, update) {
2222 origin: change.origin,
2223 cancel: function() { this.canceled = true; }
2225 if (update) obj.update = function(from, to, text, origin) {
2226 if (from) this.from = clipPos(doc, from);
2227 if (to) this.to = clipPos(doc, to);
2228 if (text) this.text = text;
2229 if (origin !== undefined) this.origin = origin;
2231 signal(doc, "beforeChange", doc, obj);
2232 if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
2234 if (obj.canceled) return null;
2235 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
2238 // Replace the range from from to to by the strings in replacement.
2239 // change is a {from, to, text [, origin]} object
2240 function makeChange(doc, change, selUpdate, ignoreReadOnly) {
2242 if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
2243 if (doc.cm.state.suppressEdits) return;
2246 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
2247 change = filterChange(doc, change, true);
2248 if (!change) return;
2251 // Possibly split or suppress the update based on the presence
2252 // of read-only spans in its range.
2253 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
2255 for (var i = split.length - 1; i >= 1; --i)
2256 makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
2258 makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
2260 makeChangeNoReadonly(doc, change, selUpdate);
2264 function makeChangeNoReadonly(doc, change, selUpdate) {
2265 var selAfter = computeSelAfterChange(doc, change, selUpdate);
2266 addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
2268 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
2271 linkedDocs(doc, function(doc, sharedHist) {
2272 if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2273 rebaseHist(doc.history, change);
2274 rebased.push(doc.history);
2276 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
2280 function makeChangeFromHistory(doc, type) {
2281 if (doc.cm && doc.cm.state.suppressEdits) return;
2283 var hist = doc.history;
2284 var event = (type == "undo" ? hist.done : hist.undone).pop();
2287 var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
2288 anchorAfter: event.anchorBefore, headAfter: event.headBefore,
2289 generation: hist.generation};
2290 (type == "undo" ? hist.undone : hist.done).push(anti);
2291 hist.generation = event.generation || ++hist.maxGeneration;
2293 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
2295 for (var i = event.changes.length - 1; i >= 0; --i) {
2296 var change = event.changes[i];
2297 change.origin = type;
2298 if (filter && !filterChange(doc, change, false)) {
2299 (type == "undo" ? hist.done : hist.undone).length = 0;
2303 anti.changes.push(historyChangeFromChange(doc, change));
2305 var after = i ? computeSelAfterChange(doc, change, null)
2306 : {anchor: event.anchorBefore, head: event.headBefore};
2307 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
2310 linkedDocs(doc, function(doc, sharedHist) {
2311 if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2312 rebaseHist(doc.history, change);
2313 rebased.push(doc.history);
2315 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
2320 function shiftDoc(doc, distance) {
2321 function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
2322 doc.first += distance;
2323 if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
2324 doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
2325 doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
2328 function makeChangeSingleDoc(doc, change, selAfter, spans) {
2329 if (doc.cm && !doc.cm.curOp)
2330 return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
2332 if (change.to.line < doc.first) {
2333 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
2336 if (change.from.line > doc.lastLine()) return;
2338 // Clip the change to the size of this doc
2339 if (change.from.line < doc.first) {
2340 var shift = change.text.length - 1 - (doc.first - change.from.line);
2341 shiftDoc(doc, shift);
2342 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
2343 text: [lst(change.text)], origin: change.origin};
2345 var last = doc.lastLine();
2346 if (change.to.line > last) {
2347 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
2348 text: [change.text[0]], origin: change.origin};
2351 change.removed = getBetween(doc, change.from, change.to);
2353 if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
2354 if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
2355 else updateDoc(doc, change, spans, selAfter);
2358 function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
2359 var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
2361 var recomputeMaxLength = false, checkWidthStart = from.line;
2362 if (!cm.options.lineWrapping) {
2363 checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
2364 doc.iter(checkWidthStart, to.line + 1, function(line) {
2365 if (line == display.maxLine) {
2366 recomputeMaxLength = true;
2372 if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
2373 cm.curOp.cursorActivity = true;
2375 updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
2377 if (!cm.options.lineWrapping) {
2378 doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
2379 var len = lineLength(doc, line);
2380 if (len > display.maxLineLength) {
2381 display.maxLine = line;
2382 display.maxLineLength = len;
2383 display.maxLineChanged = true;
2384 recomputeMaxLength = false;
2387 if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
2390 // Adjust frontier, schedule worker
2391 doc.frontier = Math.min(doc.frontier, from.line);
2392 startWorker(cm, 400);
2394 var lendiff = change.text.length - (to.line - from.line) - 1;
2395 // Remember that these lines changed, for updating the display
2396 regChange(cm, from.line, to.line + 1, lendiff);
2398 if (hasHandler(cm, "change")) {
2399 var changeObj = {from: from, to: to,
2401 removed: change.removed,
2402 origin: change.origin};
2403 if (cm.curOp.textChanged) {
2404 for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
2405 cur.next = changeObj;
2406 } else cm.curOp.textChanged = changeObj;
2410 function replaceRange(doc, code, from, to, origin) {
2412 if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
2413 if (typeof code == "string") code = splitLines(code);
2414 makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
2419 function Pos(line, ch) {
2420 if (!(this instanceof Pos)) return new Pos(line, ch);
2421 this.line = line; this.ch = ch;
2423 CodeMirror.Pos = Pos;
2425 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2426 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2427 function copyPos(x) {return Pos(x.line, x.ch);}
2431 function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
2432 function clipPos(doc, pos) {
2433 if (pos.line < doc.first) return Pos(doc.first, 0);
2434 var last = doc.first + doc.size - 1;
2435 if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
2436 return clipToLen(pos, getLine(doc, pos.line).text.length);
2438 function clipToLen(pos, linelen) {
2440 if (ch == null || ch > linelen) return Pos(pos.line, linelen);
2441 else if (ch < 0) return Pos(pos.line, 0);
2444 function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
2446 // If shift is held, this will move the selection anchor. Otherwise,
2447 // it'll set the whole selection.
2448 function extendSelection(doc, pos, other, bias) {
2449 if (doc.sel.shift || doc.sel.extend) {
2450 var anchor = doc.sel.anchor;
2452 var posBefore = posLess(pos, anchor);
2453 if (posBefore != posLess(other, anchor)) {
2456 } else if (posBefore != posLess(pos, other)) {
2460 setSelection(doc, anchor, pos, bias);
2462 setSelection(doc, pos, other || pos, bias);
2464 if (doc.cm) doc.cm.curOp.userSelChange = true;
2467 function filterSelectionChange(doc, anchor, head) {
2468 var obj = {anchor: anchor, head: head};
2469 signal(doc, "beforeSelectionChange", doc, obj);
2470 if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
2471 obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
2475 // Update the selection. Last two args are only used by
2476 // updateDoc, since they have to be expressed in the line
2477 // numbers before the update.
2478 function setSelection(doc, anchor, head, bias, checkAtomic) {
2479 if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
2480 var filtered = filterSelectionChange(doc, anchor, head);
2481 head = filtered.head;
2482 anchor = filtered.anchor;
2486 sel.goalColumn = null;
2487 // Skip over atomic spans.
2488 if (checkAtomic || !posEq(anchor, sel.anchor))
2489 anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
2490 if (checkAtomic || !posEq(head, sel.head))
2491 head = skipAtomic(doc, head, bias, checkAtomic != "push");
2493 if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
2495 sel.anchor = anchor; sel.head = head;
2496 var inv = posLess(head, anchor);
2497 sel.from = inv ? head : anchor;
2498 sel.to = inv ? anchor : head;
2501 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
2502 doc.cm.curOp.cursorActivity = true;
2504 signalLater(doc, "cursorActivity", doc);
2507 function reCheckSelection(cm) {
2508 setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
2511 function skipAtomic(doc, pos, bias, mayClear) {
2512 var flipped = false, curPos = pos;
2513 var dir = bias || 1;
2514 doc.cantEdit = false;
2516 var line = getLine(doc, curPos.line);
2517 if (line.markedSpans) {
2518 for (var i = 0; i < line.markedSpans.length; ++i) {
2519 var sp = line.markedSpans[i], m = sp.marker;
2520 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2521 (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2523 signal(m, "beforeCursorEnter");
2524 if (m.explicitlyCleared) {
2525 if (!line.markedSpans) break;
2526 else {--i; continue;}
2529 if (!m.atomic) continue;
2530 var newPos = m.find()[dir < 0 ? "from" : "to"];
2531 if (posEq(newPos, curPos)) {
2533 if (newPos.ch < 0) {
2534 if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
2536 } else if (newPos.ch > line.text.length) {
2537 if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
2542 // Driven in a corner -- no valid cursor position found at all
2543 // -- try again *with* clearing, if we didn't already
2544 if (!mayClear) return skipAtomic(doc, pos, bias, true);
2545 // Otherwise, turn off editing until further notice, and return the start of the doc
2546 doc.cantEdit = true;
2547 return Pos(doc.first, 0);
2549 flipped = true; newPos = pos; dir = -dir;
2563 function scrollCursorIntoView(cm) {
2564 var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin);
2565 if (!cm.state.focused) return;
2566 var display = cm.display, box = getRect(display.sizer), doScroll = null;
2567 if (coords.top + box.top < 0) doScroll = true;
2568 else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2569 if (doScroll != null && !phantom) {
2570 var hidden = display.cursor.style.display == "none";
2572 display.cursor.style.display = "";
2573 display.cursor.style.left = coords.left + "px";
2574 display.cursor.style.top = (coords.top - display.viewOffset) + "px";
2576 display.cursor.scrollIntoView(doScroll);
2577 if (hidden) display.cursor.style.display = "none";
2581 function scrollPosIntoView(cm, pos, margin) {
2582 if (margin == null) margin = 0;
2584 var changed = false, coords = cursorCoords(cm, pos);
2585 var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin);
2586 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
2587 if (scrollPos.scrollTop != null) {
2588 setScrollTop(cm, scrollPos.scrollTop);
2589 if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
2591 if (scrollPos.scrollLeft != null) {
2592 setScrollLeft(cm, scrollPos.scrollLeft);
2593 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
2595 if (!changed) return coords;
2599 function scrollIntoView(cm, x1, y1, x2, y2) {
2600 var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
2601 if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
2602 if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
2605 function calculateScrollPos(cm, x1, y1, x2, y2) {
2606 var display = cm.display, snapMargin = textHeight(cm.display);
2608 var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2609 var docBottom = cm.doc.height + paddingVert(display);
2610 var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
2611 if (y1 < screentop) {
2612 result.scrollTop = atTop ? 0 : y1;
2613 } else if (y2 > screentop + screen) {
2614 var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
2615 if (newTop != screentop) result.scrollTop = newTop;
2618 var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2619 x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
2620 var gutterw = display.gutters.offsetWidth;
2621 var atLeft = x1 < gutterw + 10;
2622 if (x1 < screenleft + gutterw || atLeft) {
2624 result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
2625 } else if (x2 > screenw + screenleft - 3) {
2626 result.scrollLeft = x2 + 10 - screenw;
2631 function updateScrollPos(cm, left, top) {
2632 cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
2633 scrollTop: top == null ? cm.doc.scrollTop : top};
2636 function addToScrollPos(cm, left, top) {
2637 var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});
2638 var scroll = cm.display.scroller;
2639 pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));
2640 pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));
2645 function indentLine(cm, n, how, aggressive) {
2647 if (how == null) how = "add";
2648 if (how == "smart") {
2649 if (!cm.doc.mode.indent) how = "prev";
2650 else var state = getStateBefore(cm, n);
2653 var tabSize = cm.options.tabSize;
2654 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
2655 var curSpaceString = line.text.match(/^\s*/)[0], indentation;
2656 if (how == "smart") {
2657 indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2658 if (indentation == Pass) {
2659 if (!aggressive) return;
2663 if (how == "prev") {
2664 if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
2665 else indentation = 0;
2666 } else if (how == "add") {
2667 indentation = curSpace + cm.options.indentUnit;
2668 } else if (how == "subtract") {
2669 indentation = curSpace - cm.options.indentUnit;
2670 } else if (typeof how == "number") {
2671 indentation = curSpace + how;
2673 indentation = Math.max(0, indentation);
2675 var indentString = "", pos = 0;
2676 if (cm.options.indentWithTabs)
2677 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
2678 if (pos < indentation) indentString += spaceStr(indentation - pos);
2680 if (indentString != curSpaceString)
2681 replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
2682 line.stateAfter = null;
2685 function changeLine(cm, handle, op) {
2686 var no = handle, line = handle, doc = cm.doc;
2687 if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
2688 else no = lineNo(handle);
2689 if (no == null) return null;
2690 if (op(line, no)) regChange(cm, no, no + 1);
2695 function findPosH(doc, pos, dir, unit, visually) {
2696 var line = pos.line, ch = pos.ch, origDir = dir;
2697 var lineObj = getLine(doc, line);
2698 var possible = true;
2699 function findNextLine() {
2701 if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
2703 return lineObj = getLine(doc, l);
2705 function moveOnce(boundToLine) {
2706 var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
2708 if (!boundToLine && findNextLine()) {
2709 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
2710 else ch = dir < 0 ? lineObj.text.length : 0;
2711 } else return (possible = false);
2716 if (unit == "char") moveOnce();
2717 else if (unit == "column") moveOnce(true);
2718 else if (unit == "word" || unit == "group") {
2719 var sawType = null, group = unit == "group";
2720 for (var first = true;; first = false) {
2721 if (dir < 0 && !moveOnce(!first)) break;
2722 var cur = lineObj.text.charAt(ch) || "\n";
2723 var type = isWordChar(cur) ? "w"
2725 : /\s/.test(cur) ? null
2727 if (sawType && sawType != type) {
2728 if (dir < 0) {dir = 1; moveOnce();}
2731 if (type) sawType = type;
2732 if (dir > 0 && !moveOnce(!first)) break;
2735 var result = skipAtomic(doc, Pos(line, ch), origDir, true);
2736 if (!possible) result.hitSide = true;
2740 function findPosV(cm, pos, dir, unit) {
2741 var doc = cm.doc, x = pos.left, y;
2742 if (unit == "page") {
2743 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2744 y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
2745 } else if (unit == "line") {
2746 y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2749 var target = coordsChar(cm, x, y);
2750 if (!target.outside) break;
2751 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
2757 function findWordAt(line, pos) {
2758 var start = pos.ch, end = pos.ch;
2760 if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
2761 var startChar = line.charAt(start);
2762 var check = isWordChar(startChar) ? isWordChar
2763 : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
2764 : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2765 while (start > 0 && check(line.charAt(start - 1))) --start;
2766 while (end < line.length && check(line.charAt(end))) ++end;
2768 return {from: Pos(pos.line, start), to: Pos(pos.line, end)};
2771 function selectLine(cm, line) {
2772 extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
2777 // The publicly visible API. Note that operation(null, f) means
2778 // 'wrap f in an operation, performed on its `this` parameter'
2780 CodeMirror.prototype = {
2781 constructor: CodeMirror,
2782 focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2784 setOption: function(option, value) {
2785 var options = this.options, old = options[option];
2786 if (options[option] == value && option != "mode") return;
2787 options[option] = value;
2788 if (optionHandlers.hasOwnProperty(option))
2789 operation(this, optionHandlers[option])(this, value, old);
2792 getOption: function(option) {return this.options[option];},
2793 getDoc: function() {return this.doc;},
2795 addKeyMap: function(map, bottom) {
2796 this.state.keyMaps[bottom ? "push" : "unshift"](map);
2798 removeKeyMap: function(map) {
2799 var maps = this.state.keyMaps;
2800 for (var i = 0; i < maps.length; ++i)
2801 if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
2807 addOverlay: operation(null, function(spec, options) {
2808 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
2809 if (mode.startState) throw new Error("Overlays may not be stateful.");
2810 this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
2811 this.state.modeGen++;
2814 removeOverlay: operation(null, function(spec) {
2815 var overlays = this.state.overlays;
2816 for (var i = 0; i < overlays.length; ++i) {
2817 var cur = overlays[i].modeSpec;
2818 if (cur == spec || typeof spec == "string" && cur.name == spec) {
2819 overlays.splice(i, 1);
2820 this.state.modeGen++;
2827 indentLine: operation(null, function(n, dir, aggressive) {
2828 if (typeof dir != "string" && typeof dir != "number") {
2829 if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2830 else dir = dir ? "add" : "subtract";
2832 if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
2834 indentSelection: operation(null, function(how) {
2835 var sel = this.doc.sel;
2836 if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
2837 var e = sel.to.line - (sel.to.ch ? 0 : 1);
2838 for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
2841 // Fetch the parser token for a given character. Useful for hacks
2842 // that want to inspect the mode state (say, for completion).
2843 getTokenAt: function(pos, precise) {
2845 pos = clipPos(doc, pos);
2846 var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
2847 var line = getLine(doc, pos.line);
2848 var stream = new StringStream(line.text, this.options.tabSize);
2849 while (stream.pos < pos.ch && !stream.eol()) {
2850 stream.start = stream.pos;
2851 var style = mode.token(stream, state);
2853 return {start: stream.start,
2855 string: stream.current(),
2856 className: style || null, // Deprecated, use 'type' instead
2857 type: style || null,
2861 getTokenTypeAt: function(pos) {
2862 pos = clipPos(this.doc, pos);
2863 var styles = getLineStyles(this, getLine(this.doc, pos.line));
2864 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
2865 if (ch == 0) return styles[2];
2867 var mid = (before + after) >> 1;
2868 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
2869 else if (styles[mid * 2 + 1] < ch) before = mid + 1;
2870 else return styles[mid * 2 + 2];
2874 getModeAt: function(pos) {
2875 var mode = this.doc.mode;
2876 if (!mode.innerMode) return mode;
2877 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
2880 getHelper: function(pos, type) {
2881 if (!helpers.hasOwnProperty(type)) return;
2882 var help = helpers[type], mode = this.getModeAt(pos);
2883 return mode[type] && help[mode[type]] ||
2884 mode.helperType && help[mode.helperType] ||
2888 getStateAfter: function(line, precise) {
2890 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
2891 return getStateBefore(this, line + 1, precise);
2894 cursorCoords: function(start, mode) {
2895 var pos, sel = this.doc.sel;
2896 if (start == null) pos = sel.head;
2897 else if (typeof start == "object") pos = clipPos(this.doc, start);
2898 else pos = start ? sel.from : sel.to;
2899 return cursorCoords(this, pos, mode || "page");
2902 charCoords: function(pos, mode) {
2903 return charCoords(this, clipPos(this.doc, pos), mode || "page");
2906 coordsChar: function(coords, mode) {
2907 coords = fromCoordSystem(this, coords, mode || "page");
2908 return coordsChar(this, coords.left, coords.top);
2911 lineAtHeight: function(height, mode) {
2912 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
2913 return lineAtHeight(this.doc, height + this.display.viewOffset);
2915 heightAtLine: function(line, mode) {
2916 var end = false, last = this.doc.first + this.doc.size - 1;
2917 if (line < this.doc.first) line = this.doc.first;
2918 else if (line > last) { line = last; end = true; }
2919 var lineObj = getLine(this.doc, line);
2920 return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
2921 (end ? lineObj.height : 0);
2924 defaultTextHeight: function() { return textHeight(this.display); },
2925 defaultCharWidth: function() { return charWidth(this.display); },
2927 setGutterMarker: operation(null, function(line, gutterID, value) {
2928 return changeLine(this, line, function(line) {
2929 var markers = line.gutterMarkers || (line.gutterMarkers = {});
2930 markers[gutterID] = value;
2931 if (!value && isEmpty(markers)) line.gutterMarkers = null;
2936 clearGutter: operation(null, function(gutterID) {
2937 var cm = this, doc = cm.doc, i = doc.first;
2938 doc.iter(function(line) {
2939 if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
2940 line.gutterMarkers[gutterID] = null;
2941 regChange(cm, i, i + 1);
2942 if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
2948 addLineClass: operation(null, function(handle, where, cls) {
2949 return changeLine(this, handle, function(line) {
2950 var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2951 if (!line[prop]) line[prop] = cls;
2952 else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
2953 else line[prop] += " " + cls;
2958 removeLineClass: operation(null, function(handle, where, cls) {
2959 return changeLine(this, handle, function(line) {
2960 var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2961 var cur = line[prop];
2962 if (!cur) return false;
2963 else if (cls == null) line[prop] = null;
2965 var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
2966 if (!found) return false;
2967 var end = found.index + found[0].length;
2968 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
2974 addLineWidget: operation(null, function(handle, node, options) {
2975 return addLineWidget(this, handle, node, options);
2978 removeLineWidget: function(widget) { widget.clear(); },
2980 lineInfo: function(line) {
2981 if (typeof line == "number") {
2982 if (!isLine(this.doc, line)) return null;
2984 line = getLine(this.doc, line);
2985 if (!line) return null;
2987 var n = lineNo(line);
2988 if (n == null) return null;
2990 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
2991 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
2992 widgets: line.widgets};
2995 getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
2997 addWidget: function(pos, node, scroll, vert, horiz) {
2998 var display = this.display;
2999 pos = cursorCoords(this, clipPos(this.doc, pos));
3000 var top = pos.bottom, left = pos.left;
3001 node.style.position = "absolute";
3002 display.sizer.appendChild(node);
3003 if (vert == "over") {
3005 } else if (vert == "above" || vert == "near") {
3006 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
3007 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
3008 // Default to positioning above (if specified and possible); otherwise default to positioning below
3009 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
3010 top = pos.top - node.offsetHeight;
3011 else if (pos.bottom + node.offsetHeight <= vspace)
3013 if (left + node.offsetWidth > hspace)
3014 left = hspace - node.offsetWidth;
3016 node.style.top = top + "px";
3017 node.style.left = node.style.right = "";
3018 if (horiz == "right") {
3019 left = display.sizer.clientWidth - node.offsetWidth;
3020 node.style.right = "0px";
3022 if (horiz == "left") left = 0;
3023 else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
3024 node.style.left = left + "px";
3027 scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
3030 triggerOnKeyDown: operation(null, onKeyDown),
3032 execCommand: function(cmd) {return commands[cmd](this);},
3034 findPosH: function(from, amount, unit, visually) {
3036 if (amount < 0) { dir = -1; amount = -amount; }
3037 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
3038 cur = findPosH(this.doc, cur, dir, unit, visually);
3039 if (cur.hitSide) break;
3044 moveH: operation(null, function(dir, unit) {
3045 var sel = this.doc.sel, pos;
3046 if (sel.shift || sel.extend || posEq(sel.from, sel.to))
3047 pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
3049 pos = dir < 0 ? sel.from : sel.to;
3050 extendSelection(this.doc, pos, pos, dir);
3053 deleteH: operation(null, function(dir, unit) {
3054 var sel = this.doc.sel;
3055 if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
3056 else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
3057 this.curOp.userSelChange = true;
3060 findPosV: function(from, amount, unit, goalColumn) {
3061 var dir = 1, x = goalColumn;
3062 if (amount < 0) { dir = -1; amount = -amount; }
3063 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
3064 var coords = cursorCoords(this, cur, "div");
3065 if (x == null) x = coords.left;
3066 else coords.left = x;
3067 cur = findPosV(this, coords, dir, unit);
3068 if (cur.hitSide) break;
3073 moveV: operation(null, function(dir, unit) {
3074 var sel = this.doc.sel;
3075 var pos = cursorCoords(this, sel.head, "div");
3076 if (sel.goalColumn != null) pos.left = sel.goalColumn;
3077 var target = findPosV(this, pos, dir, unit);
3079 if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
3080 extendSelection(this.doc, target, target, dir);
3081 sel.goalColumn = pos.left;
3084 toggleOverwrite: function(value) {
3085 if (value != null && value == this.state.overwrite) return;
3086 if (this.state.overwrite = !this.state.overwrite)
3087 this.display.cursor.className += " CodeMirror-overwrite";
3089 this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
3091 hasFocus: function() { return this.state.focused; },
3093 scrollTo: operation(null, function(x, y) {
3094 updateScrollPos(this, x, y);
3096 getScrollInfo: function() {
3097 var scroller = this.display.scroller, co = scrollerCutOff;
3098 return {left: scroller.scrollLeft, top: scroller.scrollTop,
3099 height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
3100 clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
3103 scrollIntoView: operation(null, function(pos, margin) {
3104 if (typeof pos == "number") pos = Pos(pos, 0);
3105 if (!margin) margin = 0;
3108 if (!pos || pos.line != null) {
3109 this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
3110 this.curOp.scrollToPosMargin = margin;
3111 coords = cursorCoords(this, this.curOp.scrollToPos);
3113 var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin);
3114 updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
3117 setSize: function(width, height) {
3118 function interpret(val) {
3119 return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
3121 if (width != null) this.display.wrapper.style.width = interpret(width);
3122 if (height != null) this.display.wrapper.style.height = interpret(height);
3126 operation: function(f){return runInOp(this, f);},
3128 refresh: operation(null, function() {
3130 updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);