1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
4 // This is CodeMirror (http://codemirror.net), a code editor
5 // implemented in JavaScript on top of the browser's DOM.
7 // You can find some technical background for some of the code below
8 // at http://marijnhaverbeke.nl/blog/#cm-internals .
11 if (typeof exports
== "object" && typeof module
== "object") // CommonJS
12 module
.exports
= mod();
13 else if (typeof define
== "function" && define
.amd
) // AMD
14 return define([], mod
);
15 else // Plain browser env
16 this.CodeMirror
= mod();
22 // Kludges for bugs and behavior differences that can't be feature
23 // detected are enabled based on userAgent etc sniffing.
25 var gecko
= /gecko\/\d/i.test(navigator
.userAgent
);
26 var ie_upto10
= /MSIE
\d
/.test(navigator
.userAgent
);
27 var ie_11up
= /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator
.userAgent
);
28 var ie
= ie_upto10
|| ie_11up
;
29 var ie_version
= ie
&& (ie_upto10
? document
.documentMode
|| 6 : ie_11up
[1]);
30 var webkit
= /WebKit\//.test(navigator
.userAgent
);
31 var qtwebkit
= webkit
&& /Qt\/\d+\.\d+/.test(navigator
.userAgent
);
32 var chrome
= /Chrome\//.test(navigator
.userAgent
);
33 var presto
= /Opera\//.test(navigator
.userAgent
);
34 var safari
= /Apple Computer
/.test(navigator
.vendor
);
35 var mac_geMountainLion
= /Mac OS X
1\d
\D([8-9]|\d
\d
)\D
/.test(navigator
.userAgent
);
36 var phantom
= /PhantomJS/.test(navigator
.userAgent
);
38 var ios
= /AppleWebKit/.test(navigator
.userAgent
) && /Mobile\/\w+/.test(navigator
.userAgent
);
39 // This is woefully incomplete. Suggestions for alternative methods welcome.
40 var mobile
= ios
|| /Android
|webOS
|BlackBerry
|Opera Mini
|Opera Mobi
|IEMobile
/i
.test(navigator
.userAgent
);
41 var mac
= ios
|| /Mac/.test(navigator
.platform
);
42 var windows
= /win/i.test(navigator
.platform
);
44 var presto_version
= presto
&& navigator
.userAgent
.match(/Version\/(\d*\.\d*)/);
45 if (presto_version
) presto_version
= Number(presto_version
[1]);
46 if (presto_version
&& presto_version
>= 15) { presto
= false; webkit
= true; }
47 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
48 var flipCtrlCmd
= mac
&& (qtwebkit
|| presto
&& (presto_version
== null || presto_version
< 12.11));
49 var captureRightClick
= gecko
|| (ie
&& ie_version
>= 9);
51 // Optimize some code when these features are not used.
52 var sawReadOnlySpans
= false, sawCollapsedSpans
= false;
56 // A CodeMirror instance represents an editor. This is the object
57 // that user code is usually dealing with.
59 function CodeMirror(place
, options
) {
60 if (!(this instanceof CodeMirror
)) return new CodeMirror(place
, options
);
62 this.options
= options
= options
? copyObj(options
) : {};
63 // Determine effective options based on given values and defaults.
64 copyObj(defaults
, options
, false);
65 setGuttersForLineNumbers(options
);
67 var doc
= options
.value
;
68 if (typeof doc
== "string") doc
= new Doc(doc
, options
.mode
);
71 var input
= new CodeMirror
.inputStyles
[options
.inputStyle
](this);
72 var display
= this.display
= new Display(place
, doc
, input
);
73 display
.wrapper
.CodeMirror
= this;
76 if (options
.lineWrapping
)
77 this.display
.wrapper
.className
+= " CodeMirror-wrap";
78 if (options
.autofocus
&& !mobile
) display
.input
.focus();
82 keyMaps: [], // stores maps added by addKeyMap
83 overlays: [], // highlighting overlays, as added by addOverlay
84 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
86 delayingBlurEvent: false,
88 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
89 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
91 highlight: new Delayed(), // stores highlight worker timeout
92 keySeq: null, // Unfinished key sequence
98 // Override magic textarea content restore that IE sometimes does
99 // on our hidden textarea on reload
100 if (ie
&& ie_version
< 11) setTimeout(function() { cm
.display
.input
.reset(true); }, 20);
102 registerEventHandlers(this);
103 ensureGlobalHandlers();
105 startOperation(this);
106 this.curOp
.forceUpdate
= true;
107 attachDoc(this, doc
);
109 if ((options
.autofocus
&& !mobile
) || cm
.hasFocus())
110 setTimeout(bind(onFocus
, this), 20);
114 for (var opt
in optionHandlers
) if (optionHandlers
.hasOwnProperty(opt
))
115 optionHandlers
[opt
](this, options
[opt
], Init
);
116 maybeUpdateLineNumberWidth(this);
117 if (options
.finishInit
) options
.finishInit(this);
118 for (var i
= 0; i
< initHooks
.length
; ++i
) initHooks
[i
](this);
120 // Suppress optimizelegibility in Webkit, since it breaks text
121 // measuring on line wrapping boundaries.
122 if (webkit
&& options
.lineWrapping
&&
123 getComputedStyle(display
.lineDiv
).textRendering
== "optimizelegibility")
124 display
.lineDiv
.style
.textRendering
= "auto";
127 // DISPLAY CONSTRUCTOR
129 // The display handles the DOM integration, both for input reading
130 // and content drawing. It holds references to DOM nodes and
131 // display-related state.
133 function Display(place
, doc
, input
) {
137 // Covers bottom-right square when both scrollbars are present.
138 d
.scrollbarFiller
= elt("div", null, "CodeMirror-scrollbar-filler");
139 d
.scrollbarFiller
.setAttribute("cm-not-content", "true");
140 // Covers bottom of gutter when coverGutterNextToScrollbar is on
141 // and h scrollbar is present.
142 d
.gutterFiller
= elt("div", null, "CodeMirror-gutter-filler");
143 d
.gutterFiller
.setAttribute("cm-not-content", "true");
144 // Will contain the actual code, positioned to cover the viewport.
145 d
.lineDiv
= elt("div", null, "CodeMirror-code");
146 // Elements are added to these to represent selection and cursors.
147 d
.selectionDiv
= elt("div", null, null, "position: relative; z-index: 1");
148 d
.cursorDiv
= elt("div", null, "CodeMirror-cursors");
149 // A visibility: hidden element used to find the size of things.
150 d
.measure
= elt("div", null, "CodeMirror-measure");
151 // When lines outside of the viewport are measured, they are drawn in this.
152 d
.lineMeasure
= elt("div", null, "CodeMirror-measure");
153 // Wraps everything that needs to exist inside the vertically-padded coordinate system
154 d
.lineSpace
= elt("div", [d
.measure
, d
.lineMeasure
, d
.selectionDiv
, d
.cursorDiv
, d
.lineDiv
],
155 null, "position: relative; outline: none");
156 // Moved around its parent to cover visible view.
157 d
.mover
= elt("div", [elt("div", [d
.lineSpace
], "CodeMirror-lines")], null, "position: relative");
158 // Set to the height of the document, allowing scrolling.
159 d
.sizer
= elt("div", [d
.mover
], "CodeMirror-sizer");
161 // Behavior of elts with overflow: auto and padding is
162 // inconsistent across browsers. This is used to ensure the
163 // scrollable area is big enough.
164 d
.heightForcer
= elt("div", null, null, "position: absolute; height: " + scrollerGap
+ "px; width: 1px;");
165 // Will contain the gutters, if any.
166 d
.gutters
= elt("div", null, "CodeMirror-gutters");
168 // Actual scrollable element.
169 d
.scroller
= elt("div", [d
.sizer
, d
.heightForcer
, d
.gutters
], "CodeMirror-scroll");
170 d
.scroller
.setAttribute("tabIndex", "-1");
171 // The element in which the editor lives.
172 d
.wrapper
= elt("div", [d
.scrollbarFiller
, d
.gutterFiller
, d
.scroller
], "CodeMirror");
174 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
175 if (ie
&& ie_version
< 8) { d
.gutters
.style
.zIndex
= -1; d
.scroller
.style
.paddingRight
= 0; }
176 if (!webkit
&& !(gecko
&& mobile
)) d
.scroller
.draggable
= true;
179 if (place
.appendChild
) place
.appendChild(d
.wrapper
);
180 else place(d
.wrapper
);
183 // Current rendered range (may be bigger than the view window).
184 d
.viewFrom
= d
.viewTo
= doc
.first
;
185 d
.reportedViewFrom
= d
.reportedViewTo
= doc
.first
;
186 // Information about the rendered lines.
188 d
.renderedView
= null;
189 // Holds info about a single rendered line when it was rendered
190 // for measurement, while not in view.
191 d
.externalMeasured
= null;
192 // Empty space (in pixels) above the view
194 d
.lastWrapHeight
= d
.lastWrapWidth
= 0;
195 d
.updateLineNumbers
= null;
197 d
.nativeBarWidth
= d
.barHeight
= d
.barWidth
= 0;
198 d
.scrollbarsClipped
= false;
200 // Used to only resize the line number gutter when necessary (when
201 // the amount of lines crosses a boundary that makes its width change)
202 d
.lineNumWidth
= d
.lineNumInnerWidth
= d
.lineNumChars
= null;
203 // Set to true when a non-horizontal-scrolling line widget is
204 // added. As an optimization, line widget aligning is skipped when
206 d
.alignWidgets
= false;
208 d
.cachedCharWidth
= d
.cachedTextHeight
= d
.cachedPaddingH
= null;
210 // Tracks the maximum line length so that the horizontal scrollbar
211 // can be kept static when scrolling.
214 d
.maxLineChanged
= false;
216 // Used for measuring wheel scrolling granularity
217 d
.wheelDX
= d
.wheelDY
= d
.wheelStartX
= d
.wheelStartY
= null;
219 // True when shift is held down.
222 // Used to track whether anything happened since the context menu
224 d
.selForContextMenu
= null;
226 d
.activeTouch
= null;
233 // Used to get the editor into a consistent state again when options change.
235 function loadMode(cm
) {
236 cm
.doc
.mode
= CodeMirror
.getMode(cm
.options
, cm
.doc
.modeOption
);
240 function resetModeState(cm
) {
241 cm
.doc
.iter(function(line
) {
242 if (line
.stateAfter
) line
.stateAfter
= null;
243 if (line
.styles
) line
.styles
= null;
245 cm
.doc
.frontier
= cm
.doc
.first
;
246 startWorker(cm
, 100);
248 if (cm
.curOp
) regChange(cm
);
251 function wrappingChanged(cm
) {
252 if (cm
.options
.lineWrapping
) {
253 addClass(cm
.display
.wrapper
, "CodeMirror-wrap");
254 cm
.display
.sizer
.style
.minWidth
= "";
255 cm
.display
.sizerWidth
= null;
257 rmClass(cm
.display
.wrapper
, "CodeMirror-wrap");
260 estimateLineHeights(cm
);
263 setTimeout(function(){updateScrollbars(cm
);}, 100);
266 // Returns a function that estimates the height of a line, to use as
267 // first approximation until the line becomes visible (and is thus
268 // properly measurable).
269 function estimateHeight(cm
) {
270 var th
= textHeight(cm
.display
), wrapping
= cm
.options
.lineWrapping
;
271 var perLine
= wrapping
&& Math
.max(5, cm
.display
.scroller
.clientWidth
/ charWidth(cm
.display
) - 3);
272 return function(line
) {
273 if (lineIsHidden(cm
.doc
, line
)) return 0;
275 var widgetsHeight
= 0;
276 if (line
.widgets
) for (var i
= 0; i
< line
.widgets
.length
; i
++) {
277 if (line
.widgets
[i
].height
) widgetsHeight
+= line
.widgets
[i
].height
;
281 return widgetsHeight
+ (Math
.ceil(line
.text
.length
/ perLine
) || 1) * th
;
283 return widgetsHeight
+ th
;
287 function estimateLineHeights(cm
) {
288 var doc
= cm
.doc
, est
= estimateHeight(cm
);
289 doc
.iter(function(line
) {
290 var estHeight
= est(line
);
291 if (estHeight
!= line
.height
) updateLineHeight(line
, estHeight
);
295 function themeChanged(cm
) {
296 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(/\s*cm-s-\S+/g, "") +
297 cm
.options
.theme
.replace(/(^|\s)\s*/g, " cm-s-");
301 function guttersChanged(cm
) {
304 setTimeout(function(){alignHorizontally(cm
);}, 20);
307 // Rebuild the gutter elements, ensure the margin to the left of the
308 // code matches their width.
309 function updateGutters(cm
) {
310 var gutters
= cm
.display
.gutters
, specs
= cm
.options
.gutters
;
311 removeChildren(gutters
);
312 for (var i
= 0; i
< specs
.length
; ++i
) {
313 var gutterClass
= specs
[i
];
314 var gElt
= gutters
.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass
));
315 if (gutterClass
== "CodeMirror-linenumbers") {
316 cm
.display
.lineGutter
= gElt
;
317 gElt
.style
.width
= (cm
.display
.lineNumWidth
|| 1) + "px";
320 gutters
.style
.display
= i
? "" : "none";
321 updateGutterSpace(cm
);
324 function updateGutterSpace(cm
) {
325 var width
= cm
.display
.gutters
.offsetWidth
;
326 cm
.display
.sizer
.style
.marginLeft
= width
+ "px";
329 // Compute the character length of a line, taking into account
330 // collapsed ranges (see markText) that might hide parts, and join
331 // other lines onto it.
332 function lineLength(line
) {
333 if (line
.height
== 0) return 0;
334 var len
= line
.text
.length
, merged
, cur
= line
;
335 while (merged
= collapsedSpanAtStart(cur
)) {
336 var found
= merged
.find(0, true);
337 cur
= found
.from.line
;
338 len
+= found
.from.ch
- found
.to
.ch
;
341 while (merged
= collapsedSpanAtEnd(cur
)) {
342 var found
= merged
.find(0, true);
343 len
-= cur
.text
.length
- found
.from.ch
;
345 len
+= cur
.text
.length
- found
.to
.ch
;
350 // Find the longest line in the document.
351 function findMaxLine(cm
) {
352 var d
= cm
.display
, doc
= cm
.doc
;
353 d
.maxLine
= getLine(doc
, doc
.first
);
354 d
.maxLineLength
= lineLength(d
.maxLine
);
355 d
.maxLineChanged
= true;
356 doc
.iter(function(line
) {
357 var len
= lineLength(line
);
358 if (len
> d
.maxLineLength
) {
359 d
.maxLineLength
= len
;
365 // Make sure the gutters options contains the element
366 // "CodeMirror-linenumbers" when the lineNumbers option is true.
367 function setGuttersForLineNumbers(options
) {
368 var found
= indexOf(options
.gutters
, "CodeMirror-linenumbers");
369 if (found
== -1 && options
.lineNumbers
) {
370 options
.gutters
= options
.gutters
.concat(["CodeMirror-linenumbers"]);
371 } else if (found
> -1 && !options
.lineNumbers
) {
372 options
.gutters
= options
.gutters
.slice(0);
373 options
.gutters
.splice(found
, 1);
379 // Prepare DOM reads needed to update the scrollbars. Done in one
380 // shot to minimize update/measure roundtrips.
381 function measureForScrollbars(cm
) {
382 var d
= cm
.display
, gutterW
= d
.gutters
.offsetWidth
;
383 var docH
= Math
.round(cm
.doc
.height
+ paddingVert(cm
.display
));
385 clientHeight: d
.scroller
.clientHeight
,
386 viewHeight: d
.wrapper
.clientHeight
,
387 scrollWidth: d
.scroller
.scrollWidth
, clientWidth: d
.scroller
.clientWidth
,
388 viewWidth: d
.wrapper
.clientWidth
,
389 barLeft: cm
.options
.fixedGutter
? gutterW : 0,
391 scrollHeight: docH
+ scrollGap(cm
) + d
.barHeight
,
392 nativeBarWidth: d
.nativeBarWidth
,
397 function NativeScrollbars(place
, scroll
, cm
) {
399 var vert
= this.vert
= elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
400 var horiz
= this.horiz
= elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
401 place(vert
); place(horiz
);
403 on(vert
, "scroll", function() {
404 if (vert
.clientHeight
) scroll(vert
.scrollTop
, "vertical");
406 on(horiz
, "scroll", function() {
407 if (horiz
.clientWidth
) scroll(horiz
.scrollLeft
, "horizontal");
410 this.checkedOverlay
= false;
411 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
412 if (ie
&& ie_version
< 8) this.horiz
.style
.minHeight
= this.vert
.style
.minWidth
= "18px";
415 NativeScrollbars
.prototype = copyObj({
416 update: function(measure
) {
417 var needsH
= measure
.scrollWidth
> measure
.clientWidth
+ 1;
418 var needsV
= measure
.scrollHeight
> measure
.clientHeight
+ 1;
419 var sWidth
= measure
.nativeBarWidth
;
422 this.vert
.style
.display
= "block";
423 this.vert
.style
.bottom
= needsH
? sWidth
+ "px" : "0";
424 var totalHeight
= measure
.viewHeight
- (needsH
? sWidth : 0);
425 // A bug in IE8 can cause this value to be negative, so guard it.
426 this.vert
.firstChild
.style
.height
=
427 Math
.max(0, measure
.scrollHeight
- measure
.clientHeight
+ totalHeight
) + "px";
429 this.vert
.style
.display
= "";
430 this.vert
.firstChild
.style
.height
= "0";
434 this.horiz
.style
.display
= "block";
435 this.horiz
.style
.right
= needsV
? sWidth
+ "px" : "0";
436 this.horiz
.style
.left
= measure
.barLeft
+ "px";
437 var totalWidth
= measure
.viewWidth
- measure
.barLeft
- (needsV
? sWidth : 0);
438 this.horiz
.firstChild
.style
.width
=
439 (measure
.scrollWidth
- measure
.clientWidth
+ totalWidth
) + "px";
441 this.horiz
.style
.display
= "";
442 this.horiz
.firstChild
.style
.width
= "0";
445 if (!this.checkedOverlay
&& measure
.clientHeight
> 0) {
446 if (sWidth
== 0) this.overlayHack();
447 this.checkedOverlay
= true;
450 return {right: needsV
? sWidth : 0, bottom: needsH
? sWidth : 0};
452 setScrollLeft: function(pos
) {
453 if (this.horiz
.scrollLeft
!= pos
) this.horiz
.scrollLeft
= pos
;
455 setScrollTop: function(pos
) {
456 if (this.vert
.scrollTop
!= pos
) this.vert
.scrollTop
= pos
;
458 overlayHack: function() {
459 var w
= mac
&& !mac_geMountainLion
? "12px" : "18px";
460 this.horiz
.style
.minHeight
= this.vert
.style
.minWidth
= w
;
462 var barMouseDown = function(e
) {
463 if (e_target(e
) != self
.vert
&& e_target(e
) != self
.horiz
)
464 operation(self
.cm
, onMouseDown
)(e
);
466 on(this.vert
, "mousedown", barMouseDown
);
467 on(this.horiz
, "mousedown", barMouseDown
);
470 var parent
= this.horiz
.parentNode
;
471 parent
.removeChild(this.horiz
);
472 parent
.removeChild(this.vert
);
474 }, NativeScrollbars
.prototype);
476 function NullScrollbars() {}
478 NullScrollbars
.prototype = copyObj({
479 update: function() { return {bottom: 0, right: 0}; },
480 setScrollLeft: function() {},
481 setScrollTop: function() {},
483 }, NullScrollbars
.prototype);
485 CodeMirror
.scrollbarModel
= {"native": NativeScrollbars
, "null": NullScrollbars
};
487 function initScrollbars(cm
) {
488 if (cm
.display
.scrollbars
) {
489 cm
.display
.scrollbars
.clear();
490 if (cm
.display
.scrollbars
.addClass
)
491 rmClass(cm
.display
.wrapper
, cm
.display
.scrollbars
.addClass
);
494 cm
.display
.scrollbars
= new CodeMirror
.scrollbarModel
[cm
.options
.scrollbarStyle
](function(node
) {
495 cm
.display
.wrapper
.insertBefore(node
, cm
.display
.scrollbarFiller
);
496 // Prevent clicks in the scrollbars from killing focus
497 on(node
, "mousedown", function() {
498 if (cm
.state
.focused
) setTimeout(function() { cm
.display
.input
.focus(); }, 0);
500 node
.setAttribute("cm-not-content", "true");
501 }, function(pos
, axis
) {
502 if (axis
== "horizontal") setScrollLeft(cm
, pos
);
503 else setScrollTop(cm
, pos
);
505 if (cm
.display
.scrollbars
.addClass
)
506 addClass(cm
.display
.wrapper
, cm
.display
.scrollbars
.addClass
);
509 function updateScrollbars(cm
, measure
) {
510 if (!measure
) measure
= measureForScrollbars(cm
);
511 var startWidth
= cm
.display
.barWidth
, startHeight
= cm
.display
.barHeight
;
512 updateScrollbarsInner(cm
, measure
);
513 for (var i
= 0; i
< 4 && startWidth
!= cm
.display
.barWidth
|| startHeight
!= cm
.display
.barHeight
; i
++) {
514 if (startWidth
!= cm
.display
.barWidth
&& cm
.options
.lineWrapping
)
515 updateHeightsInViewport(cm
);
516 updateScrollbarsInner(cm
, measureForScrollbars(cm
));
517 startWidth
= cm
.display
.barWidth
; startHeight
= cm
.display
.barHeight
;
521 // Re-synchronize the fake scrollbars with the actual size of the
523 function updateScrollbarsInner(cm
, measure
) {
525 var sizes
= d
.scrollbars
.update(measure
);
527 d
.sizer
.style
.paddingRight
= (d
.barWidth
= sizes
.right
) + "px";
528 d
.sizer
.style
.paddingBottom
= (d
.barHeight
= sizes
.bottom
) + "px";
530 if (sizes
.right
&& sizes
.bottom
) {
531 d
.scrollbarFiller
.style
.display
= "block";
532 d
.scrollbarFiller
.style
.height
= sizes
.bottom
+ "px";
533 d
.scrollbarFiller
.style
.width
= sizes
.right
+ "px";
534 } else d
.scrollbarFiller
.style
.display
= "";
535 if (sizes
.bottom
&& cm
.options
.coverGutterNextToScrollbar
&& cm
.options
.fixedGutter
) {
536 d
.gutterFiller
.style
.display
= "block";
537 d
.gutterFiller
.style
.height
= sizes
.bottom
+ "px";
538 d
.gutterFiller
.style
.width
= measure
.gutterWidth
+ "px";
539 } else d
.gutterFiller
.style
.display
= "";
542 // Compute the lines that are visible in a given viewport (defaults
543 // the the current scroll position). viewport may contain top,
544 // height, and ensure (see op.scrollToPos) properties.
545 function visibleLines(display
, doc
, viewport
) {
546 var top
= viewport
&& viewport
.top
!= null ? Math
.max(0, viewport
.top
) : display
.scroller
.scrollTop
;
547 top
= Math
.floor(top
- paddingTop(display
));
548 var bottom
= viewport
&& viewport
.bottom
!= null ? viewport
.bottom : top
+ display
.wrapper
.clientHeight
;
550 var from = lineAtHeight(doc
, top
), to
= lineAtHeight(doc
, bottom
);
551 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
552 // forces those lines into the viewport (if possible).
553 if (viewport
&& viewport
.ensure
) {
554 var ensureFrom
= viewport
.ensure
.from.line
, ensureTo
= viewport
.ensure
.to
.line
;
555 if (ensureFrom
< from) {
557 to
= lineAtHeight(doc
, heightAtLine(getLine(doc
, ensureFrom
)) + display
.wrapper
.clientHeight
);
558 } else if (Math
.min(ensureTo
, doc
.lastLine()) >= to
) {
559 from = lineAtHeight(doc
, heightAtLine(getLine(doc
, ensureTo
)) - display
.wrapper
.clientHeight
);
563 return {from: from, to: Math
.max(to
, from + 1)};
568 // Re-align line numbers and gutter marks to compensate for
569 // horizontal scrolling.
570 function alignHorizontally(cm
) {
571 var display
= cm
.display
, view
= display
.view
;
572 if (!display
.alignWidgets
&& (!display
.gutters
.firstChild
|| !cm
.options
.fixedGutter
)) return;
573 var comp
= compensateForHScroll(display
) - display
.scroller
.scrollLeft
+ cm
.doc
.scrollLeft
;
574 var gutterW
= display
.gutters
.offsetWidth
, left
= comp
+ "px";
575 for (var i
= 0; i
< view
.length
; i
++) if (!view
[i
].hidden
) {
576 if (cm
.options
.fixedGutter
&& view
[i
].gutter
)
577 view
[i
].gutter
.style
.left
= left
;
578 var align
= view
[i
].alignable
;
579 if (align
) for (var j
= 0; j
< align
.length
; j
++)
580 align
[j
].style
.left
= left
;
582 if (cm
.options
.fixedGutter
)
583 display
.gutters
.style
.left
= (comp
+ gutterW
) + "px";
586 // Used to ensure that the line number gutter is still the right
587 // size for the current document size. Returns true when an update
589 function maybeUpdateLineNumberWidth(cm
) {
590 if (!cm
.options
.lineNumbers
) return false;
591 var doc
= cm
.doc
, last
= lineNumberFor(cm
.options
, doc
.first
+ doc
.size
- 1), display
= cm
.display
;
592 if (last
.length
!= display
.lineNumChars
) {
593 var test
= display
.measure
.appendChild(elt("div", [elt("div", last
)],
594 "CodeMirror-linenumber CodeMirror-gutter-elt"));
595 var innerW
= test
.firstChild
.offsetWidth
, padding
= test
.offsetWidth
- innerW
;
596 display
.lineGutter
.style
.width
= "";
597 display
.lineNumInnerWidth
= Math
.max(innerW
, display
.lineGutter
.offsetWidth
- padding
) + 1;
598 display
.lineNumWidth
= display
.lineNumInnerWidth
+ padding
;
599 display
.lineNumChars
= display
.lineNumInnerWidth
? last
.length : -1;
600 display
.lineGutter
.style
.width
= display
.lineNumWidth
+ "px";
601 updateGutterSpace(cm
);
607 function lineNumberFor(options
, i
) {
608 return String(options
.lineNumberFormatter(i
+ options
.firstLineNumber
));
611 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
612 // but using getBoundingClientRect to get a sub-pixel-accurate
614 function compensateForHScroll(display
) {
615 return display
.scroller
.getBoundingClientRect().left
- display
.sizer
.getBoundingClientRect().left
;
620 function DisplayUpdate(cm
, viewport
, force
) {
621 var display
= cm
.display
;
623 this.viewport
= viewport
;
624 // Store some values that we'll need later (but don't want to force a relayout for)
625 this.visible
= visibleLines(display
, cm
.doc
, viewport
);
626 this.editorIsHidden
= !display
.wrapper
.offsetWidth
;
627 this.wrapperHeight
= display
.wrapper
.clientHeight
;
628 this.wrapperWidth
= display
.wrapper
.clientWidth
;
629 this.oldDisplayWidth
= displayWidth(cm
);
631 this.dims
= getDimensions(cm
);
635 DisplayUpdate
.prototype.signal = function(emitter
, type
) {
636 if (hasHandler(emitter
, type
))
637 this.events
.push(arguments
);
639 DisplayUpdate
.prototype.finish = function() {
640 for (var i
= 0; i
< this.events
.length
; i
++)
641 signal
.apply(null, this.events
[i
]);
644 function maybeClipScrollbars(cm
) {
645 var display
= cm
.display
;
646 if (!display
.scrollbarsClipped
&& display
.scroller
.offsetWidth
) {
647 display
.nativeBarWidth
= display
.scroller
.offsetWidth
- display
.scroller
.clientWidth
;
648 display
.heightForcer
.style
.height
= scrollGap(cm
) + "px";
649 display
.sizer
.style
.marginBottom
= -display
.nativeBarWidth
+ "px";
650 display
.sizer
.style
.borderRightWidth
= scrollGap(cm
) + "px";
651 display
.scrollbarsClipped
= true;
655 // Does the actual updating of the line display. Bails out
656 // (returning false) when there is nothing to be done and forced is
658 function updateDisplayIfNeeded(cm
, update
) {
659 var display
= cm
.display
, doc
= cm
.doc
;
661 if (update
.editorIsHidden
) {
666 // Bail out if the visible area is already rendered and nothing changed.
668 update
.visible
.from >= display
.viewFrom
&& update
.visible
.to
<= display
.viewTo
&&
669 (display
.updateLineNumbers
== null || display
.updateLineNumbers
>= display
.viewTo
) &&
670 display
.renderedView
== display
.view
&& countDirtyView(cm
) == 0)
673 if (maybeUpdateLineNumberWidth(cm
)) {
675 update
.dims
= getDimensions(cm
);
678 // Compute a suitable new viewport (from & to)
679 var end
= doc
.first
+ doc
.size
;
680 var from = Math
.max(update
.visible
.from - cm
.options
.viewportMargin
, doc
.first
);
681 var to
= Math
.min(end
, update
.visible
.to
+ cm
.options
.viewportMargin
);
682 if (display
.viewFrom
< from && from - display
.viewFrom
< 20) from = Math
.max(doc
.first
, display
.viewFrom
);
683 if (display
.viewTo
> to
&& display
.viewTo
- to
< 20) to
= Math
.min(end
, display
.viewTo
);
684 if (sawCollapsedSpans
) {
685 from = visualLineNo(cm
.doc
, from);
686 to
= visualLineEndNo(cm
.doc
, to
);
689 var different
= from != display
.viewFrom
|| to
!= display
.viewTo
||
690 display
.lastWrapHeight
!= update
.wrapperHeight
|| display
.lastWrapWidth
!= update
.wrapperWidth
;
691 adjustView(cm
, from, to
);
693 display
.viewOffset
= heightAtLine(getLine(cm
.doc
, display
.viewFrom
));
694 // Position the mover div to align with the current scroll position
695 cm
.display
.mover
.style
.top
= display
.viewOffset
+ "px";
697 var toUpdate
= countDirtyView(cm
);
698 if (!different
&& toUpdate
== 0 && !update
.force
&& display
.renderedView
== display
.view
&&
699 (display
.updateLineNumbers
== null || display
.updateLineNumbers
>= display
.viewTo
))
702 // For big changes, we hide the enclosing element during the
703 // update, since that speeds up the operations on most browsers.
704 var focused
= activeElt();
705 if (toUpdate
> 4) display
.lineDiv
.style
.display
= "none";
706 patchDisplay(cm
, display
.updateLineNumbers
, update
.dims
);
707 if (toUpdate
> 4) display
.lineDiv
.style
.display
= "";
708 display
.renderedView
= display
.view
;
709 // There might have been a widget with a focused element that got
710 // hidden or updated, if so re-focus it.
711 if (focused
&& activeElt() != focused
&& focused
.offsetHeight
) focused
.focus();
713 // Prevent selection and cursors from interfering with the scroll
715 removeChildren(display
.cursorDiv
);
716 removeChildren(display
.selectionDiv
);
717 display
.gutters
.style
.height
= 0;
720 display
.lastWrapHeight
= update
.wrapperHeight
;
721 display
.lastWrapWidth
= update
.wrapperWidth
;
722 startWorker(cm
, 400);
725 display
.updateLineNumbers
= null;
730 function postUpdateDisplay(cm
, update
) {
731 var force
= update
.force
, viewport
= update
.viewport
;
732 for (var first
= true;; first
= false) {
733 if (first
&& cm
.options
.lineWrapping
&& update
.oldDisplayWidth
!= displayWidth(cm
)) {
737 // Clip forced viewport to actual scrollable area.
738 if (viewport
&& viewport
.top
!= null)
739 viewport
= {top: Math
.min(cm
.doc
.height
+ paddingVert(cm
.display
) - displayHeight(cm
), viewport
.top
)};
740 // Updated line heights might result in the drawn area not
741 // actually covering the viewport. Keep looping until it does.
742 update
.visible
= visibleLines(cm
.display
, cm
.doc
, viewport
);
743 if (update
.visible
.from >= cm
.display
.viewFrom
&& update
.visible
.to
<= cm
.display
.viewTo
)
746 if (!updateDisplayIfNeeded(cm
, update
)) break;
747 updateHeightsInViewport(cm
);
748 var barMeasure
= measureForScrollbars(cm
);
750 setDocumentHeight(cm
, barMeasure
);
751 updateScrollbars(cm
, barMeasure
);
754 update
.signal(cm
, "update", cm
);
755 if (cm
.display
.viewFrom
!= cm
.display
.reportedViewFrom
|| cm
.display
.viewTo
!= cm
.display
.reportedViewTo
) {
756 update
.signal(cm
, "viewportChange", cm
, cm
.display
.viewFrom
, cm
.display
.viewTo
);
757 cm
.display
.reportedViewFrom
= cm
.display
.viewFrom
; cm
.display
.reportedViewTo
= cm
.display
.viewTo
;
761 function updateDisplaySimple(cm
, viewport
) {
762 var update
= new DisplayUpdate(cm
, viewport
);
763 if (updateDisplayIfNeeded(cm
, update
)) {
764 updateHeightsInViewport(cm
);
765 postUpdateDisplay(cm
, update
);
766 var barMeasure
= measureForScrollbars(cm
);
768 setDocumentHeight(cm
, barMeasure
);
769 updateScrollbars(cm
, barMeasure
);
774 function setDocumentHeight(cm
, measure
) {
775 cm
.display
.sizer
.style
.minHeight
= measure
.docHeight
+ "px";
776 var total
= measure
.docHeight
+ cm
.display
.barHeight
;
777 cm
.display
.heightForcer
.style
.top
= total
+ "px";
778 cm
.display
.gutters
.style
.height
= Math
.max(total
+ scrollGap(cm
), measure
.clientHeight
) + "px";
781 // Read the actual heights of the rendered lines, and update their
782 // stored heights to match.
783 function updateHeightsInViewport(cm
) {
784 var display
= cm
.display
;
785 var prevBottom
= display
.lineDiv
.offsetTop
;
786 for (var i
= 0; i
< display
.view
.length
; i
++) {
787 var cur
= display
.view
[i
], height
;
788 if (cur
.hidden
) continue;
789 if (ie
&& ie_version
< 8) {
790 var bot
= cur
.node
.offsetTop
+ cur
.node
.offsetHeight
;
791 height
= bot
- prevBottom
;
794 var box
= cur
.node
.getBoundingClientRect();
795 height
= box
.bottom
- box
.top
;
797 var diff
= cur
.line
.height
- height
;
798 if (height
< 2) height
= textHeight(display
);
799 if (diff
> .001 || diff
< -.001) {
800 updateLineHeight(cur
.line
, height
);
801 updateWidgetHeight(cur
.line
);
802 if (cur
.rest
) for (var j
= 0; j
< cur
.rest
.length
; j
++)
803 updateWidgetHeight(cur
.rest
[j
]);
808 // Read and store the height of line widgets associated with the
810 function updateWidgetHeight(line
) {
811 if (line
.widgets
) for (var i
= 0; i
< line
.widgets
.length
; ++i
)
812 line
.widgets
[i
].height
= line
.widgets
[i
].node
.offsetHeight
;
815 // Do a bulk-read of the DOM positions and sizes needed to draw the
816 // view, so that we don't interleave reading and writing to the DOM.
817 function getDimensions(cm
) {
818 var d
= cm
.display
, left
= {}, width
= {};
819 var gutterLeft
= d
.gutters
.clientLeft
;
820 for (var n
= d
.gutters
.firstChild
, i
= 0; n
; n
= n
.nextSibling
, ++i
) {
821 left
[cm
.options
.gutters
[i
]] = n
.offsetLeft
+ n
.clientLeft
+ gutterLeft
;
822 width
[cm
.options
.gutters
[i
]] = n
.clientWidth
;
824 return {fixedPos: compensateForHScroll(d
),
825 gutterTotalWidth: d
.gutters
.offsetWidth
,
828 wrapperWidth: d
.wrapper
.clientWidth
};
831 // Sync the actual display DOM structure with display.view, removing
832 // nodes for lines that are no longer in view, and creating the ones
833 // that are not there yet, and updating the ones that are out of
835 function patchDisplay(cm
, updateNumbersFrom
, dims
) {
836 var display
= cm
.display
, lineNumbers
= cm
.options
.lineNumbers
;
837 var container
= display
.lineDiv
, cur
= container
.firstChild
;
840 var next
= node
.nextSibling
;
841 // Works around a throw-scroll bug in OS X Webkit
842 if (webkit
&& mac
&& cm
.display
.currentWheelTarget
== node
)
843 node
.style
.display
= "none";
845 node
.parentNode
.removeChild(node
);
849 var view
= display
.view
, lineN
= display
.viewFrom
;
850 // Loop over the elements in the view, syncing cur (the DOM nodes
851 // in display.lineDiv) with the view as we go.
852 for (var i
= 0; i
< view
.length
; i
++) {
853 var lineView
= view
[i
];
854 if (lineView
.hidden
) {
855 } else if (!lineView
.node
|| lineView
.node
.parentNode
!= container
) { // Not drawn yet
856 var node
= buildLineElement(cm
, lineView
, lineN
, dims
);
857 container
.insertBefore(node
, cur
);
858 } else { // Already drawn
859 while (cur
!= lineView
.node
) cur
= rm(cur
);
860 var updateNumber
= lineNumbers
&& updateNumbersFrom
!= null &&
861 updateNumbersFrom
<= lineN
&& lineView
.lineNumber
;
862 if (lineView
.changes
) {
863 if (indexOf(lineView
.changes
, "gutter") > -1) updateNumber
= false;
864 updateLineForChanges(cm
, lineView
, lineN
, dims
);
867 removeChildren(lineView
.lineNumber
);
868 lineView
.lineNumber
.appendChild(document
.createTextNode(lineNumberFor(cm
.options
, lineN
)));
870 cur
= lineView
.node
.nextSibling
;
872 lineN
+= lineView
.size
;
874 while (cur
) cur
= rm(cur
);
877 // When an aspect of a line changes, a string is added to
878 // lineView.changes. This updates the relevant part of the line's
880 function updateLineForChanges(cm
, lineView
, lineN
, dims
) {
881 for (var j
= 0; j
< lineView
.changes
.length
; j
++) {
882 var type
= lineView
.changes
[j
];
883 if (type
== "text") updateLineText(cm
, lineView
);
884 else if (type
== "gutter") updateLineGutter(cm
, lineView
, lineN
, dims
);
885 else if (type
== "class") updateLineClasses(lineView
);
886 else if (type
== "widget") updateLineWidgets(cm
, lineView
, dims
);
888 lineView
.changes
= null;
891 // Lines with gutter elements, widgets or a background class need to
892 // be wrapped, and have the extra elements added to the wrapper div
893 function ensureLineWrapped(lineView
) {
894 if (lineView
.node
== lineView
.text
) {
895 lineView
.node
= elt("div", null, null, "position: relative");
896 if (lineView
.text
.parentNode
)
897 lineView
.text
.parentNode
.replaceChild(lineView
.node
, lineView
.text
);
898 lineView
.node
.appendChild(lineView
.text
);
899 if (ie
&& ie_version
< 8) lineView
.node
.style
.zIndex
= 2;
901 return lineView
.node
;
904 function updateLineBackground(lineView
) {
905 var cls
= lineView
.bgClass
? lineView
.bgClass
+ " " + (lineView
.line
.bgClass
|| "") : lineView
.line
.bgClass
;
906 if (cls
) cls
+= " CodeMirror-linebackground";
907 if (lineView
.background
) {
908 if (cls
) lineView
.background
.className
= cls
;
909 else { lineView
.background
.parentNode
.removeChild(lineView
.background
); lineView
.background
= null; }
911 var wrap
= ensureLineWrapped(lineView
);
912 lineView
.background
= wrap
.insertBefore(elt("div", null, cls
), wrap
.firstChild
);
916 // Wrapper around buildLineContent which will reuse the structure
917 // in display.externalMeasured when possible.
918 function getLineContent(cm
, lineView
) {
919 var ext
= cm
.display
.externalMeasured
;
920 if (ext
&& ext
.line
== lineView
.line
) {
921 cm
.display
.externalMeasured
= null;
922 lineView
.measure
= ext
.measure
;
925 return buildLineContent(cm
, lineView
);
928 // Redraw the line's text. Interacts with the background and text
929 // classes because the mode may output tokens that influence these
931 function updateLineText(cm
, lineView
) {
932 var cls
= lineView
.text
.className
;
933 var built
= getLineContent(cm
, lineView
);
934 if (lineView
.text
== lineView
.node
) lineView
.node
= built
.pre
;
935 lineView
.text
.parentNode
.replaceChild(built
.pre
, lineView
.text
);
936 lineView
.text
= built
.pre
;
937 if (built
.bgClass
!= lineView
.bgClass
|| built
.textClass
!= lineView
.textClass
) {
938 lineView
.bgClass
= built
.bgClass
;
939 lineView
.textClass
= built
.textClass
;
940 updateLineClasses(lineView
);
942 lineView
.text
.className
= cls
;
946 function updateLineClasses(lineView
) {
947 updateLineBackground(lineView
);
948 if (lineView
.line
.wrapClass
)
949 ensureLineWrapped(lineView
).className
= lineView
.line
.wrapClass
;
950 else if (lineView
.node
!= lineView
.text
)
951 lineView
.node
.className
= "";
952 var textClass
= lineView
.textClass
? lineView
.textClass
+ " " + (lineView
.line
.textClass
|| "") : lineView
.line
.textClass
;
953 lineView
.text
.className
= textClass
|| "";
956 function updateLineGutter(cm
, lineView
, lineN
, dims
) {
957 if (lineView
.gutter
) {
958 lineView
.node
.removeChild(lineView
.gutter
);
959 lineView
.gutter
= null;
961 var markers
= lineView
.line
.gutterMarkers
;
962 if (cm
.options
.lineNumbers
|| markers
) {
963 var wrap
= ensureLineWrapped(lineView
);
964 var gutterWrap
= lineView
.gutter
= elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
965 (cm
.options
.fixedGutter
? dims
.fixedPos : -dims
.gutterTotalWidth
) +
966 "px; width: " + dims
.gutterTotalWidth
+ "px");
967 cm
.display
.input
.setUneditable(gutterWrap
);
968 wrap
.insertBefore(gutterWrap
, lineView
.text
);
969 if (lineView
.line
.gutterClass
)
970 gutterWrap
.className
+= " " + lineView
.line
.gutterClass
;
971 if (cm
.options
.lineNumbers
&& (!markers
|| !markers
["CodeMirror-linenumbers"]))
972 lineView
.lineNumber
= gutterWrap
.appendChild(
973 elt("div", lineNumberFor(cm
.options
, lineN
),
974 "CodeMirror-linenumber CodeMirror-gutter-elt",
975 "left: " + dims
.gutterLeft
["CodeMirror-linenumbers"] + "px; width: "
976 + cm
.display
.lineNumInnerWidth
+ "px"));
977 if (markers
) for (var k
= 0; k
< cm
.options
.gutters
.length
; ++k
) {
978 var id
= cm
.options
.gutters
[k
], found
= markers
.hasOwnProperty(id
) && markers
[id
];
980 gutterWrap
.appendChild(elt("div", [found
], "CodeMirror-gutter-elt", "left: " +
981 dims
.gutterLeft
[id
] + "px; width: " + dims
.gutterWidth
[id
] + "px"));
986 function updateLineWidgets(cm
, lineView
, dims
) {
987 if (lineView
.alignable
) lineView
.alignable
= null;
988 for (var node
= lineView
.node
.firstChild
, next
; node
; node
= next
) {
989 var next
= node
.nextSibling
;
990 if (node
.className
== "CodeMirror-linewidget")
991 lineView
.node
.removeChild(node
);
993 insertLineWidgets(cm
, lineView
, dims
);
996 // Build a line's DOM representation from scratch
997 function buildLineElement(cm
, lineView
, lineN
, dims
) {
998 var built
= getLineContent(cm
, lineView
);
999 lineView
.text
= lineView
.node
= built
.pre
;
1000 if (built
.bgClass
) lineView
.bgClass
= built
.bgClass
;
1001 if (built
.textClass
) lineView
.textClass
= built
.textClass
;
1003 updateLineClasses(lineView
);
1004 updateLineGutter(cm
, lineView
, lineN
, dims
);
1005 insertLineWidgets(cm
, lineView
, dims
);
1006 return lineView
.node
;
1009 // A lineView may contain multiple logical lines (when merged by
1010 // collapsed spans). The widgets for all of them need to be drawn.
1011 function insertLineWidgets(cm
, lineView
, dims
) {
1012 insertLineWidgetsFor(cm
, lineView
.line
, lineView
, dims
, true);
1013 if (lineView
.rest
) for (var i
= 0; i
< lineView
.rest
.length
; i
++)
1014 insertLineWidgetsFor(cm
, lineView
.rest
[i
], lineView
, dims
, false);
1017 function insertLineWidgetsFor(cm
, line
, lineView
, dims
, allowAbove
) {
1018 if (!line
.widgets
) return;
1019 var wrap
= ensureLineWrapped(lineView
);
1020 for (var i
= 0, ws
= line
.widgets
; i
< ws
.length
; ++i
) {
1021 var widget
= ws
[i
], node
= elt("div", [widget
.node
], "CodeMirror-linewidget");
1022 if (!widget
.handleMouseEvents
) node
.setAttribute("cm-ignore-events", "true");
1023 positionLineWidget(widget
, node
, lineView
, dims
);
1024 cm
.display
.input
.setUneditable(node
);
1025 if (allowAbove
&& widget
.above
)
1026 wrap
.insertBefore(node
, lineView
.gutter
|| lineView
.text
);
1028 wrap
.appendChild(node
);
1029 signalLater(widget
, "redraw");
1033 function positionLineWidget(widget
, node
, lineView
, dims
) {
1034 if (widget
.noHScroll
) {
1035 (lineView
.alignable
|| (lineView
.alignable
= [])).push(node
);
1036 var width
= dims
.wrapperWidth
;
1037 node
.style
.left
= dims
.fixedPos
+ "px";
1038 if (!widget
.coverGutter
) {
1039 width
-= dims
.gutterTotalWidth
;
1040 node
.style
.paddingLeft
= dims
.gutterTotalWidth
+ "px";
1042 node
.style
.width
= width
+ "px";
1044 if (widget
.coverGutter
) {
1045 node
.style
.zIndex
= 5;
1046 node
.style
.position
= "relative";
1047 if (!widget
.noHScroll
) node
.style
.marginLeft
= -dims
.gutterTotalWidth
+ "px";
1053 // A Pos instance represents a position within the text.
1054 var Pos
= CodeMirror
.Pos = function(line
, ch
) {
1055 if (!(this instanceof Pos
)) return new Pos(line
, ch
);
1056 this.line
= line
; this.ch
= ch
;
1059 // Compare two positions, return 0 if they are the same, a negative
1060 // number when a is less, and a positive number otherwise.
1061 var cmp
= CodeMirror
.cmpPos = function(a
, b
) { return a
.line
- b
.line
|| a
.ch
- b
.ch
; };
1063 function copyPos(x
) {return Pos(x
.line
, x
.ch
);}
1064 function maxPos(a
, b
) { return cmp(a
, b
) < 0 ? b : a
; }
1065 function minPos(a
, b
) { return cmp(a
, b
) < 0 ? a : b
; }
1069 function ensureFocus(cm
) {
1070 if (!cm
.state
.focused
) { cm
.display
.input
.focus(); onFocus(cm
); }
1073 function isReadOnly(cm
) {
1074 return cm
.options
.readOnly
|| cm
.doc
.cantEdit
;
1077 // This will be set to an array of strings when copying, so that,
1078 // when pasting, we know what kind of selections the copied text
1080 var lastCopied
= null;
1082 function applyTextInput(cm
, inserted
, deleted
, sel
, origin
) {
1084 cm
.display
.shift
= false;
1085 if (!sel
) sel
= doc
.sel
;
1087 var textLines
= splitLines(inserted
), multiPaste
= null;
1088 // When pasing N lines into N selections, insert one line per selection
1089 if (cm
.state
.pasteIncoming
&& sel
.ranges
.length
> 1) {
1090 if (lastCopied
&& lastCopied
.join("\n") == inserted
)
1091 multiPaste
= sel
.ranges
.length
% lastCopied
.length
== 0 && map(lastCopied
, splitLines
);
1092 else if (textLines
.length
== sel
.ranges
.length
)
1093 multiPaste
= map(textLines
, function(l
) { return [l
]; });
1096 // Normal behavior is to insert the new text into every selection
1097 for (var i
= sel
.ranges
.length
- 1; i
>= 0; i
--) {
1098 var range
= sel
.ranges
[i
];
1099 var from = range
.from(), to
= range
.to();
1100 if (range
.empty()) {
1101 if (deleted
&& deleted
> 0) // Handle deletion
1102 from = Pos(from.line
, from.ch
- deleted
);
1103 else if (cm
.state
.overwrite
&& !cm
.state
.pasteIncoming
) // Handle overwrite
1104 to
= Pos(to
.line
, Math
.min(getLine(doc
, to
.line
).text
.length
, to
.ch
+ lst(textLines
).length
));
1106 var updateInput
= cm
.curOp
.updateInput
;
1107 var changeEvent
= {from: from, to: to
, text: multiPaste
? multiPaste
[i
% multiPaste
.length
] : textLines
,
1108 origin: origin
|| (cm
.state
.pasteIncoming
? "paste" : cm
.state
.cutIncoming
? "cut" : "+input")};
1109 makeChange(cm
.doc
, changeEvent
);
1110 signalLater(cm
, "inputRead", cm
, changeEvent
);
1111 // When an 'electric' character is inserted, immediately trigger a reindent
1112 if (inserted
&& !cm
.state
.pasteIncoming
&& cm
.options
.electricChars
&&
1113 cm
.options
.smartIndent
&& range
.head
.ch
< 100 &&
1114 (!i
|| sel
.ranges
[i
- 1].head
.line
!= range
.head
.line
)) {
1115 var mode
= cm
.getModeAt(range
.head
);
1116 var end
= changeEnd(changeEvent
);
1117 var indented
= false;
1118 if (mode
.electricChars
) {
1119 for (var j
= 0; j
< mode
.electricChars
.length
; j
++)
1120 if (inserted
.indexOf(mode
.electricChars
.charAt(j
)) > -1) {
1121 indented
= indentLine(cm
, end
.line
, "smart");
1124 } else if (mode
.electricInput
) {
1125 if (mode
.electricInput
.test(getLine(doc
, end
.line
).text
.slice(0, end
.ch
)))
1126 indented
= indentLine(cm
, end
.line
, "smart");
1128 if (indented
) signalLater(cm
, "electricInput", cm
, end
.line
);
1131 ensureCursorVisible(cm
);
1132 cm
.curOp
.updateInput
= updateInput
;
1133 cm
.curOp
.typing
= true;
1134 cm
.state
.pasteIncoming
= cm
.state
.cutIncoming
= false;
1137 function copyableRanges(cm
) {
1138 var text
= [], ranges
= [];
1139 for (var i
= 0; i
< cm
.doc
.sel
.ranges
.length
; i
++) {
1140 var line
= cm
.doc
.sel
.ranges
[i
].head
.line
;
1141 var lineRange
= {anchor: Pos(line
, 0), head: Pos(line
+ 1, 0)};
1142 ranges
.push(lineRange
);
1143 text
.push(cm
.getRange(lineRange
.anchor
, lineRange
.head
));
1145 return {text: text
, ranges: ranges
};
1148 function disableBrowserMagic(field
) {
1149 field
.setAttribute("autocorrect", "off");
1150 field
.setAttribute("autocapitalize", "off");
1151 field
.setAttribute("spellcheck", "false");
1154 // TEXTAREA INPUT STYLE
1156 function TextareaInput(cm
) {
1158 // See input.poll and input.reset
1159 this.prevInput
= "";
1161 // Flag that indicates whether we expect input to appear real soon
1162 // now (after some event like 'keypress' or 'input') and are
1163 // polling intensively.
1164 this.pollingFast
= false;
1165 // Self-resetting timeout for the poller
1166 this.polling
= new Delayed();
1167 // Tracks when input.reset has punted to just putting a short
1168 // string into the textarea instead of the full selection.
1169 this.inaccurateSelection
= false;
1170 // Used to work around IE issue with selection being forgotten when focus moves away from textarea
1171 this.hasSelection
= false;
1172 this.composing
= null;
1175 function hiddenTextarea() {
1176 var te
= elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
1177 var div
= elt("div", [te
], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
1178 // The textarea is kept positioned near the cursor to prevent the
1179 // fact that it'll be scrolled into view on input from scrolling
1180 // our fake cursor out of view. On webkit, when wrap=off, paste is
1181 // very slow. So make the area wide instead.
1182 if (webkit
) te
.style
.width
= "1000px";
1183 else te
.setAttribute("wrap", "off");
1184 // If border: 0; -- iOS fails to open keyboard (issue #1287)
1185 if (ios
) te
.style
.border
= "1px solid black";
1186 disableBrowserMagic(te
);
1190 TextareaInput
.prototype = copyObj({
1191 init: function(display
) {
1192 var input
= this, cm
= this.cm
;
1194 // Wraps and hides input textarea
1195 var div
= this.wrapper
= hiddenTextarea();
1196 // The semihidden textarea that is focused when the editor is
1197 // focused, and receives input.
1198 var te
= this.textarea
= div
.firstChild
;
1199 display
.wrapper
.insertBefore(div
, display
.wrapper
.firstChild
);
1201 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
1202 if (ios
) te
.style
.width
= "0px";
1204 on(te
, "input", function() {
1205 if (ie
&& ie_version
>= 9 && input
.hasSelection
) input
.hasSelection
= null;
1209 on(te
, "paste", function() {
1210 // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
1211 // Add a char to the end of textarea before paste occur so that
1212 // selection doesn't span to the end of textarea.
1213 if (webkit
&& !cm
.state
.fakedLastChar
&& !(new Date
- cm
.state
.lastMiddleDown
< 200)) {
1214 var start
= te
.selectionStart
, end
= te
.selectionEnd
;
1216 // The selection end needs to be set before the start, otherwise there
1217 // can be an intermediate non-empty selection between the two, which
1218 // can override the middle-click paste buffer on linux and cause the
1219 // wrong thing to get pasted.
1220 te
.selectionEnd
= end
;
1221 te
.selectionStart
= start
;
1222 cm
.state
.fakedLastChar
= true;
1224 cm
.state
.pasteIncoming
= true;
1228 function prepareCopyCut(e
) {
1229 if (cm
.somethingSelected()) {
1230 lastCopied
= cm
.getSelections();
1231 if (input
.inaccurateSelection
) {
1232 input
.prevInput
= "";
1233 input
.inaccurateSelection
= false;
1234 te
.value
= lastCopied
.join("\n");
1237 } else if (!cm
.options
.lineWiseCopyCut
) {
1240 var ranges
= copyableRanges(cm
);
1241 lastCopied
= ranges
.text
;
1242 if (e
.type
== "cut") {
1243 cm
.setSelections(ranges
.ranges
, null, sel_dontScroll
);
1245 input
.prevInput
= "";
1246 te
.value
= ranges
.text
.join("\n");
1250 if (e
.type
== "cut") cm
.state
.cutIncoming
= true;
1252 on(te
, "cut", prepareCopyCut
);
1253 on(te
, "copy", prepareCopyCut
);
1255 on(display
.scroller
, "paste", function(e
) {
1256 if (eventInWidget(display
, e
)) return;
1257 cm
.state
.pasteIncoming
= true;
1261 // Prevent normal selection in the editor (we handle our own)
1262 on(display
.lineSpace
, "selectstart", function(e
) {
1263 if (!eventInWidget(display
, e
)) e_preventDefault(e
);
1266 on(te
, "compositionstart", function() {
1267 var start
= cm
.getCursor("from");
1270 range: cm
.markText(start
, cm
.getCursor("to"), {className: "CodeMirror-composing"})
1273 on(te
, "compositionend", function() {
1274 if (input
.composing
) {
1276 input
.composing
.range
.clear();
1277 input
.composing
= null;
1282 prepareSelection: function() {
1283 // Redraw the selection and/or cursor
1284 var cm
= this.cm
, display
= cm
.display
, doc
= cm
.doc
;
1285 var result
= prepareSelection(cm
);
1287 // Move the hidden textarea near the cursor to prevent scrolling artifacts
1288 if (cm
.options
.moveInputWithCursor
) {
1289 var headPos
= cursorCoords(cm
, doc
.sel
.primary().head
, "div");
1290 var wrapOff
= display
.wrapper
.getBoundingClientRect(), lineOff
= display
.lineDiv
.getBoundingClientRect();
1291 result
.teTop
= Math
.max(0, Math
.min(display
.wrapper
.clientHeight
- 10,
1292 headPos
.top
+ lineOff
.top
- wrapOff
.top
));
1293 result
.teLeft
= Math
.max(0, Math
.min(display
.wrapper
.clientWidth
- 10,
1294 headPos
.left
+ lineOff
.left
- wrapOff
.left
));
1300 showSelection: function(drawn
) {
1301 var cm
= this.cm
, display
= cm
.display
;
1302 removeChildrenAndAdd(display
.cursorDiv
, drawn
.cursors
);
1303 removeChildrenAndAdd(display
.selectionDiv
, drawn
.selection
);
1304 if (drawn
.teTop
!= null) {
1305 this.wrapper
.style
.top
= drawn
.teTop
+ "px";
1306 this.wrapper
.style
.left
= drawn
.teLeft
+ "px";
1310 // Reset the input to correspond to the selection (or to be empty,
1311 // when not typing and nothing is selected)
1312 reset: function(typing
) {
1313 if (this.contextMenuPending
) return;
1314 var minimal
, selected
, cm
= this.cm
, doc
= cm
.doc
;
1315 if (cm
.somethingSelected()) {
1316 this.prevInput
= "";
1317 var range
= doc
.sel
.primary();
1318 minimal
= hasCopyEvent
&&
1319 (range
.to().line
- range
.from().line
> 100 || (selected
= cm
.getSelection()).length
> 1000);
1320 var content
= minimal
? "-" : selected
|| cm
.getSelection();
1321 this.textarea
.value
= content
;
1322 if (cm
.state
.focused
) selectInput(this.textarea
);
1323 if (ie
&& ie_version
>= 9) this.hasSelection
= content
;
1324 } else if (!typing
) {
1325 this.prevInput
= this.textarea
.value
= "";
1326 if (ie
&& ie_version
>= 9) this.hasSelection
= null;
1328 this.inaccurateSelection
= minimal
;
1331 getField: function() { return this.textarea
; },
1333 supportsTouch: function() { return false; },
1336 if (this.cm
.options
.readOnly
!= "nocursor" && (!mobile
|| activeElt() != this.textarea
)) {
1337 try { this.textarea
.focus(); }
1338 catch (e
) {} // IE8 will throw if the textarea is display: none or not in DOM
1342 blur: function() { this.textarea
.blur(); },
1344 resetPosition: function() {
1345 this.wrapper
.style
.top
= this.wrapper
.style
.left
= 0;
1348 receivedFocus: function() { this.slowPoll(); },
1350 // Poll for input changes, using the normal rate of polling. This
1351 // runs as long as the editor is focused.
1352 slowPoll: function() {
1354 if (input
.pollingFast
) return;
1355 input
.polling
.set(this.cm
.options
.pollInterval
, function() {
1357 if (input
.cm
.state
.focused
) input
.slowPoll();
1361 // When an event has just come in that is likely to add or change
1362 // something in the input textarea, we poll faster, to ensure that
1363 // the change appears on the screen quickly.
1364 fastPoll: function() {
1365 var missed
= false, input
= this;
1366 input
.pollingFast
= true;
1368 var changed
= input
.poll();
1369 if (!changed
&& !missed
) {missed
= true; input
.polling
.set(60, p
);}
1370 else {input
.pollingFast
= false; input
.slowPoll();}
1372 input
.polling
.set(20, p
);
1375 // Read input from the textarea, and update the document to match.
1376 // When something is selected, it is present in the textarea, and
1377 // selected (unless it is huge, in which case a placeholder is
1378 // used). When nothing is selected, the cursor sits after previously
1379 // seen text (can be empty), which is stored in prevInput (we must
1380 // not reset the textarea when typing, because that breaks IME).
1382 var cm
= this.cm
, input
= this.textarea
, prevInput
= this.prevInput
;
1383 // Since this is called a *lot*, try to bail out as cheaply as
1384 // possible when it is clear that nothing happened. hasSelection
1385 // will be the case when there is a lot of text in the textarea,
1386 // in which case reading its value would be expensive.
1387 if (!cm
.state
.focused
|| (hasSelection(input
) && !prevInput
) ||
1388 isReadOnly(cm
) || cm
.options
.disableInput
|| cm
.state
.keySeq
)
1390 // See paste handler for more on the fakedLastChar kludge
1391 if (cm
.state
.pasteIncoming
&& cm
.state
.fakedLastChar
) {
1392 input
.value
= input
.value
.substring(0, input
.value
.length
- 1);
1393 cm
.state
.fakedLastChar
= false;
1395 var text
= input
.value
;
1396 // If nothing changed, bail.
1397 if (text
== prevInput
&& !cm
.somethingSelected()) return false;
1398 // Work around nonsensical selection resetting in IE9/10, and
1399 // inexplicable appearance of private area unicode characters on
1400 // some key combos in Mac (#2689).
1401 if (ie
&& ie_version
>= 9 && this.hasSelection
=== text
||
1402 mac
&& /[\uf700-\uf7ff]/.test(text
)) {
1403 cm
.display
.input
.reset();
1407 if (cm
.doc
.sel
== cm
.display
.selForContextMenu
) {
1408 var first
= text
.charCodeAt(0);
1409 if (first
== 0x200b && !prevInput
) prevInput
= "\u200b";
1410 if (first
== 0x21da) { this.reset(); return this.cm
.execCommand("undo"); }
1412 // Find the part of the input that is actually new
1413 var same
= 0, l
= Math
.min(prevInput
.length
, text
.length
);
1414 while (same
< l
&& prevInput
.charCodeAt(same
) == text
.charCodeAt(same
)) ++same
;
1417 runInOp(cm
, function() {
1418 applyTextInput(cm
, text
.slice(same
), prevInput
.length
- same
,
1419 null, self
.composing
? "*compose" : null);
1421 // Don't leave long text in the textarea, since it makes further polling slow
1422 if (text
.length
> 1000 || text
.indexOf("\n") > -1) input
.value
= self
.prevInput
= "";
1423 else self
.prevInput
= text
;
1425 if (self
.composing
) {
1426 self
.composing
.range
.clear();
1427 self
.composing
.range
= cm
.markText(self
.composing
.start
, cm
.getCursor("to"),
1428 {className: "CodeMirror-composing"});
1434 ensurePolled: function() {
1435 if (this.pollingFast
&& this.poll()) this.pollingFast
= false;
1438 onKeyPress: function() {
1439 if (ie
&& ie_version
>= 9) this.hasSelection
= null;
1443 onContextMenu: function(e
) {
1444 var input
= this, cm
= input
.cm
, display
= cm
.display
, te
= input
.textarea
;
1445 var pos
= posFromMouse(cm
, e
), scrollPos
= display
.scroller
.scrollTop
;
1446 if (!pos
|| presto
) return; // Opera is difficult.
1448 // Reset the current text selection only if the click is done outside of the selection
1449 // and 'resetSelectionOnContextMenu' option is true.
1450 var reset
= cm
.options
.resetSelectionOnContextMenu
;
1451 if (reset
&& cm
.doc
.sel
.contains(pos
) == -1)
1452 operation(cm
, setSelection
)(cm
.doc
, simpleSelection(pos
), sel_dontScroll
);
1454 var oldCSS
= te
.style
.cssText
;
1455 input
.wrapper
.style
.position
= "absolute";
1456 te
.style
.cssText
= "position: fixed; width: 30px; height: 30px; top: " + (e
.clientY
- 5) +
1457 "px; left: " + (e
.clientX
- 5) + "px; z-index: 1000; background: " +
1458 (ie
? "rgba(255, 255, 255, .05)" : "transparent") +
1459 "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1460 if (webkit
) var oldScrollY
= window
.scrollY
; // Work around Chrome issue (#2712)
1461 display
.input
.focus();
1462 if (webkit
) window
.scrollTo(null, oldScrollY
);
1463 display
.input
.reset();
1464 // Adds "Select all" to context menu in FF
1465 if (!cm
.somethingSelected()) te
.value
= input
.prevInput
= " ";
1466 input
.contextMenuPending
= true;
1467 display
.selForContextMenu
= cm
.doc
.sel
;
1468 clearTimeout(display
.detectingSelectAll
);
1470 // Select-all will be greyed out if there's nothing to select, so
1471 // this adds a zero-width space so that we can later check whether
1473 function prepareSelectAllHack() {
1474 if (te
.selectionStart
!= null) {
1475 var selected
= cm
.somethingSelected();
1476 var extval
= "\u200b" + (selected
? te
.value : "");
1477 te
.value
= "\u21da"; // Used to catch context-menu undo
1479 input
.prevInput
= selected
? "" : "\u200b";
1480 te
.selectionStart
= 1; te
.selectionEnd
= extval
.length
;
1481 // Re-set this, in case some other handler touched the
1482 // selection in the meantime.
1483 display
.selForContextMenu
= cm
.doc
.sel
;
1487 input
.contextMenuPending
= false;
1488 input
.wrapper
.style
.position
= "relative";
1489 te
.style
.cssText
= oldCSS
;
1490 if (ie
&& ie_version
< 9) display
.scrollbars
.setScrollTop(display
.scroller
.scrollTop
= scrollPos
);
1492 // Try to detect the user choosing select-all
1493 if (te
.selectionStart
!= null) {
1494 if (!ie
|| (ie
&& ie_version
< 9)) prepareSelectAllHack();
1495 var i
= 0, poll = function() {
1496 if (display
.selForContextMenu
== cm
.doc
.sel
&& te
.selectionStart
== 0 &&
1497 te
.selectionEnd
> 0 && input
.prevInput
== "\u200b")
1498 operation(cm
, commands
.selectAll
)(cm
);
1499 else if (i
++ < 10) display
.detectingSelectAll
= setTimeout(poll
, 500);
1500 else display
.input
.reset();
1502 display
.detectingSelectAll
= setTimeout(poll
, 200);
1506 if (ie
&& ie_version
>= 9) prepareSelectAllHack();
1507 if (captureRightClick
) {
1509 var mouseup = function() {
1510 off(window
, "mouseup", mouseup
);
1511 setTimeout(rehide
, 20);
1513 on(window
, "mouseup", mouseup
);
1515 setTimeout(rehide
, 50);
1519 setUneditable: nothing
,
1521 needsContentAttribute: false
1522 }, TextareaInput
.prototype);
1524 // CONTENTEDITABLE INPUT STYLE
1526 function ContentEditableInput(cm
) {
1528 this.lastAnchorNode
= this.lastAnchorOffset
= this.lastFocusNode
= this.lastFocusOffset
= null;
1529 this.polling
= new Delayed();
1530 this.gracePeriod
= false;
1533 ContentEditableInput
.prototype = copyObj({
1534 init: function(display
) {
1535 var input
= this, cm
= input
.cm
;
1536 var div
= input
.div
= display
.lineDiv
;
1537 div
.contentEditable
= "true";
1538 disableBrowserMagic(div
);
1540 on(div
, "paste", function(e
) {
1541 var pasted
= e
.clipboardData
&& e
.clipboardData
.getData("text/plain");
1544 cm
.replaceSelection(pasted
, null, "paste");
1548 on(div
, "compositionstart", function(e
) {
1550 input
.composing
= {sel: cm
.doc
.sel
, data: data
, startData: data
};
1552 var prim
= cm
.doc
.sel
.primary();
1553 var line
= cm
.getLine(prim
.head
.line
);
1554 var found
= line
.indexOf(data
, Math
.max(0, prim
.head
.ch
- data
.length
));
1555 if (found
> -1 && found
<= prim
.head
.ch
)
1556 input
.composing
.sel
= simpleSelection(Pos(prim
.head
.line
, found
),
1557 Pos(prim
.head
.line
, found
+ data
.length
));
1559 on(div
, "compositionupdate", function(e
) {
1560 input
.composing
.data
= e
.data
;
1562 on(div
, "compositionend", function(e
) {
1563 var ours
= input
.composing
;
1565 if (e
.data
!= ours
.startData
&& !/\u200b/.test(e
.data
))
1567 // Need a small delay to prevent other code (input event,
1568 // selection polling) from doing damage when fired right after
1570 setTimeout(function() {
1572 input
.applyComposition(ours
);
1573 if (input
.composing
== ours
)
1574 input
.composing
= null;
1578 on(div
, "touchstart", function() {
1579 input
.forceCompositionEnd();
1582 on(div
, "input", function() {
1583 if (input
.composing
) return;
1584 if (!input
.pollContent())
1585 runInOp(input
.cm
, function() {regChange(cm
);});
1588 function onCopyCut(e
) {
1589 if (cm
.somethingSelected()) {
1590 lastCopied
= cm
.getSelections();
1591 if (e
.type
== "cut") cm
.replaceSelection("", null, "cut");
1592 } else if (!cm
.options
.lineWiseCopyCut
) {
1595 var ranges
= copyableRanges(cm
);
1596 lastCopied
= ranges
.text
;
1597 if (e
.type
== "cut") {
1598 cm
.operation(function() {
1599 cm
.setSelections(ranges
.ranges
, 0, sel_dontScroll
);
1600 cm
.replaceSelection("", null, "cut");
1604 // iOS exposes the clipboard API, but seems to discard content inserted into it
1605 if (e
.clipboardData
&& !ios
) {
1607 e
.clipboardData
.clearData();
1608 e
.clipboardData
.setData("text/plain", lastCopied
.join("\n"));
1610 // Old-fashioned briefly-focus-a-textarea hack
1611 var kludge
= hiddenTextarea(), te
= kludge
.firstChild
;
1612 cm
.display
.lineSpace
.insertBefore(kludge
, cm
.display
.lineSpace
.firstChild
);
1613 te
.value
= lastCopied
.join("\n");
1614 var hadFocus
= document
.activeElement
;
1616 setTimeout(function() {
1617 cm
.display
.lineSpace
.removeChild(kludge
);
1622 on(div
, "copy", onCopyCut
);
1623 on(div
, "cut", onCopyCut
);
1626 prepareSelection: function() {
1627 var result
= prepareSelection(this.cm
, false);
1628 result
.focus
= this.cm
.state
.focused
;
1632 showSelection: function(info
) {
1633 if (!info
|| !this.cm
.display
.view
.length
) return;
1634 if (info
.focus
) this.showPrimarySelection();
1635 this.showMultipleSelections(info
);
1638 showPrimarySelection: function() {
1639 var sel
= window
.getSelection(), prim
= this.cm
.doc
.sel
.primary();
1640 var curAnchor
= domToPos(this.cm
, sel
.anchorNode
, sel
.anchorOffset
);
1641 var curFocus
= domToPos(this.cm
, sel
.focusNode
, sel
.focusOffset
);
1642 if (curAnchor
&& !curAnchor
.bad
&& curFocus
&& !curFocus
.bad
&&
1643 cmp(minPos(curAnchor
, curFocus
), prim
.from()) == 0 &&
1644 cmp(maxPos(curAnchor
, curFocus
), prim
.to()) == 0)
1647 var start
= posToDOM(this.cm
, prim
.from());
1648 var end
= posToDOM(this.cm
, prim
.to());
1649 if (!start
&& !end
) return;
1651 var view
= this.cm
.display
.view
;
1652 var old
= sel
.rangeCount
&& sel
.getRangeAt(0);
1654 start
= {node: view
[0].measure
.map
[2], offset: 0};
1655 } else if (!end
) { // FIXME dangerously hacky
1656 var measure
= view
[view
.length
- 1].measure
;
1657 var map
= measure
.maps
? measure
.maps
[measure
.maps
.length
- 1] : measure
.map
;
1658 end
= {node: map
[map
.length
- 1], offset: map
[map
.length
- 2] - map
[map
.length
- 3]};
1661 try { var rng
= range(start
.node
, start
.offset
, end
.offset
, end
.node
); }
1662 catch(e
) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
1664 sel
.removeAllRanges();
1666 if (old
&& sel
.anchorNode
== null) sel
.addRange(old
);
1667 else if (gecko
) this.startGracePeriod();
1669 this.rememberSelection();
1672 startGracePeriod: function() {
1674 clearTimeout(this.gracePeriod
);
1675 this.gracePeriod
= setTimeout(function() {
1676 input
.gracePeriod
= false;
1677 if (input
.selectionChanged())
1678 input
.cm
.operation(function() { input
.cm
.curOp
.selectionChanged
= true; });
1682 showMultipleSelections: function(info
) {
1683 removeChildrenAndAdd(this.cm
.display
.cursorDiv
, info
.cursors
);
1684 removeChildrenAndAdd(this.cm
.display
.selectionDiv
, info
.selection
);
1687 rememberSelection: function() {
1688 var sel
= window
.getSelection();
1689 this.lastAnchorNode
= sel
.anchorNode
; this.lastAnchorOffset
= sel
.anchorOffset
;
1690 this.lastFocusNode
= sel
.focusNode
; this.lastFocusOffset
= sel
.focusOffset
;
1693 selectionInEditor: function() {
1694 var sel
= window
.getSelection();
1695 if (!sel
.rangeCount
) return false;
1696 var node
= sel
.getRangeAt(0).commonAncestorContainer
;
1697 return contains(this.div
, node
);
1701 if (this.cm
.options
.readOnly
!= "nocursor") this.div
.focus();
1703 blur: function() { this.div
.blur(); },
1704 getField: function() { return this.div
; },
1706 supportsTouch: function() { return true; },
1708 receivedFocus: function() {
1710 if (this.selectionInEditor())
1711 this.pollSelection();
1713 runInOp(this.cm
, function() { input
.cm
.curOp
.selectionChanged
= true; });
1716 if (input
.cm
.state
.focused
) {
1717 input
.pollSelection();
1718 input
.polling
.set(input
.cm
.options
.pollInterval
, poll
);
1721 this.polling
.set(this.cm
.options
.pollInterval
, poll
);
1724 selectionChanged: function() {
1725 var sel
= window
.getSelection();
1726 return sel
.anchorNode
!= this.lastAnchorNode
|| sel
.anchorOffset
!= this.lastAnchorOffset
||
1727 sel
.focusNode
!= this.lastFocusNode
|| sel
.focusOffset
!= this.lastFocusOffset
;
1730 pollSelection: function() {
1731 if (!this.composing
&& !this.gracePeriod
&& this.selectionChanged()) {
1732 var sel
= window
.getSelection(), cm
= this.cm
;
1733 this.rememberSelection();
1734 var anchor
= domToPos(cm
, sel
.anchorNode
, sel
.anchorOffset
);
1735 var head
= domToPos(cm
, sel
.focusNode
, sel
.focusOffset
);
1736 if (anchor
&& head
) runInOp(cm
, function() {
1737 setSelection(cm
.doc
, simpleSelection(anchor
, head
), sel_dontScroll
);
1738 if (anchor
.bad
|| head
.bad
) cm
.curOp
.selectionChanged
= true;
1743 pollContent: function() {
1744 var cm
= this.cm
, display
= cm
.display
, sel
= cm
.doc
.sel
.primary();
1745 var from = sel
.from(), to
= sel
.to();
1746 if (from.line
< display
.viewFrom
|| to
.line
> display
.viewTo
- 1) return false;
1749 if (from.line
== display
.viewFrom
|| (fromIndex
= findViewIndex(cm
, from.line
)) == 0) {
1750 var fromLine
= lineNo(display
.view
[0].line
);
1751 var fromNode
= display
.view
[0].node
;
1753 var fromLine
= lineNo(display
.view
[fromIndex
].line
);
1754 var fromNode
= display
.view
[fromIndex
- 1].node
.nextSibling
;
1756 var toIndex
= findViewIndex(cm
, to
.line
);
1757 if (toIndex
== display
.view
.length
- 1) {
1758 var toLine
= display
.viewTo
- 1;
1759 var toNode
= display
.view
[toIndex
].node
;
1761 var toLine
= lineNo(display
.view
[toIndex
+ 1].line
) - 1;
1762 var toNode
= display
.view
[toIndex
+ 1].node
.previousSibling
;
1765 var newText
= splitLines(domTextBetween(cm
, fromNode
, toNode
, fromLine
, toLine
));
1766 var oldText
= getBetween(cm
.doc
, Pos(fromLine
, 0), Pos(toLine
, getLine(cm
.doc
, toLine
).text
.length
));
1767 while (newText
.length
> 1 && oldText
.length
> 1) {
1768 if (lst(newText
) == lst(oldText
)) { newText
.pop(); oldText
.pop(); toLine
--; }
1769 else if (newText
[0] == oldText
[0]) { newText
.shift(); oldText
.shift(); fromLine
++; }
1773 var cutFront
= 0, cutEnd
= 0;
1774 var newTop
= newText
[0], oldTop
= oldText
[0], maxCutFront
= Math
.min(newTop
.length
, oldTop
.length
);
1775 while (cutFront
< maxCutFront
&& newTop
.charCodeAt(cutFront
) == oldTop
.charCodeAt(cutFront
))
1777 var newBot
= lst(newText
), oldBot
= lst(oldText
);
1778 var maxCutEnd
= Math
.min(newBot
.length
- (newText
.length
== 1 ? cutFront : 0),
1779 oldBot
.length
- (oldText
.length
== 1 ? cutFront : 0));
1780 while (cutEnd
< maxCutEnd
&&
1781 newBot
.charCodeAt(newBot
.length
- cutEnd
- 1) == oldBot
.charCodeAt(oldBot
.length
- cutEnd
- 1))
1784 newText
[newText
.length
- 1] = newBot
.slice(0, newBot
.length
- cutEnd
);
1785 newText
[0] = newText
[0].slice(cutFront
);
1787 var chFrom
= Pos(fromLine
, cutFront
);
1788 var chTo
= Pos(toLine
, oldText
.length
? lst(oldText
).length
- cutEnd : 0);
1789 if (newText
.length
> 1 || newText
[0] || cmp(chFrom
, chTo
)) {
1790 replaceRange(cm
.doc
, newText
, chFrom
, chTo
, "+input");
1795 ensurePolled: function() {
1796 this.forceCompositionEnd();
1799 this.forceCompositionEnd();
1801 forceCompositionEnd: function() {
1802 if (!this.composing
|| this.composing
.handled
) return;
1803 this.applyComposition(this.composing
);
1804 this.composing
.handled
= true;
1808 applyComposition: function(composing
) {
1809 if (composing
.data
&& composing
.data
!= composing
.startData
)
1810 operation(this.cm
, applyTextInput
)(this.cm
, composing
.data
, 0, composing
.sel
);
1813 setUneditable: function(node
) {
1814 node
.setAttribute("contenteditable", "false");
1817 onKeyPress: function(e
) {
1819 operation(this.cm
, applyTextInput
)(this.cm
, String
.fromCharCode(e
.charCode
== null ? e
.keyCode : e
.charCode
), 0);
1822 onContextMenu: nothing
,
1823 resetPosition: nothing
,
1825 needsContentAttribute: true
1826 }, ContentEditableInput
.prototype);
1828 function posToDOM(cm
, pos
) {
1829 var view
= findViewForLine(cm
, pos
.line
);
1830 if (!view
|| view
.hidden
) return null;
1831 var line
= getLine(cm
.doc
, pos
.line
);
1832 var info
= mapFromLineView(view
, line
, pos
.line
);
1834 var order
= getOrder(line
), side
= "left";
1836 var partPos
= getBidiPartAt(order
, pos
.ch
);
1837 side
= partPos
% 2 ? "right" : "left";
1839 var result
= nodeAndOffsetInLineMap(info
.map
, pos
.ch
, "left");
1840 result
.offset
= result
.collapse
== "right" ? result
.end : result
.start
;
1844 function badPos(pos
, bad
) { if (bad
) pos
.bad
= true; return pos
; }
1846 function domToPos(cm
, node
, offset
) {
1848 if (node
== cm
.display
.lineDiv
) {
1849 lineNode
= cm
.display
.lineDiv
.childNodes
[offset
];
1850 if (!lineNode
) return badPos(cm
.clipPos(Pos(cm
.display
.viewTo
- 1)), true);
1851 node
= null; offset
= 0;
1853 for (lineNode
= node
;; lineNode
= lineNode
.parentNode
) {
1854 if (!lineNode
|| lineNode
== cm
.display
.lineDiv
) return null;
1855 if (lineNode
.parentNode
&& lineNode
.parentNode
== cm
.display
.lineDiv
) break;
1858 for (var i
= 0; i
< cm
.display
.view
.length
; i
++) {
1859 var lineView
= cm
.display
.view
[i
];
1860 if (lineView
.node
== lineNode
)
1861 return locateNodeInLineView(lineView
, node
, offset
);
1865 function locateNodeInLineView(lineView
, node
, offset
) {
1866 var wrapper
= lineView
.text
.firstChild
, bad
= false;
1867 if (!node
|| !contains(wrapper
, node
)) return badPos(Pos(lineNo(lineView
.line
), 0), true);
1868 if (node
== wrapper
) {
1870 node
= wrapper
.childNodes
[offset
];
1873 var line
= lineView
.rest
? lst(lineView
.rest
) : lineView
.line
;
1874 return badPos(Pos(lineNo(line
), line
.text
.length
), bad
);
1878 var textNode
= node
.nodeType
== 3 ? node : null, topNode
= node
;
1879 if (!textNode
&& node
.childNodes
.length
== 1 && node
.firstChild
.nodeType
== 3) {
1880 textNode
= node
.firstChild
;
1881 if (offset
) offset
= textNode
.nodeValue
.length
;
1883 while (topNode
.parentNode
!= wrapper
) topNode
= topNode
.parentNode
;
1884 var measure
= lineView
.measure
, maps
= measure
.maps
;
1886 function find(textNode
, topNode
, offset
) {
1887 for (var i
= -1; i
< (maps
? maps
.length : 0); i
++) {
1888 var map
= i
< 0 ? measure
.map : maps
[i
];
1889 for (var j
= 0; j
< map
.length
; j
+= 3) {
1890 var curNode
= map
[j
+ 2];
1891 if (curNode
== textNode
|| curNode
== topNode
) {
1892 var line
= lineNo(i
< 0 ? lineView
.line : lineView
.rest
[i
]);
1893 var ch
= map
[j
] + offset
;
1894 if (offset
< 0 || curNode
!= textNode
) ch
= map
[j
+ (offset
? 1 : 0)];
1895 return Pos(line
, ch
);
1900 var found
= find(textNode
, topNode
, offset
);
1901 if (found
) return badPos(found
, bad
);
1903 // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
1904 for (var after
= topNode
.nextSibling
, dist
= textNode
? textNode
.nodeValue
.length
- offset : 0; after
; after
= after
.nextSibling
) {
1905 found
= find(after
, after
.firstChild
, 0);
1907 return badPos(Pos(found
.line
, found
.ch
- dist
), bad
);
1909 dist
+= after
.textContent
.length
;
1911 for (var before
= topNode
.previousSibling
, dist
= offset
; before
; before
= before
.previousSibling
) {
1912 found
= find(before
, before
.firstChild
, -1);
1914 return badPos(Pos(found
.line
, found
.ch
+ dist
), bad
);
1916 dist
+= after
.textContent
.length
;
1920 function domTextBetween(cm
, from, to
, fromLine
, toLine
) {
1921 var text
= "", closing
= false;
1922 function recognizeMarker(id
) { return function(marker
) { return marker
.id
== id
; }; }
1923 function walk(node
) {
1924 if (node
.nodeType
== 1) {
1925 var cmText
= node
.getAttribute("cm-text");
1926 if (cmText
!= null) {
1927 if (cmText
== "") cmText
= node
.textContent
.replace(/\u200b/g, "");
1931 var markerID
= node
.getAttribute("cm-marker"), range
;
1933 var found
= cm
.findMarks(Pos(fromLine
, 0), Pos(toLine
+ 1, 0), recognizeMarker(+markerID
));
1934 if (found
.length
&& (range
= found
[0].find()))
1935 text
+= getBetween(cm
.doc
, range
.from, range
.to
).join("\n");
1938 if (node
.getAttribute("contenteditable") == "false") return;
1939 for (var i
= 0; i
< node
.childNodes
.length
; i
++)
1940 walk(node
.childNodes
[i
]);
1941 if (/^(pre|div|p)$/i.test(node
.nodeName
))
1943 } else if (node
.nodeType
== 3) {
1944 var val
= node
.nodeValue
;
1955 if (from == to
) break;
1956 from = from.nextSibling
;
1961 CodeMirror
.inputStyles
= {"textarea": TextareaInput
, "contenteditable": ContentEditableInput
};
1963 // SELECTION / CURSOR
1965 // Selection objects are immutable. A new one is created every time
1966 // the selection changes. A selection is one or more non-overlapping
1967 // (and non-touching) ranges, sorted, and an integer that indicates
1968 // which one is the primary selection (the one that's scrolled into
1969 // view, that getCursor returns, etc).
1970 function Selection(ranges
, primIndex
) {
1971 this.ranges
= ranges
;
1972 this.primIndex
= primIndex
;
1975 Selection
.prototype = {
1976 primary: function() { return this.ranges
[this.primIndex
]; },
1977 equals: function(other
) {
1978 if (other
== this) return true;
1979 if (other
.primIndex
!= this.primIndex
|| other
.ranges
.length
!= this.ranges
.length
) return false;
1980 for (var i
= 0; i
< this.ranges
.length
; i
++) {
1981 var here
= this.ranges
[i
], there
= other
.ranges
[i
];
1982 if (cmp(here
.anchor
, there
.anchor
) != 0 || cmp(here
.head
, there
.head
) != 0) return false;
1986 deepCopy: function() {
1987 for (var out
= [], i
= 0; i
< this.ranges
.length
; i
++)
1988 out
[i
] = new Range(copyPos(this.ranges
[i
].anchor
), copyPos(this.ranges
[i
].head
));
1989 return new Selection(out
, this.primIndex
);
1991 somethingSelected: function() {
1992 for (var i
= 0; i
< this.ranges
.length
; i
++)
1993 if (!this.ranges
[i
].empty()) return true;
1996 contains: function(pos
, end
) {
1997 if (!end
) end
= pos
;
1998 for (var i
= 0; i
< this.ranges
.length
; i
++) {
1999 var range
= this.ranges
[i
];
2000 if (cmp(end
, range
.from()) >= 0 && cmp(pos
, range
.to()) <= 0)
2007 function Range(anchor
, head
) {
2008 this.anchor
= anchor
; this.head
= head
;
2012 from: function() { return minPos(this.anchor
, this.head
); },
2013 to: function() { return maxPos(this.anchor
, this.head
); },
2015 return this.head
.line
== this.anchor
.line
&& this.head
.ch
== this.anchor
.ch
;
2019 // Take an unsorted, potentially overlapping set of ranges, and
2020 // build a selection out of it. 'Consumes' ranges array (modifying
2022 function normalizeSelection(ranges
, primIndex
) {
2023 var prim
= ranges
[primIndex
];
2024 ranges
.sort(function(a
, b
) { return cmp(a
.from(), b
.from()); });
2025 primIndex
= indexOf(ranges
, prim
);
2026 for (var i
= 1; i
< ranges
.length
; i
++) {
2027 var cur
= ranges
[i
], prev
= ranges
[i
- 1];
2028 if (cmp(prev
.to(), cur
.from()) >= 0) {
2029 var from = minPos(prev
.from(), cur
.from()), to
= maxPos(prev
.to(), cur
.to());
2030 var inv
= prev
.empty() ? cur
.from() == cur
.head : prev
.from() == prev
.head
;
2031 if (i
<= primIndex
) --primIndex
;
2032 ranges
.splice(--i
, 2, new Range(inv
? to : from, inv
? from : to
));
2035 return new Selection(ranges
, primIndex
);
2038 function simpleSelection(anchor
, head
) {
2039 return new Selection([new Range(anchor
, head
|| anchor
)], 0);
2042 // Most of the external API clips given positions to make sure they
2043 // actually exist within the document.
2044 function clipLine(doc
, n
) {return Math
.max(doc
.first
, Math
.min(n
, doc
.first
+ doc
.size
- 1));}
2045 function clipPos(doc
, pos
) {
2046 if (pos
.line
< doc
.first
) return Pos(doc
.first
, 0);
2047 var last
= doc
.first
+ doc
.size
- 1;
2048 if (pos
.line
> last
) return Pos(last
, getLine(doc
, last
).text
.length
);
2049 return clipToLen(pos
, getLine(doc
, pos
.line
).text
.length
);
2051 function clipToLen(pos
, linelen
) {
2053 if (ch
== null || ch
> linelen
) return Pos(pos
.line
, linelen
);
2054 else if (ch
< 0) return Pos(pos
.line
, 0);
2057 function isLine(doc
, l
) {return l
>= doc
.first
&& l
< doc
.first
+ doc
.size
;}
2058 function clipPosArray(doc
, array
) {
2059 for (var out
= [], i
= 0; i
< array
.length
; i
++) out
[i
] = clipPos(doc
, array
[i
]);
2063 // SELECTION UPDATES
2065 // The 'scroll' parameter given to many of these indicated whether
2066 // the new cursor position should be scrolled into view after
2067 // modifying the selection.
2069 // If shift is held or the extend flag is set, extends a range to
2070 // include a given position (and optionally a second position).
2071 // Otherwise, simply returns the range between the given positions.
2072 // Used for cursor motion and such.
2073 function extendRange(doc
, range
, head
, other
) {
2074 if (doc
.cm
&& doc
.cm
.display
.shift
|| doc
.extend
) {
2075 var anchor
= range
.anchor
;
2077 var posBefore
= cmp(head
, anchor
) < 0;
2078 if (posBefore
!= (cmp(other
, anchor
) < 0)) {
2081 } else if (posBefore
!= (cmp(head
, other
) < 0)) {
2085 return new Range(anchor
, head
);
2087 return new Range(other
|| head
, head
);
2091 // Extend the primary selection range, discard the rest.
2092 function extendSelection(doc
, head
, other
, options
) {
2093 setSelection(doc
, new Selection([extendRange(doc
, doc
.sel
.primary(), head
, other
)], 0), options
);
2096 // Extend all selections (pos is an array of selections with length
2097 // equal the number of selections)
2098 function extendSelections(doc
, heads
, options
) {
2099 for (var out
= [], i
= 0; i
< doc
.sel
.ranges
.length
; i
++)
2100 out
[i
] = extendRange(doc
, doc
.sel
.ranges
[i
], heads
[i
], null);
2101 var newSel
= normalizeSelection(out
, doc
.sel
.primIndex
);
2102 setSelection(doc
, newSel
, options
);
2105 // Updates a single range in the selection.
2106 function replaceOneSelection(doc
, i
, range
, options
) {
2107 var ranges
= doc
.sel
.ranges
.slice(0);
2109 setSelection(doc
, normalizeSelection(ranges
, doc
.sel
.primIndex
), options
);
2112 // Reset the selection to a single range.
2113 function setSimpleSelection(doc
, anchor
, head
, options
) {
2114 setSelection(doc
, simpleSelection(anchor
, head
), options
);
2117 // Give beforeSelectionChange handlers a change to influence a
2118 // selection update.
2119 function filterSelectionChange(doc
, sel
) {
2122 update: function(ranges
) {
2124 for (var i
= 0; i
< ranges
.length
; i
++)
2125 this.ranges
[i
] = new Range(clipPos(doc
, ranges
[i
].anchor
),
2126 clipPos(doc
, ranges
[i
].head
));
2129 signal(doc
, "beforeSelectionChange", doc
, obj
);
2130 if (doc
.cm
) signal(doc
.cm
, "beforeSelectionChange", doc
.cm
, obj
);
2131 if (obj
.ranges
!= sel
.ranges
) return normalizeSelection(obj
.ranges
, obj
.ranges
.length
- 1);
2135 function setSelectionReplaceHistory(doc
, sel
, options
) {
2136 var done
= doc
.history
.done
, last
= lst(done
);
2137 if (last
&& last
.ranges
) {
2138 done
[done
.length
- 1] = sel
;
2139 setSelectionNoUndo(doc
, sel
, options
);
2141 setSelection(doc
, sel
, options
);
2145 // Set a new selection.
2146 function setSelection(doc
, sel
, options
) {
2147 setSelectionNoUndo(doc
, sel
, options
);
2148 addSelectionToHistory(doc
, doc
.sel
, doc
.cm
? doc
.cm
.curOp
.id : NaN
, options
);
2151 function setSelectionNoUndo(doc
, sel
, options
) {
2152 if (hasHandler(doc
, "beforeSelectionChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeSelectionChange"))
2153 sel
= filterSelectionChange(doc
, sel
);
2155 var bias
= options
&& options
.bias
||
2156 (cmp(sel
.primary().head
, doc
.sel
.primary().head
) < 0 ? -1 : 1);
2157 setSelectionInner(doc
, skipAtomicInSelection(doc
, sel
, bias
, true));
2159 if (!(options
&& options
.scroll
=== false) && doc
.cm
)
2160 ensureCursorVisible(doc
.cm
);
2163 function setSelectionInner(doc
, sel
) {
2164 if (sel
.equals(doc
.sel
)) return;
2169 doc
.cm
.curOp
.updateInput
= doc
.cm
.curOp
.selectionChanged
= true;
2170 signalCursorActivity(doc
.cm
);
2172 signalLater(doc
, "cursorActivity", doc
);
2175 // Verify that the selection does not partially select any atomic
2177 function reCheckSelection(doc
) {
2178 setSelectionInner(doc
, skipAtomicInSelection(doc
, doc
.sel
, null, false), sel_dontScroll
);
2181 // Return a selection that does not partially select any atomic
2183 function skipAtomicInSelection(doc
, sel
, bias
, mayClear
) {
2185 for (var i
= 0; i
< sel
.ranges
.length
; i
++) {
2186 var range
= sel
.ranges
[i
];
2187 var newAnchor
= skipAtomic(doc
, range
.anchor
, bias
, mayClear
);
2188 var newHead
= skipAtomic(doc
, range
.head
, bias
, mayClear
);
2189 if (out
|| newAnchor
!= range
.anchor
|| newHead
!= range
.head
) {
2190 if (!out
) out
= sel
.ranges
.slice(0, i
);
2191 out
[i
] = new Range(newAnchor
, newHead
);
2194 return out
? normalizeSelection(out
, sel
.primIndex
) : sel
;
2197 // Ensure a given position is not inside an atomic range.
2198 function skipAtomic(doc
, pos
, bias
, mayClear
) {
2199 var flipped
= false, curPos
= pos
;
2200 var dir
= bias
|| 1;
2201 doc
.cantEdit
= false;
2203 var line
= getLine(doc
, curPos
.line
);
2204 if (line
.markedSpans
) {
2205 for (var i
= 0; i
< line
.markedSpans
.length
; ++i
) {
2206 var sp
= line
.markedSpans
[i
], m
= sp
.marker
;
2207 if ((sp
.from == null || (m
.inclusiveLeft
? sp
.from <= curPos
.ch : sp
.from < curPos
.ch
)) &&
2208 (sp
.to
== null || (m
.inclusiveRight
? sp
.to
>= curPos
.ch : sp
.to
> curPos
.ch
))) {
2210 signal(m
, "beforeCursorEnter");
2211 if (m
.explicitlyCleared
) {
2212 if (!line
.markedSpans
) break;
2213 else {--i
; continue;}
2216 if (!m
.atomic
) continue;
2217 var newPos
= m
.find(dir
< 0 ? -1 : 1);
2218 if (cmp(newPos
, curPos
) == 0) {
2220 if (newPos
.ch
< 0) {
2221 if (newPos
.line
> doc
.first
) newPos
= clipPos(doc
, Pos(newPos
.line
- 1));
2223 } else if (newPos
.ch
> line
.text
.length
) {
2224 if (newPos
.line
< doc
.first
+ doc
.size
- 1) newPos
= Pos(newPos
.line
+ 1, 0);
2229 // Driven in a corner -- no valid cursor position found at all
2230 // -- try again *with* clearing, if we didn't already
2231 if (!mayClear
) return skipAtomic(doc
, pos
, bias
, true);
2232 // Otherwise, turn off editing until further notice, and return the start of the doc
2233 doc
.cantEdit
= true;
2234 return Pos(doc
.first
, 0);
2236 flipped
= true; newPos
= pos
; dir
= -dir
;
2248 // SELECTION DRAWING
2250 function updateSelection(cm
) {
2251 cm
.display
.input
.showSelection(cm
.display
.input
.prepareSelection());
2254 function prepareSelection(cm
, primary
) {
2255 var doc
= cm
.doc
, result
= {};
2256 var curFragment
= result
.cursors
= document
.createDocumentFragment();
2257 var selFragment
= result
.selection
= document
.createDocumentFragment();
2259 for (var i
= 0; i
< doc
.sel
.ranges
.length
; i
++) {
2260 if (primary
=== false && i
== doc
.sel
.primIndex
) continue;
2261 var range
= doc
.sel
.ranges
[i
];
2262 var collapsed
= range
.empty();
2263 if (collapsed
|| cm
.options
.showCursorWhenSelecting
)
2264 drawSelectionCursor(cm
, range
, curFragment
);
2266 drawSelectionRange(cm
, range
, selFragment
);
2271 // Draws a cursor for the given range
2272 function drawSelectionCursor(cm
, range
, output
) {
2273 var pos
= cursorCoords(cm
, range
.head
, "div", null, null, !cm
.options
.singleCursorHeightPerLine
);
2275 var cursor
= output
.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
2276 cursor
.style
.left
= pos
.left
+ "px";
2277 cursor
.style
.top
= pos
.top
+ "px";
2278 cursor
.style
.height
= Math
.max(0, pos
.bottom
- pos
.top
) * cm
.options
.cursorHeight
+ "px";
2281 // Secondary cursor, shown when on a 'jump' in bi-directional text
2282 var otherCursor
= output
.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
2283 otherCursor
.style
.display
= "";
2284 otherCursor
.style
.left
= pos
.other
.left
+ "px";
2285 otherCursor
.style
.top
= pos
.other
.top
+ "px";
2286 otherCursor
.style
.height
= (pos
.other
.bottom
- pos
.other
.top
) * .85 + "px";
2290 // Draws the given range as a highlighted selection
2291 function drawSelectionRange(cm
, range
, output
) {
2292 var display
= cm
.display
, doc
= cm
.doc
;
2293 var fragment
= document
.createDocumentFragment();
2294 var padding
= paddingH(cm
.display
), leftSide
= padding
.left
;
2295 var rightSide
= Math
.max(display
.sizerWidth
, displayWidth(cm
) - display
.sizer
.offsetLeft
) - padding
.right
;
2297 function add(left
, top
, width
, bottom
) {
2298 if (top
< 0) top
= 0;
2299 top
= Math
.round(top
);
2300 bottom
= Math
.round(bottom
);
2301 fragment
.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left
+
2302 "px; top: " + top
+ "px; width: " + (width
== null ? rightSide
- left : width
) +
2303 "px; height: " + (bottom
- top
) + "px"));
2306 function drawForLine(line
, fromArg
, toArg
) {
2307 var lineObj
= getLine(doc
, line
);
2308 var lineLen
= lineObj
.text
.length
;
2310 function coords(ch
, bias
) {
2311 return charCoords(cm
, Pos(line
, ch
), "div", lineObj
, bias
);
2314 iterateBidiSections(getOrder(lineObj
), fromArg
|| 0, toArg
== null ? lineLen : toArg
, function(from, to
, dir
) {
2315 var leftPos
= coords(from, "left"), rightPos
, left
, right
;
2318 left
= right
= leftPos
.left
;
2320 rightPos
= coords(to
- 1, "right");
2321 if (dir
== "rtl") { var tmp
= leftPos
; leftPos
= rightPos
; rightPos
= tmp
; }
2322 left
= leftPos
.left
;
2323 right
= rightPos
.right
;
2325 if (fromArg
== null && from == 0) left
= leftSide
;
2326 if (rightPos
.top
- leftPos
.top
> 3) { // Different lines, draw top part
2327 add(left
, leftPos
.top
, null, leftPos
.bottom
);
2329 if (leftPos
.bottom
< rightPos
.top
) add(left
, leftPos
.bottom
, null, rightPos
.top
);
2331 if (toArg
== null && to
== lineLen
) right
= rightSide
;
2332 if (!start
|| leftPos
.top
< start
.top
|| leftPos
.top
== start
.top
&& leftPos
.left
< start
.left
)
2334 if (!end
|| rightPos
.bottom
> end
.bottom
|| rightPos
.bottom
== end
.bottom
&& rightPos
.right
> end
.right
)
2336 if (left
< leftSide
+ 1) left
= leftSide
;
2337 add(left
, rightPos
.top
, right
- left
, rightPos
.bottom
);
2339 return {start: start
, end: end
};
2342 var sFrom
= range
.from(), sTo
= range
.to();
2343 if (sFrom
.line
== sTo
.line
) {
2344 drawForLine(sFrom
.line
, sFrom
.ch
, sTo
.ch
);
2346 var fromLine
= getLine(doc
, sFrom
.line
), toLine
= getLine(doc
, sTo
.line
);
2347 var singleVLine
= visualLine(fromLine
) == visualLine(toLine
);
2348 var leftEnd
= drawForLine(sFrom
.line
, sFrom
.ch
, singleVLine
? fromLine
.text
.length
+ 1 : null).end
;
2349 var rightStart
= drawForLine(sTo
.line
, singleVLine
? 0 : null, sTo
.ch
).start
;
2351 if (leftEnd
.top
< rightStart
.top
- 2) {
2352 add(leftEnd
.right
, leftEnd
.top
, null, leftEnd
.bottom
);
2353 add(leftSide
, rightStart
.top
, rightStart
.left
, rightStart
.bottom
);
2355 add(leftEnd
.right
, leftEnd
.top
, rightStart
.left
- leftEnd
.right
, leftEnd
.bottom
);
2358 if (leftEnd
.bottom
< rightStart
.top
)
2359 add(leftSide
, leftEnd
.bottom
, null, rightStart
.top
);
2362 output
.appendChild(fragment
);
2366 function restartBlink(cm
) {
2367 if (!cm
.state
.focused
) return;
2368 var display
= cm
.display
;
2369 clearInterval(display
.blinker
);
2371 display
.cursorDiv
.style
.visibility
= "";
2372 if (cm
.options
.cursorBlinkRate
> 0)
2373 display
.blinker
= setInterval(function() {
2374 display
.cursorDiv
.style
.visibility
= (on
= !on
) ? "" : "hidden";
2375 }, cm
.options
.cursorBlinkRate
);
2376 else if (cm
.options
.cursorBlinkRate
< 0)
2377 display
.cursorDiv
.style
.visibility
= "hidden";
2382 function startWorker(cm
, time
) {
2383 if (cm
.doc
.mode
.startState
&& cm
.doc
.frontier
< cm
.display
.viewTo
)
2384 cm
.state
.highlight
.set(time
, bind(highlightWorker
, cm
));
2387 function highlightWorker(cm
) {
2389 if (doc
.frontier
< doc
.first
) doc
.frontier
= doc
.first
;
2390 if (doc
.frontier
>= cm
.display
.viewTo
) return;
2391 var end
= +new Date
+ cm
.options
.workTime
;
2392 var state
= copyState(doc
.mode
, getStateBefore(cm
, doc
.frontier
));
2393 var changedLines
= [];
2395 doc
.iter(doc
.frontier
, Math
.min(doc
.first
+ doc
.size
, cm
.display
.viewTo
+ 500), function(line
) {
2396 if (doc
.frontier
>= cm
.display
.viewFrom
) { // Visible
2397 var oldStyles
= line
.styles
;
2398 var highlighted
= highlightLine(cm
, line
, state
, true);
2399 line
.styles
= highlighted
.styles
;
2400 var oldCls
= line
.styleClasses
, newCls
= highlighted
.classes
;
2401 if (newCls
) line
.styleClasses
= newCls
;
2402 else if (oldCls
) line
.styleClasses
= null;
2403 var ischange
= !oldStyles
|| oldStyles
.length
!= line
.styles
.length
||
2404 oldCls
!= newCls
&& (!oldCls
|| !newCls
|| oldCls
.bgClass
!= newCls
.bgClass
|| oldCls
.textClass
!= newCls
.textClass
);
2405 for (var i
= 0; !ischange
&& i
< oldStyles
.length
; ++i
) ischange
= oldStyles
[i
] != line
.styles
[i
];
2406 if (ischange
) changedLines
.push(doc
.frontier
);
2407 line
.stateAfter
= copyState(doc
.mode
, state
);
2409 processLine(cm
, line
.text
, state
);
2410 line
.stateAfter
= doc
.frontier
% 5 == 0 ? copyState(doc
.mode
, state
) : null;
2413 if (+new Date
> end
) {
2414 startWorker(cm
, cm
.options
.workDelay
);
2418 if (changedLines
.length
) runInOp(cm
, function() {
2419 for (var i
= 0; i
< changedLines
.length
; i
++)
2420 regLineChange(cm
, changedLines
[i
], "text");
2424 // Finds the line to start with when starting a parse. Tries to
2425 // find a line with a stateAfter, so that it can start with a
2426 // valid state. If that fails, it returns the line with the
2427 // smallest indentation, which tends to need the least context to
2429 function findStartLine(cm
, n
, precise
) {
2430 var minindent
, minline
, doc
= cm
.doc
;
2431 var lim
= precise
? -1 : n
- (cm
.doc
.mode
.innerMode
? 1000 : 100);
2432 for (var search
= n
; search
> lim
; --search
) {
2433 if (search
<= doc
.first
) return doc
.first
;
2434 var line
= getLine(doc
, search
- 1);
2435 if (line
.stateAfter
&& (!precise
|| search
<= doc
.frontier
)) return search
;
2436 var indented
= countColumn(line
.text
, null, cm
.options
.tabSize
);
2437 if (minline
== null || minindent
> indented
) {
2438 minline
= search
- 1;
2439 minindent
= indented
;
2445 function getStateBefore(cm
, n
, precise
) {
2446 var doc
= cm
.doc
, display
= cm
.display
;
2447 if (!doc
.mode
.startState
) return true;
2448 var pos
= findStartLine(cm
, n
, precise
), state
= pos
> doc
.first
&& getLine(doc
, pos
-1).stateAfter
;
2449 if (!state
) state
= startState(doc
.mode
);
2450 else state
= copyState(doc
.mode
, state
);
2451 doc
.iter(pos
, n
, function(line
) {
2452 processLine(cm
, line
.text
, state
);
2453 var save
= pos
== n
- 1 || pos
% 5 == 0 || pos
>= display
.viewFrom
&& pos
< display
.viewTo
;
2454 line
.stateAfter
= save
? copyState(doc
.mode
, state
) : null;
2457 if (precise
) doc
.frontier
= pos
;
2461 // POSITION MEASUREMENT
2463 function paddingTop(display
) {return display
.lineSpace
.offsetTop
;}
2464 function paddingVert(display
) {return display
.mover
.offsetHeight
- display
.lineSpace
.offsetHeight
;}
2465 function paddingH(display
) {
2466 if (display
.cachedPaddingH
) return display
.cachedPaddingH
;
2467 var e
= removeChildrenAndAdd(display
.measure
, elt("pre", "x"));
2468 var style
= window
.getComputedStyle
? window
.getComputedStyle(e
) : e
.currentStyle
;
2469 var data
= {left: parseInt(style
.paddingLeft
), right: parseInt(style
.paddingRight
)};
2470 if (!isNaN(data
.left
) && !isNaN(data
.right
)) display
.cachedPaddingH
= data
;
2474 function scrollGap(cm
) { return scrollerGap
- cm
.display
.nativeBarWidth
; }
2475 function displayWidth(cm
) {
2476 return cm
.display
.scroller
.clientWidth
- scrollGap(cm
) - cm
.display
.barWidth
;
2478 function displayHeight(cm
) {
2479 return cm
.display
.scroller
.clientHeight
- scrollGap(cm
) - cm
.display
.barHeight
;
2482 // Ensure the lineView.wrapping.heights array is populated. This is
2483 // an array of bottom offsets for the lines that make up a drawn
2484 // line. When lineWrapping is on, there might be more than one
2486 function ensureLineHeights(cm
, lineView
, rect
) {
2487 var wrapping
= cm
.options
.lineWrapping
;
2488 var curWidth
= wrapping
&& displayWidth(cm
);
2489 if (!lineView
.measure
.heights
|| wrapping
&& lineView
.measure
.width
!= curWidth
) {
2490 var heights
= lineView
.measure
.heights
= [];
2492 lineView
.measure
.width
= curWidth
;
2493 var rects
= lineView
.text
.firstChild
.getClientRects();
2494 for (var i
= 0; i
< rects
.length
- 1; i
++) {
2495 var cur
= rects
[i
], next
= rects
[i
+ 1];
2496 if (Math
.abs(cur
.bottom
- next
.bottom
) > 2)
2497 heights
.push((cur
.bottom
+ next
.top
) / 2 - rect
.top
);
2500 heights
.push(rect
.bottom
- rect
.top
);
2504 // Find a line map (mapping character offsets to text nodes) and a
2505 // measurement cache for the given line number. (A line view might
2506 // contain multiple lines when collapsed ranges are present.)
2507 function mapFromLineView(lineView
, line
, lineN
) {
2508 if (lineView
.line
== line
)
2509 return {map: lineView
.measure
.map
, cache: lineView
.measure
.cache
};
2510 for (var i
= 0; i
< lineView
.rest
.length
; i
++)
2511 if (lineView
.rest
[i
] == line
)
2512 return {map: lineView
.measure
.maps
[i
], cache: lineView
.measure
.caches
[i
]};
2513 for (var i
= 0; i
< lineView
.rest
.length
; i
++)
2514 if (lineNo(lineView
.rest
[i
]) > lineN
)
2515 return {map: lineView
.measure
.maps
[i
], cache: lineView
.measure
.caches
[i
], before: true};
2518 // Render a line into the hidden node display.externalMeasured. Used
2519 // when measurement is needed for a line that's not in the viewport.
2520 function updateExternalMeasurement(cm
, line
) {
2521 line
= visualLine(line
);
2522 var lineN
= lineNo(line
);
2523 var view
= cm
.display
.externalMeasured
= new LineView(cm
.doc
, line
, lineN
);
2525 var built
= view
.built
= buildLineContent(cm
, view
);
2526 view
.text
= built
.pre
;
2527 removeChildrenAndAdd(cm
.display
.lineMeasure
, built
.pre
);
2531 // Get a {top, bottom, left, right} box (in line-local coordinates)
2532 // for a given character.
2533 function measureChar(cm
, line
, ch
, bias
) {
2534 return measureCharPrepared(cm
, prepareMeasureForLine(cm
, line
), ch
, bias
);
2537 // Find a line view that corresponds to the given line number.
2538 function findViewForLine(cm
, lineN
) {
2539 if (lineN
>= cm
.display
.viewFrom
&& lineN
< cm
.display
.viewTo
)
2540 return cm
.display
.view
[findViewIndex(cm
, lineN
)];
2541 var ext
= cm
.display
.externalMeasured
;
2542 if (ext
&& lineN
>= ext
.lineN
&& lineN
< ext
.lineN
+ ext
.size
)
2546 // Measurement can be split in two steps, the set-up work that
2547 // applies to the whole line, and the measurement of the actual
2548 // character. Functions like coordsChar, that need to do a lot of
2549 // measurements in a row, can thus ensure that the set-up work is
2551 function prepareMeasureForLine(cm
, line
) {
2552 var lineN
= lineNo(line
);
2553 var view
= findViewForLine(cm
, lineN
);
2554 if (view
&& !view
.text
)
2556 else if (view
&& view
.changes
)
2557 updateLineForChanges(cm
, view
, lineN
, getDimensions(cm
));
2559 view
= updateExternalMeasurement(cm
, line
);
2561 var info
= mapFromLineView(view
, line
, lineN
);
2563 line: line
, view: view
, rect: null,
2564 map: info
.map
, cache: info
.cache
, before: info
.before
,
2569 // Given a prepared measurement object, measures the position of an
2570 // actual character (or fetches it from the cache).
2571 function measureCharPrepared(cm
, prepared
, ch
, bias
, varHeight
) {
2572 if (prepared
.before
) ch
= -1;
2573 var key
= ch
+ (bias
|| ""), found
;
2574 if (prepared
.cache
.hasOwnProperty(key
)) {
2575 found
= prepared
.cache
[key
];
2578 prepared
.rect
= prepared
.view
.text
.getBoundingClientRect();
2579 if (!prepared
.hasHeights
) {
2580 ensureLineHeights(cm
, prepared
.view
, prepared
.rect
);
2581 prepared
.hasHeights
= true;
2583 found
= measureCharInner(cm
, prepared
, ch
, bias
);
2584 if (!found
.bogus
) prepared
.cache
[key
] = found
;
2586 return {left: found
.left
, right: found
.right
,
2587 top: varHeight
? found
.rtop : found
.top
,
2588 bottom: varHeight
? found
.rbottom : found
.bottom
};
2591 var nullRect
= {left: 0, right: 0, top: 0, bottom: 0};
2593 function nodeAndOffsetInLineMap(map
, ch
, bias
) {
2594 var node
, start
, end
, collapse
;
2595 // First, search the line map for the text node corresponding to,
2596 // or closest to, the target character.
2597 for (var i
= 0; i
< map
.length
; i
+= 3) {
2598 var mStart
= map
[i
], mEnd
= map
[i
+ 1];
2602 } else if (ch
< mEnd
) {
2603 start
= ch
- mStart
;
2605 } else if (i
== map
.length
- 3 || ch
== mEnd
&& map
[i
+ 3] > ch
) {
2606 end
= mEnd
- mStart
;
2608 if (ch
>= mEnd
) collapse
= "right";
2610 if (start
!= null) {
2612 if (mStart
== mEnd
&& bias
== (node
.insertLeft
? "left" : "right"))
2614 if (bias
== "left" && start
== 0)
2615 while (i
&& map
[i
- 2] == map
[i
- 3] && map
[i
- 1].insertLeft
) {
2616 node
= map
[(i
-= 3) + 2];
2619 if (bias
== "right" && start
== mEnd
- mStart
)
2620 while (i
< map
.length
- 3 && map
[i
+ 3] == map
[i
+ 4] && !map
[i
+ 5].insertLeft
) {
2621 node
= map
[(i
+= 3) + 2];
2627 return {node: node
, start: start
, end: end
, collapse: collapse
, coverStart: mStart
, coverEnd: mEnd
};
2630 function measureCharInner(cm
, prepared
, ch
, bias
) {
2631 var place
= nodeAndOffsetInLineMap(prepared
.map
, ch
, bias
);
2632 var node
= place
.node
, start
= place
.start
, end
= place
.end
, collapse
= place
.collapse
;
2635 if (node
.nodeType
== 3) { // If it is a text node, use a range to retrieve the coordinates.
2636 for (var i
= 0; i
< 4; i
++) { // Retry a maximum of 4 times when nonsense rectangles are returned
2637 while (start
&& isExtendingChar(prepared
.line
.text
.charAt(place
.coverStart
+ start
))) --start
;
2638 while (place
.coverStart
+ end
< place
.coverEnd
&& isExtendingChar(prepared
.line
.text
.charAt(place
.coverStart
+ end
))) ++end
;
2639 if (ie
&& ie_version
< 9 && start
== 0 && end
== place
.coverEnd
- place
.coverStart
) {
2640 rect
= node
.parentNode
.getBoundingClientRect();
2641 } else if (ie
&& cm
.options
.lineWrapping
) {
2642 var rects
= range(node
, start
, end
).getClientRects();
2644 rect
= rects
[bias
== "right" ? rects
.length
- 1 : 0];
2648 rect
= range(node
, start
, end
).getBoundingClientRect() || nullRect
;
2650 if (rect
.left
|| rect
.right
|| start
== 0) break;
2655 if (ie
&& ie_version
< 11) rect
= maybeUpdateRectForZooming(cm
.display
.measure
, rect
);
2656 } else { // If it is a widget, simply get the box for the whole widget.
2657 if (start
> 0) collapse
= bias
= "right";
2659 if (cm
.options
.lineWrapping
&& (rects
= node
.getClientRects()).length
> 1)
2660 rect
= rects
[bias
== "right" ? rects
.length
- 1 : 0];
2662 rect
= node
.getBoundingClientRect();
2664 if (ie
&& ie_version
< 9 && !start
&& (!rect
|| !rect
.left
&& !rect
.right
)) {
2665 var rSpan
= node
.parentNode
.getClientRects()[0];
2667 rect
= {left: rSpan
.left
, right: rSpan
.left
+ charWidth(cm
.display
), top: rSpan
.top
, bottom: rSpan
.bottom
};
2672 var rtop
= rect
.top
- prepared
.rect
.top
, rbot
= rect
.bottom
- prepared
.rect
.top
;
2673 var mid
= (rtop
+ rbot
) / 2;
2674 var heights
= prepared
.view
.measure
.heights
;
2675 for (var i
= 0; i
< heights
.length
- 1; i
++)
2676 if (mid
< heights
[i
]) break;
2677 var top
= i
? heights
[i
- 1] : 0, bot
= heights
[i
];
2678 var result
= {left: (collapse
== "right" ? rect
.right : rect
.left
) - prepared
.rect
.left
,
2679 right: (collapse
== "left" ? rect
.left : rect
.right
) - prepared
.rect
.left
,
2680 top: top
, bottom: bot
};
2681 if (!rect
.left
&& !rect
.right
) result
.bogus
= true;
2682 if (!cm
.options
.singleCursorHeightPerLine
) { result
.rtop
= rtop
; result
.rbottom
= rbot
; }
2687 // Work around problem with bounding client rects on ranges being
2688 // returned incorrectly when zoomed on IE10 and below.
2689 function maybeUpdateRectForZooming(measure
, rect
) {
2690 if (!window
.screen
|| screen
.logicalXDPI
== null ||
2691 screen
.logicalXDPI
== screen
.deviceXDPI
|| !hasBadZoomedRects(measure
))
2693 var scaleX
= screen
.logicalXDPI
/ screen
.deviceXDPI
;
2694 var scaleY
= screen
.logicalYDPI
/ screen
.deviceYDPI
;
2695 return {left: rect
.left
* scaleX
, right: rect
.right
* scaleX
,
2696 top: rect
.top
* scaleY
, bottom: rect
.bottom
* scaleY
};
2699 function clearLineMeasurementCacheFor(lineView
) {
2700 if (lineView
.measure
) {
2701 lineView
.measure
.cache
= {};
2702 lineView
.measure
.heights
= null;
2703 if (lineView
.rest
) for (var i
= 0; i
< lineView
.rest
.length
; i
++)
2704 lineView
.measure
.caches
[i
] = {};
2708 function clearLineMeasurementCache(cm
) {
2709 cm
.display
.externalMeasure
= null;
2710 removeChildren(cm
.display
.lineMeasure
);
2711 for (var i
= 0; i
< cm
.display
.view
.length
; i
++)
2712 clearLineMeasurementCacheFor(cm
.display
.view
[i
]);
2715 function clearCaches(cm
) {
2716 clearLineMeasurementCache(cm
);
2717 cm
.display
.cachedCharWidth
= cm
.display
.cachedTextHeight
= cm
.display
.cachedPaddingH
= null;
2718 if (!cm
.options
.lineWrapping
) cm
.display
.maxLineChanged
= true;
2719 cm
.display
.lineNumChars
= null;
2722 function pageScrollX() { return window
.pageXOffset
|| (document
.documentElement
|| document
.body
).scrollLeft
; }
2723 function pageScrollY() { return window
.pageYOffset
|| (document
.documentElement
|| document
.body
).scrollTop
; }
2725 // Converts a {top, bottom, left, right} box from line-local
2726 // coordinates into another coordinate system. Context may be one of
2727 // "line", "div" (display.lineDiv), "local"/null (editor), "window",
2729 function intoCoordSystem(cm
, lineObj
, rect
, context
) {
2730 if (lineObj
.widgets
) for (var i
= 0; i
< lineObj
.widgets
.length
; ++i
) if (lineObj
.widgets
[i
].above
) {
2731 var size
= widgetHeight(lineObj
.widgets
[i
]);
2732 rect
.top
+= size
; rect
.bottom
+= size
;
2734 if (context
== "line") return rect
;
2735 if (!context
) context
= "local";
2736 var yOff
= heightAtLine(lineObj
);
2737 if (context
== "local") yOff
+= paddingTop(cm
.display
);
2738 else yOff
-= cm
.display
.viewOffset
;
2739 if (context
== "page" || context
== "window") {
2740 var lOff
= cm
.display
.lineSpace
.getBoundingClientRect();
2741 yOff
+= lOff
.top
+ (context
== "window" ? 0 : pageScrollY());
2742 var xOff
= lOff
.left
+ (context
== "window" ? 0 : pageScrollX());
2743 rect
.left
+= xOff
; rect
.right
+= xOff
;
2745 rect
.top
+= yOff
; rect
.bottom
+= yOff
;
2749 // Coverts a box from "div" coords to another coordinate system.
2750 // Context may be "window", "page", "div", or "local"/null.
2751 function fromCoordSystem(cm
, coords
, context
) {
2752 if (context
== "div") return coords
;
2753 var left
= coords
.left
, top
= coords
.top
;
2754 // First move into "page" coordinate system
2755 if (context
== "page") {
2756 left
-= pageScrollX();
2757 top
-= pageScrollY();
2758 } else if (context
== "local" || !context
) {
2759 var localBox
= cm
.display
.sizer
.getBoundingClientRect();
2760 left
+= localBox
.left
;
2761 top
+= localBox
.top
;
2764 var lineSpaceBox
= cm
.display
.lineSpace
.getBoundingClientRect();
2765 return {left: left
- lineSpaceBox
.left
, top: top
- lineSpaceBox
.top
};
2768 function charCoords(cm
, pos
, context
, lineObj
, bias
) {
2769 if (!lineObj
) lineObj
= getLine(cm
.doc
, pos
.line
);
2770 return intoCoordSystem(cm
, lineObj
, measureChar(cm
, lineObj
, pos
.ch
, bias
), context
);
2773 // Returns a box for a given cursor position, which may have an
2774 // 'other' property containing the position of the secondary cursor
2775 // on a bidi boundary.
2776 function cursorCoords(cm
, pos
, context
, lineObj
, preparedMeasure
, varHeight
) {
2777 lineObj
= lineObj
|| getLine(cm
.doc
, pos
.line
);
2778 if (!preparedMeasure
) preparedMeasure
= prepareMeasureForLine(cm
, lineObj
);
2779 function get(ch
, right
) {
2780 var m
= measureCharPrepared(cm
, preparedMeasure
, ch
, right
? "right" : "left", varHeight
);
2781 if (right
) m
.left
= m
.right
; else m
.right
= m
.left
;
2782 return intoCoordSystem(cm
, lineObj
, m
, context
);
2784 function getBidi(ch
, partPos
) {
2785 var part
= order
[partPos
], right
= part
.level
% 2;
2786 if (ch
== bidiLeft(part
) && partPos
&& part
.level
< order
[partPos
- 1].level
) {
2787 part
= order
[--partPos
];
2788 ch
= bidiRight(part
) - (part
.level
% 2 ? 0 : 1);
2790 } else if (ch
== bidiRight(part
) && partPos
< order
.length
- 1 && part
.level
< order
[partPos
+ 1].level
) {
2791 part
= order
[++partPos
];
2792 ch
= bidiLeft(part
) - part
.level
% 2;
2795 if (right
&& ch
== part
.to
&& ch
> part
.from) return get(ch
- 1);
2796 return get(ch
, right
);
2798 var order
= getOrder(lineObj
), ch
= pos
.ch
;
2799 if (!order
) return get(ch
);
2800 var partPos
= getBidiPartAt(order
, ch
);
2801 var val
= getBidi(ch
, partPos
);
2802 if (bidiOther
!= null) val
.other
= getBidi(ch
, bidiOther
);
2806 // Used to cheaply estimate the coordinates for a position. Used for
2807 // intermediate scroll updates.
2808 function estimateCoords(cm
, pos
) {
2809 var left
= 0, pos
= clipPos(cm
.doc
, pos
);
2810 if (!cm
.options
.lineWrapping
) left
= charWidth(cm
.display
) * pos
.ch
;
2811 var lineObj
= getLine(cm
.doc
, pos
.line
);
2812 var top
= heightAtLine(lineObj
) + paddingTop(cm
.display
);
2813 return {left: left
, right: left
, top: top
, bottom: top
+ lineObj
.height
};
2816 // Positions returned by coordsChar contain some extra information.
2817 // xRel is the relative x position of the input coordinates compared
2818 // to the found position (so xRel > 0 means the coordinates are to
2819 // the right of the character position, for example). When outside
2820 // is true, that means the coordinates lie outside the line's
2822 function PosWithInfo(line
, ch
, outside
, xRel
) {
2823 var pos
= Pos(line
, ch
);
2825 if (outside
) pos
.outside
= true;
2829 // Compute the character position closest to the given coordinates.
2830 // Input must be lineSpace-local ("div" coordinate system).
2831 function coordsChar(cm
, x
, y
) {
2833 y
+= cm
.display
.viewOffset
;
2834 if (y
< 0) return PosWithInfo(doc
.first
, 0, true, -1);
2835 var lineN
= lineAtHeight(doc
, y
), last
= doc
.first
+ doc
.size
- 1;
2837 return PosWithInfo(doc
.first
+ doc
.size
- 1, getLine(doc
, last
).text
.length
, true, 1);
2840 var lineObj
= getLine(doc
, lineN
);
2842 var found
= coordsCharInner(cm
, lineObj
, lineN
, x
, y
);
2843 var merged
= collapsedSpanAtEnd(lineObj
);
2844 var mergedPos
= merged
&& merged
.find(0, true);
2845 if (merged
&& (found
.ch
> mergedPos
.from.ch
|| found
.ch
== mergedPos
.from.ch
&& found
.xRel
> 0))
2846 lineN
= lineNo(lineObj
= mergedPos
.to
.line
);
2852 function coordsCharInner(cm
, lineObj
, lineNo
, x
, y
) {
2853 var innerOff
= y
- heightAtLine(lineObj
);
2854 var wrongLine
= false, adjust
= 2 * cm
.display
.wrapper
.clientWidth
;
2855 var preparedMeasure
= prepareMeasureForLine(cm
, lineObj
);
2858 var sp
= cursorCoords(cm
, Pos(lineNo
, ch
), "line", lineObj
, preparedMeasure
);
2860 if (innerOff
> sp
.bottom
) return sp
.left
- adjust
;
2861 else if (innerOff
< sp
.top
) return sp
.left
+ adjust
;
2862 else wrongLine
= false;
2866 var bidi
= getOrder(lineObj
), dist
= lineObj
.text
.length
;
2867 var from = lineLeft(lineObj
), to
= lineRight(lineObj
);
2868 var fromX
= getX(from), fromOutside
= wrongLine
, toX
= getX(to
), toOutside
= wrongLine
;
2870 if (x
> toX
) return PosWithInfo(lineNo
, to
, toOutside
, 1);
2871 // Do a binary search between these bounds.
2873 if (bidi
? to
== from || to
== moveVisually(lineObj
, from, 1) : to
- from <= 1) {
2874 var ch
= x
< fromX
|| x
- fromX
<= toX
- x
? from : to
;
2875 var xDiff
= x
- (ch
== from ? fromX : toX
);
2876 while (isExtendingChar(lineObj
.text
.charAt(ch
))) ++ch
;
2877 var pos
= PosWithInfo(lineNo
, ch
, ch
== from ? fromOutside : toOutside
,
2878 xDiff
< -1 ? -1 : xDiff
> 1 ? 1 : 0);
2881 var step
= Math
.ceil(dist
/ 2), middle
= from + step
;
2884 for (var i
= 0; i
< step
; ++i
) middle
= moveVisually(lineObj
, middle
, 1);
2886 var middleX
= getX(middle
);
2887 if (middleX
> x
) {to
= middle
; toX
= middleX
; if (toOutside
= wrongLine
) toX
+= 1000; dist
= step
;}
2888 else {from = middle
; fromX
= middleX
; fromOutside
= wrongLine
; dist
-= step
;}
2893 // Compute the default text height.
2894 function textHeight(display
) {
2895 if (display
.cachedTextHeight
!= null) return display
.cachedTextHeight
;
2896 if (measureText
== null) {
2897 measureText
= elt("pre");
2898 // Measure a bunch of lines, for browsers that compute
2899 // fractional heights.
2900 for (var i
= 0; i
< 49; ++i
) {
2901 measureText
.appendChild(document
.createTextNode("x"));
2902 measureText
.appendChild(elt("br"));
2904 measureText
.appendChild(document
.createTextNode("x"));
2906 removeChildrenAndAdd(display
.measure
, measureText
);
2907 var height
= measureText
.offsetHeight
/ 50;
2908 if (height
> 3) display
.cachedTextHeight
= height
;
2909 removeChildren(display
.measure
);
2913 // Compute the default character width.
2914 function charWidth(display
) {
2915 if (display
.cachedCharWidth
!= null) return display
.cachedCharWidth
;
2916 var anchor
= elt("span", "xxxxxxxxxx");
2917 var pre
= elt("pre", [anchor
]);
2918 removeChildrenAndAdd(display
.measure
, pre
);
2919 var rect
= anchor
.getBoundingClientRect(), width
= (rect
.right
- rect
.left
) / 10;
2920 if (width
> 2) display
.cachedCharWidth
= width
;
2926 // Operations are used to wrap a series of changes to the editor
2927 // state in such a way that each change won't have to update the
2928 // cursor and display (which would be awkward, slow, and
2929 // error-prone). Instead, display updates are batched and then all
2930 // combined and executed at once.
2932 var operationGroup
= null;
2935 // Start a new operation.
2936 function startOperation(cm
) {
2939 viewChanged: false, // Flag that indicates that lines might need to be redrawn
2940 startHeight: cm
.doc
.height
, // Used to detect need to update scrollbar
2941 forceUpdate: false, // Used to force a redraw
2942 updateInput: null, // Whether to reset the input textarea
2943 typing: false, // Whether this reset should be careful to leave existing text (for compositing)
2944 changeObjs: null, // Accumulated changes, for firing change events
2945 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
2946 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
2947 selectionChanged: false, // Whether the selection needs to be redrawn
2948 updateMaxLine: false, // Set when the widest line needs to be determined anew
2949 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
2950 scrollToPos: null, // Used to scroll to a specific position
2952 id: ++nextOpId
// Unique ID
2954 if (operationGroup
) {
2955 operationGroup
.ops
.push(cm
.curOp
);
2957 cm
.curOp
.ownsGroup
= operationGroup
= {
2959 delayedCallbacks: []
2964 function fireCallbacksForOps(group
) {
2965 // Calls delayed callbacks and cursorActivity handlers until no
2967 var callbacks
= group
.delayedCallbacks
, i
= 0;
2969 for (; i
< callbacks
.length
; i
++)
2971 for (var j
= 0; j
< group
.ops
.length
; j
++) {
2972 var op
= group
.ops
[j
];
2973 if (op
.cursorActivityHandlers
)
2974 while (op
.cursorActivityCalled
< op
.cursorActivityHandlers
.length
)
2975 op
.cursorActivityHandlers
[op
.cursorActivityCalled
++](op
.cm
);
2977 } while (i
< callbacks
.length
);
2980 // Finish an operation, updating the display and signalling delayed events
2981 function endOperation(cm
) {
2982 var op
= cm
.curOp
, group
= op
.ownsGroup
;
2985 try { fireCallbacksForOps(group
); }
2987 operationGroup
= null;
2988 for (var i
= 0; i
< group
.ops
.length
; i
++)
2989 group
.ops
[i
].cm
.curOp
= null;
2990 endOperations(group
);
2994 // The DOM updates done when an operation finishes are batched so
2995 // that the minimum number of relayouts are required.
2996 function endOperations(group
) {
2997 var ops
= group
.ops
;
2998 for (var i
= 0; i
< ops
.length
; i
++) // Read DOM
2999 endOperation_R1(ops
[i
]);
3000 for (var i
= 0; i
< ops
.length
; i
++) // Write DOM (maybe)
3001 endOperation_W1(ops
[i
]);
3002 for (var i
= 0; i
< ops
.length
; i
++) // Read DOM
3003 endOperation_R2(ops
[i
]);
3004 for (var i
= 0; i
< ops
.length
; i
++) // Write DOM (maybe)
3005 endOperation_W2(ops
[i
]);
3006 for (var i
= 0; i
< ops
.length
; i
++) // Read DOM
3007 endOperation_finish(ops
[i
]);
3010 function endOperation_R1(op
) {
3011 var cm
= op
.cm
, display
= cm
.display
;
3012 maybeClipScrollbars(cm
);
3013 if (op
.updateMaxLine
) findMaxLine(cm
);
3015 op
.mustUpdate
= op
.viewChanged
|| op
.forceUpdate
|| op
.scrollTop
!= null ||
3016 op
.scrollToPos
&& (op
.scrollToPos
.from.line
< display
.viewFrom
||
3017 op
.scrollToPos
.to
.line
>= display
.viewTo
) ||
3018 display
.maxLineChanged
&& cm
.options
.lineWrapping
;
3019 op
.update
= op
.mustUpdate
&&
3020 new DisplayUpdate(cm
, op
.mustUpdate
&& {top: op
.scrollTop
, ensure: op
.scrollToPos
}, op
.forceUpdate
);
3023 function endOperation_W1(op
) {
3024 op
.updatedDisplay
= op
.mustUpdate
&& updateDisplayIfNeeded(op
.cm
, op
.update
);
3027 function endOperation_R2(op
) {
3028 var cm
= op
.cm
, display
= cm
.display
;
3029 if (op
.updatedDisplay
) updateHeightsInViewport(cm
);
3031 op
.barMeasure
= measureForScrollbars(cm
);
3033 // If the max line changed since it was last measured, measure it,
3034 // and ensure the document's width matches it.
3035 // updateDisplay_W2 will use these properties to do the actual resizing
3036 if (display
.maxLineChanged
&& !cm
.options
.lineWrapping
) {
3037 op
.adjustWidthTo
= measureChar(cm
, display
.maxLine
, display
.maxLine
.text
.length
).left
+ 3;
3038 cm
.display
.sizerWidth
= op
.adjustWidthTo
;
3039 op
.barMeasure
.scrollWidth
=
3040 Math
.max(display
.scroller
.clientWidth
, display
.sizer
.offsetLeft
+ op
.adjustWidthTo
+ scrollGap(cm
) + cm
.display
.barWidth
);
3041 op
.maxScrollLeft
= Math
.max(0, display
.sizer
.offsetLeft
+ op
.adjustWidthTo
- displayWidth(cm
));
3044 if (op
.updatedDisplay
|| op
.selectionChanged
)
3045 op
.preparedSelection
= display
.input
.prepareSelection();
3048 function endOperation_W2(op
) {
3051 if (op
.adjustWidthTo
!= null) {
3052 cm
.display
.sizer
.style
.minWidth
= op
.adjustWidthTo
+ "px";
3053 if (op
.maxScrollLeft
< cm
.doc
.scrollLeft
)
3054 setScrollLeft(cm
, Math
.min(cm
.display
.scroller
.scrollLeft
, op
.maxScrollLeft
), true);
3055 cm
.display
.maxLineChanged
= false;
3058 if (op
.preparedSelection
)
3059 cm
.display
.input
.showSelection(op
.preparedSelection
);
3060 if (op
.updatedDisplay
)
3061 setDocumentHeight(cm
, op
.barMeasure
);
3062 if (op
.updatedDisplay
|| op
.startHeight
!= cm
.doc
.height
)
3063 updateScrollbars(cm
, op
.barMeasure
);
3065 if (op
.selectionChanged
) restartBlink(cm
);
3067 if (cm
.state
.focused
&& op
.updateInput
)
3068 cm
.display
.input
.reset(op
.typing
);
3069 if (op
.focus
&& op
.focus
== activeElt()) ensureFocus(op
.cm
);
3072 function endOperation_finish(op
) {
3073 var cm
= op
.cm
, display
= cm
.display
, doc
= cm
.doc
;
3075 if (op
.updatedDisplay
) postUpdateDisplay(cm
, op
.update
);
3077 // Abort mouse wheel delta measurement, when scrolling explicitly
3078 if (display
.wheelStartX
!= null && (op
.scrollTop
!= null || op
.scrollLeft
!= null || op
.scrollToPos
))
3079 display
.wheelStartX
= display
.wheelStartY
= null;
3081 // Propagate the scroll position to the actual DOM scroller
3082 if (op
.scrollTop
!= null && (display
.scroller
.scrollTop
!= op
.scrollTop
|| op
.forceScroll
)) {
3083 doc
.scrollTop
= Math
.max(0, Math
.min(display
.scroller
.scrollHeight
- display
.scroller
.clientHeight
, op
.scrollTop
));
3084 display
.scrollbars
.setScrollTop(doc
.scrollTop
);
3085 display
.scroller
.scrollTop
= doc
.scrollTop
;
3087 if (op
.scrollLeft
!= null && (display
.scroller
.scrollLeft
!= op
.scrollLeft
|| op
.forceScroll
)) {
3088 doc
.scrollLeft
= Math
.max(0, Math
.min(display
.scroller
.scrollWidth
- displayWidth(cm
), op
.scrollLeft
));
3089 display
.scrollbars
.setScrollLeft(doc
.scrollLeft
);
3090 display
.scroller
.scrollLeft
= doc
.scrollLeft
;
3091 alignHorizontally(cm
);
3093 // If we need to scroll a specific position into view, do so.
3094 if (op
.scrollToPos
) {
3095 var coords
= scrollPosIntoView(cm
, clipPos(doc
, op
.scrollToPos
.from),
3096 clipPos(doc
, op
.scrollToPos
.to
), op
.scrollToPos
.margin
);
3097 if (op
.scrollToPos
.isCursor
&& cm
.state
.focused
) maybeScrollWindow(cm
, coords
);
3100 // Fire events for markers that are hidden/unidden by editing or
3102 var hidden
= op
.maybeHiddenMarkers
, unhidden
= op
.maybeUnhiddenMarkers
;
3103 if (hidden
) for (var i
= 0; i
< hidden
.length
; ++i
)
3104 if (!hidden
[i
].lines
.length
) signal(hidden
[i
], "hide");
3105 if (unhidden
) for (var i
= 0; i
< unhidden
.length
; ++i
)
3106 if (unhidden
[i
].lines
.length
) signal(unhidden
[i
], "unhide");
3108 if (display
.wrapper
.offsetHeight
)
3109 doc
.scrollTop
= cm
.display
.scroller
.scrollTop
;
3111 // Fire change events, and delayed event handlers
3113 signal(cm
, "changes", cm
, op
.changeObjs
);
3118 // Run the given function in an operation
3119 function runInOp(cm
, f
) {
3120 if (cm
.curOp
) return f();
3123 finally { endOperation(cm
); }
3125 // Wraps a function in an operation. Returns the wrapped function.
3126 function operation(cm
, f
) {
3128 if (cm
.curOp
) return f
.apply(cm
, arguments
);
3130 try { return f
.apply(cm
, arguments
); }
3131 finally { endOperation(cm
); }
3134 // Used to add methods to editor and doc instances, wrapping them in
3136 function methodOp(f
) {
3138 if (this.curOp
) return f
.apply(this, arguments
);
3139 startOperation(this);
3140 try { return f
.apply(this, arguments
); }
3141 finally { endOperation(this); }
3144 function docMethodOp(f
) {
3147 if (!cm
|| cm
.curOp
) return f
.apply(this, arguments
);
3149 try { return f
.apply(this, arguments
); }
3150 finally { endOperation(cm
); }
3156 // These objects are used to represent the visible (currently drawn)
3157 // part of the document. A LineView may correspond to multiple
3158 // logical lines, if those are connected by collapsed ranges.
3159 function LineView(doc
, line
, lineN
) {
3160 // The starting line
3162 // Continuing lines, if any
3163 this.rest
= visualLineContinued(line
);
3164 // Number of logical lines in this visual line
3165 this.size
= this.rest
? lineNo(lst(this.rest
)) - lineN
+ 1 : 1;
3166 this.node
= this.text
= null;
3167 this.hidden
= lineIsHidden(doc
, line
);
3170 // Create a range of LineView objects for the given lines.
3171 function buildViewArray(cm
, from, to
) {
3172 var array
= [], nextPos
;
3173 for (var pos
= from; pos
< to
; pos
= nextPos
) {
3174 var view
= new LineView(cm
.doc
, getLine(cm
.doc
, pos
), pos
);
3175 nextPos
= pos
+ view
.size
;
3181 // Updates the display.view data structure for a given change to the
3182 // document. From and to are in pre-change coordinates. Lendiff is
3183 // the amount of lines added or subtracted by the change. This is
3184 // used for changes that span multiple lines, or change the way
3185 // lines are divided into visual lines. regLineChange (below)
3186 // registers single-line changes.
3187 function regChange(cm
, from, to
, lendiff
) {
3188 if (from == null) from = cm
.doc
.first
;
3189 if (to
== null) to
= cm
.doc
.first
+ cm
.doc
.size
;
3190 if (!lendiff
) lendiff
= 0;
3192 var display
= cm
.display
;
3193 if (lendiff
&& to
< display
.viewTo
&&
3194 (display
.updateLineNumbers
== null || display
.updateLineNumbers
> from))
3195 display
.updateLineNumbers
= from;
3197 cm
.curOp
.viewChanged
= true;
3199 if (from >= display
.viewTo
) { // Change after
3200 if (sawCollapsedSpans
&& visualLineNo(cm
.doc
, from) < display
.viewTo
)
3202 } else if (to
<= display
.viewFrom
) { // Change before
3203 if (sawCollapsedSpans
&& visualLineEndNo(cm
.doc
, to
+ lendiff
) > display
.viewFrom
) {
3206 display
.viewFrom
+= lendiff
;
3207 display
.viewTo
+= lendiff
;
3209 } else if (from <= display
.viewFrom
&& to
>= display
.viewTo
) { // Full overlap
3211 } else if (from <= display
.viewFrom
) { // Top overlap
3212 var cut
= viewCuttingPoint(cm
, to
, to
+ lendiff
, 1);
3214 display
.view
= display
.view
.slice(cut
.index
);
3215 display
.viewFrom
= cut
.lineN
;
3216 display
.viewTo
+= lendiff
;
3220 } else if (to
>= display
.viewTo
) { // Bottom overlap
3221 var cut
= viewCuttingPoint(cm
, from, from, -1);
3223 display
.view
= display
.view
.slice(0, cut
.index
);
3224 display
.viewTo
= cut
.lineN
;
3228 } else { // Gap in the middle
3229 var cutTop
= viewCuttingPoint(cm
, from, from, -1);
3230 var cutBot
= viewCuttingPoint(cm
, to
, to
+ lendiff
, 1);
3231 if (cutTop
&& cutBot
) {
3232 display
.view
= display
.view
.slice(0, cutTop
.index
)
3233 .concat(buildViewArray(cm
, cutTop
.lineN
, cutBot
.lineN
))
3234 .concat(display
.view
.slice(cutBot
.index
));
3235 display
.viewTo
+= lendiff
;
3241 var ext
= display
.externalMeasured
;
3244 ext
.lineN
+= lendiff
;
3245 else if (from < ext
.lineN
+ ext
.size
)
3246 display
.externalMeasured
= null;
3250 // Register a change to a single line. Type must be one of "text",
3251 // "gutter", "class", "widget"
3252 function regLineChange(cm
, line
, type
) {
3253 cm
.curOp
.viewChanged
= true;
3254 var display
= cm
.display
, ext
= cm
.display
.externalMeasured
;
3255 if (ext
&& line
>= ext
.lineN
&& line
< ext
.lineN
+ ext
.size
)
3256 display
.externalMeasured
= null;
3258 if (line
< display
.viewFrom
|| line
>= display
.viewTo
) return;
3259 var lineView
= display
.view
[findViewIndex(cm
, line
)];
3260 if (lineView
.node
== null) return;
3261 var arr
= lineView
.changes
|| (lineView
.changes
= []);
3262 if (indexOf(arr
, type
) == -1) arr
.push(type
);
3266 function resetView(cm
) {
3267 cm
.display
.viewFrom
= cm
.display
.viewTo
= cm
.doc
.first
;
3268 cm
.display
.view
= [];
3269 cm
.display
.viewOffset
= 0;
3272 // Find the view element corresponding to a given line. Return null
3273 // when the line isn't visible.
3274 function findViewIndex(cm
, n
) {
3275 if (n
>= cm
.display
.viewTo
) return null;
3276 n
-= cm
.display
.viewFrom
;
3277 if (n
< 0) return null;
3278 var view
= cm
.display
.view
;
3279 for (var i
= 0; i
< view
.length
; i
++) {
3281 if (n
< 0) return i
;
3285 function viewCuttingPoint(cm
, oldN
, newN
, dir
) {
3286 var index
= findViewIndex(cm
, oldN
), diff
, view
= cm
.display
.view
;
3287 if (!sawCollapsedSpans
|| newN
== cm
.doc
.first
+ cm
.doc
.size
)
3288 return {index: index
, lineN: newN
};
3289 for (var i
= 0, n
= cm
.display
.viewFrom
; i
< index
; i
++)
3293 if (index
== view
.length
- 1) return null;
3294 diff
= (n
+ view
[index
].size
) - oldN
;
3299 oldN
+= diff
; newN
+= diff
;
3301 while (visualLineNo(cm
.doc
, newN
) != newN
) {
3302 if (index
== (dir
< 0 ? 0 : view
.length
- 1)) return null;
3303 newN
+= dir
* view
[index
- (dir
< 0 ? 1 : 0)].size
;
3306 return {index: index
, lineN: newN
};
3309 // Force the view to cover a given range, adding empty view element
3310 // or clipping off existing ones as needed.
3311 function adjustView(cm
, from, to
) {
3312 var display
= cm
.display
, view
= display
.view
;
3313 if (view
.length
== 0 || from >= display
.viewTo
|| to
<= display
.viewFrom
) {
3314 display
.view
= buildViewArray(cm
, from, to
);
3315 display
.viewFrom
= from;
3317 if (display
.viewFrom
> from)
3318 display
.view
= buildViewArray(cm
, from, display
.viewFrom
).concat(display
.view
);
3319 else if (display
.viewFrom
< from)
3320 display
.view
= display
.view
.slice(findViewIndex(cm
, from));
3321 display
.viewFrom
= from;
3322 if (display
.viewTo
< to
)
3323 display
.view
= display
.view
.concat(buildViewArray(cm
, display
.viewTo
, to
));
3324 else if (display
.viewTo
> to
)
3325 display
.view
= display
.view
.slice(0, findViewIndex(cm
, to
));
3327 display
.viewTo
= to
;
3330 // Count the number of lines in the view whose DOM representation is
3331 // out of date (or nonexistent).
3332 function countDirtyView(cm
) {
3333 var view
= cm
.display
.view
, dirty
= 0;
3334 for (var i
= 0; i
< view
.length
; i
++) {
3335 var lineView
= view
[i
];
3336 if (!lineView
.hidden
&& (!lineView
.node
|| lineView
.changes
)) ++dirty
;
3343 // Attach the necessary event handlers when initializing the editor
3344 function registerEventHandlers(cm
) {
3346 on(d
.scroller
, "mousedown", operation(cm
, onMouseDown
));
3347 // Older IE's will not fire a second mousedown for a double click
3348 if (ie
&& ie_version
< 11)
3349 on(d
.scroller
, "dblclick", operation(cm
, function(e
) {
3350 if (signalDOMEvent(cm
, e
)) return;
3351 var pos
= posFromMouse(cm
, e
);
3352 if (!pos
|| clickInGutter(cm
, e
) || eventInWidget(cm
.display
, e
)) return;
3353 e_preventDefault(e
);
3354 var word
= cm
.findWordAt(pos
);
3355 extendSelection(cm
.doc
, word
.anchor
, word
.head
);
3358 on(d
.scroller
, "dblclick", function(e
) { signalDOMEvent(cm
, e
) || e_preventDefault(e
); });
3359 // Some browsers fire contextmenu *after* opening the menu, at
3360 // which point we can't mess with it anymore. Context menu is
3361 // handled in onMouseDown for these browsers.
3362 if (!captureRightClick
) on(d
.scroller
, "contextmenu", function(e
) {onContextMenu(cm
, e
);});
3364 // Used to suppress mouse event handling when a touch happens
3365 var touchFinished
, prevTouch
= {end: 0};
3366 function finishTouch() {
3367 if (d
.activeTouch
) {
3368 touchFinished
= setTimeout(function() {d
.activeTouch
= null;}, 1000);
3369 prevTouch
= d
.activeTouch
;
3370 prevTouch
.end
= +new Date
;
3373 function isMouseLikeTouchEvent(e
) {
3374 if (e
.touches
.length
!= 1) return false;
3375 var touch
= e
.touches
[0];
3376 return touch
.radiusX
<= 1 && touch
.radiusY
<= 1;
3378 function farAway(touch
, other
) {
3379 if (other
.left
== null) return true;
3380 var dx
= other
.left
- touch
.left
, dy
= other
.top
- touch
.top
;
3381 return dx
* dx
+ dy
* dy
> 20 * 20;
3383 on(d
.scroller
, "touchstart", function(e
) {
3384 if (!isMouseLikeTouchEvent(e
)) {
3385 clearTimeout(touchFinished
);
3386 var now
= +new Date
;
3387 d
.activeTouch
= {start: now
, moved: false,
3388 prev: now
- prevTouch
.end
<= 300 ? prevTouch : null};
3389 if (e
.touches
.length
== 1) {
3390 d
.activeTouch
.left
= e
.touches
[0].pageX
;
3391 d
.activeTouch
.top
= e
.touches
[0].pageY
;
3395 on(d
.scroller
, "touchmove", function() {
3396 if (d
.activeTouch
) d
.activeTouch
.moved
= true;
3398 on(d
.scroller
, "touchend", function(e
) {
3399 var touch
= d
.activeTouch
;
3400 if (touch
&& !eventInWidget(d
, e
) && touch
.left
!= null &&
3401 !touch
.moved
&& new Date
- touch
.start
< 300) {
3402 var pos
= cm
.coordsChar(d
.activeTouch
, "page"), range
;
3403 if (!touch
.prev
|| farAway(touch
, touch
.prev
)) // Single tap
3404 range
= new Range(pos
, pos
);
3405 else if (!touch
.prev
.prev
|| farAway(touch
, touch
.prev
.prev
)) // Double tap
3406 range
= cm
.findWordAt(pos
);
3408 range
= new Range(Pos(pos
.line
, 0), clipPos(cm
.doc
, Pos(pos
.line
+ 1, 0)));
3409 cm
.setSelection(range
.anchor
, range
.head
);
3411 e_preventDefault(e
);
3415 on(d
.scroller
, "touchcancel", finishTouch
);
3417 // Sync scrolling between fake scrollbars and real scrollable
3418 // area, ensure viewport is updated when scrolling.
3419 on(d
.scroller
, "scroll", function() {
3420 if (d
.scroller
.clientHeight
) {
3421 setScrollTop(cm
, d
.scroller
.scrollTop
);
3422 setScrollLeft(cm
, d
.scroller
.scrollLeft
, true);
3423 signal(cm
, "scroll", cm
);
3427 // Listen to wheel events in order to try and update the viewport on time.
3428 on(d
.scroller
, "mousewheel", function(e
){onScrollWheel(cm
, e
);});
3429 on(d
.scroller
, "DOMMouseScroll", function(e
){onScrollWheel(cm
, e
);});
3431 // Prevent wrapper from ever scrolling
3432 on(d
.wrapper
, "scroll", function() { d
.wrapper
.scrollTop
= d
.wrapper
.scrollLeft
= 0; });
3435 simple: function(e
) {if (!signalDOMEvent(cm
, e
)) e_stop(e
);},
3436 start: function(e
){onDragStart(cm
, e
);},
3437 drop: operation(cm
, onDrop
)
3440 var inp
= d
.input
.getField();
3441 on(inp
, "keyup", function(e
) { onKeyUp
.call(cm
, e
); });
3442 on(inp
, "keydown", operation(cm
, onKeyDown
));
3443 on(inp
, "keypress", operation(cm
, onKeyPress
));
3444 on(inp
, "focus", bind(onFocus
, cm
));
3445 on(inp
, "blur", bind(onBlur
, cm
));
3448 function dragDropChanged(cm
, value
, old
) {
3449 var wasOn
= old
&& old
!= CodeMirror
.Init
;
3450 if (!value
!= !wasOn
) {
3451 var funcs
= cm
.display
.dragFunctions
;
3452 var toggle
= value
? on : off
;
3453 toggle(cm
.display
.scroller
, "dragstart", funcs
.start
);
3454 toggle(cm
.display
.scroller
, "dragenter", funcs
.simple
);
3455 toggle(cm
.display
.scroller
, "dragover", funcs
.simple
);
3456 toggle(cm
.display
.scroller
, "drop", funcs
.drop
);
3460 // Called when the window resizes
3461 function onResize(cm
) {
3463 if (d
.lastWrapHeight
== d
.wrapper
.clientHeight
&& d
.lastWrapWidth
== d
.wrapper
.clientWidth
)
3465 // Might be a text scaling operation, clear size caches.
3466 d
.cachedCharWidth
= d
.cachedTextHeight
= d
.cachedPaddingH
= null;
3467 d
.scrollbarsClipped
= false;
3473 // Return true when the given mouse event happened in a widget
3474 function eventInWidget(display
, e
) {
3475 for (var n
= e_target(e
); n
!= display
.wrapper
; n
= n
.parentNode
) {
3476 if (!n
|| (n
.nodeType
== 1 && n
.getAttribute("cm-ignore-events") == "true") ||
3477 (n
.parentNode
== display
.sizer
&& n
!= display
.mover
))
3482 // Given a mouse event, find the corresponding position. If liberal
3483 // is false, it checks whether a gutter or scrollbar was clicked,
3484 // and returns null if it was. forRect is used by rectangular
3485 // selections, and tries to estimate a character position even for
3486 // coordinates beyond the right of the text.
3487 function posFromMouse(cm
, e
, liberal
, forRect
) {
3488 var display
= cm
.display
;
3489 if (!liberal
&& e_target(e
).getAttribute("cm-not-content") == "true") return null;
3491 var x
, y
, space
= display
.lineSpace
.getBoundingClientRect();
3492 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
3493 try { x
= e
.clientX
- space
.left
; y
= e
.clientY
- space
.top
; }
3494 catch (e
) { return null; }
3495 var coords
= coordsChar(cm
, x
, y
), line
;
3496 if (forRect
&& coords
.xRel
== 1 && (line
= getLine(cm
.doc
, coords
.line
).text
).length
== coords
.ch
) {
3497 var colDiff
= countColumn(line
, line
.length
, cm
.options
.tabSize
) - line
.length
;
3498 coords
= Pos(coords
.line
, Math
.max(0, Math
.round((x
- paddingH(cm
.display
).left
) / charWidth(cm
.display
)) - colDiff
));
3503 // A mouse down can be a single click, double click, triple click,
3504 // start of selection drag, start of text drag, new cursor
3505 // (ctrl-click), rectangle drag (alt-drag), or xwin
3506 // middle-click-paste. Or it might be a click on something we should
3507 // not interfere with, such as a scrollbar or widget.
3508 function onMouseDown(e
) {
3509 var cm
= this, display
= cm
.display
;
3510 if (display
.activeTouch
&& display
.input
.supportsTouch() || signalDOMEvent(cm
, e
)) return;
3511 display
.shift
= e
.shiftKey
;
3513 if (eventInWidget(display
, e
)) {
3515 // Briefly turn off draggability, to allow widgets to do
3516 // normal dragging things.
3517 display
.scroller
.draggable
= false;
3518 setTimeout(function(){display
.scroller
.draggable
= true;}, 100);
3522 if (clickInGutter(cm
, e
)) return;
3523 var start
= posFromMouse(cm
, e
);
3526 switch (e_button(e
)) {
3529 leftButtonDown(cm
, e
, start
);
3530 else if (e_target(e
) == display
.scroller
)
3531 e_preventDefault(e
);
3534 if (webkit
) cm
.state
.lastMiddleDown
= +new Date
;
3535 if (start
) extendSelection(cm
.doc
, start
);
3536 setTimeout(function() {display
.input
.focus();}, 20);
3537 e_preventDefault(e
);
3540 if (captureRightClick
) onContextMenu(cm
, e
);
3541 else delayBlurEvent(cm
);
3546 var lastClick
, lastDoubleClick
;
3547 function leftButtonDown(cm
, e
, start
) {
3548 if (ie
) setTimeout(bind(ensureFocus
, cm
), 0);
3549 else cm
.curOp
.focus
= activeElt();
3551 var now
= +new Date
, type
;
3552 if (lastDoubleClick
&& lastDoubleClick
.time
> now
- 400 && cmp(lastDoubleClick
.pos
, start
) == 0) {
3554 } else if (lastClick
&& lastClick
.time
> now
- 400 && cmp(lastClick
.pos
, start
) == 0) {
3556 lastDoubleClick
= {time: now
, pos: start
};
3559 lastClick
= {time: now
, pos: start
};
3562 var sel
= cm
.doc
.sel
, modifier
= mac
? e
.metaKey : e
.ctrlKey
, contained
;
3563 if (cm
.options
.dragDrop
&& dragAndDrop
&& !isReadOnly(cm
) &&
3564 type
== "single" && (contained
= sel
.contains(start
)) > -1 &&
3565 !sel
.ranges
[contained
].empty())
3566 leftButtonStartDrag(cm
, e
, start
, modifier
);
3568 leftButtonSelect(cm
, e
, start
, type
, modifier
);
3571 // Start a text drag. When it ends, see if any dragging actually
3572 // happen, and treat as a click if it didn't.
3573 function leftButtonStartDrag(cm
, e
, start
, modifier
) {
3574 var display
= cm
.display
, startTime
= +new Date
;
3575 var dragEnd
= operation(cm
, function(e2
) {
3576 if (webkit
) display
.scroller
.draggable
= false;
3577 cm
.state
.draggingText
= false;
3578 off(document
, "mouseup", dragEnd
);
3579 off(display
.scroller
, "drop", dragEnd
);
3580 if (Math
.abs(e
.clientX
- e2
.clientX
) + Math
.abs(e
.clientY
- e2
.clientY
) < 10) {
3581 e_preventDefault(e2
);
3582 if (!modifier
&& +new Date
- 200 < startTime
)
3583 extendSelection(cm
.doc
, start
);
3584 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
3585 if (webkit
|| ie
&& ie_version
== 9)
3586 setTimeout(function() {document
.body
.focus(); display
.input
.focus();}, 20);
3588 display
.input
.focus();
3591 // Let the drag handler handle this.
3592 if (webkit
) display
.scroller
.draggable
= true;
3593 cm
.state
.draggingText
= dragEnd
;
3594 // IE's approach to draggable
3595 if (display
.scroller
.dragDrop
) display
.scroller
.dragDrop();
3596 on(document
, "mouseup", dragEnd
);
3597 on(display
.scroller
, "drop", dragEnd
);
3600 // Normal selection, as opposed to text dragging.
3601 function leftButtonSelect(cm
, e
, start
, type
, addNew
) {
3602 var display
= cm
.display
, doc
= cm
.doc
;
3603 e_preventDefault(e
);
3605 var ourRange
, ourIndex
, startSel
= doc
.sel
, ranges
= startSel
.ranges
;
3606 if (addNew
&& !e
.shiftKey
) {
3607 ourIndex
= doc
.sel
.contains(start
);
3609 ourRange
= ranges
[ourIndex
];
3611 ourRange
= new Range(start
, start
);
3613 ourRange
= doc
.sel
.primary();
3614 ourIndex
= doc
.sel
.primIndex
;
3619 if (!addNew
) ourRange
= new Range(start
, start
);
3620 start
= posFromMouse(cm
, e
, true, true);
3622 } else if (type
== "double") {
3623 var word
= cm
.findWordAt(start
);
3624 if (cm
.display
.shift
|| doc
.extend
)
3625 ourRange
= extendRange(doc
, ourRange
, word
.anchor
, word
.head
);
3628 } else if (type
== "triple") {
3629 var line
= new Range(Pos(start
.line
, 0), clipPos(doc
, Pos(start
.line
+ 1, 0)));
3630 if (cm
.display
.shift
|| doc
.extend
)
3631 ourRange
= extendRange(doc
, ourRange
, line
.anchor
, line
.head
);
3635 ourRange
= extendRange(doc
, ourRange
, start
);
3640 setSelection(doc
, new Selection([ourRange
], 0), sel_mouse
);
3642 } else if (ourIndex
== -1) {
3643 ourIndex
= ranges
.length
;
3644 setSelection(doc
, normalizeSelection(ranges
.concat([ourRange
]), ourIndex
),
3645 {scroll: false, origin: "*mouse"});
3646 } else if (ranges
.length
> 1 && ranges
[ourIndex
].empty() && type
== "single" && !e
.shiftKey
) {
3647 setSelection(doc
, normalizeSelection(ranges
.slice(0, ourIndex
).concat(ranges
.slice(ourIndex
+ 1)), 0));
3650 replaceOneSelection(doc
, ourIndex
, ourRange
, sel_mouse
);
3653 var lastPos
= start
;
3654 function extendTo(pos
) {
3655 if (cmp(lastPos
, pos
) == 0) return;
3658 if (type
== "rect") {
3659 var ranges
= [], tabSize
= cm
.options
.tabSize
;
3660 var startCol
= countColumn(getLine(doc
, start
.line
).text
, start
.ch
, tabSize
);
3661 var posCol
= countColumn(getLine(doc
, pos
.line
).text
, pos
.ch
, tabSize
);
3662 var left
= Math
.min(startCol
, posCol
), right
= Math
.max(startCol
, posCol
);
3663 for (var line
= Math
.min(start
.line
, pos
.line
), end
= Math
.min(cm
.lastLine(), Math
.max(start
.line
, pos
.line
));
3664 line
<= end
; line
++) {
3665 var text
= getLine(doc
, line
).text
, leftPos
= findColumn(text
, left
, tabSize
);
3667 ranges
.push(new Range(Pos(line
, leftPos
), Pos(line
, leftPos
)));
3668 else if (text
.length
> leftPos
)
3669 ranges
.push(new Range(Pos(line
, leftPos
), Pos(line
, findColumn(text
, right
, tabSize
))));
3671 if (!ranges
.length
) ranges
.push(new Range(start
, start
));
3672 setSelection(doc
, normalizeSelection(startSel
.ranges
.slice(0, ourIndex
).concat(ranges
), ourIndex
),
3673 {origin: "*mouse", scroll: false});
3674 cm
.scrollIntoView(pos
);
3676 var oldRange
= ourRange
;
3677 var anchor
= oldRange
.anchor
, head
= pos
;
3678 if (type
!= "single") {
3679 if (type
== "double")
3680 var range
= cm
.findWordAt(pos
);
3682 var range
= new Range(Pos(pos
.line
, 0), clipPos(doc
, Pos(pos
.line
+ 1, 0)));
3683 if (cmp(range
.anchor
, anchor
) > 0) {
3685 anchor
= minPos(oldRange
.from(), range
.anchor
);
3687 head
= range
.anchor
;
3688 anchor
= maxPos(oldRange
.to(), range
.head
);
3691 var ranges
= startSel
.ranges
.slice(0);
3692 ranges
[ourIndex
] = new Range(clipPos(doc
, anchor
), head
);
3693 setSelection(doc
, normalizeSelection(ranges
, ourIndex
), sel_mouse
);
3697 var editorSize
= display
.wrapper
.getBoundingClientRect();
3698 // Used to ensure timeout re-tries don't fire when another extend
3699 // happened in the meantime (clearTimeout isn't reliable -- at
3700 // least on Chrome, the timeouts still happen even when cleared,
3701 // if the clear happens after their scheduled firing time).
3704 function extend(e
) {
3705 var curCount
= ++counter
;
3706 var cur
= posFromMouse(cm
, e
, true, type
== "rect");
3708 if (cmp(cur
, lastPos
) != 0) {
3709 cm
.curOp
.focus
= activeElt();
3711 var visible
= visibleLines(display
, doc
);
3712 if (cur
.line
>= visible
.to
|| cur
.line
< visible
.from)
3713 setTimeout(operation(cm
, function(){if (counter
== curCount
) extend(e
);}), 150);
3715 var outside
= e
.clientY
< editorSize
.top
? -20 : e
.clientY
> editorSize
.bottom
? 20 : 0;
3716 if (outside
) setTimeout(operation(cm
, function() {
3717 if (counter
!= curCount
) return;
3718 display
.scroller
.scrollTop
+= outside
;
3726 e_preventDefault(e
);
3727 display
.input
.focus();
3728 off(document
, "mousemove", move);
3729 off(document
, "mouseup", up
);
3730 doc
.history
.lastSelOrigin
= null;
3733 var move = operation(cm
, function(e
) {
3734 if (!e_button(e
)) done(e
);
3737 var up
= operation(cm
, done
);
3738 on(document
, "mousemove", move);
3739 on(document
, "mouseup", up
);
3742 // Determines whether an event happened in the gutter, and fires the
3743 // handlers for the corresponding event.
3744 function gutterEvent(cm
, e
, type
, prevent
, signalfn
) {
3745 try { var mX
= e
.clientX
, mY
= e
.clientY
; }
3746 catch(e
) { return false; }
3747 if (mX
>= Math
.floor(cm
.display
.gutters
.getBoundingClientRect().right
)) return false;
3748 if (prevent
) e_preventDefault(e
);
3750 var display
= cm
.display
;
3751 var lineBox
= display
.lineDiv
.getBoundingClientRect();
3753 if (mY
> lineBox
.bottom
|| !hasHandler(cm
, type
)) return e_defaultPrevented(e
);
3754 mY
-= lineBox
.top
- display
.viewOffset
;
3756 for (var i
= 0; i
< cm
.options
.gutters
.length
; ++i
) {
3757 var g
= display
.gutters
.childNodes
[i
];
3758 if (g
&& g
.getBoundingClientRect().right
>= mX
) {
3759 var line
= lineAtHeight(cm
.doc
, mY
);
3760 var gutter
= cm
.options
.gutters
[i
];
3761 signalfn(cm
, type
, cm
, line
, gutter
, e
);
3762 return e_defaultPrevented(e
);
3767 function clickInGutter(cm
, e
) {
3768 return gutterEvent(cm
, e
, "gutterClick", true, signalLater
);
3771 // Kludge to work around strange IE behavior where it'll sometimes
3772 // re-fire a series of drag-related events right after the drop (#1551)
3775 function onDrop(e
) {
3777 if (signalDOMEvent(cm
, e
) || eventInWidget(cm
.display
, e
))
3779 e_preventDefault(e
);
3780 if (ie
) lastDrop
= +new Date
;
3781 var pos
= posFromMouse(cm
, e
, true), files
= e
.dataTransfer
.files
;
3782 if (!pos
|| isReadOnly(cm
)) return;
3783 // Might be a file drop, in which case we simply extract the text
3785 if (files
&& files
.length
&& window
.FileReader
&& window
.File
) {
3786 var n
= files
.length
, text
= Array(n
), read
= 0;
3787 var loadFile = function(file
, i
) {
3788 var reader
= new FileReader
;
3789 reader
.onload
= operation(cm
, function() {
3790 text
[i
] = reader
.result
;
3792 pos
= clipPos(cm
.doc
, pos
);
3793 var change
= {from: pos
, to: pos
, text: splitLines(text
.join("\n")), origin: "paste"};
3794 makeChange(cm
.doc
, change
);
3795 setSelectionReplaceHistory(cm
.doc
, simpleSelection(pos
, changeEnd(change
)));
3798 reader
.readAsText(file
);
3800 for (var i
= 0; i
< n
; ++i
) loadFile(files
[i
], i
);
3801 } else { // Normal drop
3802 // Don't do a replace if the drop happened inside of the selected text.
3803 if (cm
.state
.draggingText
&& cm
.doc
.sel
.contains(pos
) > -1) {
3804 cm
.state
.draggingText(e
);
3805 // Ensure the editor is re-focused
3806 setTimeout(function() {cm
.display
.input
.focus();}, 20);
3810 var text
= e
.dataTransfer
.getData("Text");
3812 if (cm
.state
.draggingText
&& !(mac
? e
.altKey : e
.ctrlKey
))
3813 var selected
= cm
.listSelections();
3814 setSelectionNoUndo(cm
.doc
, simpleSelection(pos
, pos
));
3815 if (selected
) for (var i
= 0; i
< selected
.length
; ++i
)
3816 replaceRange(cm
.doc
, "", selected
[i
].anchor
, selected
[i
].head
, "drag");
3817 cm
.replaceSelection(text
, "around", "paste");
3818 cm
.display
.input
.focus();
3825 function onDragStart(cm
, e
) {
3826 if (ie
&& (!cm
.state
.draggingText
|| +new Date
- lastDrop
< 100)) { e_stop(e
); return; }
3827 if (signalDOMEvent(cm
, e
) || eventInWidget(cm
.display
, e
)) return;
3829 e
.dataTransfer
.setData("Text", cm
.getSelection());
3831 // Use dummy image instead of default browsers image.
3832 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
3833 if (e
.dataTransfer
.setDragImage
&& !safari
) {
3834 var img
= elt("img", null, null, "position: fixed; left: 0; top: 0;");
3835 img
.src
= "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
3837 img
.width
= img
.height
= 1;
3838 cm
.display
.wrapper
.appendChild(img
);
3839 // Force a relayout, or Opera won't use our image for some obscure reason
3840 img
._top
= img
.offsetTop
;
3842 e
.dataTransfer
.setDragImage(img
, 0, 0);
3843 if (presto
) img
.parentNode
.removeChild(img
);
3849 // Sync the scrollable area and scrollbars, ensure the viewport
3850 // covers the visible area.
3851 function setScrollTop(cm
, val
) {
3852 if (Math
.abs(cm
.doc
.scrollTop
- val
) < 2) return;
3853 cm
.doc
.scrollTop
= val
;
3854 if (!gecko
) updateDisplaySimple(cm
, {top: val
});
3855 if (cm
.display
.scroller
.scrollTop
!= val
) cm
.display
.scroller
.scrollTop
= val
;
3856 cm
.display
.scrollbars
.setScrollTop(val
);
3857 if (gecko
) updateDisplaySimple(cm
);
3858 startWorker(cm
, 100);
3860 // Sync scroller and scrollbar, ensure the gutter elements are
3862 function setScrollLeft(cm
, val
, isScroller
) {
3863 if (isScroller
? val
== cm
.doc
.scrollLeft : Math
.abs(cm
.doc
.scrollLeft
- val
) < 2) return;
3864 val
= Math
.min(val
, cm
.display
.scroller
.scrollWidth
- cm
.display
.scroller
.clientWidth
);
3865 cm
.doc
.scrollLeft
= val
;
3866 alignHorizontally(cm
);
3867 if (cm
.display
.scroller
.scrollLeft
!= val
) cm
.display
.scroller
.scrollLeft
= val
;
3868 cm
.display
.scrollbars
.setScrollLeft(val
);
3871 // Since the delta values reported on mouse wheel events are
3872 // unstandardized between browsers and even browser versions, and
3873 // generally horribly unpredictable, this code starts by measuring
3874 // the scroll effect that the first few mouse wheel events have,
3875 // and, from that, detects the way it can convert deltas to pixel
3876 // offsets afterwards.
3878 // The reason we want to know the amount a wheel event will scroll
3879 // is that it gives us a chance to update the display before the
3880 // actual scrolling happens, reducing flickering.
3882 var wheelSamples
= 0, wheelPixelsPerUnit
= null;
3883 // Fill in a browser-detected starting value on browsers where we
3884 // know one. These don't have to be accurate -- the result of them
3885 // being wrong would just be a slight flicker on the first wheel
3886 // scroll (if it is large enough).
3887 if (ie
) wheelPixelsPerUnit
= -.53;
3888 else if (gecko
) wheelPixelsPerUnit
= 15;
3889 else if (chrome
) wheelPixelsPerUnit
= -.7;
3890 else if (safari
) wheelPixelsPerUnit
= -1/3;
3892 var wheelEventDelta = function(e
) {
3893 var dx
= e
.wheelDeltaX
, dy
= e
.wheelDeltaY
;
3894 if (dx
== null && e
.detail
&& e
.axis
== e
.HORIZONTAL_AXIS
) dx
= e
.detail
;
3895 if (dy
== null && e
.detail
&& e
.axis
== e
.VERTICAL_AXIS
) dy
= e
.detail
;
3896 else if (dy
== null) dy
= e
.wheelDelta
;
3897 return {x: dx
, y: dy
};
3899 CodeMirror
.wheelEventPixels = function(e
) {
3900 var delta
= wheelEventDelta(e
);
3901 delta
.x
*= wheelPixelsPerUnit
;
3902 delta
.y
*= wheelPixelsPerUnit
;
3906 function onScrollWheel(cm
, e
) {
3907 var delta
= wheelEventDelta(e
), dx
= delta
.x
, dy
= delta
.y
;
3909 var display
= cm
.display
, scroll
= display
.scroller
;
3910 // Quit if there's nothing to scroll here
3911 if (!(dx
&& scroll
.scrollWidth
> scroll
.clientWidth
||
3912 dy
&& scroll
.scrollHeight
> scroll
.clientHeight
)) return;
3914 // Webkit browsers on OS X abort momentum scrolls when the target
3915 // of the scroll event is removed from the scrollable element.
3916 // This hack (see related code in patchDisplay) makes sure the
3917 // element is kept around.
3918 if (dy
&& mac
&& webkit
) {
3919 outer: for (var cur
= e
.target
, view
= display
.view
; cur
!= scroll
; cur
= cur
.parentNode
) {
3920 for (var i
= 0; i
< view
.length
; i
++) {
3921 if (view
[i
].node
== cur
) {
3922 cm
.display
.currentWheelTarget
= cur
;
3929 // On some browsers, horizontal scrolling will cause redraws to
3930 // happen before the gutter has been realigned, causing it to
3931 // wriggle around in a most unseemly way. When we have an
3932 // estimated pixels/delta value, we just handle horizontal
3933 // scrolling entirely here. It'll be slightly off from native, but
3934 // better than glitching out.
3935 if (dx
&& !gecko
&& !presto
&& wheelPixelsPerUnit
!= null) {
3937 setScrollTop(cm
, Math
.max(0, Math
.min(scroll
.scrollTop
+ dy
* wheelPixelsPerUnit
, scroll
.scrollHeight
- scroll
.clientHeight
)));
3938 setScrollLeft(cm
, Math
.max(0, Math
.min(scroll
.scrollLeft
+ dx
* wheelPixelsPerUnit
, scroll
.scrollWidth
- scroll
.clientWidth
)));
3939 e_preventDefault(e
);
3940 display
.wheelStartX
= null; // Abort measurement, if in progress
3944 // 'Project' the visible viewport to cover the area that is being
3945 // scrolled into view (if we know enough to estimate it).
3946 if (dy
&& wheelPixelsPerUnit
!= null) {
3947 var pixels
= dy
* wheelPixelsPerUnit
;
3948 var top
= cm
.doc
.scrollTop
, bot
= top
+ display
.wrapper
.clientHeight
;
3949 if (pixels
< 0) top
= Math
.max(0, top
+ pixels
- 50);
3950 else bot
= Math
.min(cm
.doc
.height
, bot
+ pixels
+ 50);
3951 updateDisplaySimple(cm
, {top: top
, bottom: bot
});
3954 if (wheelSamples
< 20) {
3955 if (display
.wheelStartX
== null) {
3956 display
.wheelStartX
= scroll
.scrollLeft
; display
.wheelStartY
= scroll
.scrollTop
;
3957 display
.wheelDX
= dx
; display
.wheelDY
= dy
;
3958 setTimeout(function() {
3959 if (display
.wheelStartX
== null) return;
3960 var movedX
= scroll
.scrollLeft
- display
.wheelStartX
;
3961 var movedY
= scroll
.scrollTop
- display
.wheelStartY
;
3962 var sample
= (movedY
&& display
.wheelDY
&& movedY
/ display
.wheelDY
) ||
3963 (movedX
&& display
.wheelDX
&& movedX
/ display
.wheelDX
);
3964 display
.wheelStartX
= display
.wheelStartY
= null;
3965 if (!sample
) return;
3966 wheelPixelsPerUnit
= (wheelPixelsPerUnit
* wheelSamples
+ sample
) / (wheelSamples
+ 1);
3970 display
.wheelDX
+= dx
; display
.wheelDY
+= dy
;
3977 // Run a handler that was bound to a key.
3978 function doHandleBinding(cm
, bound
, dropShift
) {
3979 if (typeof bound
== "string") {
3980 bound
= commands
[bound
];
3981 if (!bound
) return false;
3983 // Ensure previous input has been read, so that the handler sees a
3984 // consistent view of the document
3985 cm
.display
.input
.ensurePolled();
3986 var prevShift
= cm
.display
.shift
, done
= false;
3988 if (isReadOnly(cm
)) cm
.state
.suppressEdits
= true;
3989 if (dropShift
) cm
.display
.shift
= false;
3990 done
= bound(cm
) != Pass
;
3992 cm
.display
.shift
= prevShift
;
3993 cm
.state
.suppressEdits
= false;
3998 function lookupKeyForEditor(cm
, name
, handle
) {
3999 for (var i
= 0; i
< cm
.state
.keyMaps
.length
; i
++) {
4000 var result
= lookupKey(name
, cm
.state
.keyMaps
[i
], handle
, cm
);
4001 if (result
) return result
;
4003 return (cm
.options
.extraKeys
&& lookupKey(name
, cm
.options
.extraKeys
, handle
, cm
))
4004 || lookupKey(name
, cm
.options
.keyMap
, handle
, cm
);
4007 var stopSeq
= new Delayed
;
4008 function dispatchKey(cm
, name
, e
, handle
) {
4009 var seq
= cm
.state
.keySeq
;
4011 if (isModifierKey(name
)) return "handled";
4012 stopSeq
.set(50, function() {
4013 if (cm
.state
.keySeq
== seq
) {
4014 cm
.state
.keySeq
= null;
4015 cm
.display
.input
.reset();
4018 name
= seq
+ " " + name
;
4020 var result
= lookupKeyForEditor(cm
, name
, handle
);
4022 if (result
== "multi")
4023 cm
.state
.keySeq
= name
;
4024 if (result
== "handled")
4025 signalLater(cm
, "keyHandled", cm
, name
, e
);
4027 if (result
== "handled" || result
== "multi") {
4028 e_preventDefault(e
);
4032 if (seq
&& !result
&& /\'$/.test(name
)) {
4033 e_preventDefault(e
);
4039 // Handle a key from the keydown event.
4040 function handleKeyBinding(cm
, e
) {
4041 var name
= keyName(e
, true);
4042 if (!name
) return false;
4044 if (e
.shiftKey
&& !cm
.state
.keySeq
) {
4045 // First try to resolve full name (including 'Shift-'). Failing
4046 // that, see if there is a cursor-motion command (starting with
4047 // 'go') bound to the keyname without 'Shift-'.
4048 return dispatchKey(cm
, "Shift-" + name
, e
, function(b
) {return doHandleBinding(cm
, b
, true);})
4049 || dispatchKey(cm
, name
, e
, function(b
) {
4050 if (typeof b
== "string" ? /^go[A-Z]/.test(b
) : b
.motion
)
4051 return doHandleBinding(cm
, b
);
4054 return dispatchKey(cm
, name
, e
, function(b
) { return doHandleBinding(cm
, b
); });
4058 // Handle a key from the keypress event
4059 function handleCharBinding(cm
, e
, ch
) {
4060 return dispatchKey(cm
, "'" + ch
+ "'", e
,
4061 function(b
) { return doHandleBinding(cm
, b
, true); });
4064 var lastStoppedKey
= null;
4065 function onKeyDown(e
) {
4067 cm
.curOp
.focus
= activeElt();
4068 if (signalDOMEvent(cm
, e
)) return;
4069 // IE does strange things with escape.
4070 if (ie
&& ie_version
< 11 && e
.keyCode
== 27) e
.returnValue
= false;
4071 var code
= e
.keyCode
;
4072 cm
.display
.shift
= code
== 16 || e
.shiftKey
;
4073 var handled
= handleKeyBinding(cm
, e
);
4075 lastStoppedKey
= handled
? code : null;
4076 // Opera has no cut event... we try to at least catch the key combo
4077 if (!handled
&& code
== 88 && !hasCopyEvent
&& (mac
? e
.metaKey : e
.ctrlKey
))
4078 cm
.replaceSelection("", null, "cut");
4081 // Turn mouse into crosshair when Alt is held on Mac.
4082 if (code
== 18 && !/\bCodeMirror-crosshair\b/.test(cm
.display
.lineDiv
.className
))
4086 function showCrossHair(cm
) {
4087 var lineDiv
= cm
.display
.lineDiv
;
4088 addClass(lineDiv
, "CodeMirror-crosshair");
4091 if (e
.keyCode
== 18 || !e
.altKey
) {
4092 rmClass(lineDiv
, "CodeMirror-crosshair");
4093 off(document
, "keyup", up
);
4094 off(document
, "mouseover", up
);
4097 on(document
, "keyup", up
);
4098 on(document
, "mouseover", up
);
4101 function onKeyUp(e
) {
4102 if (e
.keyCode
== 16) this.doc
.sel
.shift
= false;
4103 signalDOMEvent(this, e
);
4106 function onKeyPress(e
) {
4108 if (eventInWidget(cm
.display
, e
) || signalDOMEvent(cm
, e
) || e
.ctrlKey
&& !e
.altKey
|| mac
&& e
.metaKey
) return;
4109 var keyCode
= e
.keyCode
, charCode
= e
.charCode
;
4110 if (presto
&& keyCode
== lastStoppedKey
) {lastStoppedKey
= null; e_preventDefault(e
); return;}
4111 if ((presto
&& (!e
.which
|| e
.which
< 10)) && handleKeyBinding(cm
, e
)) return;
4112 var ch
= String
.fromCharCode(charCode
== null ? keyCode : charCode
);
4113 if (handleCharBinding(cm
, e
, ch
)) return;
4114 cm
.display
.input
.onKeyPress(e
);
4117 // FOCUS/BLUR EVENTS
4119 function delayBlurEvent(cm
) {
4120 cm
.state
.delayingBlurEvent
= true;
4121 setTimeout(function() {
4122 if (cm
.state
.delayingBlurEvent
) {
4123 cm
.state
.delayingBlurEvent
= false;
4129 function onFocus(cm
) {
4130 if (cm
.state
.delayingBlurEvent
) cm
.state
.delayingBlurEvent
= false;
4132 if (cm
.options
.readOnly
== "nocursor") return;
4133 if (!cm
.state
.focused
) {
4134 signal(cm
, "focus", cm
);
4135 cm
.state
.focused
= true;
4136 addClass(cm
.display
.wrapper
, "CodeMirror-focused");
4137 // This test prevents this from firing when a context
4138 // menu is closed (since the input reset would kill the
4139 // select-all detection hack)
4140 if (!cm
.curOp
&& cm
.display
.selForContextMenu
!= cm
.doc
.sel
) {
4141 cm
.display
.input
.reset();
4142 if (webkit
) setTimeout(function() { cm
.display
.input
.reset(true); }, 20); // Issue #1730
4144 cm
.display
.input
.receivedFocus();
4148 function onBlur(cm
) {
4149 if (cm
.state
.delayingBlurEvent
) return;
4151 if (cm
.state
.focused
) {
4152 signal(cm
, "blur", cm
);
4153 cm
.state
.focused
= false;
4154 rmClass(cm
.display
.wrapper
, "CodeMirror-focused");
4156 clearInterval(cm
.display
.blinker
);
4157 setTimeout(function() {if (!cm
.state
.focused
) cm
.display
.shift
= false;}, 150);
4160 // CONTEXT MENU HANDLING
4162 // To make the context menu work, we need to briefly unhide the
4163 // textarea (making it as unobtrusive as possible) to let the
4164 // right-click take effect on it.
4165 function onContextMenu(cm
, e
) {
4166 if (eventInWidget(cm
.display
, e
) || contextMenuInGutter(cm
, e
)) return;
4167 cm
.display
.input
.onContextMenu(e
);
4170 function contextMenuInGutter(cm
, e
) {
4171 if (!hasHandler(cm
, "gutterContextMenu")) return false;
4172 return gutterEvent(cm
, e
, "gutterContextMenu", false, signal
);
4177 // Compute the position of the end of a change (its 'to' property
4178 // refers to the pre-change end).
4179 var changeEnd
= CodeMirror
.changeEnd = function(change
) {
4180 if (!change
.text
) return change
.to
;
4181 return Pos(change
.from.line
+ change
.text
.length
- 1,
4182 lst(change
.text
).length
+ (change
.text
.length
== 1 ? change
.from.ch : 0));
4185 // Adjust a position to refer to the post-change position of the
4186 // same text, or the end of the change if the change covers it.
4187 function adjustForChange(pos
, change
) {
4188 if (cmp(pos
, change
.from) < 0) return pos
;
4189 if (cmp(pos
, change
.to
) <= 0) return changeEnd(change
);
4191 var line
= pos
.line
+ change
.text
.length
- (change
.to
.line
- change
.from.line
) - 1, ch
= pos
.ch
;
4192 if (pos
.line
== change
.to
.line
) ch
+= changeEnd(change
).ch
- change
.to
.ch
;
4193 return Pos(line
, ch
);
4196 function computeSelAfterChange(doc
, change
) {
4198 for (var i
= 0; i
< doc
.sel
.ranges
.length
; i
++) {
4199 var range
= doc
.sel
.ranges
[i
];
4200 out
.push(new Range(adjustForChange(range
.anchor
, change
),
4201 adjustForChange(range
.head
, change
)));
4203 return normalizeSelection(out
, doc
.sel
.primIndex
);
4206 function offsetPos(pos
, old
, nw
) {
4207 if (pos
.line
== old
.line
)
4208 return Pos(nw
.line
, pos
.ch
- old
.ch
+ nw
.ch
);
4210 return Pos(nw
.line
+ (pos
.line
- old
.line
), pos
.ch
);
4213 // Used by replaceSelections to allow moving the selection to the
4214 // start or around the replaced test. Hint may be "start" or "around".
4215 function computeReplacedSel(doc
, changes
, hint
) {
4217 var oldPrev
= Pos(doc
.first
, 0), newPrev
= oldPrev
;
4218 for (var i
= 0; i
< changes
.length
; i
++) {
4219 var change
= changes
[i
];
4220 var from = offsetPos(change
.from, oldPrev
, newPrev
);
4221 var to
= offsetPos(changeEnd(change
), oldPrev
, newPrev
);
4222 oldPrev
= change
.to
;
4224 if (hint
== "around") {
4225 var range
= doc
.sel
.ranges
[i
], inv
= cmp(range
.head
, range
.anchor
) < 0;
4226 out
[i
] = new Range(inv
? to : from, inv
? from : to
);
4228 out
[i
] = new Range(from, from);
4231 return new Selection(out
, doc
.sel
.primIndex
);
4234 // Allow "beforeChange" event handlers to influence a change
4235 function filterChange(doc
, change
, update
) {
4241 origin: change
.origin
,
4242 cancel: function() { this.canceled
= true; }
4244 if (update
) obj
.update = function(from, to
, text
, origin
) {
4245 if (from) this.from = clipPos(doc
, from);
4246 if (to
) this.to
= clipPos(doc
, to
);
4247 if (text
) this.text
= text
;
4248 if (origin
!== undefined) this.origin
= origin
;
4250 signal(doc
, "beforeChange", doc
, obj
);
4251 if (doc
.cm
) signal(doc
.cm
, "beforeChange", doc
.cm
, obj
);
4253 if (obj
.canceled
) return null;
4254 return {from: obj
.from, to: obj
.to
, text: obj
.text
, origin: obj
.origin
};
4257 // Apply a change to a document, and add it to the document's
4258 // history, and propagating it to all linked documents.
4259 function makeChange(doc
, change
, ignoreReadOnly
) {
4261 if (!doc
.cm
.curOp
) return operation(doc
.cm
, makeChange
)(doc
, change
, ignoreReadOnly
);
4262 if (doc
.cm
.state
.suppressEdits
) return;
4265 if (hasHandler(doc
, "beforeChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeChange")) {
4266 change
= filterChange(doc
, change
, true);
4267 if (!change
) return;
4270 // Possibly split or suppress the update based on the presence
4271 // of read-only spans in its range.
4272 var split
= sawReadOnlySpans
&& !ignoreReadOnly
&& removeReadOnlyRanges(doc
, change
.from, change
.to
);
4274 for (var i
= split
.length
- 1; i
>= 0; --i
)
4275 makeChangeInner(doc
, {from: split
[i
].from, to: split
[i
].to
, text: i
? [""] : change
.text
});
4277 makeChangeInner(doc
, change
);
4281 function makeChangeInner(doc
, change
) {
4282 if (change
.text
.length
== 1 && change
.text
[0] == "" && cmp(change
.from, change
.to
) == 0) return;
4283 var selAfter
= computeSelAfterChange(doc
, change
);
4284 addChangeToHistory(doc
, change
, selAfter
, doc
.cm
? doc
.cm
.curOp
.id : NaN
);
4286 makeChangeSingleDoc(doc
, change
, selAfter
, stretchSpansOverChange(doc
, change
));
4289 linkedDocs(doc
, function(doc
, sharedHist
) {
4290 if (!sharedHist
&& indexOf(rebased
, doc
.history
) == -1) {
4291 rebaseHist(doc
.history
, change
);
4292 rebased
.push(doc
.history
);
4294 makeChangeSingleDoc(doc
, change
, null, stretchSpansOverChange(doc
, change
));
4298 // Revert a change stored in a document's history.
4299 function makeChangeFromHistory(doc
, type
, allowSelectionOnly
) {
4300 if (doc
.cm
&& doc
.cm
.state
.suppressEdits
) return;
4302 var hist
= doc
.history
, event
, selAfter
= doc
.sel
;
4303 var source
= type
== "undo" ? hist
.done : hist
.undone
, dest
= type
== "undo" ? hist
.undone : hist
.done
;
4305 // Verify that there is a useable event (so that ctrl-z won't
4306 // needlessly clear selection events)
4307 for (var i
= 0; i
< source
.length
; i
++) {
4309 if (allowSelectionOnly
? event
.ranges
&& !event
.equals(doc
.sel
) : !event
.ranges
)
4312 if (i
== source
.length
) return;
4313 hist
.lastOrigin
= hist
.lastSelOrigin
= null;
4316 event
= source
.pop();
4318 pushSelectionToHistory(event
, dest
);
4319 if (allowSelectionOnly
&& !event
.equals(doc
.sel
)) {
4320 setSelection(doc
, event
, {clearRedo: false});
4328 // Build up a reverse change object to add to the opposite history
4329 // stack (redo when undoing, and vice versa).
4330 var antiChanges
= [];
4331 pushSelectionToHistory(selAfter
, dest
);
4332 dest
.push({changes: antiChanges
, generation: hist
.generation
});
4333 hist
.generation
= event
.generation
|| ++hist
.maxGeneration
;
4335 var filter
= hasHandler(doc
, "beforeChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeChange");
4337 for (var i
= event
.changes
.length
- 1; i
>= 0; --i
) {
4338 var change
= event
.changes
[i
];
4339 change
.origin
= type
;
4340 if (filter
&& !filterChange(doc
, change
, false)) {
4345 antiChanges
.push(historyChangeFromChange(doc
, change
));
4347 var after
= i
? computeSelAfterChange(doc
, change
) : lst(source
);
4348 makeChangeSingleDoc(doc
, change
, after
, mergeOldSpans(doc
, change
));
4349 if (!i
&& doc
.cm
) doc
.cm
.scrollIntoView({from: change
.from, to: changeEnd(change
)});
4352 // Propagate to the linked documents
4353 linkedDocs(doc
, function(doc
, sharedHist
) {
4354 if (!sharedHist
&& indexOf(rebased
, doc
.history
) == -1) {
4355 rebaseHist(doc
.history
, change
);
4356 rebased
.push(doc
.history
);
4358 makeChangeSingleDoc(doc
, change
, null, mergeOldSpans(doc
, change
));
4363 // Sub-views need their line numbers shifted when text is added
4364 // above or below them in the parent document.
4365 function shiftDoc(doc
, distance
) {
4366 if (distance
== 0) return;
4367 doc
.first
+= distance
;
4368 doc
.sel
= new Selection(map(doc
.sel
.ranges
, function(range
) {
4369 return new Range(Pos(range
.anchor
.line
+ distance
, range
.anchor
.ch
),
4370 Pos(range
.head
.line
+ distance
, range
.head
.ch
));
4371 }), doc
.sel
.primIndex
);
4373 regChange(doc
.cm
, doc
.first
, doc
.first
- distance
, distance
);
4374 for (var d
= doc
.cm
.display
, l
= d
.viewFrom
; l
< d
.viewTo
; l
++)
4375 regLineChange(doc
.cm
, l
, "gutter");
4379 // More lower-level change function, handling only a single document
4380 // (not linked ones).
4381 function makeChangeSingleDoc(doc
, change
, selAfter
, spans
) {
4382 if (doc
.cm
&& !doc
.cm
.curOp
)
4383 return operation(doc
.cm
, makeChangeSingleDoc
)(doc
, change
, selAfter
, spans
);
4385 if (change
.to
.line
< doc
.first
) {
4386 shiftDoc(doc
, change
.text
.length
- 1 - (change
.to
.line
- change
.from.line
));
4389 if (change
.from.line
> doc
.lastLine()) return;
4391 // Clip the change to the size of this doc
4392 if (change
.from.line
< doc
.first
) {
4393 var shift
= change
.text
.length
- 1 - (doc
.first
- change
.from.line
);
4394 shiftDoc(doc
, shift
);
4395 change
= {from: Pos(doc
.first
, 0), to: Pos(change
.to
.line
+ shift
, change
.to
.ch
),
4396 text: [lst(change
.text
)], origin: change
.origin
};
4398 var last
= doc
.lastLine();
4399 if (change
.to
.line
> last
) {
4400 change
= {from: change
.from, to: Pos(last
, getLine(doc
, last
).text
.length
),
4401 text: [change
.text
[0]], origin: change
.origin
};
4404 change
.removed
= getBetween(doc
, change
.from, change
.to
);
4406 if (!selAfter
) selAfter
= computeSelAfterChange(doc
, change
);
4407 if (doc
.cm
) makeChangeSingleDocInEditor(doc
.cm
, change
, spans
);
4408 else updateDoc(doc
, change
, spans
);
4409 setSelectionNoUndo(doc
, selAfter
, sel_dontScroll
);
4412 // Handle the interaction of a change to a document with the editor
4413 // that this document is part of.
4414 function makeChangeSingleDocInEditor(cm
, change
, spans
) {
4415 var doc
= cm
.doc
, display
= cm
.display
, from = change
.from, to
= change
.to
;
4417 var recomputeMaxLength
= false, checkWidthStart
= from.line
;
4418 if (!cm
.options
.lineWrapping
) {
4419 checkWidthStart
= lineNo(visualLine(getLine(doc
, from.line
)));
4420 doc
.iter(checkWidthStart
, to
.line
+ 1, function(line
) {
4421 if (line
== display
.maxLine
) {
4422 recomputeMaxLength
= true;
4428 if (doc
.sel
.contains(change
.from, change
.to
) > -1)
4429 signalCursorActivity(cm
);
4431 updateDoc(doc
, change
, spans
, estimateHeight(cm
));
4433 if (!cm
.options
.lineWrapping
) {
4434 doc
.iter(checkWidthStart
, from.line
+ change
.text
.length
, function(line
) {
4435 var len
= lineLength(line
);
4436 if (len
> display
.maxLineLength
) {
4437 display
.maxLine
= line
;
4438 display
.maxLineLength
= len
;
4439 display
.maxLineChanged
= true;
4440 recomputeMaxLength
= false;
4443 if (recomputeMaxLength
) cm
.curOp
.updateMaxLine
= true;
4446 // Adjust frontier, schedule worker
4447 doc
.frontier
= Math
.min(doc
.frontier
, from.line
);
4448 startWorker(cm
, 400);
4450 var lendiff
= change
.text
.length
- (to
.line
- from.line
) - 1;
4451 // Remember that these lines changed, for updating the display
4454 else if (from.line
== to
.line
&& change
.text
.length
== 1 && !isWholeLineUpdate(cm
.doc
, change
))
4455 regLineChange(cm
, from.line
, "text");
4457 regChange(cm
, from.line
, to
.line
+ 1, lendiff
);
4459 var changesHandler
= hasHandler(cm
, "changes"), changeHandler
= hasHandler(cm
, "change");
4460 if (changeHandler
|| changesHandler
) {
4464 removed: change
.removed
,
4465 origin: change
.origin
4467 if (changeHandler
) signalLater(cm
, "change", cm
, obj
);
4468 if (changesHandler
) (cm
.curOp
.changeObjs
|| (cm
.curOp
.changeObjs
= [])).push(obj
);
4470 cm
.display
.selForContextMenu
= null;
4473 function replaceRange(doc
, code
, from, to
, origin
) {
4475 if (cmp(to
, from) < 0) { var tmp
= to
; to
= from; from = tmp
; }
4476 if (typeof code
== "string") code
= splitLines(code
);
4477 makeChange(doc
, {from: from, to: to
, text: code
, origin: origin
});
4480 // SCROLLING THINGS INTO VIEW
4482 // If an editor sits on the top or bottom of the window, partially
4483 // scrolled out of view, this ensures that the cursor is visible.
4484 function maybeScrollWindow(cm
, coords
) {
4485 if (signalDOMEvent(cm
, "scrollCursorIntoView")) return;
4487 var display
= cm
.display
, box
= display
.sizer
.getBoundingClientRect(), doScroll
= null;
4488 if (coords
.top
+ box
.top
< 0) doScroll
= true;
4489 else if (coords
.bottom
+ box
.top
> (window
.innerHeight
|| document
.documentElement
.clientHeight
)) doScroll
= false;
4490 if (doScroll
!= null && !phantom
) {
4491 var scrollNode
= elt("div", "\u200b", null, "position: absolute; top: " +
4492 (coords
.top
- display
.viewOffset
- paddingTop(cm
.display
)) + "px; height: " +
4493 (coords
.bottom
- coords
.top
+ scrollGap(cm
) + display
.barHeight
) + "px; left: " +
4494 coords
.left
+ "px; width: 2px;");
4495 cm
.display
.lineSpace
.appendChild(scrollNode
);
4496 scrollNode
.scrollIntoView(doScroll
);
4497 cm
.display
.lineSpace
.removeChild(scrollNode
);
4501 // Scroll a given position into view (immediately), verifying that
4502 // it actually became visible (as line heights are accurately
4503 // measured, the position of something may 'drift' during drawing).
4504 function scrollPosIntoView(cm
, pos
, end
, margin
) {
4505 if (margin
== null) margin
= 0;
4506 for (var limit
= 0; limit
< 5; limit
++) {
4507 var changed
= false, coords
= cursorCoords(cm
, pos
);
4508 var endCoords
= !end
|| end
== pos
? coords : cursorCoords(cm
, end
);
4509 var scrollPos
= calculateScrollPos(cm
, Math
.min(coords
.left
, endCoords
.left
),
4510 Math
.min(coords
.top
, endCoords
.top
) - margin
,
4511 Math
.max(coords
.left
, endCoords
.left
),
4512 Math
.max(coords
.bottom
, endCoords
.bottom
) + margin
);
4513 var startTop
= cm
.doc
.scrollTop
, startLeft
= cm
.doc
.scrollLeft
;
4514 if (scrollPos
.scrollTop
!= null) {
4515 setScrollTop(cm
, scrollPos
.scrollTop
);
4516 if (Math
.abs(cm
.doc
.scrollTop
- startTop
) > 1) changed
= true;
4518 if (scrollPos
.scrollLeft
!= null) {
4519 setScrollLeft(cm
, scrollPos
.scrollLeft
);
4520 if (Math
.abs(cm
.doc
.scrollLeft
- startLeft
) > 1) changed
= true;
4522 if (!changed
) break;
4527 // Scroll a given set of coordinates into view (immediately).
4528 function scrollIntoView(cm
, x1
, y1
, x2
, y2
) {
4529 var scrollPos
= calculateScrollPos(cm
, x1
, y1
, x2
, y2
);
4530 if (scrollPos
.scrollTop
!= null) setScrollTop(cm
, scrollPos
.scrollTop
);
4531 if (scrollPos
.scrollLeft
!= null) setScrollLeft(cm
, scrollPos
.scrollLeft
);
4534 // Calculate a new scroll position needed to scroll the given
4535 // rectangle into view. Returns an object with scrollTop and
4536 // scrollLeft properties. When these are undefined, the
4537 // vertical/horizontal position does not need to be adjusted.
4538 function calculateScrollPos(cm
, x1
, y1
, x2
, y2
) {
4539 var display
= cm
.display
, snapMargin
= textHeight(cm
.display
);
4541 var screentop
= cm
.curOp
&& cm
.curOp
.scrollTop
!= null ? cm
.curOp
.scrollTop : display
.scroller
.scrollTop
;
4542 var screen
= displayHeight(cm
), result
= {};
4543 if (y2
- y1
> screen
) y2
= y1
+ screen
;
4544 var docBottom
= cm
.doc
.height
+ paddingVert(display
);
4545 var atTop
= y1
< snapMargin
, atBottom
= y2
> docBottom
- snapMargin
;
4546 if (y1
< screentop
) {
4547 result
.scrollTop
= atTop
? 0 : y1
;
4548 } else if (y2
> screentop
+ screen
) {
4549 var newTop
= Math
.min(y1
, (atBottom
? docBottom : y2
) - screen
);
4550 if (newTop
!= screentop
) result
.scrollTop
= newTop
;
4553 var screenleft
= cm
.curOp
&& cm
.curOp
.scrollLeft
!= null ? cm
.curOp
.scrollLeft : display
.scroller
.scrollLeft
;
4554 var screenw
= displayWidth(cm
) - (cm
.options
.fixedGutter
? display
.gutters
.offsetWidth : 0);
4555 var tooWide
= x2
- x1
> screenw
;
4556 if (tooWide
) x2
= x1
+ screenw
;
4558 result
.scrollLeft
= 0;
4559 else if (x1
< screenleft
)
4560 result
.scrollLeft
= Math
.max(0, x1
- (tooWide
? 0 : 10));
4561 else if (x2
> screenw
+ screenleft
- 3)
4562 result
.scrollLeft
= x2
+ (tooWide
? 0 : 10) - screenw
;
4566 // Store a relative adjustment to the scroll position in the current
4567 // operation (to be applied when the operation finishes).
4568 function addToScrollPos(cm
, left
, top
) {
4569 if (left
!= null || top
!= null) resolveScrollToPos(cm
);
4571 cm
.curOp
.scrollLeft
= (cm
.curOp
.scrollLeft
== null ? cm
.doc
.scrollLeft : cm
.curOp
.scrollLeft
) + left
;
4573 cm
.curOp
.scrollTop
= (cm
.curOp
.scrollTop
== null ? cm
.doc
.scrollTop : cm
.curOp
.scrollTop
) + top
;
4576 // Make sure that at the end of the operation the current cursor is
4578 function ensureCursorVisible(cm
) {
4579 resolveScrollToPos(cm
);
4580 var cur
= cm
.getCursor(), from = cur
, to
= cur
;
4581 if (!cm
.options
.lineWrapping
) {
4582 from = cur
.ch
? Pos(cur
.line
, cur
.ch
- 1) : cur
;
4583 to
= Pos(cur
.line
, cur
.ch
+ 1);
4585 cm
.curOp
.scrollToPos
= {from: from, to: to
, margin: cm
.options
.cursorScrollMargin
, isCursor: true};
4588 // When an operation has its scrollToPos property set, and another
4589 // scroll action is applied before the end of the operation, this
4590 // 'simulates' scrolling that position into view in a cheap way, so
4591 // that the effect of intermediate scroll commands is not ignored.
4592 function resolveScrollToPos(cm
) {
4593 var range
= cm
.curOp
.scrollToPos
;
4595 cm
.curOp
.scrollToPos
= null;
4596 var from = estimateCoords(cm
, range
.from), to
= estimateCoords(cm
, range
.to
);
4597 var sPos
= calculateScrollPos(cm
, Math
.min(from.left
, to
.left
),
4598 Math
.min(from.top
, to
.top
) - range
.margin
,
4599 Math
.max(from.right
, to
.right
),
4600 Math
.max(from.bottom
, to
.bottom
) + range
.margin
);
4601 cm
.scrollTo(sPos
.scrollLeft
, sPos
.scrollTop
);
4607 // Indent the given line. The how parameter can be "smart",
4608 // "add"/null, "subtract", or "prev". When aggressive is false
4609 // (typically set to true for forced single-line indents), empty
4610 // lines are not indented, and places where the mode returns Pass
4612 function indentLine(cm
, n
, how
, aggressive
) {
4613 var doc
= cm
.doc
, state
;
4614 if (how
== null) how
= "add";
4615 if (how
== "smart") {
4616 // Fall back to "prev" when the mode doesn't have an indentation
4618 if (!doc
.mode
.indent
) how
= "prev";
4619 else state
= getStateBefore(cm
, n
);
4622 var tabSize
= cm
.options
.tabSize
;
4623 var line
= getLine(doc
, n
), curSpace
= countColumn(line
.text
, null, tabSize
);
4624 if (line
.stateAfter
) line
.stateAfter
= null;
4625 var curSpaceString
= line
.text
.match(/^\s*/)[0], indentation
;
4626 if (!aggressive
&& !/\S/.test(line
.text
)) {
4629 } else if (how
== "smart") {
4630 indentation
= doc
.mode
.indent(state
, line
.text
.slice(curSpaceString
.length
), line
.text
);
4631 if (indentation
== Pass
|| indentation
> 150) {
4632 if (!aggressive
) return;
4636 if (how
== "prev") {
4637 if (n
> doc
.first
) indentation
= countColumn(getLine(doc
, n
-1).text
, null, tabSize
);
4638 else indentation
= 0;
4639 } else if (how
== "add") {
4640 indentation
= curSpace
+ cm
.options
.indentUnit
;
4641 } else if (how
== "subtract") {
4642 indentation
= curSpace
- cm
.options
.indentUnit
;
4643 } else if (typeof how
== "number") {
4644 indentation
= curSpace
+ how
;
4646 indentation
= Math
.max(0, indentation
);
4648 var indentString
= "", pos
= 0;
4649 if (cm
.options
.indentWithTabs
)
4650 for (var i
= Math
.floor(indentation
/ tabSize
); i
; --i
) {pos
+= tabSize
; indentString
+= "\t";}
4651 if (pos
< indentation
) indentString
+= spaceStr(indentation
- pos
);
4653 if (indentString
!= curSpaceString
) {
4654 replaceRange(doc
, indentString
, Pos(n
, 0), Pos(n
, curSpaceString
.length
), "+input");
4655 line
.stateAfter
= null;
4658 // Ensure that, if the cursor was in the whitespace at the start
4659 // of the line, it is moved to the end of that space.
4660 for (var i
= 0; i
< doc
.sel
.ranges
.length
; i
++) {
4661 var range
= doc
.sel
.ranges
[i
];
4662 if (range
.head
.line
== n
&& range
.head
.ch
< curSpaceString
.length
) {
4663 var pos
= Pos(n
, curSpaceString
.length
);
4664 replaceOneSelection(doc
, i
, new Range(pos
, pos
));
4671 // Utility for applying a change to a line by handle or number,
4672 // returning the number and optionally registering the line as
4674 function changeLine(doc
, handle
, changeType
, op
) {
4675 var no
= handle
, line
= handle
;
4676 if (typeof handle
== "number") line
= getLine(doc
, clipLine(doc
, handle
));
4677 else no
= lineNo(handle
);
4678 if (no
== null) return null;
4679 if (op(line
, no
) && doc
.cm
) regLineChange(doc
.cm
, no
, changeType
);
4683 // Helper for deleting text near the selection(s), used to implement
4684 // backspace, delete, and similar functionality.
4685 function deleteNearSelection(cm
, compute
) {
4686 var ranges
= cm
.doc
.sel
.ranges
, kill
= [];
4687 // Build up a set of ranges to kill first, merging overlapping
4689 for (var i
= 0; i
< ranges
.length
; i
++) {
4690 var toKill
= compute(ranges
[i
]);
4691 while (kill
.length
&& cmp(toKill
.from, lst(kill
).to
) <= 0) {
4692 var replaced
= kill
.pop();
4693 if (cmp(replaced
.from, toKill
.from) < 0) {
4694 toKill
.from = replaced
.from;
4700 // Next, remove those actual ranges.
4701 runInOp(cm
, function() {
4702 for (var i
= kill
.length
- 1; i
>= 0; i
--)
4703 replaceRange(cm
.doc
, "", kill
[i
].from, kill
[i
].to
, "+delete");
4704 ensureCursorVisible(cm
);
4708 // Used for horizontal relative motion. Dir is -1 or 1 (left or
4709 // right), unit can be "char", "column" (like char, but doesn't
4710 // cross line boundaries), "word" (across next word), or "group" (to
4711 // the start of next group of word or non-word-non-whitespace
4712 // chars). The visually param controls whether, in right-to-left
4713 // text, direction 1 means to move towards the next index in the
4714 // string, or towards the character to the right of the current
4715 // position. The resulting position will have a hitSide=true
4716 // property if it reached the end of the document.
4717 function findPosH(doc
, pos
, dir
, unit
, visually
) {
4718 var line
= pos
.line
, ch
= pos
.ch
, origDir
= dir
;
4719 var lineObj
= getLine(doc
, line
);
4720 var possible
= true;
4721 function findNextLine() {
4723 if (l
< doc
.first
|| l
>= doc
.first
+ doc
.size
) return (possible
= false);
4725 return lineObj
= getLine(doc
, l
);
4727 function moveOnce(boundToLine
) {
4728 var next
= (visually
? moveVisually : moveLogically
)(lineObj
, ch
, dir
, true);
4730 if (!boundToLine
&& findNextLine()) {
4731 if (visually
) ch
= (dir
< 0 ? lineRight : lineLeft
)(lineObj
);
4732 else ch
= dir
< 0 ? lineObj
.text
.length : 0;
4733 } else return (possible
= false);
4738 if (unit
== "char") moveOnce();
4739 else if (unit
== "column") moveOnce(true);
4740 else if (unit
== "word" || unit
== "group") {
4741 var sawType
= null, group
= unit
== "group";
4742 var helper
= doc
.cm
&& doc
.cm
.getHelper(pos
, "wordChars");
4743 for (var first
= true;; first
= false) {
4744 if (dir
< 0 && !moveOnce(!first
)) break;
4745 var cur
= lineObj
.text
.charAt(ch
) || "\n";
4746 var type
= isWordChar(cur
, helper
) ? "w"
4747 : group
&& cur
== "\n" ? "n"
4748 : !group
|| /\s/.test(cur
) ? null
4750 if (group
&& !first
&& !type
) type
= "s";
4751 if (sawType
&& sawType
!= type
) {
4752 if (dir
< 0) {dir
= 1; moveOnce();}
4756 if (type
) sawType
= type
;
4757 if (dir
> 0 && !moveOnce(!first
)) break;
4760 var result
= skipAtomic(doc
, Pos(line
, ch
), origDir
, true);
4761 if (!possible
) result
.hitSide
= true;
4765 // For relative vertical movement. Dir may be -1 or 1. Unit can be
4766 // "page" or "line". The resulting position will have a hitSide=true
4767 // property if it reached the end of the document.
4768 function findPosV(cm
, pos
, dir
, unit
) {
4769 var doc
= cm
.doc
, x
= pos
.left
, y
;
4770 if (unit
== "page") {
4771 var pageSize
= Math
.min(cm
.display
.wrapper
.clientHeight
, window
.innerHeight
|| document
.documentElement
.clientHeight
);
4772 y
= pos
.top
+ dir
* (pageSize
- (dir
< 0 ? 1.5 : .5) * textHeight(cm
.display
));
4773 } else if (unit
== "line") {
4774 y
= dir
> 0 ? pos
.bottom
+ 3 : pos
.top
- 3;
4777 var target
= coordsChar(cm
, x
, y
);
4778 if (!target
.outside
) break;
4779 if (dir
< 0 ? y
<= 0 : y
>= doc
.height
) { target
.hitSide
= true; break; }
4787 // The publicly visible API. Note that methodOp(f) means
4788 // 'wrap f in an operation, performed on its `this` parameter'.
4790 // This is not the complete set of editor methods. Most of the
4791 // methods defined on the Doc type are also injected into
4792 // CodeMirror.prototype, for backwards compatibility and
4795 CodeMirror
.prototype = {
4796 constructor: CodeMirror
,
4797 focus: function(){window
.focus(); this.display
.input
.focus();},
4799 setOption: function(option
, value
) {
4800 var options
= this.options
, old
= options
[option
];
4801 if (options
[option
] == value
&& option
!= "mode") return;
4802 options
[option
] = value
;
4803 if (optionHandlers
.hasOwnProperty(option
))
4804 operation(this, optionHandlers
[option
])(this, value
, old
);
4807 getOption: function(option
) {return this.options
[option
];},
4808 getDoc: function() {return this.doc
;},
4810 addKeyMap: function(map
, bottom
) {
4811 this.state
.keyMaps
[bottom
? "push" : "unshift"](getKeyMap(map
));
4813 removeKeyMap: function(map
) {
4814 var maps
= this.state
.keyMaps
;
4815 for (var i
= 0; i
< maps
.length
; ++i
)
4816 if (maps
[i
] == map
|| maps
[i
].name
== map
) {
4822 addOverlay: methodOp(function(spec
, options
) {
4823 var mode
= spec
.token
? spec : CodeMirror
.getMode(this.options
, spec
);
4824 if (mode
.startState
) throw new Error("Overlays may not be stateful.");
4825 this.state
.overlays
.push({mode: mode
, modeSpec: spec
, opaque: options
&& options
.opaque
});
4826 this.state
.modeGen
++;
4829 removeOverlay: methodOp(function(spec
) {
4830 var overlays
= this.state
.overlays
;
4831 for (var i
= 0; i
< overlays
.length
; ++i
) {
4832 var cur
= overlays
[i
].modeSpec
;
4833 if (cur
== spec
|| typeof spec
== "string" && cur
.name
== spec
) {
4834 overlays
.splice(i
, 1);
4835 this.state
.modeGen
++;
4842 indentLine: methodOp(function(n
, dir
, aggressive
) {
4843 if (typeof dir
!= "string" && typeof dir
!= "number") {
4844 if (dir
== null) dir
= this.options
.smartIndent
? "smart" : "prev";
4845 else dir
= dir
? "add" : "subtract";
4847 if (isLine(this.doc
, n
)) indentLine(this, n
, dir
, aggressive
);
4849 indentSelection: methodOp(function(how
) {
4850 var ranges
= this.doc
.sel
.ranges
, end
= -1;
4851 for (var i
= 0; i
< ranges
.length
; i
++) {
4852 var range
= ranges
[i
];
4853 if (!range
.empty()) {
4854 var from = range
.from(), to
= range
.to();
4855 var start
= Math
.max(end
, from.line
);
4856 end
= Math
.min(this.lastLine(), to
.line
- (to
.ch
? 0 : 1)) + 1;
4857 for (var j
= start
; j
< end
; ++j
)
4858 indentLine(this, j
, how
);
4859 var newRanges
= this.doc
.sel
.ranges
;
4860 if (from.ch
== 0 && ranges
.length
== newRanges
.length
&& newRanges
[i
].from().ch
> 0)
4861 replaceOneSelection(this.doc
, i
, new Range(from, newRanges
[i
].to()), sel_dontScroll
);
4862 } else if (range
.head
.line
> end
) {
4863 indentLine(this, range
.head
.line
, how
, true);
4864 end
= range
.head
.line
;
4865 if (i
== this.doc
.sel
.primIndex
) ensureCursorVisible(this);
4870 // Fetch the parser token for a given character. Useful for hacks
4871 // that want to inspect the mode state (say, for completion).
4872 getTokenAt: function(pos
, precise
) {
4873 return takeToken(this, pos
, precise
);
4876 getLineTokens: function(line
, precise
) {
4877 return takeToken(this, Pos(line
), precise
, true);
4880 getTokenTypeAt: function(pos
) {
4881 pos
= clipPos(this.doc
, pos
);
4882 var styles
= getLineStyles(this, getLine(this.doc
, pos
.line
));
4883 var before
= 0, after
= (styles
.length
- 1) / 2, ch
= pos
.ch
;
4885 if (ch
== 0) type
= styles
[2];
4887 var mid
= (before
+ after
) >> 1;
4888 if ((mid
? styles
[mid
* 2 - 1] : 0) >= ch
) after
= mid
;
4889 else if (styles
[mid
* 2 + 1] < ch
) before
= mid
+ 1;
4890 else { type
= styles
[mid
* 2 + 2]; break; }
4892 var cut
= type
? type
.indexOf("cm-overlay ") : -1;
4893 return cut
< 0 ? type : cut
== 0 ? null : type
.slice(0, cut
- 1);
4896 getModeAt: function(pos
) {
4897 var mode
= this.doc
.mode
;
4898 if (!mode
.innerMode
) return mode
;
4899 return CodeMirror
.innerMode(mode
, this.getTokenAt(pos
).state
).mode
;
4902 getHelper: function(pos
, type
) {
4903 return this.getHelpers(pos
, type
)[0];
4906 getHelpers: function(pos
, type
) {
4908 if (!helpers
.hasOwnProperty(type
)) return found
;
4909 var help
= helpers
[type
], mode
= this.getModeAt(pos
);
4910 if (typeof mode
[type
] == "string") {
4911 if (help
[mode
[type
]]) found
.push(help
[mode
[type
]]);
4912 } else if (mode
[type
]) {
4913 for (var i
= 0; i
< mode
[type
].length
; i
++) {
4914 var val
= help
[mode
[type
][i
]];
4915 if (val
) found
.push(val
);
4917 } else if (mode
.helperType
&& help
[mode
.helperType
]) {
4918 found
.push(help
[mode
.helperType
]);
4919 } else if (help
[mode
.name
]) {
4920 found
.push(help
[mode
.name
]);
4922 for (var i
= 0; i
< help
._global
.length
; i
++) {
4923 var cur
= help
._global
[i
];
4924 if (cur
.pred(mode
, this) && indexOf(found
, cur
.val
) == -1)
4925 found
.push(cur
.val
);
4930 getStateAfter: function(line
, precise
) {
4932 line
= clipLine(doc
, line
== null ? doc
.first
+ doc
.size
- 1: line
);
4933 return getStateBefore(this, line
+ 1, precise
);
4936 cursorCoords: function(start
, mode
) {
4937 var pos
, range
= this.doc
.sel
.primary();
4938 if (start
== null) pos
= range
.head
;
4939 else if (typeof start
== "object") pos
= clipPos(this.doc
, start
);
4940 else pos
= start
? range
.from() : range
.to();
4941 return cursorCoords(this, pos
, mode
|| "page");
4944 charCoords: function(pos
, mode
) {
4945 return charCoords(this, clipPos(this.doc
, pos
), mode
|| "page");
4948 coordsChar: function(coords
, mode
) {
4949 coords
= fromCoordSystem(this, coords
, mode
|| "page");
4950 return coordsChar(this, coords
.left
, coords
.top
);
4953 lineAtHeight: function(height
, mode
) {
4954 height
= fromCoordSystem(this, {top: height
, left: 0}, mode
|| "page").top
;
4955 return lineAtHeight(this.doc
, height
+ this.display
.viewOffset
);
4957 heightAtLine: function(line
, mode
) {
4958 var end
= false, lineObj
;
4959 if (typeof line
== "number") {
4960 var last
= this.doc
.first
+ this.doc
.size
- 1;
4961 if (line
< this.doc
.first
) line
= this.doc
.first
;
4962 else if (line
> last
) { line
= last
; end
= true; }
4963 lineObj
= getLine(this.doc
, line
);
4967 return intoCoordSystem(this, lineObj
, {top: 0, left: 0}, mode
|| "page").top
+
4968 (end
? this.doc
.height
- heightAtLine(lineObj
) : 0);
4971 defaultTextHeight: function() { return textHeight(this.display
); },
4972 defaultCharWidth: function() { return charWidth(this.display
); },
4974 setGutterMarker: methodOp(function(line
, gutterID
, value
) {
4975 return changeLine(this.doc
, line
, "gutter", function(line
) {
4976 var markers
= line
.gutterMarkers
|| (line
.gutterMarkers
= {});
4977 markers
[gutterID
] = value
;
4978 if (!value
&& isEmpty(markers
)) line
.gutterMarkers
= null;
4983 clearGutter: methodOp(function(gutterID
) {
4984 var cm
= this, doc
= cm
.doc
, i
= doc
.first
;
4985 doc
.iter(function(line
) {
4986 if (line
.gutterMarkers
&& line
.gutterMarkers
[gutterID
]) {
4987 line
.gutterMarkers
[gutterID
] = null;
4988 regLineChange(cm
, i
, "gutter");
4989 if (isEmpty(line
.gutterMarkers
)) line
.gutterMarkers
= null;
4995 lineInfo: function(line
) {
4996 if (typeof line
== "number") {
4997 if (!isLine(this.doc
, line
)) return null;
4999 line
= getLine(this.doc
, line
);
5000 if (!line
) return null;
5002 var n
= lineNo(line
);
5003 if (n
== null) return null;
5005 return {line: n
, handle: line
, text: line
.text
, gutterMarkers: line
.gutterMarkers
,
5006 textClass: line
.textClass
, bgClass: line
.bgClass
, wrapClass: line
.wrapClass
,
5007 widgets: line
.widgets
};
5010 getViewport: function() { return {from: this.display
.viewFrom
, to: this.display
.viewTo
};},
5012 addWidget: function(pos
, node
, scroll
, vert
, horiz
) {
5013 var display
= this.display
;
5014 pos
= cursorCoords(this, clipPos(this.doc
, pos
));
5015 var top
= pos
.bottom
, left
= pos
.left
;
5016 node
.style
.position
= "absolute";
5017 node
.setAttribute("cm-ignore-events", "true");
5018 this.display
.input
.setUneditable(node
);
5019 display
.sizer
.appendChild(node
);
5020 if (vert
== "over") {
5022 } else if (vert
== "above" || vert
== "near") {
5023 var vspace
= Math
.max(display
.wrapper
.clientHeight
, this.doc
.height
),
5024 hspace
= Math
.max(display
.sizer
.clientWidth
, display
.lineSpace
.clientWidth
);
5025 // Default to positioning above (if specified and possible); otherwise default to positioning below
5026 if ((vert
== 'above' || pos
.bottom
+ node
.offsetHeight
> vspace
) && pos
.top
> node
.offsetHeight
)
5027 top
= pos
.top
- node
.offsetHeight
;
5028 else if (pos
.bottom
+ node
.offsetHeight
<= vspace
)
5030 if (left
+ node
.offsetWidth
> hspace
)
5031 left
= hspace
- node
.offsetWidth
;
5033 node
.style
.top
= top
+ "px";
5034 node
.style
.left
= node
.style
.right
= "";
5035 if (horiz
== "right") {
5036 left
= display
.sizer
.clientWidth
- node
.offsetWidth
;
5037 node
.style
.right
= "0px";
5039 if (horiz
== "left") left
= 0;
5040 else if (horiz
== "middle") left
= (display
.sizer
.clientWidth
- node
.offsetWidth
) / 2;
5041 node
.style
.left
= left
+ "px";
5044 scrollIntoView(this, left
, top
, left
+ node
.offsetWidth
, top
+ node
.offsetHeight
);
5047 triggerOnKeyDown: methodOp(onKeyDown
),
5048 triggerOnKeyPress: methodOp(onKeyPress
),
5049 triggerOnKeyUp: onKeyUp
,
5051 execCommand: function(cmd
) {
5052 if (commands
.hasOwnProperty(cmd
))
5053 return commands
[cmd
](this);
5056 findPosH: function(from, amount
, unit
, visually
) {
5058 if (amount
< 0) { dir
= -1; amount
= -amount
; }
5059 for (var i
= 0, cur
= clipPos(this.doc
, from); i
< amount
; ++i
) {
5060 cur
= findPosH(this.doc
, cur
, dir
, unit
, visually
);
5061 if (cur
.hitSide
) break;
5066 moveH: methodOp(function(dir
, unit
) {
5068 cm
.extendSelectionsBy(function(range
) {
5069 if (cm
.display
.shift
|| cm
.doc
.extend
|| range
.empty())
5070 return findPosH(cm
.doc
, range
.head
, dir
, unit
, cm
.options
.rtlMoveVisually
);
5072 return dir
< 0 ? range
.from() : range
.to();
5076 deleteH: methodOp(function(dir
, unit
) {
5077 var sel
= this.doc
.sel
, doc
= this.doc
;
5078 if (sel
.somethingSelected())
5079 doc
.replaceSelection("", null, "+delete");
5081 deleteNearSelection(this, function(range
) {
5082 var other
= findPosH(doc
, range
.head
, dir
, unit
, false);
5083 return dir
< 0 ? {from: other
, to: range
.head
} : {from: range
.head
, to: other
};
5087 findPosV: function(from, amount
, unit
, goalColumn
) {
5088 var dir
= 1, x
= goalColumn
;
5089 if (amount
< 0) { dir
= -1; amount
= -amount
; }
5090 for (var i
= 0, cur
= clipPos(this.doc
, from); i
< amount
; ++i
) {
5091 var coords
= cursorCoords(this, cur
, "div");
5092 if (x
== null) x
= coords
.left
;
5093 else coords
.left
= x
;
5094 cur
= findPosV(this, coords
, dir
, unit
);
5095 if (cur
.hitSide
) break;
5100 moveV: methodOp(function(dir
, unit
) {
5101 var cm
= this, doc
= this.doc
, goals
= [];
5102 var collapse
= !cm
.display
.shift
&& !doc
.extend
&& doc
.sel
.somethingSelected();
5103 doc
.extendSelectionsBy(function(range
) {
5105 return dir
< 0 ? range
.from() : range
.to();
5106 var headPos
= cursorCoords(cm
, range
.head
, "div");
5107 if (range
.goalColumn
!= null) headPos
.left
= range
.goalColumn
;
5108 goals
.push(headPos
.left
);
5109 var pos
= findPosV(cm
, headPos
, dir
, unit
);
5110 if (unit
== "page" && range
== doc
.sel
.primary())
5111 addToScrollPos(cm
, null, charCoords(cm
, pos
, "div").top
- headPos
.top
);
5114 if (goals
.length
) for (var i
= 0; i
< doc
.sel
.ranges
.length
; i
++)
5115 doc
.sel
.ranges
[i
].goalColumn
= goals
[i
];
5118 // Find the word at the given position (as returned by coordsChar).
5119 findWordAt: function(pos
) {
5120 var doc
= this.doc
, line
= getLine(doc
, pos
.line
).text
;
5121 var start
= pos
.ch
, end
= pos
.ch
;
5123 var helper
= this.getHelper(pos
, "wordChars");
5124 if ((pos
.xRel
< 0 || end
== line
.length
) && start
) --start
; else ++end
;
5125 var startChar
= line
.charAt(start
);
5126 var check
= isWordChar(startChar
, helper
)
5127 ? function(ch
) { return isWordChar(ch
, helper
); }
5128 : /\s/.test(startChar
) ? function(ch
) {return /\s/.test(ch
);}
5129 : function(ch
) {return !/\s/.test(ch
) && !isWordChar(ch
);};
5130 while (start
> 0 && check(line
.charAt(start
- 1))) --start
;
5131 while (end
< line
.length
&& check(line
.charAt(end
))) ++end
;
5133 return new Range(Pos(pos
.line
, start
), Pos(pos
.line
, end
));
5136 toggleOverwrite: function(value
) {
5137 if (value
!= null && value
== this.state
.overwrite
) return;
5138 if (this.state
.overwrite
= !this.state
.overwrite
)
5139 addClass(this.display
.cursorDiv
, "CodeMirror-overwrite");
5141 rmClass(this.display
.cursorDiv
, "CodeMirror-overwrite");
5143 signal(this, "overwriteToggle", this, this.state
.overwrite
);
5145 hasFocus: function() { return this.display
.input
.getField() == activeElt(); },
5147 scrollTo: methodOp(function(x
, y
) {
5148 if (x
!= null || y
!= null) resolveScrollToPos(this);
5149 if (x
!= null) this.curOp
.scrollLeft
= x
;
5150 if (y
!= null) this.curOp
.scrollTop
= y
;
5152 getScrollInfo: function() {
5153 var scroller
= this.display
.scroller
;
5154 return {left: scroller
.scrollLeft
, top: scroller
.scrollTop
,
5155 height: scroller
.scrollHeight
- scrollGap(this) - this.display
.barHeight
,
5156 width: scroller
.scrollWidth
- scrollGap(this) - this.display
.barWidth
,
5157 clientHeight: displayHeight(this), clientWidth: displayWidth(this)};
5160 scrollIntoView: methodOp(function(range
, margin
) {
5161 if (range
== null) {
5162 range
= {from: this.doc
.sel
.primary().head
, to: null};
5163 if (margin
== null) margin
= this.options
.cursorScrollMargin
;
5164 } else if (typeof range
== "number") {
5165 range
= {from: Pos(range
, 0), to: null};
5166 } else if (range
.from == null) {
5167 range
= {from: range
, to: null};
5169 if (!range
.to
) range
.to
= range
.from;
5170 range
.margin
= margin
|| 0;
5172 if (range
.from.line
!= null) {
5173 resolveScrollToPos(this);
5174 this.curOp
.scrollToPos
= range
;
5176 var sPos
= calculateScrollPos(this, Math
.min(range
.from.left
, range
.to
.left
),
5177 Math
.min(range
.from.top
, range
.to
.top
) - range
.margin
,
5178 Math
.max(range
.from.right
, range
.to
.right
),
5179 Math
.max(range
.from.bottom
, range
.to
.bottom
) + range
.margin
);
5180 this.scrollTo(sPos
.scrollLeft
, sPos
.scrollTop
);
5184 setSize: methodOp(function(width
, height
) {
5186 function interpret(val
) {
5187 return typeof val
== "number" || /^\d+$/.test(String(val
)) ? val
+ "px" : val
;
5189 if (width
!= null) cm
.display
.wrapper
.style
.width
= interpret(width
);
5190 if (height
!= null) cm
.display
.wrapper
.style
.height
= interpret(height
);
5191 if (cm
.options
.lineWrapping
) clearLineMeasurementCache(this);
5192 var lineNo
= cm
.display
.viewFrom
;
5193 cm
.doc
.iter(lineNo
, cm
.display
.viewTo
, function(line
) {
5194 if (line
.widgets
) for (var i
= 0; i
< line
.widgets
.length
; i
++)
5195 if (line
.widgets
[i
].noHScroll
) { regLineChange(cm
, lineNo
, "widget"); break; }
5198 cm
.curOp
.forceUpdate
= true;
5199 signal(cm
, "refresh", this);
5202 operation: function(f
){return runInOp(this, f
);},
5204 refresh: methodOp(function() {
5205 var oldHeight
= this.display
.cachedTextHeight
;
5207 this.curOp
.forceUpdate
= true;
5209 this.scrollTo(this.doc
.scrollLeft
, this.doc
.scrollTop
);
5210 updateGutterSpace(this);
5211 if (oldHeight
== null || Math
.abs(oldHeight
- textHeight(this.display
)) > .5)
5212 estimateLineHeights(this);
5213 signal(this, "refresh", this);
5216 swapDoc: methodOp(function(doc
) {
5219 attachDoc(this, doc
);
5221 this.display
.input
.reset();
5222 this.scrollTo(doc
.scrollLeft
, doc
.scrollTop
);
5223 this.curOp
.forceScroll
= true;
5224 signalLater(this, "swapDoc", this, old
);
5228 getInputField: function(){return this.display
.input
.getField();},
5229 getWrapperElement: function(){return this.display
.wrapper
;},
5230 getScrollerElement: function(){return this.display
.scroller
;},
5231 getGutterElement: function(){return this.display
.gutters
;}
5233 eventMixin(CodeMirror
);
5237 // The default configuration options.
5238 var defaults
= CodeMirror
.defaults
= {};
5239 // Functions to run when options are changed.
5240 var optionHandlers
= CodeMirror
.optionHandlers
= {};
5242 function option(name
, deflt
, handle
, notOnInit
) {
5243 CodeMirror
.defaults
[name
] = deflt
;
5244 if (handle
) optionHandlers
[name
] =
5245 notOnInit
? function(cm
, val
, old
) {if (old
!= Init
) handle(cm
, val
, old
);} : handle
;
5248 // Passed to option handlers when there is no old value.
5249 var Init
= CodeMirror
.Init
= {toString: function(){return "CodeMirror.Init";}};
5251 // These two are, on init, called from the constructor because they
5252 // have to be initialized before the editor can start at all.
5253 option("value", "", function(cm
, val
) {
5256 option("mode", null, function(cm
, val
) {
5257 cm
.doc
.modeOption
= val
;
5261 option("indentUnit", 2, loadMode
, true);
5262 option("indentWithTabs", false);
5263 option("smartIndent", true);
5264 option("tabSize", 4, function(cm
) {
5269 option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm
, val
, old
) {
5270 cm
.state
.specialChars
= new RegExp(val
.source
+ (val
.test("\t") ? "" : "|\t"), "g");
5271 if (old
!= CodeMirror
.Init
) cm
.refresh();
5273 option("specialCharPlaceholder", defaultSpecialCharPlaceholder
, function(cm
) {cm
.refresh();}, true);
5274 option("electricChars", true);
5275 option("inputStyle", mobile
? "contenteditable" : "textarea", function() {
5276 throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME
5278 option("rtlMoveVisually", !windows
);
5279 option("wholeLineUpdateBefore", true);
5281 option("theme", "default", function(cm
) {
5285 option("keyMap", "default", function(cm
, val
, old
) {
5286 var next
= getKeyMap(val
);
5287 var prev
= old
!= CodeMirror
.Init
&& getKeyMap(old
);
5288 if (prev
&& prev
.detach
) prev
.detach(cm
, next
);
5289 if (next
.attach
) next
.attach(cm
, prev
|| null);
5291 option("extraKeys", null);
5293 option("lineWrapping", false, wrappingChanged
, true);
5294 option("gutters", [], function(cm
) {
5295 setGuttersForLineNumbers(cm
.options
);
5298 option("fixedGutter", true, function(cm
, val
) {
5299 cm
.display
.gutters
.style
.left
= val
? compensateForHScroll(cm
.display
) + "px" : "0";
5302 option("coverGutterNextToScrollbar", false, function(cm
) {updateScrollbars(cm
);}, true);
5303 option("scrollbarStyle", "native", function(cm
) {
5305 updateScrollbars(cm
);
5306 cm
.display
.scrollbars
.setScrollTop(cm
.doc
.scrollTop
);
5307 cm
.display
.scrollbars
.setScrollLeft(cm
.doc
.scrollLeft
);
5309 option("lineNumbers", false, function(cm
) {
5310 setGuttersForLineNumbers(cm
.options
);
5313 option("firstLineNumber", 1, guttersChanged
, true);
5314 option("lineNumberFormatter", function(integer
) {return integer
;}, guttersChanged
, true);
5315 option("showCursorWhenSelecting", false, updateSelection
, true);
5317 option("resetSelectionOnContextMenu", true);
5318 option("lineWiseCopyCut", true);
5320 option("readOnly", false, function(cm
, val
) {
5321 if (val
== "nocursor") {
5323 cm
.display
.input
.blur();
5324 cm
.display
.disabled
= true;
5326 cm
.display
.disabled
= false;
5327 if (!val
) cm
.display
.input
.reset();
5330 option("disableInput", false, function(cm
, val
) {if (!val
) cm
.display
.input
.reset();}, true);
5331 option("dragDrop", true, dragDropChanged
);
5333 option("cursorBlinkRate", 530);
5334 option("cursorScrollMargin", 0);
5335 option("cursorHeight", 1, updateSelection
, true);
5336 option("singleCursorHeightPerLine", true, updateSelection
, true);
5337 option("workTime", 100);
5338 option("workDelay", 100);
5339 option("flattenSpans", true, resetModeState
, true);
5340 option("addModeClass", false, resetModeState
, true);
5341 option("pollInterval", 100);
5342 option("undoDepth", 200, function(cm
, val
){cm
.doc
.history
.undoDepth
= val
;});
5343 option("historyEventDelay", 1250);
5344 option("viewportMargin", 10, function(cm
){cm
.refresh();}, true);
5345 option("maxHighlightLength", 10000, resetModeState
, true);
5346 option("moveInputWithCursor", true, function(cm
, val
) {
5347 if (!val
) cm
.display
.input
.resetPosition();
5350 option("tabindex", null, function(cm
, val
) {
5351 cm
.display
.input
.getField().tabIndex
= val
|| "";
5353 option("autofocus", null);
5355 // MODE DEFINITION AND QUERYING
5357 // Known modes, by name and by MIME
5358 var modes
= CodeMirror
.modes
= {}, mimeModes
= CodeMirror
.mimeModes
= {};
5360 // Extra arguments are stored as the mode's dependencies, which is
5361 // used by (legacy) mechanisms like loadmode.js to automatically
5362 // load a mode. (Preferred mechanism is the require/define calls.)
5363 CodeMirror
.defineMode = function(name
, mode
) {
5364 if (!CodeMirror
.defaults
.mode
&& name
!= "null") CodeMirror
.defaults
.mode
= name
;
5365 if (arguments
.length
> 2)
5366 mode
.dependencies
= Array
.prototype.slice
.call(arguments
, 2);
5370 CodeMirror
.defineMIME = function(mime
, spec
) {
5371 mimeModes
[mime
] = spec
;
5374 // Given a MIME type, a {name, ...options} config object, or a name
5375 // string, return a mode config object.
5376 CodeMirror
.resolveMode = function(spec
) {
5377 if (typeof spec
== "string" && mimeModes
.hasOwnProperty(spec
)) {
5378 spec
= mimeModes
[spec
];
5379 } else if (spec
&& typeof spec
.name
== "string" && mimeModes
.hasOwnProperty(spec
.name
)) {
5380 var found
= mimeModes
[spec
.name
];
5381 if (typeof found
== "string") found
= {name: found
};
5382 spec
= createObj(found
, spec
);
5383 spec
.name
= found
.name
;
5384 } else if (typeof spec
== "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec
)) {
5385 return CodeMirror
.resolveMode("application/xml");
5387 if (typeof spec
== "string") return {name: spec
};
5388 else return spec
|| {name: "null"};
5391 // Given a mode spec (anything that resolveMode accepts), find and
5392 // initialize an actual mode object.
5393 CodeMirror
.getMode = function(options
, spec
) {
5394 var spec
= CodeMirror
.resolveMode(spec
);
5395 var mfactory
= modes
[spec
.name
];
5396 if (!mfactory
) return CodeMirror
.getMode(options
, "text/plain");
5397 var modeObj
= mfactory(options
, spec
);
5398 if (modeExtensions
.hasOwnProperty(spec
.name
)) {
5399 var exts
= modeExtensions
[spec
.name
];
5400 for (var prop
in exts
) {
5401 if (!exts
.hasOwnProperty(prop
)) continue;
5402 if (modeObj
.hasOwnProperty(prop
)) modeObj
["_" + prop
] = modeObj
[prop
];
5403 modeObj
[prop
] = exts
[prop
];
5406 modeObj
.name
= spec
.name
;
5407 if (spec
.helperType
) modeObj
.helperType
= spec
.helperType
;
5408 if (spec
.modeProps
) for (var prop
in spec
.modeProps
)
5409 modeObj
[prop
] = spec
.modeProps
[prop
];
5414 // Minimal default mode.
5415 CodeMirror
.defineMode("null", function() {
5416 return {token: function(stream
) {stream
.skipToEnd();}};
5418 CodeMirror
.defineMIME("text/plain", "null");
5420 // This can be used to attach properties to mode objects from
5421 // outside the actual mode definition.
5422 var modeExtensions
= CodeMirror
.modeExtensions
= {};
5423 CodeMirror
.extendMode = function(mode
, properties
) {
5424 var exts
= modeExtensions
.hasOwnProperty(mode
) ? modeExtensions
[mode
] : (modeExtensions
[mode
] = {});
5425 copyObj(properties
, exts
);
5430 CodeMirror
.defineExtension = function(name
, func
) {
5431 CodeMirror
.prototype[name
] = func
;
5433 CodeMirror
.defineDocExtension = function(name
, func
) {
5434 Doc
.prototype[name
] = func
;
5436 CodeMirror
.defineOption
= option
;
5439 CodeMirror
.defineInitHook = function(f
) {initHooks
.push(f
);};
5441 var helpers
= CodeMirror
.helpers
= {};
5442 CodeMirror
.registerHelper = function(type
, name
, value
) {
5443 if (!helpers
.hasOwnProperty(type
)) helpers
[type
] = CodeMirror
[type
] = {_global: []};
5444 helpers
[type
][name
] = value
;
5446 CodeMirror
.registerGlobalHelper = function(type
, name
, predicate
, value
) {
5447 CodeMirror
.registerHelper(type
, name
, value
);
5448 helpers
[type
]._global
.push({pred: predicate
, val: value
});
5451 // MODE STATE HANDLING
5453 // Utility functions for working with state. Exported because nested
5454 // modes need to do this for their inner modes.
5456 var copyState
= CodeMirror
.copyState = function(mode
, state
) {
5457 if (state
=== true) return state
;
5458 if (mode
.copyState
) return mode
.copyState(state
);
5460 for (var n
in state
) {
5462 if (val
instanceof Array
) val
= val
.concat([]);
5468 var startState
= CodeMirror
.startState = function(mode
, a1
, a2
) {
5469 return mode
.startState
? mode
.startState(a1
, a2
) : true;
5472 // Given a mode and a state (for that mode), find the inner mode and
5473 // state at the position that the state refers to.
5474 CodeMirror
.innerMode = function(mode
, state
) {
5475 while (mode
.innerMode
) {
5476 var info
= mode
.innerMode(state
);
5477 if (!info
|| info
.mode
== mode
) break;
5481 return info
|| {mode: mode
, state: state
};
5484 // STANDARD COMMANDS
5486 // Commands are parameter-less actions that can be performed on an
5487 // editor, mostly used for keybindings.
5488 var commands
= CodeMirror
.commands
= {
5489 selectAll: function(cm
) {cm
.setSelection(Pos(cm
.firstLine(), 0), Pos(cm
.lastLine()), sel_dontScroll
);},
5490 singleSelection: function(cm
) {
5491 cm
.setSelection(cm
.getCursor("anchor"), cm
.getCursor("head"), sel_dontScroll
);
5493 killLine: function(cm
) {
5494 deleteNearSelection(cm
, function(range
) {
5495 if (range
.empty()) {
5496 var len
= getLine(cm
.doc
, range
.head
.line
).text
.length
;
5497 if (range
.head
.ch
== len
&& range
.head
.line
< cm
.lastLine())
5498 return {from: range
.head
, to: Pos(range
.head
.line
+ 1, 0)};
5500 return {from: range
.head
, to: Pos(range
.head
.line
, len
)};
5502 return {from: range
.from(), to: range
.to()};
5506 deleteLine: function(cm
) {
5507 deleteNearSelection(cm
, function(range
) {
5508 return {from: Pos(range
.from().line
, 0),
5509 to: clipPos(cm
.doc
, Pos(range
.to().line
+ 1, 0))};
5512 delLineLeft: function(cm
) {
5513 deleteNearSelection(cm
, function(range
) {
5514 return {from: Pos(range
.from().line
, 0), to: range
.from()};
5517 delWrappedLineLeft: function(cm
) {
5518 deleteNearSelection(cm
, function(range
) {
5519 var top
= cm
.charCoords(range
.head
, "div").top
+ 5;
5520 var leftPos
= cm
.coordsChar({left: 0, top: top
}, "div");
5521 return {from: leftPos
, to: range
.from()};
5524 delWrappedLineRight: function(cm
) {
5525 deleteNearSelection(cm
, function(range
) {
5526 var top
= cm
.charCoords(range
.head
, "div").top
+ 5;
5527 var rightPos
= cm
.coordsChar({left: cm
.display
.lineDiv
.offsetWidth
+ 100, top: top
}, "div");
5528 return {from: range
.from(), to: rightPos
};
5531 undo: function(cm
) {cm
.undo();},
5532 redo: function(cm
) {cm
.redo();},
5533 undoSelection: function(cm
) {cm
.undoSelection();},
5534 redoSelection: function(cm
) {cm
.redoSelection();},
5535 goDocStart: function(cm
) {cm
.extendSelection(Pos(cm
.firstLine(), 0));},
5536 goDocEnd: function(cm
) {cm
.extendSelection(Pos(cm
.lastLine()));},
5537 goLineStart: function(cm
) {
5538 cm
.extendSelectionsBy(function(range
) { return lineStart(cm
, range
.head
.line
); },
5539 {origin: "+move", bias: 1});
5541 goLineStartSmart: function(cm
) {
5542 cm
.extendSelectionsBy(function(range
) {
5543 return lineStartSmart(cm
, range
.head
);
5544 }, {origin: "+move", bias: 1});
5546 goLineEnd: function(cm
) {
5547 cm
.extendSelectionsBy(function(range
) { return lineEnd(cm
, range
.head
.line
); },
5548 {origin: "+move", bias: -1});
5550 goLineRight: function(cm
) {
5551 cm
.extendSelectionsBy(function(range
) {
5552 var top
= cm
.charCoords(range
.head
, "div").top
+ 5;
5553 return cm
.coordsChar({left: cm
.display
.lineDiv
.offsetWidth
+ 100, top: top
}, "div");
5556 goLineLeft: function(cm
) {
5557 cm
.extendSelectionsBy(function(range
) {
5558 var top
= cm
.charCoords(range
.head
, "div").top
+ 5;
5559 return cm
.coordsChar({left: 0, top: top
}, "div");
5562 goLineLeftSmart: function(cm
) {
5563 cm
.extendSelectionsBy(function(range
) {
5564 var top
= cm
.charCoords(range
.head
, "div").top
+ 5;
5565 var pos
= cm
.coordsChar({left: 0, top: top
}, "div");
5566 if (pos
.ch
< cm
.getLine(pos
.line
).search(/\S/)) return lineStartSmart(cm
, range
.head
);
5570 goLineUp: function(cm
) {cm
.moveV(-1, "line");},
5571 goLineDown: function(cm
) {cm
.moveV(1, "line");},
5572 goPageUp: function(cm
) {cm
.moveV(-1, "page");},
5573 goPageDown: function(cm
) {cm
.moveV(1, "page");},
5574 goCharLeft: function(cm
) {cm
.moveH(-1, "char");},
5575 goCharRight: function(cm
) {cm
.moveH(1, "char");},
5576 goColumnLeft: function(cm
) {cm
.moveH(-1, "column");},
5577 goColumnRight: function(cm
) {cm
.moveH(1, "column");},
5578 goWordLeft: function(cm
) {cm
.moveH(-1, "word");},
5579 goGroupRight: function(cm
) {cm
.moveH(1, "group");},
5580 goGroupLeft: function(cm
) {cm
.moveH(-1, "group");},
5581 goWordRight: function(cm
) {cm
.moveH(1, "word");},
5582 delCharBefore: function(cm
) {cm
.deleteH(-1, "char");},
5583 delCharAfter: function(cm
) {cm
.deleteH(1, "char");},
5584 delWordBefore: function(cm
) {cm
.deleteH(-1, "word");},
5585 delWordAfter: function(cm
) {cm
.deleteH(1, "word");},
5586 delGroupBefore: function(cm
) {cm
.deleteH(-1, "group");},
5587 delGroupAfter: function(cm
) {cm
.deleteH(1, "group");},
5588 indentAuto: function(cm
) {cm
.indentSelection("smart");},
5589 indentMore: function(cm
) {cm
.indentSelection("add");},
5590 indentLess: function(cm
) {cm
.indentSelection("subtract");},
5591 insertTab: function(cm
) {cm
.replaceSelection("\t");},
5592 insertSoftTab: function(cm
) {
5593 var spaces
= [], ranges
= cm
.listSelections(), tabSize
= cm
.options
.tabSize
;
5594 for (var i
= 0; i
< ranges
.length
; i
++) {
5595 var pos
= ranges
[i
].from();
5596 var col
= countColumn(cm
.getLine(pos
.line
), pos
.ch
, tabSize
);
5597 spaces
.push(new Array(tabSize
- col
% tabSize
+ 1).join(" "));
5599 cm
.replaceSelections(spaces
);
5601 defaultTab: function(cm
) {
5602 if (cm
.somethingSelected()) cm
.indentSelection("add");
5603 else cm
.execCommand("insertTab");
5605 transposeChars: function(cm
) {
5606 runInOp(cm
, function() {
5607 var ranges
= cm
.listSelections(), newSel
= [];
5608 for (var i
= 0; i
< ranges
.length
; i
++) {
5609 var cur
= ranges
[i
].head
, line
= getLine(cm
.doc
, cur
.line
).text
;
5611 if (cur
.ch
== line
.length
) cur
= new Pos(cur
.line
, cur
.ch
- 1);
5613 cur
= new Pos(cur
.line
, cur
.ch
+ 1);
5614 cm
.replaceRange(line
.charAt(cur
.ch
- 1) + line
.charAt(cur
.ch
- 2),
5615 Pos(cur
.line
, cur
.ch
- 2), cur
, "+transpose");
5616 } else if (cur
.line
> cm
.doc
.first
) {
5617 var prev
= getLine(cm
.doc
, cur
.line
- 1).text
;
5619 cm
.replaceRange(line
.charAt(0) + "\n" + prev
.charAt(prev
.length
- 1),
5620 Pos(cur
.line
- 1, prev
.length
- 1), Pos(cur
.line
, 1), "+transpose");
5623 newSel
.push(new Range(cur
, cur
));
5625 cm
.setSelections(newSel
);
5628 newlineAndIndent: function(cm
) {
5629 runInOp(cm
, function() {
5630 var len
= cm
.listSelections().length
;
5631 for (var i
= 0; i
< len
; i
++) {
5632 var range
= cm
.listSelections()[i
];
5633 cm
.replaceRange("\n", range
.anchor
, range
.head
, "+input");
5634 cm
.indentLine(range
.from().line
+ 1, null, true);
5635 ensureCursorVisible(cm
);
5639 toggleOverwrite: function(cm
) {cm
.toggleOverwrite();}
5645 var keyMap
= CodeMirror
.keyMap
= {};
5648 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
5649 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
5650 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
5651 "Tab": "defaultTab", "Shift-Tab": "indentAuto",
5652 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
5653 "Esc": "singleSelection"
5655 // Note that the save and find-related commands aren't defined by
5656 // default. User code or addons can define them. Unknown commands
5657 // are simply ignored.
5658 keyMap
.pcDefault
= {
5659 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
5660 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
5661 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
5662 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
5663 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
5664 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
5665 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
5666 fallthrough: "basic"
5668 // Very basic readline/emacs-style bindings, which are standard on Mac.
5670 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
5671 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
5672 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
5673 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
5675 keyMap
.macDefault
= {
5676 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
5677 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
5678 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
5679 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
5680 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
5681 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
5682 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
5683 fallthrough: ["basic", "emacsy"]
5685 keyMap
["default"] = mac
? keyMap
.macDefault : keyMap
.pcDefault
;
5689 function normalizeKeyName(name
) {
5690 var parts
= name
.split(/-(?!$)/), name
= parts
[parts
.length
- 1];
5691 var alt
, ctrl
, shift
, cmd
;
5692 for (var i
= 0; i
< parts
.length
- 1; i
++) {
5694 if (/^(cmd|meta|m)$/i.test(mod
)) cmd
= true;
5695 else if (/^a(lt)?$/i.test(mod
)) alt
= true;
5696 else if (/^(c|ctrl|control)$/i.test(mod
)) ctrl
= true;
5697 else if (/^s(hift)$/i.test(mod
)) shift
= true;
5698 else throw new Error("Unrecognized modifier name: " + mod
);
5700 if (alt
) name
= "Alt-" + name
;
5701 if (ctrl
) name
= "Ctrl-" + name
;
5702 if (cmd
) name
= "Cmd-" + name
;
5703 if (shift
) name
= "Shift-" + name
;
5707 // This is a kludge to keep keymaps mostly working as raw objects
5708 // (backwards compatibility) while at the same time support features
5709 // like normalization and multi-stroke key bindings. It compiles a
5710 // new normalized keymap, and then updates the old object to reflect
5712 CodeMirror
.normalizeKeyMap = function(keymap
) {
5714 for (var keyname
in keymap
) if (keymap
.hasOwnProperty(keyname
)) {
5715 var value
= keymap
[keyname
];
5716 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname
)) continue;
5717 if (value
== "...") { delete keymap
[keyname
]; continue; }
5719 var keys
= map(keyname
.split(" "), normalizeKeyName
);
5720 for (var i
= 0; i
< keys
.length
; i
++) {
5722 if (i
== keys
.length
- 1) {
5726 name
= keys
.slice(0, i
+ 1).join(" ");
5729 var prev
= copy
[name
];
5730 if (!prev
) copy
[name
] = val
;
5731 else if (prev
!= val
) throw new Error("Inconsistent bindings for " + name
);
5733 delete keymap
[keyname
];
5735 for (var prop
in copy
) keymap
[prop
] = copy
[prop
];
5739 var lookupKey
= CodeMirror
.lookupKey = function(key
, map
, handle
, context
) {
5740 map
= getKeyMap(map
);
5741 var found
= map
.call
? map
.call(key
, context
) : map
[key
];
5742 if (found
=== false) return "nothing";
5743 if (found
=== "...") return "multi";
5744 if (found
!= null && handle(found
)) return "handled";
5746 if (map
.fallthrough
) {
5747 if (Object
.prototype.toString
.call(map
.fallthrough
) != "[object Array]")
5748 return lookupKey(key
, map
.fallthrough
, handle
, context
);
5749 for (var i
= 0; i
< map
.fallthrough
.length
; i
++) {
5750 var result
= lookupKey(key
, map
.fallthrough
[i
], handle
, context
);
5751 if (result
) return result
;
5756 // Modifier key presses don't count as 'real' key presses for the
5757 // purpose of keymap fallthrough.
5758 var isModifierKey
= CodeMirror
.isModifierKey = function(value
) {
5759 var name
= typeof value
== "string" ? value : keyNames
[value
.keyCode
];
5760 return name
== "Ctrl" || name
== "Alt" || name
== "Shift" || name
== "Mod";
5763 // Look up the name of a key as indicated by an event object.
5764 var keyName
= CodeMirror
.keyName = function(event
, noShift
) {
5765 if (presto
&& event
.keyCode
== 34 && event
["char"]) return false;
5766 var base
= keyNames
[event
.keyCode
], name
= base
;
5767 if (name
== null || event
.altGraphKey
) return false;
5768 if (event
.altKey
&& base
!= "Alt") name
= "Alt-" + name
;
5769 if ((flipCtrlCmd
? event
.metaKey : event
.ctrlKey
) && base
!= "Ctrl") name
= "Ctrl-" + name
;
5770 if ((flipCtrlCmd
? event
.ctrlKey : event
.metaKey
) && base
!= "Cmd") name
= "Cmd-" + name
;
5771 if (!noShift
&& event
.shiftKey
&& base
!= "Shift") name
= "Shift-" + name
;
5775 function getKeyMap(val
) {
5776 return typeof val
== "string" ? keyMap
[val
] : val
;
5781 CodeMirror
.fromTextArea = function(textarea
, options
) {
5782 options
= options
? copyObj(options
) : {};
5783 options
.value
= textarea
.value
;
5784 if (!options
.tabindex
&& textarea
.tabIndex
)
5785 options
.tabindex
= textarea
.tabIndex
;
5786 if (!options
.placeholder
&& textarea
.placeholder
)
5787 options
.placeholder
= textarea
.placeholder
;
5788 // Set autofocus to true if this textarea is focused, or if it has
5789 // autofocus and no other element is focused.
5790 if (options
.autofocus
== null) {
5791 var hasFocus
= activeElt();
5792 options
.autofocus
= hasFocus
== textarea
||
5793 textarea
.getAttribute("autofocus") != null && hasFocus
== document
.body
;
5796 function save() {textarea
.value
= cm
.getValue();}
5797 if (textarea
.form
) {
5798 on(textarea
.form
, "submit", save
);
5799 // Deplorable hack to make the submit method do the right thing.
5800 if (!options
.leaveSubmitMethodAlone
) {
5801 var form
= textarea
.form
, realSubmit
= form
.submit
;
5803 var wrappedSubmit
= form
.submit = function() {
5805 form
.submit
= realSubmit
;
5807 form
.submit
= wrappedSubmit
;
5813 options
.finishInit = function(cm
) {
5815 cm
.getTextArea = function() { return textarea
; };
5816 cm
.toTextArea = function() {
5817 cm
.toTextArea
= isNaN
; // Prevent this from being ran twice
5819 textarea
.parentNode
.removeChild(cm
.getWrapperElement());
5820 textarea
.style
.display
= "";
5821 if (textarea
.form
) {
5822 off(textarea
.form
, "submit", save
);
5823 if (typeof textarea
.form
.submit
== "function")
5824 textarea
.form
.submit
= realSubmit
;
5829 textarea
.style
.display
= "none";
5830 var cm
= CodeMirror(function(node
) {
5831 textarea
.parentNode
.insertBefore(node
, textarea
.nextSibling
);
5838 // Fed to the mode parsers, provides helper functions to make
5839 // parsers more succinct.
5841 var StringStream
= CodeMirror
.StringStream = function(string
, tabSize
) {
5842 this.pos
= this.start
= 0;
5843 this.string
= string
;
5844 this.tabSize
= tabSize
|| 8;
5845 this.lastColumnPos
= this.lastColumnValue
= 0;
5849 StringStream
.prototype = {
5850 eol: function() {return this.pos
>= this.string
.length
;},
5851 sol: function() {return this.pos
== this.lineStart
;},
5852 peek: function() {return this.string
.charAt(this.pos
) || undefined;},
5854 if (this.pos
< this.string
.length
)
5855 return this.string
.charAt(this.pos
++);
5857 eat: function(match
) {
5858 var ch
= this.string
.charAt(this.pos
);
5859 if (typeof match
== "string") var ok
= ch
== match
;
5860 else var ok
= ch
&& (match
.test
? match
.test(ch
) : match(ch
));
5861 if (ok
) {++this.pos
; return ch
;}
5863 eatWhile: function(match
) {
5864 var start
= this.pos
;
5865 while (this.eat(match
)){}
5866 return this.pos
> start
;
5868 eatSpace: function() {
5869 var start
= this.pos
;
5870 while (/[\s\u00a0]/.test(this.string
.charAt(this.pos
))) ++this.pos
;
5871 return this.pos
> start
;
5873 skipToEnd: function() {this.pos
= this.string
.length
;},
5874 skipTo: function(ch
) {
5875 var found
= this.string
.indexOf(ch
, this.pos
);
5876 if (found
> -1) {this.pos
= found
; return true;}
5878 backUp: function(n
) {this.pos
-= n
;},
5879 column: function() {
5880 if (this.lastColumnPos
< this.start
) {
5881 this.lastColumnValue
= countColumn(this.string
, this.start
, this.tabSize
, this.lastColumnPos
, this.lastColumnValue
);
5882 this.lastColumnPos
= this.start
;
5884 return this.lastColumnValue
- (this.lineStart
? countColumn(this.string
, this.lineStart
, this.tabSize
) : 0);
5886 indentation: function() {
5887 return countColumn(this.string
, null, this.tabSize
) -
5888 (this.lineStart
? countColumn(this.string
, this.lineStart
, this.tabSize
) : 0);
5890 match: function(pattern
, consume
, caseInsensitive
) {
5891 if (typeof pattern
== "string") {
5892 var cased = function(str
) {return caseInsensitive
? str
.toLowerCase() : str
;};
5893 var substr
= this.string
.substr(this.pos
, pattern
.length
);
5894 if (cased(substr
) == cased(pattern
)) {
5895 if (consume
!== false) this.pos
+= pattern
.length
;
5899 var match
= this.string
.slice(this.pos
).match(pattern
);
5900 if (match
&& match
.index
> 0) return null;
5901 if (match
&& consume
!== false) this.pos
+= match
[0].length
;
5905 current: function(){return this.string
.slice(this.start
, this.pos
);},
5906 hideFirstChars: function(n
, inner
) {
5907 this.lineStart
+= n
;
5908 try { return inner(); }
5909 finally { this.lineStart
-= n
; }
5915 // Created with markText and setBookmark methods. A TextMarker is a
5916 // handle that can be used to clear or find a marked position in the
5917 // document. Line objects hold arrays (markedSpans) containing
5918 // {from, to, marker} object pointing to such marker objects, and
5919 // indicating that such a marker is present on that line. Multiple
5920 // lines may point to the same marker when it spans across lines.
5921 // The spans will have null for their from/to properties when the
5922 // marker continues beyond the start/end of the line. Markers have
5923 // links back to the lines they currently touch.
5925 var nextMarkerId
= 0;
5927 var TextMarker
= CodeMirror
.TextMarker = function(doc
, type
) {
5931 this.id
= ++nextMarkerId
;
5933 eventMixin(TextMarker
);
5935 // Clear the marker.
5936 TextMarker
.prototype.clear = function() {
5937 if (this.explicitlyCleared
) return;
5938 var cm
= this.doc
.cm
, withOp
= cm
&& !cm
.curOp
;
5939 if (withOp
) startOperation(cm
);
5940 if (hasHandler(this, "clear")) {
5941 var found
= this.find();
5942 if (found
) signalLater(this, "clear", found
.from, found
.to
);
5944 var min
= null, max
= null;
5945 for (var i
= 0; i
< this.lines
.length
; ++i
) {
5946 var line
= this.lines
[i
];
5947 var span
= getMarkedSpanFor(line
.markedSpans
, this);
5948 if (cm
&& !this.collapsed
) regLineChange(cm
, lineNo(line
), "text");
5950 if (span
.to
!= null) max
= lineNo(line
);
5951 if (span
.from != null) min
= lineNo(line
);
5953 line
.markedSpans
= removeMarkedSpan(line
.markedSpans
, span
);
5954 if (span
.from == null && this.collapsed
&& !lineIsHidden(this.doc
, line
) && cm
)
5955 updateLineHeight(line
, textHeight(cm
.display
));
5957 if (cm
&& this.collapsed
&& !cm
.options
.lineWrapping
) for (var i
= 0; i
< this.lines
.length
; ++i
) {
5958 var visual
= visualLine(this.lines
[i
]), len
= lineLength(visual
);
5959 if (len
> cm
.display
.maxLineLength
) {
5960 cm
.display
.maxLine
= visual
;
5961 cm
.display
.maxLineLength
= len
;
5962 cm
.display
.maxLineChanged
= true;
5966 if (min
!= null && cm
&& this.collapsed
) regChange(cm
, min
, max
+ 1);
5967 this.lines
.length
= 0;
5968 this.explicitlyCleared
= true;
5969 if (this.atomic
&& this.doc
.cantEdit
) {
5970 this.doc
.cantEdit
= false;
5971 if (cm
) reCheckSelection(cm
.doc
);
5973 if (cm
) signalLater(cm
, "markerCleared", cm
, this);
5974 if (withOp
) endOperation(cm
);
5975 if (this.parent
) this.parent
.clear();
5978 // Find the position of the marker in the document. Returns a {from,
5979 // to} object by default. Side can be passed to get a specific side
5980 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
5981 // Pos objects returned contain a line object, rather than a line
5982 // number (used to prevent looking up the same line twice).
5983 TextMarker
.prototype.find = function(side
, lineObj
) {
5984 if (side
== null && this.type
== "bookmark") side
= 1;
5986 for (var i
= 0; i
< this.lines
.length
; ++i
) {
5987 var line
= this.lines
[i
];
5988 var span
= getMarkedSpanFor(line
.markedSpans
, this);
5989 if (span
.from != null) {
5990 from = Pos(lineObj
? line : lineNo(line
), span
.from);
5991 if (side
== -1) return from;
5993 if (span
.to
!= null) {
5994 to
= Pos(lineObj
? line : lineNo(line
), span
.to
);
5995 if (side
== 1) return to
;
5998 return from && {from: from, to: to
};
6001 // Signals that the marker's widget changed, and surrounding layout
6002 // should be recomputed.
6003 TextMarker
.prototype.changed = function() {
6004 var pos
= this.find(-1, true), widget
= this, cm
= this.doc
.cm
;
6005 if (!pos
|| !cm
) return;
6006 runInOp(cm
, function() {
6007 var line
= pos
.line
, lineN
= lineNo(pos
.line
);
6008 var view
= findViewForLine(cm
, lineN
);
6010 clearLineMeasurementCacheFor(view
);
6011 cm
.curOp
.selectionChanged
= cm
.curOp
.forceUpdate
= true;
6013 cm
.curOp
.updateMaxLine
= true;
6014 if (!lineIsHidden(widget
.doc
, line
) && widget
.height
!= null) {
6015 var oldHeight
= widget
.height
;
6016 widget
.height
= null;
6017 var dHeight
= widgetHeight(widget
) - oldHeight
;
6019 updateLineHeight(line
, line
.height
+ dHeight
);
6024 TextMarker
.prototype.attachLine = function(line
) {
6025 if (!this.lines
.length
&& this.doc
.cm
) {
6026 var op
= this.doc
.cm
.curOp
;
6027 if (!op
.maybeHiddenMarkers
|| indexOf(op
.maybeHiddenMarkers
, this) == -1)
6028 (op
.maybeUnhiddenMarkers
|| (op
.maybeUnhiddenMarkers
= [])).push(this);
6030 this.lines
.push(line
);
6032 TextMarker
.prototype.detachLine = function(line
) {
6033 this.lines
.splice(indexOf(this.lines
, line
), 1);
6034 if (!this.lines
.length
&& this.doc
.cm
) {
6035 var op
= this.doc
.cm
.curOp
;
6036 (op
.maybeHiddenMarkers
|| (op
.maybeHiddenMarkers
= [])).push(this);
6040 // Collapsed markers have unique ids, in order to be able to order
6041 // them, which is needed for uniquely determining an outer marker
6042 // when they overlap (they may nest, but not partially overlap).
6043 var nextMarkerId
= 0;
6045 // Create a marker, wire it up to the right lines, and
6046 function markText(doc
, from, to
, options
, type
) {
6047 // Shared markers (across linked documents) are handled separately
6048 // (markTextShared will call out to this again, once per
6050 if (options
&& options
.shared
) return markTextShared(doc
, from, to
, options
, type
);
6051 // Ensure we are in an operation.
6052 if (doc
.cm
&& !doc
.cm
.curOp
) return operation(doc
.cm
, markText
)(doc
, from, to
, options
, type
);
6054 var marker
= new TextMarker(doc
, type
), diff
= cmp(from, to
);
6055 if (options
) copyObj(options
, marker
, false);
6056 // Don't connect empty markers unless clearWhenEmpty is false
6057 if (diff
> 0 || diff
== 0 && marker
.clearWhenEmpty
!== false)
6059 if (marker
.replacedWith
) {
6060 // Showing up as a widget implies collapsed (widget replaces text)
6061 marker
.collapsed
= true;
6062 marker
.widgetNode
= elt("span", [marker
.replacedWith
], "CodeMirror-widget");
6063 if (!options
.handleMouseEvents
) marker
.widgetNode
.setAttribute("cm-ignore-events", "true");
6064 if (options
.insertLeft
) marker
.widgetNode
.insertLeft
= true;
6066 if (marker
.collapsed
) {
6067 if (conflictingCollapsedRange(doc
, from.line
, from, to
, marker
) ||
6068 from.line
!= to
.line
&& conflictingCollapsedRange(doc
, to
.line
, from, to
, marker
))
6069 throw new Error("Inserting collapsed marker partially overlapping an existing one");
6070 sawCollapsedSpans
= true;
6073 if (marker
.addToHistory
)
6074 addChangeToHistory(doc
, {from: from, to: to
, origin: "markText"}, doc
.sel
, NaN
);
6076 var curLine
= from.line
, cm
= doc
.cm
, updateMaxLine
;
6077 doc
.iter(curLine
, to
.line
+ 1, function(line
) {
6078 if (cm
&& marker
.collapsed
&& !cm
.options
.lineWrapping
&& visualLine(line
) == cm
.display
.maxLine
)
6079 updateMaxLine
= true;
6080 if (marker
.collapsed
&& curLine
!= from.line
) updateLineHeight(line
, 0);
6081 addMarkedSpan(line
, new MarkedSpan(marker
,
6082 curLine
== from.line
? from.ch : null,
6083 curLine
== to
.line
? to
.ch : null));
6086 // lineIsHidden depends on the presence of the spans, so needs a second pass
6087 if (marker
.collapsed
) doc
.iter(from.line
, to
.line
+ 1, function(line
) {
6088 if (lineIsHidden(doc
, line
)) updateLineHeight(line
, 0);
6091 if (marker
.clearOnEnter
) on(marker
, "beforeCursorEnter", function() { marker
.clear(); });
6093 if (marker
.readOnly
) {
6094 sawReadOnlySpans
= true;
6095 if (doc
.history
.done
.length
|| doc
.history
.undone
.length
)
6098 if (marker
.collapsed
) {
6099 marker
.id
= ++nextMarkerId
;
6100 marker
.atomic
= true;
6103 // Sync editor state
6104 if (updateMaxLine
) cm
.curOp
.updateMaxLine
= true;
6105 if (marker
.collapsed
)
6106 regChange(cm
, from.line
, to
.line
+ 1);
6107 else if (marker
.className
|| marker
.title
|| marker
.startStyle
|| marker
.endStyle
|| marker
.css
)
6108 for (var i
= from.line
; i
<= to
.line
; i
++) regLineChange(cm
, i
, "text");
6109 if (marker
.atomic
) reCheckSelection(cm
.doc
);
6110 signalLater(cm
, "markerAdded", cm
, marker
);
6115 // SHARED TEXTMARKERS
6117 // A shared marker spans multiple linked documents. It is
6118 // implemented as a meta-marker-object controlling multiple normal
6120 var SharedTextMarker
= CodeMirror
.SharedTextMarker = function(markers
, primary
) {
6121 this.markers
= markers
;
6122 this.primary
= primary
;
6123 for (var i
= 0; i
< markers
.length
; ++i
)
6124 markers
[i
].parent
= this;
6126 eventMixin(SharedTextMarker
);
6128 SharedTextMarker
.prototype.clear = function() {
6129 if (this.explicitlyCleared
) return;
6130 this.explicitlyCleared
= true;
6131 for (var i
= 0; i
< this.markers
.length
; ++i
)
6132 this.markers
[i
].clear();
6133 signalLater(this, "clear");
6135 SharedTextMarker
.prototype.find = function(side
, lineObj
) {
6136 return this.primary
.find(side
, lineObj
);
6139 function markTextShared(doc
, from, to
, options
, type
) {
6140 options
= copyObj(options
);
6141 options
.shared
= false;
6142 var markers
= [markText(doc
, from, to
, options
, type
)], primary
= markers
[0];
6143 var widget
= options
.widgetNode
;
6144 linkedDocs(doc
, function(doc
) {
6145 if (widget
) options
.widgetNode
= widget
.cloneNode(true);
6146 markers
.push(markText(doc
, clipPos(doc
, from), clipPos(doc
, to
), options
, type
));
6147 for (var i
= 0; i
< doc
.linked
.length
; ++i
)
6148 if (doc
.linked
[i
].isParent
) return;
6149 primary
= lst(markers
);
6151 return new SharedTextMarker(markers
, primary
);
6154 function findSharedMarkers(doc
) {
6155 return doc
.findMarks(Pos(doc
.first
, 0), doc
.clipPos(Pos(doc
.lastLine())),
6156 function(m
) { return m
.parent
; });
6159 function copySharedMarkers(doc
, markers
) {
6160 for (var i
= 0; i
< markers
.length
; i
++) {
6161 var marker
= markers
[i
], pos
= marker
.find();
6162 var mFrom
= doc
.clipPos(pos
.from), mTo
= doc
.clipPos(pos
.to
);
6163 if (cmp(mFrom
, mTo
)) {
6164 var subMark
= markText(doc
, mFrom
, mTo
, marker
.primary
, marker
.primary
.type
);
6165 marker
.markers
.push(subMark
);
6166 subMark
.parent
= marker
;
6171 function detachSharedMarkers(markers
) {
6172 for (var i
= 0; i
< markers
.length
; i
++) {
6173 var marker
= markers
[i
], linked
= [marker
.primary
.doc
];;
6174 linkedDocs(marker
.primary
.doc
, function(d
) { linked
.push(d
); });
6175 for (var j
= 0; j
< marker
.markers
.length
; j
++) {
6176 var subMarker
= marker
.markers
[j
];
6177 if (indexOf(linked
, subMarker
.doc
) == -1) {
6178 subMarker
.parent
= null;
6179 marker
.markers
.splice(j
--, 1);
6187 function MarkedSpan(marker
, from, to
) {
6188 this.marker
= marker
;
6189 this.from = from; this.to
= to
;
6192 // Search an array of spans for a span matching the given marker.
6193 function getMarkedSpanFor(spans
, marker
) {
6194 if (spans
) for (var i
= 0; i
< spans
.length
; ++i
) {
6195 var span
= spans
[i
];
6196 if (span
.marker
== marker
) return span
;
6199 // Remove a span from an array, returning undefined if no spans are
6200 // left (we don't store arrays for lines without spans).
6201 function removeMarkedSpan(spans
, span
) {
6202 for (var r
, i
= 0; i
< spans
.length
; ++i
)
6203 if (spans
[i
] != span
) (r
|| (r
= [])).push(spans
[i
]);
6206 // Add a span to a line.
6207 function addMarkedSpan(line
, span
) {
6208 line
.markedSpans
= line
.markedSpans
? line
.markedSpans
.concat([span
]) : [span
];
6209 span
.marker
.attachLine(line
);
6212 // Used for the algorithm that adjusts markers for a change in the
6213 // document. These functions cut an array of spans at a given
6214 // character position, returning an array of remaining chunks (or
6215 // undefined if nothing remains).
6216 function markedSpansBefore(old
, startCh
, isInsert
) {
6217 if (old
) for (var i
= 0, nw
; i
< old
.length
; ++i
) {
6218 var span
= old
[i
], marker
= span
.marker
;
6219 var startsBefore
= span
.from == null || (marker
.inclusiveLeft
? span
.from <= startCh : span
.from < startCh
);
6220 if (startsBefore
|| span
.from == startCh
&& marker
.type
== "bookmark" && (!isInsert
|| !span
.marker
.insertLeft
)) {
6221 var endsAfter
= span
.to
== null || (marker
.inclusiveRight
? span
.to
>= startCh : span
.to
> startCh
);
6222 (nw
|| (nw
= [])).push(new MarkedSpan(marker
, span
.from, endsAfter
? null : span
.to
));
6227 function markedSpansAfter(old
, endCh
, isInsert
) {
6228 if (old
) for (var i
= 0, nw
; i
< old
.length
; ++i
) {
6229 var span
= old
[i
], marker
= span
.marker
;
6230 var endsAfter
= span
.to
== null || (marker
.inclusiveRight
? span
.to
>= endCh : span
.to
> endCh
);
6231 if (endsAfter
|| span
.from == endCh
&& marker
.type
== "bookmark" && (!isInsert
|| span
.marker
.insertLeft
)) {
6232 var startsBefore
= span
.from == null || (marker
.inclusiveLeft
? span
.from <= endCh : span
.from < endCh
);
6233 (nw
|| (nw
= [])).push(new MarkedSpan(marker
, startsBefore
? null : span
.from - endCh
,
6234 span
.to
== null ? null : span
.to
- endCh
));
6240 // Given a change object, compute the new set of marker spans that
6241 // cover the line in which the change took place. Removes spans
6242 // entirely within the change, reconnects spans belonging to the
6243 // same marker that appear on both sides of the change, and cuts off
6244 // spans partially within the change. Returns an array of span
6245 // arrays with one element for each line in (after) the change.
6246 function stretchSpansOverChange(doc
, change
) {
6247 if (change
.full
) return null;
6248 var oldFirst
= isLine(doc
, change
.from.line
) && getLine(doc
, change
.from.line
).markedSpans
;
6249 var oldLast
= isLine(doc
, change
.to
.line
) && getLine(doc
, change
.to
.line
).markedSpans
;
6250 if (!oldFirst
&& !oldLast
) return null;
6252 var startCh
= change
.from.ch
, endCh
= change
.to
.ch
, isInsert
= cmp(change
.from, change
.to
) == 0;
6253 // Get the spans that 'stick out' on both sides
6254 var first
= markedSpansBefore(oldFirst
, startCh
, isInsert
);
6255 var last
= markedSpansAfter(oldLast
, endCh
, isInsert
);
6257 // Next, merge those two ends
6258 var sameLine
= change
.text
.length
== 1, offset
= lst(change
.text
).length
+ (sameLine
? startCh : 0);
6260 // Fix up .to properties of first
6261 for (var i
= 0; i
< first
.length
; ++i
) {
6262 var span
= first
[i
];
6263 if (span
.to
== null) {
6264 var found
= getMarkedSpanFor(last
, span
.marker
);
6265 if (!found
) span
.to
= startCh
;
6266 else if (sameLine
) span
.to
= found
.to
== null ? null : found
.to
+ offset
;
6271 // Fix up .from in last (or move them into first in case of sameLine)
6272 for (var i
= 0; i
< last
.length
; ++i
) {
6274 if (span
.to
!= null) span
.to
+= offset
;
6275 if (span
.from == null) {
6276 var found
= getMarkedSpanFor(first
, span
.marker
);
6279 if (sameLine
) (first
|| (first
= [])).push(span
);
6282 span
.from += offset
;
6283 if (sameLine
) (first
|| (first
= [])).push(span
);
6287 // Make sure we didn't create any zero-length spans
6288 if (first
) first
= clearEmptySpans(first
);
6289 if (last
&& last
!= first
) last
= clearEmptySpans(last
);
6291 var newMarkers
= [first
];
6293 // Fill gap with whole-line-spans
6294 var gap
= change
.text
.length
- 2, gapMarkers
;
6295 if (gap
> 0 && first
)
6296 for (var i
= 0; i
< first
.length
; ++i
)
6297 if (first
[i
].to
== null)
6298 (gapMarkers
|| (gapMarkers
= [])).push(new MarkedSpan(first
[i
].marker
, null, null));
6299 for (var i
= 0; i
< gap
; ++i
)
6300 newMarkers
.push(gapMarkers
);
6301 newMarkers
.push(last
);
6306 // Remove spans that are empty and don't have a clearWhenEmpty
6308 function clearEmptySpans(spans
) {
6309 for (var i
= 0; i
< spans
.length
; ++i
) {
6310 var span
= spans
[i
];
6311 if (span
.from != null && span
.from == span
.to
&& span
.marker
.clearWhenEmpty
!== false)
6312 spans
.splice(i
--, 1);
6314 if (!spans
.length
) return null;
6318 // Used for un/re-doing changes from the history. Combines the
6319 // result of computing the existing spans with the set of spans that
6320 // existed in the history (so that deleting around a span and then
6321 // undoing brings back the span).
6322 function mergeOldSpans(doc
, change
) {
6323 var old
= getOldSpans(doc
, change
);
6324 var stretched
= stretchSpansOverChange(doc
, change
);
6325 if (!old
) return stretched
;
6326 if (!stretched
) return old
;
6328 for (var i
= 0; i
< old
.length
; ++i
) {
6329 var oldCur
= old
[i
], stretchCur
= stretched
[i
];
6330 if (oldCur
&& stretchCur
) {
6331 spans: for (var j
= 0; j
< stretchCur
.length
; ++j
) {
6332 var span
= stretchCur
[j
];
6333 for (var k
= 0; k
< oldCur
.length
; ++k
)
6334 if (oldCur
[k
].marker
== span
.marker
) continue spans
;
6337 } else if (stretchCur
) {
6338 old
[i
] = stretchCur
;
6344 // Used to 'clip' out readOnly ranges when making a change.
6345 function removeReadOnlyRanges(doc
, from, to
) {
6347 doc
.iter(from.line
, to
.line
+ 1, function(line
) {
6348 if (line
.markedSpans
) for (var i
= 0; i
< line
.markedSpans
.length
; ++i
) {
6349 var mark
= line
.markedSpans
[i
].marker
;
6350 if (mark
.readOnly
&& (!markers
|| indexOf(markers
, mark
) == -1))
6351 (markers
|| (markers
= [])).push(mark
);
6354 if (!markers
) return null;
6355 var parts
= [{from: from, to: to
}];
6356 for (var i
= 0; i
< markers
.length
; ++i
) {
6357 var mk
= markers
[i
], m
= mk
.find(0);
6358 for (var j
= 0; j
< parts
.length
; ++j
) {
6360 if (cmp(p
.to
, m
.from) < 0 || cmp(p
.from, m
.to
) > 0) continue;
6361 var newParts
= [j
, 1], dfrom
= cmp(p
.from, m
.from), dto
= cmp(p
.to
, m
.to
);
6362 if (dfrom
< 0 || !mk
.inclusiveLeft
&& !dfrom
)
6363 newParts
.push({from: p
.from, to: m
.from});
6364 if (dto
> 0 || !mk
.inclusiveRight
&& !dto
)
6365 newParts
.push({from: m
.to
, to: p
.to
});
6366 parts
.splice
.apply(parts
, newParts
);
6367 j
+= newParts
.length
- 1;
6373 // Connect or disconnect spans from a line.
6374 function detachMarkedSpans(line
) {
6375 var spans
= line
.markedSpans
;
6377 for (var i
= 0; i
< spans
.length
; ++i
)
6378 spans
[i
].marker
.detachLine(line
);
6379 line
.markedSpans
= null;
6381 function attachMarkedSpans(line
, spans
) {
6383 for (var i
= 0; i
< spans
.length
; ++i
)
6384 spans
[i
].marker
.attachLine(line
);
6385 line
.markedSpans
= spans
;
6388 // Helpers used when computing which overlapping collapsed span
6389 // counts as the larger one.
6390 function extraLeft(marker
) { return marker
.inclusiveLeft
? -1 : 0; }
6391 function extraRight(marker
) { return marker
.inclusiveRight
? 1 : 0; }
6393 // Returns a number indicating which of two overlapping collapsed
6394 // spans is larger (and thus includes the other). Falls back to
6395 // comparing ids when the spans cover exactly the same range.
6396 function compareCollapsedMarkers(a
, b
) {
6397 var lenDiff
= a
.lines
.length
- b
.lines
.length
;
6398 if (lenDiff
!= 0) return lenDiff
;
6399 var aPos
= a
.find(), bPos
= b
.find();
6400 var fromCmp
= cmp(aPos
.from, bPos
.from) || extraLeft(a
) - extraLeft(b
);
6401 if (fromCmp
) return -fromCmp
;
6402 var toCmp
= cmp(aPos
.to
, bPos
.to
) || extraRight(a
) - extraRight(b
);
6403 if (toCmp
) return toCmp
;
6407 // Find out whether a line ends or starts in a collapsed span. If
6408 // so, return the marker for that span.
6409 function collapsedSpanAtSide(line
, start
) {
6410 var sps
= sawCollapsedSpans
&& line
.markedSpans
, found
;
6411 if (sps
) for (var sp
, i
= 0; i
< sps
.length
; ++i
) {
6413 if (sp
.marker
.collapsed
&& (start
? sp
.from : sp
.to
) == null &&
6414 (!found
|| compareCollapsedMarkers(found
, sp
.marker
) < 0))
6419 function collapsedSpanAtStart(line
) { return collapsedSpanAtSide(line
, true); }
6420 function collapsedSpanAtEnd(line
) { return collapsedSpanAtSide(line
, false); }
6422 // Test whether there exists a collapsed span that partially
6423 // overlaps (covers the start or end, but not both) of a new span.
6424 // Such overlap is not allowed.
6425 function conflictingCollapsedRange(doc
, lineNo
, from, to
, marker
) {
6426 var line
= getLine(doc
, lineNo
);
6427 var sps
= sawCollapsedSpans
&& line
.markedSpans
;
6428 if (sps
) for (var i
= 0; i
< sps
.length
; ++i
) {
6430 if (!sp
.marker
.collapsed
) continue;
6431 var found
= sp
.marker
.find(0);
6432 var fromCmp
= cmp(found
.from, from) || extraLeft(sp
.marker
) - extraLeft(marker
);
6433 var toCmp
= cmp(found
.to
, to
) || extraRight(sp
.marker
) - extraRight(marker
);
6434 if (fromCmp
>= 0 && toCmp
<= 0 || fromCmp
<= 0 && toCmp
>= 0) continue;
6435 if (fromCmp
<= 0 && (cmp(found
.to
, from) > 0 || (sp
.marker
.inclusiveRight
&& marker
.inclusiveLeft
)) ||
6436 fromCmp
>= 0 && (cmp(found
.from, to
) < 0 || (sp
.marker
.inclusiveLeft
&& marker
.inclusiveRight
)))
6441 // A visual line is a line as drawn on the screen. Folding, for
6442 // example, can cause multiple logical lines to appear on the same
6443 // visual line. This finds the start of the visual line that the
6444 // given line is part of (usually that is the line itself).
6445 function visualLine(line
) {
6447 while (merged
= collapsedSpanAtStart(line
))
6448 line
= merged
.find(-1, true).line
;
6452 // Returns an array of logical lines that continue the visual line
6453 // started by the argument, or undefined if there are no such lines.
6454 function visualLineContinued(line
) {
6456 while (merged
= collapsedSpanAtEnd(line
)) {
6457 line
= merged
.find(1, true).line
;
6458 (lines
|| (lines
= [])).push(line
);
6463 // Get the line number of the start of the visual line that the
6464 // given line number is part of.
6465 function visualLineNo(doc
, lineN
) {
6466 var line
= getLine(doc
, lineN
), vis
= visualLine(line
);
6467 if (line
== vis
) return lineN
;
6470 // Get the line number of the start of the next visual line after
6472 function visualLineEndNo(doc
, lineN
) {
6473 if (lineN
> doc
.lastLine()) return lineN
;
6474 var line
= getLine(doc
, lineN
), merged
;
6475 if (!lineIsHidden(doc
, line
)) return lineN
;
6476 while (merged
= collapsedSpanAtEnd(line
))
6477 line
= merged
.find(1, true).line
;
6478 return lineNo(line
) + 1;
6481 // Compute whether a line is hidden. Lines count as hidden when they
6482 // are part of a visual line that starts with another line, or when
6483 // they are entirely covered by collapsed, non-widget span.
6484 function lineIsHidden(doc
, line
) {
6485 var sps
= sawCollapsedSpans
&& line
.markedSpans
;
6486 if (sps
) for (var sp
, i
= 0; i
< sps
.length
; ++i
) {
6488 if (!sp
.marker
.collapsed
) continue;
6489 if (sp
.from == null) return true;
6490 if (sp
.marker
.widgetNode
) continue;
6491 if (sp
.from == 0 && sp
.marker
.inclusiveLeft
&& lineIsHiddenInner(doc
, line
, sp
))
6495 function lineIsHiddenInner(doc
, line
, span
) {
6496 if (span
.to
== null) {
6497 var end
= span
.marker
.find(1, true);
6498 return lineIsHiddenInner(doc
, end
.line
, getMarkedSpanFor(end
.line
.markedSpans
, span
.marker
));
6500 if (span
.marker
.inclusiveRight
&& span
.to
== line
.text
.length
)
6502 for (var sp
, i
= 0; i
< line
.markedSpans
.length
; ++i
) {
6503 sp
= line
.markedSpans
[i
];
6504 if (sp
.marker
.collapsed
&& !sp
.marker
.widgetNode
&& sp
.from == span
.to
&&
6505 (sp
.to
== null || sp
.to
!= span
.from) &&
6506 (sp
.marker
.inclusiveLeft
|| span
.marker
.inclusiveRight
) &&
6507 lineIsHiddenInner(doc
, line
, sp
)) return true;
6513 // Line widgets are block elements displayed above or below a line.
6515 var LineWidget
= CodeMirror
.LineWidget = function(doc
, node
, options
) {
6516 if (options
) for (var opt
in options
) if (options
.hasOwnProperty(opt
))
6517 this[opt
] = options
[opt
];
6521 eventMixin(LineWidget
);
6523 function adjustScrollWhenAboveVisible(cm
, line
, diff
) {
6524 if (heightAtLine(line
) < ((cm
.curOp
&& cm
.curOp
.scrollTop
) || cm
.doc
.scrollTop
))
6525 addToScrollPos(cm
, null, diff
);
6528 LineWidget
.prototype.clear = function() {
6529 var cm
= this.doc
.cm
, ws
= this.line
.widgets
, line
= this.line
, no
= lineNo(line
);
6530 if (no
== null || !ws
) return;
6531 for (var i
= 0; i
< ws
.length
; ++i
) if (ws
[i
] == this) ws
.splice(i
--, 1);
6532 if (!ws
.length
) line
.widgets
= null;
6533 var height
= widgetHeight(this);
6534 updateLineHeight(line
, Math
.max(0, line
.height
- height
));
6535 if (cm
) runInOp(cm
, function() {
6536 adjustScrollWhenAboveVisible(cm
, line
, -height
);
6537 regLineChange(cm
, no
, "widget");
6540 LineWidget
.prototype.changed = function() {
6541 var oldH
= this.height
, cm
= this.doc
.cm
, line
= this.line
;
6543 var diff
= widgetHeight(this) - oldH
;
6545 updateLineHeight(line
, line
.height
+ diff
);
6546 if (cm
) runInOp(cm
, function() {
6547 cm
.curOp
.forceUpdate
= true;
6548 adjustScrollWhenAboveVisible(cm
, line
, diff
);
6552 function widgetHeight(widget
) {
6553 if (widget
.height
!= null) return widget
.height
;
6554 var cm
= widget
.doc
.cm
;
6556 if (!contains(document
.body
, widget
.node
)) {
6557 var parentStyle
= "position: relative;";
6558 if (widget
.coverGutter
)
6559 parentStyle
+= "margin-left: -" + cm
.display
.gutters
.offsetWidth
+ "px;";
6560 if (widget
.noHScroll
)
6561 parentStyle
+= "width: " + cm
.display
.wrapper
.clientWidth
+ "px;";
6562 removeChildrenAndAdd(cm
.display
.measure
, elt("div", [widget
.node
], null, parentStyle
));
6564 return widget
.height
= widget
.node
.offsetHeight
;
6567 function addLineWidget(doc
, handle
, node
, options
) {
6568 var widget
= new LineWidget(doc
, node
, options
);
6570 if (cm
&& widget
.noHScroll
) cm
.display
.alignWidgets
= true;
6571 changeLine(doc
, handle
, "widget", function(line
) {
6572 var widgets
= line
.widgets
|| (line
.widgets
= []);
6573 if (widget
.insertAt
== null) widgets
.push(widget
);
6574 else widgets
.splice(Math
.min(widgets
.length
- 1, Math
.max(0, widget
.insertAt
)), 0, widget
);
6576 if (cm
&& !lineIsHidden(doc
, line
)) {
6577 var aboveVisible
= heightAtLine(line
) < doc
.scrollTop
;
6578 updateLineHeight(line
, line
.height
+ widgetHeight(widget
));
6579 if (aboveVisible
) addToScrollPos(cm
, null, widget
.height
);
6580 cm
.curOp
.forceUpdate
= true;
6587 // LINE DATA STRUCTURE
6589 // Line objects. These hold state related to a line, including
6590 // highlighting info (the styles array).
6591 var Line
= CodeMirror
.Line = function(text
, markedSpans
, estimateHeight
) {
6593 attachMarkedSpans(this, markedSpans
);
6594 this.height
= estimateHeight
? estimateHeight(this) : 1;
6597 Line
.prototype.lineNo = function() { return lineNo(this); };
6599 // Change the content (text, markers) of a line. Automatically
6600 // invalidates cached information and tries to re-estimate the
6602 function updateLine(line
, text
, markedSpans
, estimateHeight
) {
6604 if (line
.stateAfter
) line
.stateAfter
= null;
6605 if (line
.styles
) line
.styles
= null;
6606 if (line
.order
!= null) line
.order
= null;
6607 detachMarkedSpans(line
);
6608 attachMarkedSpans(line
, markedSpans
);
6609 var estHeight
= estimateHeight
? estimateHeight(line
) : 1;
6610 if (estHeight
!= line
.height
) updateLineHeight(line
, estHeight
);
6613 // Detach a line from the document tree and its markers.
6614 function cleanUpLine(line
) {
6616 detachMarkedSpans(line
);
6619 function extractLineClasses(type
, output
) {
6620 if (type
) for (;;) {
6621 var lineClass
= type
.match(/(?:^|\s+)line-(background-)?(\S+)/);
6622 if (!lineClass
) break;
6623 type
= type
.slice(0, lineClass
.index
) + type
.slice(lineClass
.index
+ lineClass
[0].length
);
6624 var prop
= lineClass
[1] ? "bgClass" : "textClass";
6625 if (output
[prop
] == null)
6626 output
[prop
] = lineClass
[2];
6627 else if (!(new RegExp("(?:^|\s)" + lineClass
[2] + "(?:$|\s)")).test(output
[prop
]))
6628 output
[prop
] += " " + lineClass
[2];
6633 function callBlankLine(mode
, state
) {
6634 if (mode
.blankLine
) return mode
.blankLine(state
);
6635 if (!mode
.innerMode
) return;
6636 var inner
= CodeMirror
.innerMode(mode
, state
);
6637 if (inner
.mode
.blankLine
) return inner
.mode
.blankLine(inner
.state
);
6640 function readToken(mode
, stream
, state
, inner
) {
6641 for (var i
= 0; i
< 10; i
++) {
6642 if (inner
) inner
[0] = CodeMirror
.innerMode(mode
, state
).mode
;
6643 var style
= mode
.token(stream
, state
);
6644 if (stream
.pos
> stream
.start
) return style
;
6646 throw new Error("Mode " + mode
.name
+ " failed to advance stream.");
6649 // Utility for getTokenAt and getLineTokens
6650 function takeToken(cm
, pos
, precise
, asArray
) {
6651 function getObj(copy
) {
6652 return {start: stream
.start
, end: stream
.pos
,
6653 string: stream
.current(),
6654 type: style
|| null,
6655 state: copy
? copyState(doc
.mode
, state
) : state
};
6658 var doc
= cm
.doc
, mode
= doc
.mode
, style
;
6659 pos
= clipPos(doc
, pos
);
6660 var line
= getLine(doc
, pos
.line
), state
= getStateBefore(cm
, pos
.line
, precise
);
6661 var stream
= new StringStream(line
.text
, cm
.options
.tabSize
), tokens
;
6662 if (asArray
) tokens
= [];
6663 while ((asArray
|| stream
.pos
< pos
.ch
) && !stream
.eol()) {
6664 stream
.start
= stream
.pos
;
6665 style
= readToken(mode
, stream
, state
);
6666 if (asArray
) tokens
.push(getObj(true));
6668 return asArray
? tokens : getObj();
6671 // Run the given mode's parser over a line, calling f for each token.
6672 function runMode(cm
, text
, mode
, state
, f
, lineClasses
, forceToEnd
) {
6673 var flattenSpans
= mode
.flattenSpans
;
6674 if (flattenSpans
== null) flattenSpans
= cm
.options
.flattenSpans
;
6675 var curStart
= 0, curStyle
= null;
6676 var stream
= new StringStream(text
, cm
.options
.tabSize
), style
;
6677 var inner
= cm
.options
.addModeClass
&& [null];
6678 if (text
== "") extractLineClasses(callBlankLine(mode
, state
), lineClasses
);
6679 while (!stream
.eol()) {
6680 if (stream
.pos
> cm
.options
.maxHighlightLength
) {
6681 flattenSpans
= false;
6682 if (forceToEnd
) processLine(cm
, text
, state
, stream
.pos
);
6683 stream
.pos
= text
.length
;
6686 style
= extractLineClasses(readToken(mode
, stream
, state
, inner
), lineClasses
);
6689 var mName
= inner
[0].name
;
6690 if (mName
) style
= "m-" + (style
? mName
+ " " + style : mName
);
6692 if (!flattenSpans
|| curStyle
!= style
) {
6693 while (curStart
< stream
.start
) {
6694 curStart
= Math
.min(stream
.start
, curStart
+ 50000);
6695 f(curStart
, curStyle
);
6699 stream
.start
= stream
.pos
;
6701 while (curStart
< stream
.pos
) {
6702 // Webkit seems to refuse to render text nodes longer than 57444 characters
6703 var pos
= Math
.min(stream
.pos
, curStart
+ 50000);
6709 // Compute a style array (an array starting with a mode generation
6710 // -- for invalidation -- followed by pairs of end positions and
6711 // style strings), which is used to highlight the tokens on the
6713 function highlightLine(cm
, line
, state
, forceToEnd
) {
6714 // A styles array always starts with a number identifying the
6715 // mode/overlays that it is based on (for easy invalidation).
6716 var st
= [cm
.state
.modeGen
], lineClasses
= {};
6717 // Compute the base array of styles
6718 runMode(cm
, line
.text
, cm
.doc
.mode
, state
, function(end
, style
) {
6719 st
.push(end
, style
);
6720 }, lineClasses
, forceToEnd
);
6722 // Run overlays, adjust style array.
6723 for (var o
= 0; o
< cm
.state
.overlays
.length
; ++o
) {
6724 var overlay
= cm
.state
.overlays
[o
], i
= 1, at
= 0;
6725 runMode(cm
, line
.text
, overlay
.mode
, true, function(end
, style
) {
6727 // Ensure there's a token end at the current position, and that i points at it
6731 st
.splice(i
, 1, end
, st
[i
+1], i_end
);
6733 at
= Math
.min(end
, i_end
);
6736 if (overlay
.opaque
) {
6737 st
.splice(start
, i
- start
, end
, "cm-overlay " + style
);
6740 for (; start
< i
; start
+= 2) {
6741 var cur
= st
[start
+1];
6742 st
[start
+1] = (cur
? cur
+ " " : "") + "cm-overlay " + style
;
6748 return {styles: st
, classes: lineClasses
.bgClass
|| lineClasses
.textClass
? lineClasses : null};
6751 function getLineStyles(cm
, line
, updateFrontier
) {
6752 if (!line
.styles
|| line
.styles
[0] != cm
.state
.modeGen
) {
6753 var result
= highlightLine(cm
, line
, line
.stateAfter
= getStateBefore(cm
, lineNo(line
)));
6754 line
.styles
= result
.styles
;
6755 if (result
.classes
) line
.styleClasses
= result
.classes
;
6756 else if (line
.styleClasses
) line
.styleClasses
= null;
6757 if (updateFrontier
=== cm
.doc
.frontier
) cm
.doc
.frontier
++;
6762 // Lightweight form of highlight -- proceed over this line and
6763 // update state, but don't save a style array. Used for lines that
6764 // aren't currently visible.
6765 function processLine(cm
, text
, state
, startAt
) {
6766 var mode
= cm
.doc
.mode
;
6767 var stream
= new StringStream(text
, cm
.options
.tabSize
);
6768 stream
.start
= stream
.pos
= startAt
|| 0;
6769 if (text
== "") callBlankLine(mode
, state
);
6770 while (!stream
.eol() && stream
.pos
<= cm
.options
.maxHighlightLength
) {
6771 readToken(mode
, stream
, state
);
6772 stream
.start
= stream
.pos
;
6776 // Convert a style as returned by a mode (either null, or a string
6777 // containing one or more styles) to a CSS style. This is cached,
6778 // and also looks for line-wide styles.
6779 var styleToClassCache
= {}, styleToClassCacheWithMode
= {};
6780 function interpretTokenStyle(style
, options
) {
6781 if (!style
|| /^\s*$/.test(style
)) return null;
6782 var cache
= options
.addModeClass
? styleToClassCacheWithMode : styleToClassCache
;
6783 return cache
[style
] ||
6784 (cache
[style
] = style
.replace(/\S+/g, "cm-$&"));
6787 // Render the DOM representation of the text of a line. Also builds
6788 // up a 'line map', which points at the DOM nodes that represent
6789 // specific stretches of text, and is used by the measuring code.
6790 // The returned object contains the DOM node, this map, and
6791 // information about line-wide styles that were set by the mode.
6792 function buildLineContent(cm
, lineView
) {
6793 // The padding-right forces the element to have a 'border', which
6794 // is needed on Webkit to be able to get line-level bounding
6795 // rectangles for it (in measureChar).
6796 var content
= elt("span", null, null, webkit
? "padding-right: .1px" : null);
6797 var builder
= {pre: elt("pre", [content
]), content: content
,
6798 col: 0, pos: 0, cm: cm
,
6799 splitSpaces: (ie
|| webkit
) && cm
.getOption("lineWrapping")};
6800 lineView
.measure
= {};
6802 // Iterate over the logical lines that make up this visual line.
6803 for (var i
= 0; i
<= (lineView
.rest
? lineView
.rest
.length : 0); i
++) {
6804 var line
= i
? lineView
.rest
[i
- 1] : lineView
.line
, order
;
6806 builder
.addToken
= buildToken
;
6807 // Optionally wire in some hacks into the token-rendering
6808 // algorithm, to deal with browser quirks.
6809 if (hasBadBidiRects(cm
.display
.measure
) && (order
= getOrder(line
)))
6810 builder
.addToken
= buildTokenBadBidi(builder
.addToken
, order
);
6812 var allowFrontierUpdate
= lineView
!= cm
.display
.externalMeasured
&& lineNo(line
);
6813 insertLineContent(line
, builder
, getLineStyles(cm
, line
, allowFrontierUpdate
));
6814 if (line
.styleClasses
) {
6815 if (line
.styleClasses
.bgClass
)
6816 builder
.bgClass
= joinClasses(line
.styleClasses
.bgClass
, builder
.bgClass
|| "");
6817 if (line
.styleClasses
.textClass
)
6818 builder
.textClass
= joinClasses(line
.styleClasses
.textClass
, builder
.textClass
|| "");
6821 // Ensure at least a single node is present, for measuring.
6822 if (builder
.map
.length
== 0)
6823 builder
.map
.push(0, 0, builder
.content
.appendChild(zeroWidthElement(cm
.display
.measure
)));
6825 // Store the map and a cache object for the current logical line
6827 lineView
.measure
.map
= builder
.map
;
6828 lineView
.measure
.cache
= {};
6830 (lineView
.measure
.maps
|| (lineView
.measure
.maps
= [])).push(builder
.map
);
6831 (lineView
.measure
.caches
|| (lineView
.measure
.caches
= [])).push({});
6836 if (webkit
&& /\bcm-tab\b/.test(builder
.content
.lastChild
.className
))
6837 builder
.content
.className
= "cm-tab-wrap-hack";
6839 signal(cm
, "renderLine", cm
, lineView
.line
, builder
.pre
);
6840 if (builder
.pre
.className
)
6841 builder
.textClass
= joinClasses(builder
.pre
.className
, builder
.textClass
|| "");
6846 function defaultSpecialCharPlaceholder(ch
) {
6847 var token
= elt("span", "\u2022", "cm-invalidchar");
6848 token
.title
= "\\u" + ch
.charCodeAt(0).toString(16);
6849 token
.setAttribute("aria-label", token
.title
);
6853 // Build up the DOM representation for a single token, and add it to
6854 // the line map. Takes care to render special characters separately.
6855 function buildToken(builder
, text
, style
, startStyle
, endStyle
, title
, css
) {
6857 var displayText
= builder
.splitSpaces
? text
.replace(/ {3,}/g
, splitSpaces
) : text
;
6858 var special
= builder
.cm
.state
.specialChars
, mustWrap
= false;
6859 if (!special
.test(text
)) {
6860 builder
.col
+= text
.length
;
6861 var content
= document
.createTextNode(displayText
);
6862 builder
.map
.push(builder
.pos
, builder
.pos
+ text
.length
, content
);
6863 if (ie
&& ie_version
< 9) mustWrap
= true;
6864 builder
.pos
+= text
.length
;
6866 var content
= document
.createDocumentFragment(), pos
= 0;
6868 special
.lastIndex
= pos
;
6869 var m
= special
.exec(text
);
6870 var skipped
= m
? m
.index
- pos : text
.length
- pos
;
6872 var txt
= document
.createTextNode(displayText
.slice(pos
, pos
+ skipped
));
6873 if (ie
&& ie_version
< 9) content
.appendChild(elt("span", [txt
]));
6874 else content
.appendChild(txt
);
6875 builder
.map
.push(builder
.pos
, builder
.pos
+ skipped
, txt
);
6876 builder
.col
+= skipped
;
6877 builder
.pos
+= skipped
;
6882 var tabSize
= builder
.cm
.options
.tabSize
, tabWidth
= tabSize
- builder
.col
% tabSize
;
6883 var txt
= content
.appendChild(elt("span", spaceStr(tabWidth
), "cm-tab"));
6884 txt
.setAttribute("role", "presentation");
6885 txt
.setAttribute("cm-text", "\t");
6886 builder
.col
+= tabWidth
;
6888 var txt
= builder
.cm
.options
.specialCharPlaceholder(m
[0]);
6889 txt
.setAttribute("cm-text", m
[0]);
6890 if (ie
&& ie_version
< 9) content
.appendChild(elt("span", [txt
]));
6891 else content
.appendChild(txt
);
6894 builder
.map
.push(builder
.pos
, builder
.pos
+ 1, txt
);
6898 if (style
|| startStyle
|| endStyle
|| mustWrap
|| css
) {
6899 var fullStyle
= style
|| "";
6900 if (startStyle
) fullStyle
+= startStyle
;
6901 if (endStyle
) fullStyle
+= endStyle
;
6902 var token
= elt("span", [content
], fullStyle
, css
);
6903 if (title
) token
.title
= title
;
6904 return builder
.content
.appendChild(token
);
6906 builder
.content
.appendChild(content
);
6909 function splitSpaces(old
) {
6911 for (var i
= 0; i
< old
.length
- 2; ++i
) out
+= i
% 2 ? " " : "\u00a0";
6916 // Work around nonsense dimensions being reported for stretches of
6917 // right-to-left text.
6918 function buildTokenBadBidi(inner
, order
) {
6919 return function(builder
, text
, style
, startStyle
, endStyle
, title
, css
) {
6920 style
= style
? style
+ " cm-force-border" : "cm-force-border";
6921 var start
= builder
.pos
, end
= start
+ text
.length
;
6923 // Find the part that overlaps with the start of this text
6924 for (var i
= 0; i
< order
.length
; i
++) {
6925 var part
= order
[i
];
6926 if (part
.to
> start
&& part
.from <= start
) break;
6928 if (part
.to
>= end
) return inner(builder
, text
, style
, startStyle
, endStyle
, title
, css
);
6929 inner(builder
, text
.slice(0, part
.to
- start
), style
, startStyle
, null, title
, css
);
6931 text
= text
.slice(part
.to
- start
);
6937 function buildCollapsedSpan(builder
, size
, marker
, ignoreWidget
) {
6938 var widget
= !ignoreWidget
&& marker
.widgetNode
;
6939 if (widget
) builder
.map
.push(builder
.pos
, builder
.pos
+ size
, widget
);
6940 if (!ignoreWidget
&& builder
.cm
.display
.input
.needsContentAttribute
) {
6942 widget
= builder
.content
.appendChild(document
.createElement("span"));
6943 widget
.setAttribute("cm-marker", marker
.id
);
6946 builder
.cm
.display
.input
.setUneditable(widget
);
6947 builder
.content
.appendChild(widget
);
6949 builder
.pos
+= size
;
6952 // Outputs a number of spans to make up a line, taking highlighting
6953 // and marked text into account.
6954 function insertLineContent(line
, builder
, styles
) {
6955 var spans
= line
.markedSpans
, allText
= line
.text
, at
= 0;
6957 for (var i
= 1; i
< styles
.length
; i
+=2)
6958 builder
.addToken(builder
, allText
.slice(at
, at
= styles
[i
]), interpretTokenStyle(styles
[i
+1], builder
.cm
.options
));
6962 var len
= allText
.length
, pos
= 0, i
= 1, text
= "", style
, css
;
6963 var nextChange
= 0, spanStyle
, spanEndStyle
, spanStartStyle
, title
, collapsed
;
6965 if (nextChange
== pos
) { // Update current marker set
6966 spanStyle
= spanEndStyle
= spanStartStyle
= title
= css
= "";
6967 collapsed
= null; nextChange
= Infinity
;
6968 var foundBookmarks
= [];
6969 for (var j
= 0; j
< spans
.length
; ++j
) {
6970 var sp
= spans
[j
], m
= sp
.marker
;
6971 if (m
.type
== "bookmark" && sp
.from == pos
&& m
.widgetNode
) {
6972 foundBookmarks
.push(m
);
6973 } else if (sp
.from <= pos
&& (sp
.to
== null || sp
.to
> pos
|| m
.collapsed
&& sp
.to
== pos
&& sp
.from == pos
)) {
6974 if (sp
.to
!= null && sp
.to
!= pos
&& nextChange
> sp
.to
) {
6978 if (m
.className
) spanStyle
+= " " + m
.className
;
6979 if (m
.css
) css
= m
.css
;
6980 if (m
.startStyle
&& sp
.from == pos
) spanStartStyle
+= " " + m
.startStyle
;
6981 if (m
.endStyle
&& sp
.to
== nextChange
) spanEndStyle
+= " " + m
.endStyle
;
6982 if (m
.title
&& !title
) title
= m
.title
;
6983 if (m
.collapsed
&& (!collapsed
|| compareCollapsedMarkers(collapsed
.marker
, m
) < 0))
6985 } else if (sp
.from > pos
&& nextChange
> sp
.from) {
6986 nextChange
= sp
.from;
6989 if (collapsed
&& (collapsed
.from || 0) == pos
) {
6990 buildCollapsedSpan(builder
, (collapsed
.to
== null ? len
+ 1 : collapsed
.to
) - pos
,
6991 collapsed
.marker
, collapsed
.from == null);
6992 if (collapsed
.to
== null) return;
6993 if (collapsed
.to
== pos
) collapsed
= false;
6995 if (!collapsed
&& foundBookmarks
.length
) for (var j
= 0; j
< foundBookmarks
.length
; ++j
)
6996 buildCollapsedSpan(builder
, 0, foundBookmarks
[j
]);
6998 if (pos
>= len
) break;
7000 var upto
= Math
.min(len
, nextChange
);
7003 var end
= pos
+ text
.length
;
7005 var tokenText
= end
> upto
? text
.slice(0, upto
- pos
) : text
;
7006 builder
.addToken(builder
, tokenText
, style
? style
+ spanStyle : spanStyle
,
7007 spanStartStyle
, pos
+ tokenText
.length
== nextChange
? spanEndStyle : "", title
, css
);
7009 if (end
>= upto
) {text
= text
.slice(upto
- pos
); pos
= upto
; break;}
7011 spanStartStyle
= "";
7013 text
= allText
.slice(at
, at
= styles
[i
++]);
7014 style
= interpretTokenStyle(styles
[i
++], builder
.cm
.options
);
7019 // DOCUMENT DATA STRUCTURE
7021 // By default, updates that start and end at the beginning of a line
7022 // are treated specially, in order to make the association of line
7023 // widgets and marker elements with the text behave more intuitive.
7024 function isWholeLineUpdate(doc
, change
) {
7025 return change
.from.ch
== 0 && change
.to
.ch
== 0 && lst(change
.text
) == "" &&
7026 (!doc
.cm
|| doc
.cm
.options
.wholeLineUpdateBefore
);
7029 // Perform a change on the document data structure.
7030 function updateDoc(doc
, change
, markedSpans
, estimateHeight
) {
7031 function spansFor(n
) {return markedSpans
? markedSpans
[n
] : null;}
7032 function update(line
, text
, spans
) {
7033 updateLine(line
, text
, spans
, estimateHeight
);
7034 signalLater(line
, "change", line
, change
);
7036 function linesFor(start
, end
) {
7037 for (var i
= start
, result
= []; i
< end
; ++i
)
7038 result
.push(new Line(text
[i
], spansFor(i
), estimateHeight
));
7042 var from = change
.from, to
= change
.to
, text
= change
.text
;
7043 var firstLine
= getLine(doc
, from.line
), lastLine
= getLine(doc
, to
.line
);
7044 var lastText
= lst(text
), lastSpans
= spansFor(text
.length
- 1), nlines
= to
.line
- from.line
;
7046 // Adjust the line structure
7048 doc
.insert(0, linesFor(0, text
.length
));
7049 doc
.remove(text
.length
, doc
.size
- text
.length
);
7050 } else if (isWholeLineUpdate(doc
, change
)) {
7051 // This is a whole-line replace. Treated specially to make
7052 // sure line objects move the way they are supposed to.
7053 var added
= linesFor(0, text
.length
- 1);
7054 update(lastLine
, lastLine
.text
, lastSpans
);
7055 if (nlines
) doc
.remove(from.line
, nlines
);
7056 if (added
.length
) doc
.insert(from.line
, added
);
7057 } else if (firstLine
== lastLine
) {
7058 if (text
.length
== 1) {
7059 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + lastText
+ firstLine
.text
.slice(to
.ch
), lastSpans
);
7061 var added
= linesFor(1, text
.length
- 1);
7062 added
.push(new Line(lastText
+ firstLine
.text
.slice(to
.ch
), lastSpans
, estimateHeight
));
7063 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0], spansFor(0));
7064 doc
.insert(from.line
+ 1, added
);
7066 } else if (text
.length
== 1) {
7067 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0] + lastLine
.text
.slice(to
.ch
), spansFor(0));
7068 doc
.remove(from.line
+ 1, nlines
);
7070 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0], spansFor(0));
7071 update(lastLine
, lastText
+ lastLine
.text
.slice(to
.ch
), lastSpans
);
7072 var added
= linesFor(1, text
.length
- 1);
7073 if (nlines
> 1) doc
.remove(from.line
+ 1, nlines
- 1);
7074 doc
.insert(from.line
+ 1, added
);
7077 signalLater(doc
, "change", doc
, change
);
7080 // The document is represented as a BTree consisting of leaves, with
7081 // chunk of lines in them, and branches, with up to ten leaves or
7082 // other branch nodes below them. The top node is always a branch
7083 // node, and is the document object itself (meaning it has
7084 // additional methods and properties).
7086 // All nodes have parent links. The tree is used both to go from
7087 // line numbers to line objects, and to go from objects to numbers.
7088 // It also indexes by height, and is used to convert between height
7089 // and line object, and to find the total height of the document.
7091 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
7093 function LeafChunk(lines
) {
7096 for (var i
= 0, height
= 0; i
< lines
.length
; ++i
) {
7097 lines
[i
].parent
= this;
7098 height
+= lines
[i
].height
;
7100 this.height
= height
;
7103 LeafChunk
.prototype = {
7104 chunkSize: function() { return this.lines
.length
; },
7105 // Remove the n lines at offset 'at'.
7106 removeInner: function(at
, n
) {
7107 for (var i
= at
, e
= at
+ n
; i
< e
; ++i
) {
7108 var line
= this.lines
[i
];
7109 this.height
-= line
.height
;
7111 signalLater(line
, "delete");
7113 this.lines
.splice(at
, n
);
7115 // Helper used to collapse a small branch into a single leaf.
7116 collapse: function(lines
) {
7117 lines
.push
.apply(lines
, this.lines
);
7119 // Insert the given array of lines at offset 'at', count them as
7120 // having the given height.
7121 insertInner: function(at
, lines
, height
) {
7122 this.height
+= height
;
7123 this.lines
= this.lines
.slice(0, at
).concat(lines
).concat(this.lines
.slice(at
));
7124 for (var i
= 0; i
< lines
.length
; ++i
) lines
[i
].parent
= this;
7126 // Used to iterate over a part of the tree.
7127 iterN: function(at
, n
, op
) {
7128 for (var e
= at
+ n
; at
< e
; ++at
)
7129 if (op(this.lines
[at
])) return true;
7133 function BranchChunk(children
) {
7134 this.children
= children
;
7135 var size
= 0, height
= 0;
7136 for (var i
= 0; i
< children
.length
; ++i
) {
7137 var ch
= children
[i
];
7138 size
+= ch
.chunkSize(); height
+= ch
.height
;
7142 this.height
= height
;
7146 BranchChunk
.prototype = {
7147 chunkSize: function() { return this.size
; },
7148 removeInner: function(at
, n
) {
7150 for (var i
= 0; i
< this.children
.length
; ++i
) {
7151 var child
= this.children
[i
], sz
= child
.chunkSize();
7153 var rm
= Math
.min(n
, sz
- at
), oldHeight
= child
.height
;
7154 child
.removeInner(at
, rm
);
7155 this.height
-= oldHeight
- child
.height
;
7156 if (sz
== rm
) { this.children
.splice(i
--, 1); child
.parent
= null; }
7157 if ((n
-= rm
) == 0) break;
7161 // If the result is smaller than 25 lines, ensure that it is a
7162 // single leaf node.
7163 if (this.size
- n
< 25 &&
7164 (this.children
.length
> 1 || !(this.children
[0] instanceof LeafChunk
))) {
7166 this.collapse(lines
);
7167 this.children
= [new LeafChunk(lines
)];
7168 this.children
[0].parent
= this;
7171 collapse: function(lines
) {
7172 for (var i
= 0; i
< this.children
.length
; ++i
) this.children
[i
].collapse(lines
);
7174 insertInner: function(at
, lines
, height
) {
7175 this.size
+= lines
.length
;
7176 this.height
+= height
;
7177 for (var i
= 0; i
< this.children
.length
; ++i
) {
7178 var child
= this.children
[i
], sz
= child
.chunkSize();
7180 child
.insertInner(at
, lines
, height
);
7181 if (child
.lines
&& child
.lines
.length
> 50) {
7182 while (child
.lines
.length
> 50) {
7183 var spilled
= child
.lines
.splice(child
.lines
.length
- 25, 25);
7184 var newleaf
= new LeafChunk(spilled
);
7185 child
.height
-= newleaf
.height
;
7186 this.children
.splice(i
+ 1, 0, newleaf
);
7187 newleaf
.parent
= this;
7196 // When a node has grown, check whether it should be split.
7197 maybeSpill: function() {
7198 if (this.children
.length
<= 10) return;
7201 var spilled
= me
.children
.splice(me
.children
.length
- 5, 5);
7202 var sibling
= new BranchChunk(spilled
);
7203 if (!me
.parent
) { // Become the parent node
7204 var copy
= new BranchChunk(me
.children
);
7206 me
.children
= [copy
, sibling
];
7209 me
.size
-= sibling
.size
;
7210 me
.height
-= sibling
.height
;
7211 var myIndex
= indexOf(me
.parent
.children
, me
);
7212 me
.parent
.children
.splice(myIndex
+ 1, 0, sibling
);
7214 sibling
.parent
= me
.parent
;
7215 } while (me
.children
.length
> 10);
7216 me
.parent
.maybeSpill();
7218 iterN: function(at
, n
, op
) {
7219 for (var i
= 0; i
< this.children
.length
; ++i
) {
7220 var child
= this.children
[i
], sz
= child
.chunkSize();
7222 var used
= Math
.min(n
, sz
- at
);
7223 if (child
.iterN(at
, used
, op
)) return true;
7224 if ((n
-= used
) == 0) break;
7232 var Doc
= CodeMirror
.Doc = function(text
, mode
, firstLine
) {
7233 if (!(this instanceof Doc
)) return new Doc(text
, mode
, firstLine
);
7234 if (firstLine
== null) firstLine
= 0;
7236 BranchChunk
.call(this, [new LeafChunk([new Line("", null)])]);
7237 this.first
= firstLine
;
7238 this.scrollTop
= this.scrollLeft
= 0;
7239 this.cantEdit
= false;
7240 this.cleanGeneration
= 1;
7241 this.frontier
= firstLine
;
7242 var start
= Pos(firstLine
, 0);
7243 this.sel
= simpleSelection(start
);
7244 this.history
= new History(null);
7245 this.id
= ++nextDocId
;
7246 this.modeOption
= mode
;
7248 if (typeof text
== "string") text
= splitLines(text
);
7249 updateDoc(this, {from: start
, to: start
, text: text
});
7250 setSelection(this, simpleSelection(start
), sel_dontScroll
);
7253 Doc
.prototype = createObj(BranchChunk
.prototype, {
7255 // Iterate over the document. Supports two forms -- with only one
7256 // argument, it calls that for each line in the document. With
7257 // three, it iterates over the range given by the first two (with
7258 // the second being non-inclusive).
7259 iter: function(from, to
, op
) {
7260 if (op
) this.iterN(from - this.first
, to
- from, op
);
7261 else this.iterN(this.first
, this.first
+ this.size
, from);
7264 // Non-public interface for adding and removing lines.
7265 insert: function(at
, lines
) {
7267 for (var i
= 0; i
< lines
.length
; ++i
) height
+= lines
[i
].height
;
7268 this.insertInner(at
- this.first
, lines
, height
);
7270 remove: function(at
, n
) { this.removeInner(at
- this.first
, n
); },
7272 // From here, the methods are part of the public interface. Most
7273 // are also available from CodeMirror (editor) instances.
7275 getValue: function(lineSep
) {
7276 var lines
= getLines(this, this.first
, this.first
+ this.size
);
7277 if (lineSep
=== false) return lines
;
7278 return lines
.join(lineSep
|| "\n");
7280 setValue: docMethodOp(function(code
) {
7281 var top
= Pos(this.first
, 0), last
= this.first
+ this.size
- 1;
7282 makeChange(this, {from: top
, to: Pos(last
, getLine(this, last
).text
.length
),
7283 text: splitLines(code
), origin: "setValue", full: true}, true);
7284 setSelection(this, simpleSelection(top
));
7286 replaceRange: function(code
, from, to
, origin
) {
7287 from = clipPos(this, from);
7288 to
= to
? clipPos(this, to
) : from;
7289 replaceRange(this, code
, from, to
, origin
);
7291 getRange: function(from, to
, lineSep
) {
7292 var lines
= getBetween(this, clipPos(this, from), clipPos(this, to
));
7293 if (lineSep
=== false) return lines
;
7294 return lines
.join(lineSep
|| "\n");
7297 getLine: function(line
) {var l
= this.getLineHandle(line
); return l
&& l
.text
;},
7299 getLineHandle: function(line
) {if (isLine(this, line
)) return getLine(this, line
);},
7300 getLineNumber: function(line
) {return lineNo(line
);},
7302 getLineHandleVisualStart: function(line
) {
7303 if (typeof line
== "number") line
= getLine(this, line
);
7304 return visualLine(line
);
7307 lineCount: function() {return this.size
;},
7308 firstLine: function() {return this.first
;},
7309 lastLine: function() {return this.first
+ this.size
- 1;},
7311 clipPos: function(pos
) {return clipPos(this, pos
);},
7313 getCursor: function(start
) {
7314 var range
= this.sel
.primary(), pos
;
7315 if (start
== null || start
== "head") pos
= range
.head
;
7316 else if (start
== "anchor") pos
= range
.anchor
;
7317 else if (start
== "end" || start
== "to" || start
=== false) pos
= range
.to();
7318 else pos
= range
.from();
7321 listSelections: function() { return this.sel
.ranges
; },
7322 somethingSelected: function() {return this.sel
.somethingSelected();},
7324 setCursor: docMethodOp(function(line
, ch
, options
) {
7325 setSimpleSelection(this, clipPos(this, typeof line
== "number" ? Pos(line
, ch
|| 0) : line
), null, options
);
7327 setSelection: docMethodOp(function(anchor
, head
, options
) {
7328 setSimpleSelection(this, clipPos(this, anchor
), clipPos(this, head
|| anchor
), options
);
7330 extendSelection: docMethodOp(function(head
, other
, options
) {
7331 extendSelection(this, clipPos(this, head
), other
&& clipPos(this, other
), options
);
7333 extendSelections: docMethodOp(function(heads
, options
) {
7334 extendSelections(this, clipPosArray(this, heads
, options
));
7336 extendSelectionsBy: docMethodOp(function(f
, options
) {
7337 extendSelections(this, map(this.sel
.ranges
, f
), options
);
7339 setSelections: docMethodOp(function(ranges
, primary
, options
) {
7340 if (!ranges
.length
) return;
7341 for (var i
= 0, out
= []; i
< ranges
.length
; i
++)
7342 out
[i
] = new Range(clipPos(this, ranges
[i
].anchor
),
7343 clipPos(this, ranges
[i
].head
));
7344 if (primary
== null) primary
= Math
.min(ranges
.length
- 1, this.sel
.primIndex
);
7345 setSelection(this, normalizeSelection(out
, primary
), options
);
7347 addSelection: docMethodOp(function(anchor
, head
, options
) {
7348 var ranges
= this.sel
.ranges
.slice(0);
7349 ranges
.push(new Range(clipPos(this, anchor
), clipPos(this, head
|| anchor
)));
7350 setSelection(this, normalizeSelection(ranges
, ranges
.length
- 1), options
);
7353 getSelection: function(lineSep
) {
7354 var ranges
= this.sel
.ranges
, lines
;
7355 for (var i
= 0; i
< ranges
.length
; i
++) {
7356 var sel
= getBetween(this, ranges
[i
].from(), ranges
[i
].to());
7357 lines
= lines
? lines
.concat(sel
) : sel
;
7359 if (lineSep
=== false) return lines
;
7360 else return lines
.join(lineSep
|| "\n");
7362 getSelections: function(lineSep
) {
7363 var parts
= [], ranges
= this.sel
.ranges
;
7364 for (var i
= 0; i
< ranges
.length
; i
++) {
7365 var sel
= getBetween(this, ranges
[i
].from(), ranges
[i
].to());
7366 if (lineSep
!== false) sel
= sel
.join(lineSep
|| "\n");
7371 replaceSelection: function(code
, collapse
, origin
) {
7373 for (var i
= 0; i
< this.sel
.ranges
.length
; i
++)
7375 this.replaceSelections(dup
, collapse
, origin
|| "+input");
7377 replaceSelections: docMethodOp(function(code
, collapse
, origin
) {
7378 var changes
= [], sel
= this.sel
;
7379 for (var i
= 0; i
< sel
.ranges
.length
; i
++) {
7380 var range
= sel
.ranges
[i
];
7381 changes
[i
] = {from: range
.from(), to: range
.to(), text: splitLines(code
[i
]), origin: origin
};
7383 var newSel
= collapse
&& collapse
!= "end" && computeReplacedSel(this, changes
, collapse
);
7384 for (var i
= changes
.length
- 1; i
>= 0; i
--)
7385 makeChange(this, changes
[i
]);
7386 if (newSel
) setSelectionReplaceHistory(this, newSel
);
7387 else if (this.cm
) ensureCursorVisible(this.cm
);
7389 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
7390 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
7391 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
7392 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}),
7394 setExtending: function(val
) {this.extend
= val
;},
7395 getExtending: function() {return this.extend
;},
7397 historySize: function() {
7398 var hist
= this.history
, done
= 0, undone
= 0;
7399 for (var i
= 0; i
< hist
.done
.length
; i
++) if (!hist
.done
[i
].ranges
) ++done
;
7400 for (var i
= 0; i
< hist
.undone
.length
; i
++) if (!hist
.undone
[i
].ranges
) ++undone
;
7401 return {undo: done
, redo: undone
};
7403 clearHistory: function() {this.history
= new History(this.history
.maxGeneration
);},
7405 markClean: function() {
7406 this.cleanGeneration
= this.changeGeneration(true);
7408 changeGeneration: function(forceSplit
) {
7410 this.history
.lastOp
= this.history
.lastSelOp
= this.history
.lastOrigin
= null;
7411 return this.history
.generation
;
7413 isClean: function (gen
) {
7414 return this.history
.generation
== (gen
|| this.cleanGeneration
);
7417 getHistory: function() {
7418 return {done: copyHistoryArray(this.history
.done
),
7419 undone: copyHistoryArray(this.history
.undone
)};
7421 setHistory: function(histData
) {
7422 var hist
= this.history
= new History(this.history
.maxGeneration
);
7423 hist
.done
= copyHistoryArray(histData
.done
.slice(0), null, true);
7424 hist
.undone
= copyHistoryArray(histData
.undone
.slice(0), null, true);
7427 addLineClass: docMethodOp(function(handle
, where
, cls
) {
7428 return changeLine(this, handle
, where
== "gutter" ? "gutter" : "class", function(line
) {
7429 var prop
= where
== "text" ? "textClass"
7430 : where
== "background" ? "bgClass"
7431 : where
== "gutter" ? "gutterClass" : "wrapClass";
7432 if (!line
[prop
]) line
[prop
] = cls
;
7433 else if (classTest(cls
).test(line
[prop
])) return false;
7434 else line
[prop
] += " " + cls
;
7438 removeLineClass: docMethodOp(function(handle
, where
, cls
) {
7439 return changeLine(this, handle
, where
== "gutter" ? "gutter" : "class", function(line
) {
7440 var prop
= where
== "text" ? "textClass"
7441 : where
== "background" ? "bgClass"
7442 : where
== "gutter" ? "gutterClass" : "wrapClass";
7443 var cur
= line
[prop
];
7444 if (!cur
) return false;
7445 else if (cls
== null) line
[prop
] = null;
7447 var found
= cur
.match(classTest(cls
));
7448 if (!found
) return false;
7449 var end
= found
.index
+ found
[0].length
;
7450 line
[prop
] = cur
.slice(0, found
.index
) + (!found
.index
|| end
== cur
.length
? "" : " ") + cur
.slice(end
) || null;
7456 addLineWidget: docMethodOp(function(handle
, node
, options
) {
7457 return addLineWidget(this, handle
, node
, options
);
7459 removeLineWidget: function(widget
) { widget
.clear(); },
7461 markText: function(from, to
, options
) {
7462 return markText(this, clipPos(this, from), clipPos(this, to
), options
, "range");
7464 setBookmark: function(pos
, options
) {
7465 var realOpts
= {replacedWith: options
&& (options
.nodeType
== null ? options
.widget : options
),
7466 insertLeft: options
&& options
.insertLeft
,
7467 clearWhenEmpty: false, shared: options
&& options
.shared
,
7468 handleMouseEvents: options
&& options
.handleMouseEvents
};
7469 pos
= clipPos(this, pos
);
7470 return markText(this, pos
, pos
, realOpts
, "bookmark");
7472 findMarksAt: function(pos
) {
7473 pos
= clipPos(this, pos
);
7474 var markers
= [], spans
= getLine(this, pos
.line
).markedSpans
;
7475 if (spans
) for (var i
= 0; i
< spans
.length
; ++i
) {
7476 var span
= spans
[i
];
7477 if ((span
.from == null || span
.from <= pos
.ch
) &&
7478 (span
.to
== null || span
.to
>= pos
.ch
))
7479 markers
.push(span
.marker
.parent
|| span
.marker
);
7483 findMarks: function(from, to
, filter
) {
7484 from = clipPos(this, from); to
= clipPos(this, to
);
7485 var found
= [], lineNo
= from.line
;
7486 this.iter(from.line
, to
.line
+ 1, function(line
) {
7487 var spans
= line
.markedSpans
;
7488 if (spans
) for (var i
= 0; i
< spans
.length
; i
++) {
7489 var span
= spans
[i
];
7490 if (!(lineNo
== from.line
&& from.ch
> span
.to
||
7491 span
.from == null && lineNo
!= from.line
||
7492 lineNo
== to
.line
&& span
.from > to
.ch
) &&
7493 (!filter
|| filter(span
.marker
)))
7494 found
.push(span
.marker
.parent
|| span
.marker
);
7500 getAllMarks: function() {
7502 this.iter(function(line
) {
7503 var sps
= line
.markedSpans
;
7504 if (sps
) for (var i
= 0; i
< sps
.length
; ++i
)
7505 if (sps
[i
].from != null) markers
.push(sps
[i
].marker
);
7510 posFromIndex: function(off
) {
7511 var ch
, lineNo
= this.first
;
7512 this.iter(function(line
) {
7513 var sz
= line
.text
.length
+ 1;
7514 if (sz
> off
) { ch
= off
; return true; }
7518 return clipPos(this, Pos(lineNo
, ch
));
7520 indexFromPos: function (coords
) {
7521 coords
= clipPos(this, coords
);
7522 var index
= coords
.ch
;
7523 if (coords
.line
< this.first
|| coords
.ch
< 0) return 0;
7524 this.iter(this.first
, coords
.line
, function (line
) {
7525 index
+= line
.text
.length
+ 1;
7530 copy: function(copyHistory
) {
7531 var doc
= new Doc(getLines(this, this.first
, this.first
+ this.size
), this.modeOption
, this.first
);
7532 doc
.scrollTop
= this.scrollTop
; doc
.scrollLeft
= this.scrollLeft
;
7536 doc
.history
.undoDepth
= this.history
.undoDepth
;
7537 doc
.setHistory(this.getHistory());
7542 linkedDoc: function(options
) {
7543 if (!options
) options
= {};
7544 var from = this.first
, to
= this.first
+ this.size
;
7545 if (options
.from != null && options
.from > from) from = options
.from;
7546 if (options
.to
!= null && options
.to
< to
) to
= options
.to
;
7547 var copy
= new Doc(getLines(this, from, to
), options
.mode
|| this.modeOption
, from);
7548 if (options
.sharedHist
) copy
.history
= this.history
;
7549 (this.linked
|| (this.linked
= [])).push({doc: copy
, sharedHist: options
.sharedHist
});
7550 copy
.linked
= [{doc: this, isParent: true, sharedHist: options
.sharedHist
}];
7551 copySharedMarkers(copy
, findSharedMarkers(this));
7554 unlinkDoc: function(other
) {
7555 if (other
instanceof CodeMirror
) other
= other
.doc
;
7556 if (this.linked
) for (var i
= 0; i
< this.linked
.length
; ++i
) {
7557 var link
= this.linked
[i
];
7558 if (link
.doc
!= other
) continue;
7559 this.linked
.splice(i
, 1);
7560 other
.unlinkDoc(this);
7561 detachSharedMarkers(findSharedMarkers(this));
7564 // If the histories were shared, split them again
7565 if (other
.history
== this.history
) {
7566 var splitIds
= [other
.id
];
7567 linkedDocs(other
, function(doc
) {splitIds
.push(doc
.id
);}, true);
7568 other
.history
= new History(null);
7569 other
.history
.done
= copyHistoryArray(this.history
.done
, splitIds
);
7570 other
.history
.undone
= copyHistoryArray(this.history
.undone
, splitIds
);
7573 iterLinkedDocs: function(f
) {linkedDocs(this, f
);},
7575 getMode: function() {return this.mode
;},
7576 getEditor: function() {return this.cm
;}
7580 Doc
.prototype.eachLine
= Doc
.prototype.iter
;
7582 // Set up methods on CodeMirror's prototype to redirect to the editor's document.
7583 var dontDelegate
= "iter insert remove copy getEditor".split(" ");
7584 for (var prop
in Doc
.prototype) if (Doc
.prototype.hasOwnProperty(prop
) && indexOf(dontDelegate
, prop
) < 0)
7585 CodeMirror
.prototype[prop
] = (function(method
) {
7586 return function() {return method
.apply(this.doc
, arguments
);};
7587 })(Doc
.prototype[prop
]);
7591 // Call f for all linked documents.
7592 function linkedDocs(doc
, f
, sharedHistOnly
) {
7593 function propagate(doc
, skip
, sharedHist
) {
7594 if (doc
.linked
) for (var i
= 0; i
< doc
.linked
.length
; ++i
) {
7595 var rel
= doc
.linked
[i
];
7596 if (rel
.doc
== skip
) continue;
7597 var shared
= sharedHist
&& rel
.sharedHist
;
7598 if (sharedHistOnly
&& !shared
) continue;
7600 propagate(rel
.doc
, doc
, shared
);
7603 propagate(doc
, null, true);
7606 // Attach a document to an editor.
7607 function attachDoc(cm
, doc
) {
7608 if (doc
.cm
) throw new Error("This document is already in use.");
7611 estimateLineHeights(cm
);
7613 if (!cm
.options
.lineWrapping
) findMaxLine(cm
);
7614 cm
.options
.mode
= doc
.modeOption
;
7620 // Find the line object corresponding to the given line number.
7621 function getLine(doc
, n
) {
7623 if (n
< 0 || n
>= doc
.size
) throw new Error("There is no line " + (n
+ doc
.first
) + " in the document.");
7624 for (var chunk
= doc
; !chunk
.lines
;) {
7625 for (var i
= 0;; ++i
) {
7626 var child
= chunk
.children
[i
], sz
= child
.chunkSize();
7627 if (n
< sz
) { chunk
= child
; break; }
7631 return chunk
.lines
[n
];
7634 // Get the part of a document between two positions, as an array of
7636 function getBetween(doc
, start
, end
) {
7637 var out
= [], n
= start
.line
;
7638 doc
.iter(start
.line
, end
.line
+ 1, function(line
) {
7639 var text
= line
.text
;
7640 if (n
== end
.line
) text
= text
.slice(0, end
.ch
);
7641 if (n
== start
.line
) text
= text
.slice(start
.ch
);
7647 // Get the lines between from and to, as array of strings.
7648 function getLines(doc
, from, to
) {
7650 doc
.iter(from, to
, function(line
) { out
.push(line
.text
); });
7654 // Update the height of a line, propagating the height change
7655 // upwards to parent nodes.
7656 function updateLineHeight(line
, height
) {
7657 var diff
= height
- line
.height
;
7658 if (diff
) for (var n
= line
; n
; n
= n
.parent
) n
.height
+= diff
;
7661 // Given a line object, find its line number by walking up through
7662 // its parent links.
7663 function lineNo(line
) {
7664 if (line
.parent
== null) return null;
7665 var cur
= line
.parent
, no
= indexOf(cur
.lines
, line
);
7666 for (var chunk
= cur
.parent
; chunk
; cur
= chunk
, chunk
= chunk
.parent
) {
7667 for (var i
= 0;; ++i
) {
7668 if (chunk
.children
[i
] == cur
) break;
7669 no
+= chunk
.children
[i
].chunkSize();
7672 return no
+ cur
.first
;
7675 // Find the line at the given vertical position, using the height
7676 // information in the document tree.
7677 function lineAtHeight(chunk
, h
) {
7678 var n
= chunk
.first
;
7680 for (var i
= 0; i
< chunk
.children
.length
; ++i
) {
7681 var child
= chunk
.children
[i
], ch
= child
.height
;
7682 if (h
< ch
) { chunk
= child
; continue outer
; }
7684 n
+= child
.chunkSize();
7687 } while (!chunk
.lines
);
7688 for (var i
= 0; i
< chunk
.lines
.length
; ++i
) {
7689 var line
= chunk
.lines
[i
], lh
= line
.height
;
7697 // Find the height above the given line.
7698 function heightAtLine(lineObj
) {
7699 lineObj
= visualLine(lineObj
);
7701 var h
= 0, chunk
= lineObj
.parent
;
7702 for (var i
= 0; i
< chunk
.lines
.length
; ++i
) {
7703 var line
= chunk
.lines
[i
];
7704 if (line
== lineObj
) break;
7705 else h
+= line
.height
;
7707 for (var p
= chunk
.parent
; p
; chunk
= p
, p
= chunk
.parent
) {
7708 for (var i
= 0; i
< p
.children
.length
; ++i
) {
7709 var cur
= p
.children
[i
];
7710 if (cur
== chunk
) break;
7711 else h
+= cur
.height
;
7717 // Get the bidi ordering for the given line (and cache it). Returns
7718 // false for lines that are fully left-to-right, and an array of
7719 // BidiSpan objects otherwise.
7720 function getOrder(line
) {
7721 var order
= line
.order
;
7722 if (order
== null) order
= line
.order
= bidiOrdering(line
.text
);
7728 function History(startGen
) {
7729 // Arrays of change events and selections. Doing something adds an
7730 // event to done and clears undo. Undoing moves events from done
7731 // to undone, redoing moves them in the other direction.
7732 this.done
= []; this.undone
= [];
7733 this.undoDepth
= Infinity
;
7734 // Used to track when changes can be merged into a single undo
7736 this.lastModTime
= this.lastSelTime
= 0;
7737 this.lastOp
= this.lastSelOp
= null;
7738 this.lastOrigin
= this.lastSelOrigin
= null;
7739 // Used by the isClean() method
7740 this.generation
= this.maxGeneration
= startGen
|| 1;
7743 // Create a history change event from an updateDoc-style change
7745 function historyChangeFromChange(doc
, change
) {
7746 var histChange
= {from: copyPos(change
.from), to: changeEnd(change
), text: getBetween(doc
, change
.from, change
.to
)};
7747 attachLocalSpans(doc
, histChange
, change
.from.line
, change
.to
.line
+ 1);
7748 linkedDocs(doc
, function(doc
) {attachLocalSpans(doc
, histChange
, change
.from.line
, change
.to
.line
+ 1);}, true);
7752 // Pop all selection events off the end of a history array. Stop at
7754 function clearSelectionEvents(array
) {
7755 while (array
.length
) {
7756 var last
= lst(array
);
7757 if (last
.ranges
) array
.pop();
7762 // Find the top change event in the history. Pop off selection
7763 // events that are in the way.
7764 function lastChangeEvent(hist
, force
) {
7766 clearSelectionEvents(hist
.done
);
7767 return lst(hist
.done
);
7768 } else if (hist
.done
.length
&& !lst(hist
.done
).ranges
) {
7769 return lst(hist
.done
);
7770 } else if (hist
.done
.length
> 1 && !hist
.done
[hist
.done
.length
- 2].ranges
) {
7772 return lst(hist
.done
);
7776 // Register a change in the history. Merges changes that are within
7777 // a single operation, ore are close together with an origin that
7778 // allows merging (starting with "+") into a single event.
7779 function addChangeToHistory(doc
, change
, selAfter
, opId
) {
7780 var hist
= doc
.history
;
7781 hist
.undone
.length
= 0;
7782 var time
= +new Date
, cur
;
7784 if ((hist
.lastOp
== opId
||
7785 hist
.lastOrigin
== change
.origin
&& change
.origin
&&
7786 ((change
.origin
.charAt(0) == "+" && doc
.cm
&& hist
.lastModTime
> time
- doc
.cm
.options
.historyEventDelay
) ||
7787 change
.origin
.charAt(0) == "*")) &&
7788 (cur
= lastChangeEvent(hist
, hist
.lastOp
== opId
))) {
7789 // Merge this change into the last event
7790 var last
= lst(cur
.changes
);
7791 if (cmp(change
.from, change
.to
) == 0 && cmp(change
.from, last
.to
) == 0) {
7792 // Optimized case for simple insertion -- don't want to add
7793 // new changesets for every character typed
7794 last
.to
= changeEnd(change
);
7796 // Add new sub-event
7797 cur
.changes
.push(historyChangeFromChange(doc
, change
));
7800 // Can not be merged, start a new event.
7801 var before
= lst(hist
.done
);
7802 if (!before
|| !before
.ranges
)
7803 pushSelectionToHistory(doc
.sel
, hist
.done
);
7804 cur
= {changes: [historyChangeFromChange(doc
, change
)],
7805 generation: hist
.generation
};
7806 hist
.done
.push(cur
);
7807 while (hist
.done
.length
> hist
.undoDepth
) {
7809 if (!hist
.done
[0].ranges
) hist
.done
.shift();
7812 hist
.done
.push(selAfter
);
7813 hist
.generation
= ++hist
.maxGeneration
;
7814 hist
.lastModTime
= hist
.lastSelTime
= time
;
7815 hist
.lastOp
= hist
.lastSelOp
= opId
;
7816 hist
.lastOrigin
= hist
.lastSelOrigin
= change
.origin
;
7818 if (!last
) signal(doc
, "historyAdded");
7821 function selectionEventCanBeMerged(doc
, origin
, prev
, sel
) {
7822 var ch
= origin
.charAt(0);
7825 prev
.ranges
.length
== sel
.ranges
.length
&&
7826 prev
.somethingSelected() == sel
.somethingSelected() &&
7827 new Date
- doc
.history
.lastSelTime
<= (doc
.cm
? doc
.cm
.options
.historyEventDelay : 500);
7830 // Called whenever the selection changes, sets the new selection as
7831 // the pending selection in the history, and pushes the old pending
7832 // selection into the 'done' array when it was significantly
7833 // different (in number of selected ranges, emptiness, or time).
7834 function addSelectionToHistory(doc
, sel
, opId
, options
) {
7835 var hist
= doc
.history
, origin
= options
&& options
.origin
;
7837 // A new event is started when the previous origin does not match
7838 // the current, or the origins don't allow matching. Origins
7839 // starting with * are always merged, those starting with + are
7840 // merged when similar and close together in time.
7841 if (opId
== hist
.lastSelOp
||
7842 (origin
&& hist
.lastSelOrigin
== origin
&&
7843 (hist
.lastModTime
== hist
.lastSelTime
&& hist
.lastOrigin
== origin
||
7844 selectionEventCanBeMerged(doc
, origin
, lst(hist
.done
), sel
))))
7845 hist
.done
[hist
.done
.length
- 1] = sel
;
7847 pushSelectionToHistory(sel
, hist
.done
);
7849 hist
.lastSelTime
= +new Date
;
7850 hist
.lastSelOrigin
= origin
;
7851 hist
.lastSelOp
= opId
;
7852 if (options
&& options
.clearRedo
!== false)
7853 clearSelectionEvents(hist
.undone
);
7856 function pushSelectionToHistory(sel
, dest
) {
7857 var top
= lst(dest
);
7858 if (!(top
&& top
.ranges
&& top
.equals(sel
)))
7862 // Used to store marked span information in the history.
7863 function attachLocalSpans(doc
, change
, from, to
) {
7864 var existing
= change
["spans_" + doc
.id
], n
= 0;
7865 doc
.iter(Math
.max(doc
.first
, from), Math
.min(doc
.first
+ doc
.size
, to
), function(line
) {
7866 if (line
.markedSpans
)
7867 (existing
|| (existing
= change
["spans_" + doc
.id
] = {}))[n
] = line
.markedSpans
;
7872 // When un/re-doing restores text containing marked spans, those
7873 // that have been explicitly cleared should not be restored.
7874 function removeClearedSpans(spans
) {
7875 if (!spans
) return null;
7876 for (var i
= 0, out
; i
< spans
.length
; ++i
) {
7877 if (spans
[i
].marker
.explicitlyCleared
) { if (!out
) out
= spans
.slice(0, i
); }
7878 else if (out
) out
.push(spans
[i
]);
7880 return !out
? spans : out
.length
? out : null;
7883 // Retrieve and filter the old marked spans stored in a change event.
7884 function getOldSpans(doc
, change
) {
7885 var found
= change
["spans_" + doc
.id
];
7886 if (!found
) return null;
7887 for (var i
= 0, nw
= []; i
< change
.text
.length
; ++i
)
7888 nw
.push(removeClearedSpans(found
[i
]));
7892 // Used both to provide a JSON-safe object in .getHistory, and, when
7893 // detaching a document, to split the history in two
7894 function copyHistoryArray(events
, newGroup
, instantiateSel
) {
7895 for (var i
= 0, copy
= []; i
< events
.length
; ++i
) {
7896 var event
= events
[i
];
7898 copy
.push(instantiateSel
? Selection
.prototype.deepCopy
.call(event
) : event
);
7901 var changes
= event
.changes
, newChanges
= [];
7902 copy
.push({changes: newChanges
});
7903 for (var j
= 0; j
< changes
.length
; ++j
) {
7904 var change
= changes
[j
], m
;
7905 newChanges
.push({from: change
.from, to: change
.to
, text: change
.text
});
7906 if (newGroup
) for (var prop
in change
) if (m
= prop
.match(/^spans_(\d+)$/)) {
7907 if (indexOf(newGroup
, Number(m
[1])) > -1) {
7908 lst(newChanges
)[prop
] = change
[prop
];
7909 delete change
[prop
];
7917 // Rebasing/resetting history to deal with externally-sourced changes
7919 function rebaseHistSelSingle(pos
, from, to
, diff
) {
7920 if (to
< pos
.line
) {
7922 } else if (from < pos
.line
) {
7928 // Tries to rebase an array of history events given a change in the
7929 // document. If the change touches the same lines as the event, the
7930 // event, and everything 'behind' it, is discarded. If the change is
7931 // before the event, the event's positions are updated. Uses a
7932 // copy-on-write scheme for the positions, to avoid having to
7933 // reallocate them all on every rebase, but also avoid problems with
7934 // shared position objects being unsafely updated.
7935 function rebaseHistArray(array
, from, to
, diff
) {
7936 for (var i
= 0; i
< array
.length
; ++i
) {
7937 var sub
= array
[i
], ok
= true;
7939 if (!sub
.copied
) { sub
= array
[i
] = sub
.deepCopy(); sub
.copied
= true; }
7940 for (var j
= 0; j
< sub
.ranges
.length
; j
++) {
7941 rebaseHistSelSingle(sub
.ranges
[j
].anchor
, from, to
, diff
);
7942 rebaseHistSelSingle(sub
.ranges
[j
].head
, from, to
, diff
);
7946 for (var j
= 0; j
< sub
.changes
.length
; ++j
) {
7947 var cur
= sub
.changes
[j
];
7948 if (to
< cur
.from.line
) {
7949 cur
.from = Pos(cur
.from.line
+ diff
, cur
.from.ch
);
7950 cur
.to
= Pos(cur
.to
.line
+ diff
, cur
.to
.ch
);
7951 } else if (from <= cur
.to
.line
) {
7957 array
.splice(0, i
+ 1);
7963 function rebaseHist(hist
, change
) {
7964 var from = change
.from.line
, to
= change
.to
.line
, diff
= change
.text
.length
- (to
- from) - 1;
7965 rebaseHistArray(hist
.done
, from, to
, diff
);
7966 rebaseHistArray(hist
.undone
, from, to
, diff
);
7971 // Due to the fact that we still support jurassic IE versions, some
7972 // compatibility wrappers are needed.
7974 var e_preventDefault
= CodeMirror
.e_preventDefault = function(e
) {
7975 if (e
.preventDefault
) e
.preventDefault();
7976 else e
.returnValue
= false;
7978 var e_stopPropagation
= CodeMirror
.e_stopPropagation = function(e
) {
7979 if (e
.stopPropagation
) e
.stopPropagation();
7980 else e
.cancelBubble
= true;
7982 function e_defaultPrevented(e
) {
7983 return e
.defaultPrevented
!= null ? e
.defaultPrevented : e
.returnValue
== false;
7985 var e_stop
= CodeMirror
.e_stop = function(e
) {e_preventDefault(e
); e_stopPropagation(e
);};
7987 function e_target(e
) {return e
.target
|| e
.srcElement
;}
7988 function e_button(e
) {
7991 if (e
.button
& 1) b
= 1;
7992 else if (e
.button
& 2) b
= 3;
7993 else if (e
.button
& 4) b
= 2;
7995 if (mac
&& e
.ctrlKey
&& b
== 1) b
= 3;
8001 // Lightweight event framework. on/off also work on DOM nodes,
8002 // registering native DOM handlers.
8004 var on
= CodeMirror
.on = function(emitter
, type
, f
) {
8005 if (emitter
.addEventListener
)
8006 emitter
.addEventListener(type
, f
, false);
8007 else if (emitter
.attachEvent
)
8008 emitter
.attachEvent("on" + type
, f
);
8010 var map
= emitter
._handlers
|| (emitter
._handlers
= {});
8011 var arr
= map
[type
] || (map
[type
] = []);
8016 var off
= CodeMirror
.off = function(emitter
, type
, f
) {
8017 if (emitter
.removeEventListener
)
8018 emitter
.removeEventListener(type
, f
, false);
8019 else if (emitter
.detachEvent
)
8020 emitter
.detachEvent("on" + type
, f
);
8022 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
8024 for (var i
= 0; i
< arr
.length
; ++i
)
8025 if (arr
[i
] == f
) { arr
.splice(i
, 1); break; }
8029 var signal
= CodeMirror
.signal = function(emitter
, type
/*, values...*/) {
8030 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
8032 var args
= Array
.prototype.slice
.call(arguments
, 2);
8033 for (var i
= 0; i
< arr
.length
; ++i
) arr
[i
].apply(null, args
);
8036 var orphanDelayedCallbacks
= null;
8038 // Often, we want to signal events at a point where we are in the
8039 // middle of some work, but don't want the handler to start calling
8040 // other methods on the editor, which might be in an inconsistent
8041 // state or simply not expect any other events to happen.
8042 // signalLater looks whether there are any handlers, and schedules
8043 // them to be executed when the last operation ends, or, if no
8044 // operation is active, when a timeout fires.
8045 function signalLater(emitter
, type
/*, values...*/) {
8046 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
8048 var args
= Array
.prototype.slice
.call(arguments
, 2), list
;
8049 if (operationGroup
) {
8050 list
= operationGroup
.delayedCallbacks
;
8051 } else if (orphanDelayedCallbacks
) {
8052 list
= orphanDelayedCallbacks
;
8054 list
= orphanDelayedCallbacks
= [];
8055 setTimeout(fireOrphanDelayed
, 0);
8057 function bnd(f
) {return function(){f
.apply(null, args
);};};
8058 for (var i
= 0; i
< arr
.length
; ++i
)
8059 list
.push(bnd(arr
[i
]));
8062 function fireOrphanDelayed() {
8063 var delayed
= orphanDelayedCallbacks
;
8064 orphanDelayedCallbacks
= null;
8065 for (var i
= 0; i
< delayed
.length
; ++i
) delayed
[i
]();
8068 // The DOM events that CodeMirror handles can be overridden by
8069 // registering a (non-DOM) handler on the editor for the event name,
8070 // and preventDefault-ing the event in that handler.
8071 function signalDOMEvent(cm
, e
, override
) {
8072 if (typeof e
== "string")
8073 e
= {type: e
, preventDefault: function() { this.defaultPrevented
= true; }};
8074 signal(cm
, override
|| e
.type
, cm
, e
);
8075 return e_defaultPrevented(e
) || e
.codemirrorIgnore
;
8078 function signalCursorActivity(cm
) {
8079 var arr
= cm
._handlers
&& cm
._handlers
.cursorActivity
;
8081 var set = cm
.curOp
.cursorActivityHandlers
|| (cm
.curOp
.cursorActivityHandlers
= []);
8082 for (var i
= 0; i
< arr
.length
; ++i
) if (indexOf(set, arr
[i
]) == -1)
8086 function hasHandler(emitter
, type
) {
8087 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
8088 return arr
&& arr
.length
> 0;
8091 // Add on and off methods to a constructor's prototype, to make
8092 // registering events on such objects more convenient.
8093 function eventMixin(ctor
) {
8094 ctor
.prototype.on = function(type
, f
) {on(this, type
, f
);};
8095 ctor
.prototype.off = function(type
, f
) {off(this, type
, f
);};
8100 // Number of pixels added to scroller and sizer to hide scrollbar
8101 var scrollerGap
= 30;
8103 // Returned or thrown by various protocols to signal 'I'm not
8105 var Pass
= CodeMirror
.Pass
= {toString: function(){return "CodeMirror.Pass";}};
8107 // Reused option objects for setSelection & friends
8108 var sel_dontScroll
= {scroll: false}, sel_mouse
= {origin: "*mouse"}, sel_move
= {origin: "+move"};
8110 function Delayed() {this.id
= null;}
8111 Delayed
.prototype.set = function(ms
, f
) {
8112 clearTimeout(this.id
);
8113 this.id
= setTimeout(f
, ms
);
8116 // Counts the column offset in a string, taking tabs into account.
8117 // Used mostly to find indentation.
8118 var countColumn
= CodeMirror
.countColumn = function(string
, end
, tabSize
, startIndex
, startValue
) {
8120 end
= string
.search(/[^\s\u00a0]/);
8121 if (end
== -1) end
= string
.length
;
8123 for (var i
= startIndex
|| 0, n
= startValue
|| 0;;) {
8124 var nextTab
= string
.indexOf("\t", i
);
8125 if (nextTab
< 0 || nextTab
>= end
)
8126 return n
+ (end
- i
);
8128 n
+= tabSize
- (n
% tabSize
);
8133 // The inverse of countColumn -- find the offset that corresponds to
8134 // a particular column.
8135 function findColumn(string
, goal
, tabSize
) {
8136 for (var pos
= 0, col
= 0;;) {
8137 var nextTab
= string
.indexOf("\t", pos
);
8138 if (nextTab
== -1) nextTab
= string
.length
;
8139 var skipped
= nextTab
- pos
;
8140 if (nextTab
== string
.length
|| col
+ skipped
>= goal
)
8141 return pos
+ Math
.min(skipped
, goal
- col
);
8142 col
+= nextTab
- pos
;
8143 col
+= tabSize
- (col
% tabSize
);
8145 if (col
>= goal
) return pos
;
8149 var spaceStrs
= [""];
8150 function spaceStr(n
) {
8151 while (spaceStrs
.length
<= n
)
8152 spaceStrs
.push(lst(spaceStrs
) + " ");
8153 return spaceStrs
[n
];
8156 function lst(arr
) { return arr
[arr
.length
-1]; }
8158 var selectInput = function(node
) { node
.select(); };
8159 if (ios
) // Mobile Safari apparently has a bug where select() is broken.
8160 selectInput = function(node
) { node
.selectionStart
= 0; node
.selectionEnd
= node
.value
.length
; };
8161 else if (ie
) // Suppress mysterious IE10 errors
8162 selectInput = function(node
) { try { node
.select(); } catch(_e
) {} };
8164 function indexOf(array
, elt
) {
8165 for (var i
= 0; i
< array
.length
; ++i
)
8166 if (array
[i
] == elt
) return i
;
8169 function map(array
, f
) {
8171 for (var i
= 0; i
< array
.length
; i
++) out
[i
] = f(array
[i
], i
);
8175 function nothing() {}
8177 function createObj(base
, props
) {
8179 if (Object
.create
) {
8180 inst
= Object
.create(base
);
8182 nothing
.prototype = base
;
8183 inst
= new nothing();
8185 if (props
) copyObj(props
, inst
);
8189 function copyObj(obj
, target
, overwrite
) {
8190 if (!target
) target
= {};
8191 for (var prop
in obj
)
8192 if (obj
.hasOwnProperty(prop
) && (overwrite
!== false || !target
.hasOwnProperty(prop
)))
8193 target
[prop
] = obj
[prop
];
8198 var args
= Array
.prototype.slice
.call(arguments
, 1);
8199 return function(){return f
.apply(null, args
);};
8202 var nonASCIISingleCaseWordChar
= /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
8203 var isWordCharBasic
= CodeMirror
.isWordChar = function(ch
) {
8204 return /\w/.test(ch
) || ch
> "\x80" &&
8205 (ch
.toUpperCase() != ch
.toLowerCase() || nonASCIISingleCaseWordChar
.test(ch
));
8207 function isWordChar(ch
, helper
) {
8208 if (!helper
) return isWordCharBasic(ch
);
8209 if (helper
.source
.indexOf("\\w") > -1 && isWordCharBasic(ch
)) return true;
8210 return helper
.test(ch
);
8213 function isEmpty(obj
) {
8214 for (var n
in obj
) if (obj
.hasOwnProperty(n
) && obj
[n
]) return false;
8218 // Extending unicode characters. A series of a non-extending char +
8219 // any number of extending chars is treated as a single unit as far
8220 // as editing and measuring is concerned. This is not fully correct,
8221 // since some scripts/fonts/browsers also treat other configurations
8222 // of code points as a group.
8223 var extendingChars
= /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
8224 function isExtendingChar(ch
) { return ch
.charCodeAt(0) >= 768 && extendingChars
.test(ch
); }
8228 function elt(tag
, content
, className
, style
) {
8229 var e
= document
.createElement(tag
);
8230 if (className
) e
.className
= className
;
8231 if (style
) e
.style
.cssText
= style
;
8232 if (typeof content
== "string") e
.appendChild(document
.createTextNode(content
));
8233 else if (content
) for (var i
= 0; i
< content
.length
; ++i
) e
.appendChild(content
[i
]);
8238 if (document
.createRange
) range = function(node
, start
, end
, endNode
) {
8239 var r
= document
.createRange();
8240 r
.setEnd(endNode
|| node
, end
);
8241 r
.setStart(node
, start
);
8244 else range = function(node
, start
, end
) {
8245 var r
= document
.body
.createTextRange();
8246 try { r
.moveToElementText(node
.parentNode
); }
8247 catch(e
) { return r
; }
8249 r
.moveEnd("character", end
);
8250 r
.moveStart("character", start
);
8254 function removeChildren(e
) {
8255 for (var count
= e
.childNodes
.length
; count
> 0; --count
)
8256 e
.removeChild(e
.firstChild
);
8260 function removeChildrenAndAdd(parent
, e
) {
8261 return removeChildren(parent
).appendChild(e
);
8264 var contains
= CodeMirror
.contains = function(parent
, child
) {
8265 if (child
.nodeType
== 3) // Android browser always returns false when child is a textnode
8266 child
= child
.parentNode
;
8267 if (parent
.contains
)
8268 return parent
.contains(child
);
8270 if (child
.nodeType
== 11) child
= child
.host
;
8271 if (child
== parent
) return true;
8272 } while (child
= child
.parentNode
);
8275 function activeElt() { return document
.activeElement
; }
8276 // Older versions of IE throws unspecified error when touching
8277 // document.activeElement in some cases (during loading, in iframe)
8278 if (ie
&& ie_version
< 11) activeElt = function() {
8279 try { return document
.activeElement
; }
8280 catch(e
) { return document
.body
; }
8283 function classTest(cls
) { return new RegExp("(^|\\s)" + cls
+ "(?:$|\\s)\\s*"); }
8284 var rmClass
= CodeMirror
.rmClass = function(node
, cls
) {
8285 var current
= node
.className
;
8286 var match
= classTest(cls
).exec(current
);
8288 var after
= current
.slice(match
.index
+ match
[0].length
);
8289 node
.className
= current
.slice(0, match
.index
) + (after
? match
[1] + after : "");
8292 var addClass
= CodeMirror
.addClass = function(node
, cls
) {
8293 var current
= node
.className
;
8294 if (!classTest(cls
).test(current
)) node
.className
+= (current
? " " : "") + cls
;
8296 function joinClasses(a
, b
) {
8297 var as
= a
.split(" ");
8298 for (var i
= 0; i
< as
.length
; i
++)
8299 if (as
[i
] && !classTest(as
[i
]).test(b
)) b
+= " " + as
[i
];
8303 // WINDOW-WIDE EVENTS
8305 // These must be handled carefully, because naively registering a
8306 // handler for each editor will cause the editors to never be
8307 // garbage collected.
8309 function forEachCodeMirror(f
) {
8310 if (!document
.body
.getElementsByClassName
) return;
8311 var byClass
= document
.body
.getElementsByClassName("CodeMirror");
8312 for (var i
= 0; i
< byClass
.length
; i
++) {
8313 var cm
= byClass
[i
].CodeMirror
;
8318 var globalsRegistered
= false;
8319 function ensureGlobalHandlers() {
8320 if (globalsRegistered
) return;
8321 registerGlobalHandlers();
8322 globalsRegistered
= true;
8324 function registerGlobalHandlers() {
8325 // When the window resizes, we need to refresh active editors.
8327 on(window
, "resize", function() {
8328 if (resizeTimer
== null) resizeTimer
= setTimeout(function() {
8330 forEachCodeMirror(onResize
);
8333 // When the window loses focus, we want to show the editor as blurred
8334 on(window
, "blur", function() {
8335 forEachCodeMirror(onBlur
);
8339 // FEATURE DETECTION
8341 // Detect drag-and-drop
8342 var dragAndDrop = function() {
8343 // There is *some* kind of drag-and-drop support in IE6-8, but I
8344 // couldn't get it to work yet.
8345 if (ie
&& ie_version
< 9) return false;
8346 var div
= elt('div');
8347 return "draggable" in div
|| "dragDrop" in div
;
8351 function zeroWidthElement(measure
) {
8352 if (zwspSupported
== null) {
8353 var test
= elt("span", "\u200b");
8354 removeChildrenAndAdd(measure
, elt("span", [test
, document
.createTextNode("x")]));
8355 if (measure
.firstChild
.offsetHeight
!= 0)
8356 zwspSupported
= test
.offsetWidth
<= 1 && test
.offsetHeight
> 2 && !(ie
&& ie_version
< 8);
8358 var node
= zwspSupported
? elt("span", "\u200b") :
8359 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
8360 node
.setAttribute("cm-text", "");
8364 // Feature-detect IE's crummy client rect reporting for bidi text
8366 function hasBadBidiRects(measure
) {
8367 if (badBidiRects
!= null) return badBidiRects
;
8368 var txt
= removeChildrenAndAdd(measure
, document
.createTextNode("A\u062eA"));
8369 var r0
= range(txt
, 0, 1).getBoundingClientRect();
8370 if (!r0
|| r0
.left
== r0
.right
) return false; // Safari returns null in some cases (#2780)
8371 var r1
= range(txt
, 1, 2).getBoundingClientRect();
8372 return badBidiRects
= (r1
.right
- r0
.right
< 3);
8375 // See if "".split is the broken IE version, if so, provide an
8376 // alternative way to split lines.
8377 var splitLines
= CodeMirror
.splitLines
= "\n\nb".split(/\n/).length
!= 3 ? function(string
) {
8378 var pos
= 0, result
= [], l
= string
.length
;
8380 var nl
= string
.indexOf("\n", pos
);
8381 if (nl
== -1) nl
= string
.length
;
8382 var line
= string
.slice(pos
, string
.charAt(nl
- 1) == "\r" ? nl
- 1 : nl
);
8383 var rt
= line
.indexOf("\r");
8385 result
.push(line
.slice(0, rt
));
8393 } : function(string
){return string
.split(/\r\n?|\n/);};
8395 var hasSelection
= window
.getSelection
? function(te
) {
8396 try { return te
.selectionStart
!= te
.selectionEnd
; }
8397 catch(e
) { return false; }
8399 try {var range
= te
.ownerDocument
.selection
.createRange();}
8401 if (!range
|| range
.parentElement() != te
) return false;
8402 return range
.compareEndPoints("StartToEnd", range
) != 0;
8405 var hasCopyEvent
= (function() {
8407 if ("oncopy" in e
) return true;
8408 e
.setAttribute("oncopy", "return;");
8409 return typeof e
.oncopy
== "function";
8412 var badZoomedRects
= null;
8413 function hasBadZoomedRects(measure
) {
8414 if (badZoomedRects
!= null) return badZoomedRects
;
8415 var node
= removeChildrenAndAdd(measure
, elt("span", "x"));
8416 var normal
= node
.getBoundingClientRect();
8417 var fromRange
= range(node
, 0, 1).getBoundingClientRect();
8418 return badZoomedRects
= Math
.abs(normal
.left
- fromRange
.left
) > 1;
8423 var keyNames
= {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
8424 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
8425 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
8426 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
8427 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
8428 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
8429 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
8430 CodeMirror
.keyNames
= keyNames
;
8433 for (var i
= 0; i
< 10; i
++) keyNames
[i
+ 48] = keyNames
[i
+ 96] = String(i
);
8435 for (var i
= 65; i
<= 90; i
++) keyNames
[i
] = String
.fromCharCode(i
);
8437 for (var i
= 1; i
<= 12; i
++) keyNames
[i
+ 111] = keyNames
[i
+ 63235] = "F" + i
;
8442 function iterateBidiSections(order
, from, to
, f
) {
8443 if (!order
) return f(from, to
, "ltr");
8445 for (var i
= 0; i
< order
.length
; ++i
) {
8446 var part
= order
[i
];
8447 if (part
.from < to
&& part
.to
> from || from == to
&& part
.to
== from) {
8448 f(Math
.max(part
.from, from), Math
.min(part
.to
, to
), part
.level
== 1 ? "rtl" : "ltr");
8452 if (!found
) f(from, to
, "ltr");
8455 function bidiLeft(part
) { return part
.level
% 2 ? part
.to : part
.from; }
8456 function bidiRight(part
) { return part
.level
% 2 ? part
.from : part
.to
; }
8458 function lineLeft(line
) { var order
= getOrder(line
); return order
? bidiLeft(order
[0]) : 0; }
8459 function lineRight(line
) {
8460 var order
= getOrder(line
);
8461 if (!order
) return line
.text
.length
;
8462 return bidiRight(lst(order
));
8465 function lineStart(cm
, lineN
) {
8466 var line
= getLine(cm
.doc
, lineN
);
8467 var visual
= visualLine(line
);
8468 if (visual
!= line
) lineN
= lineNo(visual
);
8469 var order
= getOrder(visual
);
8470 var ch
= !order
? 0 : order
[0].level
% 2 ? lineRight(visual
) : lineLeft(visual
);
8471 return Pos(lineN
, ch
);
8473 function lineEnd(cm
, lineN
) {
8474 var merged
, line
= getLine(cm
.doc
, lineN
);
8475 while (merged
= collapsedSpanAtEnd(line
)) {
8476 line
= merged
.find(1, true).line
;
8479 var order
= getOrder(line
);
8480 var ch
= !order
? line
.text
.length : order
[0].level
% 2 ? lineLeft(line
) : lineRight(line
);
8481 return Pos(lineN
== null ? lineNo(line
) : lineN
, ch
);
8483 function lineStartSmart(cm
, pos
) {
8484 var start
= lineStart(cm
, pos
.line
);
8485 var line
= getLine(cm
.doc
, start
.line
);
8486 var order
= getOrder(line
);
8487 if (!order
|| order
[0].level
== 0) {
8488 var firstNonWS
= Math
.max(0, line
.text
.search(/\S/));
8489 var inWS
= pos
.line
== start
.line
&& pos
.ch
<= firstNonWS
&& pos
.ch
;
8490 return Pos(start
.line
, inWS
? 0 : firstNonWS
);
8495 function compareBidiLevel(order
, a
, b
) {
8496 var linedir
= order
[0].level
;
8497 if (a
== linedir
) return true;
8498 if (b
== linedir
) return false;
8502 function getBidiPartAt(order
, pos
) {
8504 for (var i
= 0, found
; i
< order
.length
; ++i
) {
8506 if (cur
.from < pos
&& cur
.to
> pos
) return i
;
8507 if ((cur
.from == pos
|| cur
.to
== pos
)) {
8508 if (found
== null) {
8510 } else if (compareBidiLevel(order
, cur
.level
, order
[found
].level
)) {
8511 if (cur
.from != cur
.to
) bidiOther
= found
;
8514 if (cur
.from != cur
.to
) bidiOther
= i
;
8522 function moveInLine(line
, pos
, dir
, byUnit
) {
8523 if (!byUnit
) return pos
+ dir
;
8525 while (pos
> 0 && isExtendingChar(line
.text
.charAt(pos
)));
8529 // This is needed in order to move 'visually' through bi-directional
8530 // text -- i.e., pressing left should make the cursor go left, even
8531 // when in RTL text. The tricky part is the 'jumps', where RTL and
8532 // LTR text touch each other. This often requires the cursor offset
8533 // to move more than one unit, in order to visually move one unit.
8534 function moveVisually(line
, start
, dir
, byUnit
) {
8535 var bidi
= getOrder(line
);
8536 if (!bidi
) return moveLogically(line
, start
, dir
, byUnit
);
8537 var pos
= getBidiPartAt(bidi
, start
), part
= bidi
[pos
];
8538 var target
= moveInLine(line
, start
, part
.level
% 2 ? -dir : dir
, byUnit
);
8541 if (target
> part
.from && target
< part
.to
) return target
;
8542 if (target
== part
.from || target
== part
.to
) {
8543 if (getBidiPartAt(bidi
, target
) == pos
) return target
;
8544 part
= bidi
[pos
+= dir
];
8545 return (dir
> 0) == part
.level
% 2 ? part
.to : part
.from;
8547 part
= bidi
[pos
+= dir
];
8548 if (!part
) return null;
8549 if ((dir
> 0) == part
.level
% 2)
8550 target
= moveInLine(line
, part
.to
, -1, byUnit
);
8552 target
= moveInLine(line
, part
.from, 1, byUnit
);
8557 function moveLogically(line
, start
, dir
, byUnit
) {
8558 var target
= start
+ dir
;
8559 if (byUnit
) while (target
> 0 && isExtendingChar(line
.text
.charAt(target
))) target
+= dir
;
8560 return target
< 0 || target
> line
.text
.length
? null : target
;
8563 // Bidirectional ordering algorithm
8564 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
8565 // that this (partially) implements.
8567 // One-char codes used for character types:
8568 // L (L): Left-to-Right
8569 // R (R): Right-to-Left
8570 // r (AL): Right-to-Left Arabic
8571 // 1 (EN): European Number
8572 // + (ES): European Number Separator
8573 // % (ET): European Number Terminator
8574 // n (AN): Arabic Number
8575 // , (CS): Common Number Separator
8576 // m (NSM): Non-Spacing Mark
8577 // b (BN): Boundary Neutral
8578 // s (B): Paragraph Separator
8579 // t (S): Segment Separator
8580 // w (WS): Whitespace
8581 // N (ON): Other Neutrals
8583 // Returns null if characters are ordered as they appear
8584 // (left-to-right), or an array of sections ({from, to, level}
8585 // objects) in the order in which they occur visually.
8586 var bidiOrdering
= (function() {
8587 // Character types for codepoints 0 to 0xff
8588 var lowTypes
= "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";
8589 // Character types for codepoints 0x600 to 0x6ff
8590 var arabicTypes
= "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";
8591 function charType(code
) {
8592 if (code
<= 0xf7) return lowTypes
.charAt(code
);
8593 else if (0x590 <= code
&& code
<= 0x5f4) return "R";
8594 else if (0x600 <= code
&& code
<= 0x6ed) return arabicTypes
.charAt(code
- 0x600);
8595 else if (0x6ee <= code
&& code
<= 0x8ac) return "r";
8596 else if (0x2000 <= code
&& code
<= 0x200b) return "w";
8597 else if (code
== 0x200c) return "b";
8601 var bidiRE
= /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
8602 var isNeutral
= /[stwN]/, isStrong
= /[LRr]/, countsAsLeft
= /[Lb1n]/, countsAsNum
= /[1n]/;
8603 // Browsers seem to always treat the boundaries of block elements as being L.
8604 var outerType
= "L";
8606 function BidiSpan(level
, from, to
) {
8608 this.from = from; this.to
= to
;
8611 return function(str
) {
8612 if (!bidiRE
.test(str
)) return false;
8613 var len
= str
.length
, types
= [];
8614 for (var i
= 0, type
; i
< len
; ++i
)
8615 types
.push(type
= charType(str
.charCodeAt(i
)));
8617 // W1. Examine each non-spacing mark (NSM) in the level run, and
8618 // change the type of the NSM to the type of the previous
8619 // character. If the NSM is at the start of the level run, it will
8620 // get the type of sor.
8621 for (var i
= 0, prev
= outerType
; i
< len
; ++i
) {
8622 var type
= types
[i
];
8623 if (type
== "m") types
[i
] = prev
;
8627 // W2. Search backwards from each instance of a European number
8628 // until the first strong type (R, L, AL, or sor) is found. If an
8629 // AL is found, change the type of the European number to Arabic
8631 // W3. Change all ALs to R.
8632 for (var i
= 0, cur
= outerType
; i
< len
; ++i
) {
8633 var type
= types
[i
];
8634 if (type
== "1" && cur
== "r") types
[i
] = "n";
8635 else if (isStrong
.test(type
)) { cur
= type
; if (type
== "r") types
[i
] = "R"; }
8638 // W4. A single European separator between two European numbers
8639 // changes to a European number. A single common separator between
8640 // two numbers of the same type changes to that type.
8641 for (var i
= 1, prev
= types
[0]; i
< len
- 1; ++i
) {
8642 var type
= types
[i
];
8643 if (type
== "+" && prev
== "1" && types
[i
+1] == "1") types
[i
] = "1";
8644 else if (type
== "," && prev
== types
[i
+1] &&
8645 (prev
== "1" || prev
== "n")) types
[i
] = prev
;
8649 // W5. A sequence of European terminators adjacent to European
8650 // numbers changes to all European numbers.
8651 // W6. Otherwise, separators and terminators change to Other
8653 for (var i
= 0; i
< len
; ++i
) {
8654 var type
= types
[i
];
8655 if (type
== ",") types
[i
] = "N";
8656 else if (type
== "%") {
8657 for (var end
= i
+ 1; end
< len
&& types
[end
] == "%"; ++end
) {}
8658 var replace
= (i
&& types
[i
-1] == "!") || (end
< len
&& types
[end
] == "1") ? "1" : "N";
8659 for (var j
= i
; j
< end
; ++j
) types
[j
] = replace
;
8664 // W7. Search backwards from each instance of a European number
8665 // until the first strong type (R, L, or sor) is found. If an L is
8666 // found, then change the type of the European number to L.
8667 for (var i
= 0, cur
= outerType
; i
< len
; ++i
) {
8668 var type
= types
[i
];
8669 if (cur
== "L" && type
== "1") types
[i
] = "L";
8670 else if (isStrong
.test(type
)) cur
= type
;
8673 // N1. A sequence of neutrals takes the direction of the
8674 // surrounding strong text if the text on both sides has the same
8675 // direction. European and Arabic numbers act as if they were R in
8676 // terms of their influence on neutrals. Start-of-level-run (sor)
8677 // and end-of-level-run (eor) are used at level run boundaries.
8678 // N2. Any remaining neutrals take the embedding direction.
8679 for (var i
= 0; i
< len
; ++i
) {
8680 if (isNeutral
.test(types
[i
])) {
8681 for (var end
= i
+ 1; end
< len
&& isNeutral
.test(types
[end
]); ++end
) {}
8682 var before
= (i
? types
[i
-1] : outerType
) == "L";
8683 var after
= (end
< len
? types
[end
] : outerType
) == "L";
8684 var replace
= before
|| after
? "L" : "R";
8685 for (var j
= i
; j
< end
; ++j
) types
[j
] = replace
;
8690 // Here we depart from the documented algorithm, in order to avoid
8691 // building up an actual levels array. Since there are only three
8692 // levels (0, 1, 2) in an implementation that doesn't take
8693 // explicit embedding into account, we can build up the order on
8694 // the fly, without following the level-based algorithm.
8696 for (var i
= 0; i
< len
;) {
8697 if (countsAsLeft
.test(types
[i
])) {
8699 for (++i
; i
< len
&& countsAsLeft
.test(types
[i
]); ++i
) {}
8700 order
.push(new BidiSpan(0, start
, i
));
8702 var pos
= i
, at
= order
.length
;
8703 for (++i
; i
< len
&& types
[i
] != "L"; ++i
) {}
8704 for (var j
= pos
; j
< i
;) {
8705 if (countsAsNum
.test(types
[j
])) {
8706 if (pos
< j
) order
.splice(at
, 0, new BidiSpan(1, pos
, j
));
8708 for (++j
; j
< i
&& countsAsNum
.test(types
[j
]); ++j
) {}
8709 order
.splice(at
, 0, new BidiSpan(2, nstart
, j
));
8713 if (pos
< i
) order
.splice(at
, 0, new BidiSpan(1, pos
, i
));
8716 if (order
[0].level
== 1 && (m
= str
.match(/^\s+/))) {
8717 order
[0].from = m
[0].length
;
8718 order
.unshift(new BidiSpan(0, 0, m
[0].length
));
8720 if (lst(order
).level
== 1 && (m
= str
.match(/\s+$/))) {
8721 lst(order
).to
-= m
[0].length
;
8722 order
.push(new BidiSpan(0, len
- m
[0].length
, len
));
8724 if (order
[0].level
== 2)
8725 order
.unshift(new BidiSpan(1, order
[0].to
, order
[0].to
));
8726 if (order
[0].level
!= lst(order
).level
)
8727 order
.push(new BidiSpan(order
[0].level
, len
, len
));
8735 CodeMirror
.version
= "5.2.0";