1 // CodeMirror version 3.0
3 // CodeMirror is the only global var we claim
4 window
.CodeMirror
= (function() {
9 // Crude, but necessary to handle a number of hard-to-feature-detect
10 // bugs and behavior differences.
11 var gecko
= /gecko\/\d/i.test(navigator
.userAgent
);
12 var ie
= /MSIE
\d
/.test(navigator
.userAgent
);
13 var ie_lt8
= /MSIE
[1-7]\b/.test(navigator
.userAgent
);
14 var ie_lt9
= /MSIE
[1-8]\b/.test(navigator
.userAgent
);
15 var webkit
= /WebKit\//.test(navigator
.userAgent
);
16 var qtwebkit
= webkit
&& /Qt\/\d+\.\d+/.test(navigator
.userAgent
);
17 var chrome
= /Chrome\//.test(navigator
.userAgent
);
18 var opera
= /Opera\//.test(navigator
.userAgent
);
19 var safari
= /Apple Computer
/.test(navigator
.vendor
);
20 var khtml
= /KHTML\//.test(navigator
.userAgent
);
21 var mac_geLion
= /Mac OS X
1\d
\D([7-9]|\d
\d
)\D
/.test(navigator
.userAgent
);
22 var mac_geMountainLion
= /Mac OS X
1\d
\D([8-9]|\d
\d
)\D
/.test(navigator
.userAgent
);
23 var phantom
= /PhantomJS/.test(navigator
.userAgent
);
25 var ios
= /AppleWebKit/.test(navigator
.userAgent
) && /Mobile\/\w+/.test(navigator
.userAgent
);
26 // This is woefully incomplete. Suggestions for alternative methods welcome.
27 var mobile
= ios
|| /Android
|webOS
|BlackBerry
|Opera Mini
|IEMobile
/i
.test(navigator
.userAgent
);
28 var mac
= ios
|| /Mac/.test(navigator
.platform
);
30 // Optimize some code when these features are not used
31 var sawReadOnlySpans
= false, sawCollapsedSpans
= false;
35 function CodeMirror(place
, options
) {
36 if (!(this instanceof CodeMirror
)) return new CodeMirror(place
, options
);
38 this.options
= options
= options
|| {};
39 // Determine effective options based on given values and defaults.
40 for (var opt
in defaults
) if (!options
.hasOwnProperty(opt
) && defaults
.hasOwnProperty(opt
))
41 options
[opt
] = defaults
[opt
];
42 setGuttersForLineNumbers(options
);
44 var display
= this.display
= makeDisplay(place
);
45 display
.wrapper
.CodeMirror
= this;
47 if (options
.autofocus
&& !mobile
) focusInput(this);
49 this.view
= makeView(new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display
))])]));
53 if (options
.lineWrapping
)
54 this.display
.wrapper
.className
+= " CodeMirror-wrap";
56 // Initialize the content.
57 this.setValue(options
.value
|| "");
58 // Override magic textarea content restore that IE sometimes does
59 // on our hidden textarea on reload
60 if (ie
) setTimeout(bind(resetInput
, this, true), 20);
61 this.view
.history
= makeHistory();
63 registerEventHandlers(this);
64 // IE throws unspecified error in certain cases, when
65 // trying to access activeElement before onload
66 var hasFocus
; try { hasFocus
= (document
.activeElement
== display
.input
); } catch(e
) { }
67 if (hasFocus
|| (options
.autofocus
&& !mobile
)) setTimeout(bind(onFocus
, this), 20);
70 operation(this, function() {
71 for (var opt
in optionHandlers
)
72 if (optionHandlers
.propertyIsEnumerable(opt
))
73 optionHandlers
[opt
](this, options
[opt
], Init
);
74 for (var i
= 0; i
< initHooks
.length
; ++i
) initHooks
[i
](this);
78 // DISPLAY CONSTRUCTOR
80 function makeDisplay(place
) {
82 var input
= d
.input
= elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
83 input
.setAttribute("wrap", "off"); input
.setAttribute("autocorrect", "off"); input
.setAttribute("autocapitalize", "off");
84 // Wraps and hides input textarea
85 d
.inputDiv
= elt("div", [input
], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
86 // The actual fake scrollbars.
87 d
.scrollbarH
= elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
88 d
.scrollbarV
= elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
89 d
.scrollbarFiller
= elt("div", null, "CodeMirror-scrollbar-filler");
90 // DIVs containing the selection and the actual code
91 d
.lineDiv
= elt("div");
92 d
.selectionDiv
= elt("div", null, null, "position: relative; z-index: 1");
93 // Blinky cursor, and element used to ensure cursor fits at the end of a line
94 d
.cursor
= elt("pre", "\u00a0", "CodeMirror-cursor");
95 // Secondary cursor, shown when on a 'jump' in bi-directional text
96 d
.otherCursor
= elt("pre", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
97 // Used to measure text size
98 d
.measure
= elt("div", null, "CodeMirror-measure");
99 // Wraps everything that needs to exist inside the vertically-padded coordinate system
100 d
.lineSpace
= elt("div", [d
.measure
, d
.selectionDiv
, d
.lineDiv
, d
.cursor
, d
.otherCursor
],
101 null, "position: relative; outline: none");
102 // Moved around its parent to cover visible view
103 d
.mover
= elt("div", [elt("div", [d
.lineSpace
], "CodeMirror-lines")], null, "position: relative");
104 // Set to the height of the text, causes scrolling
105 d
.sizer
= elt("div", [d
.mover
], "CodeMirror-sizer");
106 // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
107 d
.heightForcer
= elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff
+ "px");
108 // Will contain the gutters, if any
109 d
.gutters
= elt("div", null, "CodeMirror-gutters");
111 // Helper element to properly size the gutter backgrounds
112 var scrollerInner
= elt("div", [d
.sizer
, d
.heightForcer
, d
.gutters
], null, "position: relative; min-height: 100%");
113 // Provides scrolling
114 d
.scroller
= elt("div", [scrollerInner
], "CodeMirror-scroll");
115 d
.scroller
.setAttribute("tabIndex", "-1");
116 // The element in which the editor lives.
117 d
.wrapper
= elt("div", [d
.inputDiv
, d
.scrollbarH
, d
.scrollbarV
,
118 d
.scrollbarFiller
, d
.scroller
], "CodeMirror");
119 // Work around IE7 z-index bug
120 if (ie_lt8
) { d
.gutters
.style
.zIndex
= -1; d
.scroller
.style
.paddingRight
= 0; }
121 if (place
.appendChild
) place
.appendChild(d
.wrapper
); else place(d
.wrapper
);
123 // Needed to hide big blue blinking cursor on Mobile Safari
124 if (ios
) input
.style
.width
= "0px";
125 if (!webkit
) d
.scroller
.draggable
= true;
126 // Needed to handle Tab key in KHTML
127 if (khtml
) { d
.inputDiv
.style
.height
= "1px"; d
.inputDiv
.style
.position
= "absolute"; }
128 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
129 else if (ie_lt8
) d
.scrollbarH
.style
.minWidth
= d
.scrollbarV
.style
.minWidth
= "18px";
131 // Current visible range (may be bigger than the view window).
132 d
.viewOffset
= d
.showingFrom
= d
.showingTo
= d
.lastSizeC
= 0;
134 // Used to only resize the line number gutter when necessary (when
135 // the amount of lines crosses a boundary that makes its width change)
136 d
.lineNumWidth
= d
.lineNumInnerWidth
= d
.lineNumChars
= null;
137 // See readInput and resetInput
139 // Set to true when a non-horizontal-scrolling widget is added. As
140 // an optimization, widget aligning is skipped when d is false.
141 d
.alignWidgets
= false;
142 // Flag that indicates whether we currently expect input to appear
143 // (after some event like 'keypress' or 'input') and are polling
145 d
.pollingFast
= false;
146 // Self-resetting timeout for the poller
147 d
.poll
= new Delayed();
148 // True when a drag from the editor is active
149 d
.draggingText
= false;
151 d
.cachedCharWidth
= d
.cachedTextHeight
= null;
152 d
.measureLineCache
= [];
153 d
.measureLineCachePos
= 0;
155 // Tracks when resetInput has punted to just putting a short
156 // string instead of the (large) selection.
157 d
.inaccurateSelection
= false;
159 // Used to adjust overwrite behaviour when a paste has been
161 d
.pasteIncoming
= false;
168 function makeView(doc
) {
169 var selPos
= {line: 0, ch: 0};
172 // frontier is the point up to which the content has been parsed,
173 frontier: 0, highlight: new Delayed(),
174 sel: {from: selPos
, to: selPos
, head: selPos
, anchor: selPos
, shift: false, extend: false},
175 scrollTop: 0, scrollLeft: 0,
176 overwrite: false, focused: false,
177 // Tracks the maximum line length so that
178 // the horizontal scrollbar can be kept
179 // static when scrolling.
180 maxLine: getLine(doc
, 0),
182 maxLineChanged: false,
183 suppressEdits: false,
192 // Used to get the editor into a consistent state again when options change.
194 function loadMode(cm
) {
195 var doc
= cm
.view
.doc
;
196 cm
.view
.mode
= CodeMirror
.getMode(cm
.options
, cm
.options
.mode
);
197 doc
.iter(0, doc
.size
, function(line
) { line
.stateAfter
= null; });
198 cm
.view
.frontier
= 0;
199 startWorker(cm
, 100);
202 function wrappingChanged(cm
) {
203 var doc
= cm
.view
.doc
, th
= textHeight(cm
.display
);
204 if (cm
.options
.lineWrapping
) {
205 cm
.display
.wrapper
.className
+= " CodeMirror-wrap";
206 var perLine
= cm
.display
.scroller
.clientWidth
/ charWidth(cm
.display
) - 3;
207 doc
.iter(0, doc
.size
, function(line
) {
208 if (line
.height
== 0) return;
209 var guess
= Math
.ceil(line
.text
.length
/ perLine
) || 1;
210 if (guess
!= 1) updateLineHeight(line
, guess
* th
);
212 cm
.display
.sizer
.style
.minWidth
= "";
214 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(" CodeMirror-wrap", "");
215 computeMaxLength(cm
.view
);
216 doc
.iter(0, doc
.size
, function(line
) {
217 if (line
.height
!= 0) updateLineHeight(line
, th
);
220 regChange(cm
, 0, doc
.size
);
222 setTimeout(function(){updateScrollbars(cm
.display
, cm
.view
.doc
.height
);}, 100);
225 function keyMapChanged(cm
) {
226 var style
= keyMap
[cm
.options
.keyMap
].style
;
227 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(/\s*cm-keymap-\S+/g, "") +
228 (style
? " cm-keymap-" + style : "");
231 function themeChanged(cm
) {
232 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(/\s*cm-s-\S+/g, "") +
233 cm
.options
.theme
.replace(/(^|\s)\s*/g, " cm-s-");
237 function guttersChanged(cm
) {
239 updateDisplay(cm
, true);
242 function updateGutters(cm
) {
243 var gutters
= cm
.display
.gutters
, specs
= cm
.options
.gutters
;
244 removeChildren(gutters
);
245 for (var i
= 0; i
< specs
.length
; ++i
) {
246 var gutterClass
= specs
[i
];
247 var gElt
= gutters
.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass
));
248 if (gutterClass
== "CodeMirror-linenumbers") {
249 cm
.display
.lineGutter
= gElt
;
250 gElt
.style
.width
= (cm
.display
.lineNumWidth
|| 1) + "px";
253 gutters
.style
.display
= i
? "" : "none";
256 function lineLength(doc
, line
) {
257 if (line
.height
== 0) return 0;
258 var len
= line
.text
.length
, merged
, cur
= line
;
259 while (merged
= collapsedSpanAtStart(cur
)) {
260 var found
= merged
.find();
261 cur
= getLine(doc
, found
.from.line
);
262 len
+= found
.from.ch
- found
.to
.ch
;
265 while (merged
= collapsedSpanAtEnd(cur
)) {
266 var found
= merged
.find();
267 len
-= cur
.text
.length
- found
.from.ch
;
268 cur
= getLine(doc
, found
.to
.line
);
269 len
+= cur
.text
.length
- found
.to
.ch
;
274 function computeMaxLength(view
) {
275 view
.maxLine
= getLine(view
.doc
, 0);
276 view
.maxLineLength
= lineLength(view
.doc
, view
.maxLine
);
277 view
.maxLineChanged
= true;
278 view
.doc
.iter(1, view
.doc
.size
, function(line
) {
279 var len
= lineLength(view
.doc
, line
);
280 if (len
> view
.maxLineLength
) {
281 view
.maxLineLength
= len
;
287 // Make sure the gutters options contains the element
288 // "CodeMirror-linenumbers" when the lineNumbers option is true.
289 function setGuttersForLineNumbers(options
) {
291 for (var i
= 0; i
< options
.gutters
.length
; ++i
) {
292 if (options
.gutters
[i
] == "CodeMirror-linenumbers") {
293 if (options
.lineNumbers
) found
= true;
294 else options
.gutters
.splice(i
--, 1);
297 if (!found
&& options
.lineNumbers
)
298 options
.gutters
.push("CodeMirror-linenumbers");
303 // Re-synchronize the fake scrollbars with the actual size of the
304 // content. Optionally force a scrollTop.
305 function updateScrollbars(d
/* display */, docHeight
) {
306 var totalHeight
= docHeight
+ 2 * paddingTop(d
);
307 d
.sizer
.style
.minHeight
= d
.heightForcer
.style
.top
= totalHeight
+ "px";
308 var scrollHeight
= Math
.max(totalHeight
, d
.scroller
.scrollHeight
);
309 var needsH
= d
.scroller
.scrollWidth
> d
.scroller
.clientWidth
;
310 var needsV
= scrollHeight
> d
.scroller
.clientHeight
;
312 d
.scrollbarV
.style
.display
= "block";
313 d
.scrollbarV
.style
.bottom
= needsH
? scrollbarWidth(d
.measure
) + "px" : "0";
314 d
.scrollbarV
.firstChild
.style
.height
=
315 (scrollHeight
- d
.scroller
.clientHeight
+ d
.scrollbarV
.clientHeight
) + "px";
316 } else d
.scrollbarV
.style
.display
= "";
318 d
.scrollbarH
.style
.display
= "block";
319 d
.scrollbarH
.style
.right
= needsV
? scrollbarWidth(d
.measure
) + "px" : "0";
320 d
.scrollbarH
.firstChild
.style
.width
=
321 (d
.scroller
.scrollWidth
- d
.scroller
.clientWidth
+ d
.scrollbarH
.clientWidth
) + "px";
322 } else d
.scrollbarH
.style
.display
= "";
323 if (needsH
&& needsV
) {
324 d
.scrollbarFiller
.style
.display
= "block";
325 d
.scrollbarFiller
.style
.height
= d
.scrollbarFiller
.style
.width
= scrollbarWidth(d
.measure
) + "px";
326 } else d
.scrollbarFiller
.style
.display
= "";
328 if (mac_geLion
&& scrollbarWidth(d
.measure
) === 0)
329 d
.scrollbarV
.style
.minWidth
= d
.scrollbarH
.style
.minHeight
= mac_geMountainLion
? "18px" : "12px";
332 function visibleLines(display
, doc
, viewPort
) {
333 var top
= display
.scroller
.scrollTop
, height
= display
.wrapper
.clientHeight
;
334 if (typeof viewPort
== "number") top
= viewPort
;
335 else if (viewPort
) {top
= viewPort
.top
; height
= viewPort
.bottom
- viewPort
.top
;}
336 top
= Math
.floor(top
- paddingTop(display
));
337 var bottom
= Math
.ceil(top
+ height
);
338 return {from: lineAtHeight(doc
, top
), to: lineAtHeight(doc
, bottom
)};
343 function alignHorizontally(cm
) {
344 var display
= cm
.display
;
345 if (!display
.alignWidgets
&& !display
.gutters
.firstChild
) return;
346 var comp
= compensateForHScroll(display
) - display
.scroller
.scrollLeft
+ cm
.view
.scrollLeft
;
347 var gutterW
= display
.gutters
.offsetWidth
, l
= comp
+ "px";
348 for (var n
= display
.lineDiv
.firstChild
; n
; n
= n
.nextSibling
) if (n
.alignable
) {
349 for (var i
= 0, a
= n
.alignable
; i
< a
.length
; ++i
) a
[i
].style
.left
= l
;
351 display
.gutters
.style
.left
= (comp
+ gutterW
) + "px";
354 function maybeUpdateLineNumberWidth(cm
) {
355 if (!cm
.options
.lineNumbers
) return false;
356 var doc
= cm
.view
.doc
, last
= lineNumberFor(cm
.options
, doc
.size
- 1), display
= cm
.display
;
357 if (last
.length
!= display
.lineNumChars
) {
358 var test
= display
.measure
.appendChild(elt("div", [elt("div", last
)],
359 "CodeMirror-linenumber CodeMirror-gutter-elt"));
360 var innerW
= test
.firstChild
.offsetWidth
, padding
= test
.offsetWidth
- innerW
;
361 display
.lineGutter
.style
.width
= "";
362 display
.lineNumInnerWidth
= Math
.max(innerW
, display
.lineGutter
.offsetWidth
- padding
);
363 display
.lineNumWidth
= display
.lineNumInnerWidth
+ padding
;
364 display
.lineNumChars
= display
.lineNumInnerWidth
? last
.length : -1;
365 display
.lineGutter
.style
.width
= display
.lineNumWidth
+ "px";
371 function lineNumberFor(options
, i
) {
372 return String(options
.lineNumberFormatter(i
+ options
.firstLineNumber
));
374 function compensateForHScroll(display
) {
375 return display
.scroller
.getBoundingClientRect().left
- display
.sizer
.getBoundingClientRect().left
;
380 function updateDisplay(cm
, changes
, viewPort
) {
381 var oldFrom
= cm
.display
.showingFrom
, oldTo
= cm
.display
.showingTo
;
382 var updated
= updateDisplayInner(cm
, changes
, viewPort
);
384 signalLater(cm
, cm
, "update", cm
);
385 if (cm
.display
.showingFrom
!= oldFrom
|| cm
.display
.showingTo
!= oldTo
)
386 signalLater(cm
, cm
, "viewportChange", cm
, cm
.display
.showingFrom
, cm
.display
.showingTo
);
389 updateScrollbars(cm
.display
, cm
.view
.doc
.height
);
394 // Uses a set of changes plus the current scroll position to
395 // determine which DOM updates have to be made, and makes the
397 function updateDisplayInner(cm
, changes
, viewPort
) {
398 var display
= cm
.display
, doc
= cm
.view
.doc
;
399 if (!display
.wrapper
.clientWidth
) {
400 display
.showingFrom
= display
.showingTo
= display
.viewOffset
= 0;
404 // Compute the new visible window
405 // If scrollTop is specified, use that to determine which lines
406 // to render instead of the current scrollbar position.
407 var visible
= visibleLines(display
, doc
, viewPort
);
408 // Bail out if the visible area is already rendered and nothing changed.
409 if (changes
!== true && changes
.length
== 0 &&
410 visible
.from > display
.showingFrom
&& visible
.to
< display
.showingTo
)
413 if (changes
&& maybeUpdateLineNumberWidth(cm
))
415 display
.sizer
.style
.marginLeft
= display
.scrollbarH
.style
.left
= display
.gutters
.offsetWidth
+ "px";
417 // When merged lines are present, the line that needs to be
418 // redrawn might not be the one that was changed.
419 if (changes
!== true && sawCollapsedSpans
)
420 for (var i
= 0; i
< changes
.length
; ++i
) {
421 var ch
= changes
[i
], merged
;
422 while (merged
= collapsedSpanAtStart(getLine(doc
, ch
.from))) {
423 var from = merged
.find().from.line
;
424 if (ch
.diff
) ch
.diff
-= ch
.from - from;
429 // Used to determine which lines need their line numbers updated
430 var positionsChangedFrom
= changes
=== true ? 0 : Infinity
;
431 if (cm
.options
.lineNumbers
&& changes
&& changes
!== true)
432 for (var i
= 0; i
< changes
.length
; ++i
)
433 if (changes
[i
].diff
) { positionsChangedFrom
= changes
[i
].from; break; }
435 var from = Math
.max(visible
.from - cm
.options
.viewportMargin
, 0);
436 var to
= Math
.min(doc
.size
, visible
.to
+ cm
.options
.viewportMargin
);
437 if (display
.showingFrom
< from && from - display
.showingFrom
< 20) from = display
.showingFrom
;
438 if (display
.showingTo
> to
&& display
.showingTo
- to
< 20) to
= Math
.min(doc
.size
, display
.showingTo
);
439 if (sawCollapsedSpans
) {
440 from = lineNo(visualLine(doc
, getLine(doc
, from)));
441 while (to
< doc
.size
&& lineIsHidden(getLine(doc
, to
))) ++to
;
444 // Create a range of theoretically intact lines, and punch holes
445 // in that using the change info.
446 var intact
= changes
=== true ? [] :
447 computeIntact([{from: display
.showingFrom
, to: display
.showingTo
}], changes
);
448 // Clip off the parts that won't be visible
450 for (var i
= 0; i
< intact
.length
; ++i
) {
451 var range
= intact
[i
];
452 if (range
.from < from) range
.from = from;
453 if (range
.to
> to
) range
.to
= to
;
454 if (range
.from >= range
.to
) intact
.splice(i
--, 1);
455 else intactLines
+= range
.to
- range
.from;
457 if (intactLines
== to
- from && from == display
.showingFrom
&& to
== display
.showingTo
)
459 intact
.sort(function(a
, b
) {return a
.from - b
.from;});
461 if (intactLines
< (to
- from) * .7) display
.lineDiv
.style
.display
= "none";
462 patchDisplay(cm
, from, to
, intact
, positionsChangedFrom
);
463 display
.lineDiv
.style
.display
= "";
465 var different
= from != display
.showingFrom
|| to
!= display
.showingTo
||
466 display
.lastSizeC
!= display
.wrapper
.clientHeight
;
467 // This is just a bogus formula that detects when the editor is
468 // resized or the font size changes.
469 if (different
) display
.lastSizeC
= display
.wrapper
.clientHeight
;
470 display
.showingFrom
= from; display
.showingTo
= to
;
471 startWorker(cm
, 100);
473 var prevBottom
= display
.lineDiv
.offsetTop
;
474 for (var node
= display
.lineDiv
.firstChild
, height
; node
; node
= node
.nextSibling
) if (node
.lineObj
) {
476 var bot
= node
.offsetTop
+ node
.offsetHeight
;
477 height
= bot
- prevBottom
;
480 var box
= node
.getBoundingClientRect();
481 height
= box
.bottom
- box
.top
;
483 var diff
= node
.lineObj
.height
- height
;
484 if (height
< 2) height
= textHeight(display
);
485 if (diff
> .001 || diff
< -.001)
486 updateLineHeight(node
.lineObj
, height
);
488 display
.viewOffset
= heightAtLine(cm
, getLine(doc
, from));
489 // Position the mover div to align with the current virtual scroll position
490 display
.mover
.style
.top
= display
.viewOffset
+ "px";
494 function computeIntact(intact
, changes
) {
495 for (var i
= 0, l
= changes
.length
|| 0; i
< l
; ++i
) {
496 var change
= changes
[i
], intact2
= [], diff
= change
.diff
|| 0;
497 for (var j
= 0, l2
= intact
.length
; j
< l2
; ++j
) {
498 var range
= intact
[j
];
499 if (change
.to
<= range
.from && change
.diff
) {
500 intact2
.push({from: range
.from + diff
, to: range
.to
+ diff
});
501 } else if (change
.to
<= range
.from || change
.from >= range
.to
) {
504 if (change
.from > range
.from)
505 intact2
.push({from: range
.from, to: change
.from});
506 if (change
.to
< range
.to
)
507 intact2
.push({from: change
.to
+ diff
, to: range
.to
+ diff
});
515 function getDimensions(cm
) {
516 var d
= cm
.display
, left
= {}, width
= {};
517 for (var n
= d
.gutters
.firstChild
, i
= 0; n
; n
= n
.nextSibling
, ++i
) {
518 left
[cm
.options
.gutters
[i
]] = n
.offsetLeft
;
519 width
[cm
.options
.gutters
[i
]] = n
.offsetWidth
;
521 return {fixedPos: compensateForHScroll(d
),
522 gutterTotalWidth: d
.gutters
.offsetWidth
,
525 wrapperWidth: d
.wrapper
.clientWidth
};
528 function patchDisplay(cm
, from, to
, intact
, updateNumbersFrom
) {
529 var dims
= getDimensions(cm
);
530 var display
= cm
.display
, lineNumbers
= cm
.options
.lineNumbers
;
531 // IE does bad things to nodes when .innerHTML = "" is used on a parent
532 // we still need widgets and markers intact to add back to the new content later
533 if (!intact
.length
&& !ie
&& (!webkit
|| !cm
.display
.currentWheelTarget
))
534 removeChildren(display
.lineDiv
);
535 var container
= display
.lineDiv
, cur
= container
.firstChild
;
538 var next
= node
.nextSibling
;
539 if (webkit
&& mac
&& cm
.display
.currentWheelTarget
== node
) {
540 node
.style
.display
= "none";
543 container
.removeChild(node
);
548 var nextIntact
= intact
.shift(), lineNo
= from;
549 cm
.view
.doc
.iter(from, to
, function(line
) {
550 if (nextIntact
&& nextIntact
.to
== lineNo
) nextIntact
= intact
.shift();
551 if (lineIsHidden(line
)) {
552 if (line
.height
!= 0) updateLineHeight(line
, 0);
553 } else if (nextIntact
&& nextIntact
.from <= lineNo
&& nextIntact
.to
> lineNo
) {
554 // This line is intact. Skip to the actual node. Update its
555 // line number if needed.
556 while (cur
.lineObj
!= line
) cur
= rm(cur
);
557 if (lineNumbers
&& updateNumbersFrom
<= lineNo
&& cur
.lineNumber
)
558 setTextContent(cur
.lineNumber
, lineNumberFor(cm
.options
, lineNo
));
559 cur
= cur
.nextSibling
;
561 // This line needs to be generated.
562 var lineNode
= buildLineElement(cm
, line
, lineNo
, dims
);
563 container
.insertBefore(lineNode
, cur
);
564 lineNode
.lineObj
= line
;
568 while (cur
) cur
= rm(cur
);
571 function buildLineElement(cm
, line
, lineNo
, dims
) {
572 var lineElement
= lineContent(cm
, line
);
573 var markers
= line
.gutterMarkers
, display
= cm
.display
;
575 if (!cm
.options
.lineNumbers
&& !markers
&& !line
.bgClass
&& !line
.wrapClass
&&
576 (!line
.widgets
|| !line
.widgets
.length
)) return lineElement
;
578 // Lines with gutter elements or a background class need
579 // to be wrapped again, and have the extra elements added
580 // to the wrapper div
582 var wrap
= elt("div", null, line
.wrapClass
, "position: relative");
583 if (cm
.options
.lineNumbers
|| markers
) {
584 var gutterWrap
= wrap
.appendChild(elt("div", null, null, "position: absolute; left: " +
585 dims
.fixedPos
+ "px"));
586 wrap
.alignable
= [gutterWrap
];
587 if (cm
.options
.lineNumbers
&& (!markers
|| !markers
["CodeMirror-linenumbers"]))
588 wrap
.lineNumber
= gutterWrap
.appendChild(
589 elt("div", lineNumberFor(cm
.options
, lineNo
),
590 "CodeMirror-linenumber CodeMirror-gutter-elt",
591 "left: " + dims
.gutterLeft
["CodeMirror-linenumbers"] + "px; width: "
592 + display
.lineNumInnerWidth
+ "px"));
594 for (var k
= 0; k
< cm
.options
.gutters
.length
; ++k
) {
595 var id
= cm
.options
.gutters
[k
], found
= markers
.hasOwnProperty(id
) && markers
[id
];
597 gutterWrap
.appendChild(elt("div", [found
], "CodeMirror-gutter-elt", "left: " +
598 dims
.gutterLeft
[id
] + "px; width: " + dims
.gutterWidth
[id
] + "px"));
601 // Kludge to make sure the styled element lies behind the selection (by z-index)
603 wrap
.appendChild(elt("div", "\u00a0", line
.bgClass
+ " CodeMirror-linebackground"));
604 wrap
.appendChild(lineElement
);
606 for (var i
= 0, ws
= line
.widgets
; i
< ws
.length
; ++i
) {
607 var widget
= ws
[i
], node
= elt("div", [widget
.node
], "CodeMirror-linewidget");
608 node
.widget
= widget
;
609 if (widget
.noHScroll
) {
610 (wrap
.alignable
|| (wrap
.alignable
= [])).push(node
);
611 var width
= dims
.wrapperWidth
;
612 node
.style
.left
= dims
.fixedPos
+ "px";
613 if (!widget
.coverGutter
) {
614 width
-= dims
.gutterTotalWidth
;
615 node
.style
.paddingLeft
= dims
.gutterTotalWidth
+ "px";
617 node
.style
.width
= width
+ "px";
619 if (widget
.coverGutter
) {
620 node
.style
.zIndex
= 5;
621 node
.style
.position
= "relative";
622 if (!widget
.noHScroll
) node
.style
.marginLeft
= -dims
.gutterTotalWidth
+ "px";
625 wrap
.insertBefore(node
, cm
.options
.lineNumbers
&& line
.height
!= 0 ? gutterWrap : lineElement
);
627 wrap
.appendChild(node
);
630 if (ie_lt8
) wrap
.style
.zIndex
= 2;
634 // SELECTION / CURSOR
636 function updateSelection(cm
) {
637 var display
= cm
.display
;
638 var collapsed
= posEq(cm
.view
.sel
.from, cm
.view
.sel
.to
);
639 if (collapsed
|| cm
.options
.showCursorWhenSelecting
)
640 updateSelectionCursor(cm
);
642 display
.cursor
.style
.display
= display
.otherCursor
.style
.display
= "none";
644 updateSelectionRange(cm
);
646 display
.selectionDiv
.style
.display
= "none";
648 // Move the hidden textarea near the cursor to prevent scrolling artifacts
649 var headPos
= cursorCoords(cm
, cm
.view
.sel
.head
, "div");
650 var wrapOff
= display
.wrapper
.getBoundingClientRect(), lineOff
= display
.lineDiv
.getBoundingClientRect();
651 display
.inputDiv
.style
.top
= Math
.max(0, Math
.min(display
.wrapper
.clientHeight
- 10,
652 headPos
.top
+ lineOff
.top
- wrapOff
.top
)) + "px";
653 display
.inputDiv
.style
.left
= Math
.max(0, Math
.min(display
.wrapper
.clientWidth
- 10,
654 headPos
.left
+ lineOff
.left
- wrapOff
.left
)) + "px";
657 // No selection, plain cursor
658 function updateSelectionCursor(cm
) {
659 var display
= cm
.display
, pos
= cursorCoords(cm
, cm
.view
.sel
.head
, "div");
660 display
.cursor
.style
.left
= pos
.left
+ "px";
661 display
.cursor
.style
.top
= pos
.top
+ "px";
662 display
.cursor
.style
.height
= Math
.max(0, pos
.bottom
- pos
.top
) * cm
.options
.cursorHeight
+ "px";
663 display
.cursor
.style
.display
= "";
666 display
.otherCursor
.style
.display
= "";
667 display
.otherCursor
.style
.left
= pos
.other
.left
+ "px";
668 display
.otherCursor
.style
.top
= pos
.other
.top
+ "px";
669 display
.otherCursor
.style
.height
= (pos
.other
.bottom
- pos
.other
.top
) * .85 + "px";
670 } else { display
.otherCursor
.style
.display
= "none"; }
673 // Highlight selection
674 function updateSelectionRange(cm
) {
675 var display
= cm
.display
, doc
= cm
.view
.doc
, sel
= cm
.view
.sel
;
676 var fragment
= document
.createDocumentFragment();
677 var clientWidth
= display
.lineSpace
.offsetWidth
, pl
= paddingLeft(cm
.display
);
679 function add(left
, top
, width
, bottom
) {
680 if (top
< 0) top
= 0;
681 fragment
.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left
+
682 "px; top: " + top
+ "px; width: " + (width
== null ? clientWidth
- left : width
) +
683 "px; height: " + (bottom
- top
) + "px"));
686 function drawForLine(line
, fromArg
, toArg
, retTop
) {
687 var lineObj
= getLine(doc
, line
);
688 var lineLen
= lineObj
.text
.length
, rVal
= retTop
? Infinity : -Infinity
;
689 function coords(ch
) {
690 return charCoords(cm
, {line: line
, ch: ch
}, "div", lineObj
);
693 iterateBidiSections(getOrder(lineObj
), fromArg
|| 0, toArg
== null ? lineLen : toArg
, function(from, to
, dir
) {
694 var leftPos
= coords(dir
== "rtl" ? to
- 1 : from);
695 var rightPos
= coords(dir
== "rtl" ? from : to
- 1);
696 var left
= leftPos
.left
, right
= rightPos
.right
;
697 if (rightPos
.top
- leftPos
.top
> 3) { // Different lines, draw top part
698 add(left
, leftPos
.top
, null, leftPos
.bottom
);
700 if (leftPos
.bottom
< rightPos
.top
) add(left
, leftPos
.bottom
, null, rightPos
.top
);
702 if (toArg
== null && to
== lineLen
) right
= clientWidth
;
703 if (fromArg
== null && from == 0) left
= pl
;
704 rVal
= retTop
? Math
.min(rightPos
.top
, rVal
) : Math
.max(rightPos
.bottom
, rVal
);
705 if (left
< pl
+ 1) left
= pl
;
706 add(left
, rightPos
.top
, right
- left
, rightPos
.bottom
);
711 if (sel
.from.line
== sel
.to
.line
) {
712 drawForLine(sel
.from.line
, sel
.from.ch
, sel
.to
.ch
);
714 var fromObj
= getLine(doc
, sel
.from.line
);
715 var cur
= fromObj
, merged
, path
= [sel
.from.line
, sel
.from.ch
], singleLine
;
716 while (merged
= collapsedSpanAtEnd(cur
)) {
717 var found
= merged
.find();
718 path
.push(found
.from.ch
, found
.to
.line
, found
.to
.ch
);
719 if (found
.to
.line
== sel
.to
.line
) {
720 path
.push(sel
.to
.ch
);
724 cur
= getLine(doc
, found
.to
.line
);
727 // This is a single, merged line
729 for (var i
= 0; i
< path
.length
; i
+= 3)
730 drawForLine(path
[i
], path
[i
+1], path
[i
+2]);
732 var middleTop
, middleBot
, toObj
= getLine(doc
, sel
.to
.line
);
734 // Draw the first line of selection.
735 middleTop
= drawForLine(sel
.from.line
, sel
.from.ch
, null, false);
737 // Simply include it in the middle block.
738 middleTop
= heightAtLine(cm
, fromObj
) - display
.viewOffset
;
741 middleBot
= heightAtLine(cm
, toObj
) - display
.viewOffset
;
743 middleBot
= drawForLine(sel
.to
.line
, collapsedSpanAtStart(toObj
) ? null : 0, sel
.to
.ch
, true);
745 if (middleTop
< middleBot
) add(pl
, middleTop
, null, middleBot
);
749 removeChildrenAndAdd(display
.selectionDiv
, fragment
);
750 display
.selectionDiv
.style
.display
= "";
754 function restartBlink(cm
) {
755 var display
= cm
.display
;
756 clearInterval(display
.blinker
);
758 display
.cursor
.style
.visibility
= display
.otherCursor
.style
.visibility
= "";
759 display
.blinker
= setInterval(function() {
760 if (!display
.cursor
.offsetHeight
) return;
761 display
.cursor
.style
.visibility
= display
.otherCursor
.style
.visibility
= (on
= !on
) ? "" : "hidden";
762 }, cm
.options
.cursorBlinkRate
);
767 function startWorker(cm
, time
) {
768 if (cm
.view
.frontier
< cm
.display
.showingTo
)
769 cm
.view
.highlight
.set(time
, bind(highlightWorker
, cm
));
772 function highlightWorker(cm
) {
773 var view
= cm
.view
, doc
= view
.doc
;
774 if (view
.frontier
>= cm
.display
.showingTo
) return;
775 var end
= +new Date
+ cm
.options
.workTime
;
776 var state
= copyState(view
.mode
, getStateBefore(cm
, view
.frontier
));
777 var changed
= [], prevChange
;
778 doc
.iter(view
.frontier
, Math
.min(doc
.size
, cm
.display
.showingTo
+ 500), function(line
) {
779 if (view
.frontier
>= cm
.display
.showingFrom
) { // Visible
780 if (highlightLine(cm
, line
, state
) && view
.frontier
>= cm
.display
.showingFrom
) {
781 if (prevChange
&& prevChange
.end
== view
.frontier
) prevChange
.end
++;
782 else changed
.push(prevChange
= {start: view
.frontier
, end: view
.frontier
+ 1});
784 line
.stateAfter
= copyState(view
.mode
, state
);
786 processLine(cm
, line
, state
);
787 line
.stateAfter
= view
.frontier
% 5 == 0 ? copyState(view
.mode
, state
) : null;
790 if (+new Date
> end
) {
791 startWorker(cm
, cm
.options
.workDelay
);
796 operation(cm
, function() {
797 for (var i
= 0; i
< changed
.length
; ++i
)
798 regChange(this, changed
[i
].start
, changed
[i
].end
);
802 // Finds the line to start with when starting a parse. Tries to
803 // find a line with a stateAfter, so that it can start with a
804 // valid state. If that fails, it returns the line with the
805 // smallest indentation, which tends to need the least context to
807 function findStartLine(cm
, n
) {
808 var minindent
, minline
, doc
= cm
.view
.doc
;
809 for (var search
= n
, lim
= n
- 100; search
> lim
; --search
) {
810 if (search
== 0) return 0;
811 var line
= getLine(doc
, search
-1);
812 if (line
.stateAfter
) return search
;
813 var indented
= countColumn(line
.text
, null, cm
.options
.tabSize
);
814 if (minline
== null || minindent
> indented
) {
815 minline
= search
- 1;
816 minindent
= indented
;
822 function getStateBefore(cm
, n
) {
824 var pos
= findStartLine(cm
, n
), state
= pos
&& getLine(view
.doc
, pos
-1).stateAfter
;
825 if (!state
) state
= startState(view
.mode
);
826 else state
= copyState(view
.mode
, state
);
827 view
.doc
.iter(pos
, n
, function(line
) {
828 processLine(cm
, line
, state
);
829 var save
= pos
== n
- 1 || pos
% 5 == 0 || pos
>= view
.showingFrom
&& pos
< view
.showingTo
;
830 line
.stateAfter
= save
? copyState(view
.mode
, state
) : null;
836 // POSITION MEASUREMENT
838 function paddingTop(display
) {return display
.lineSpace
.offsetTop
;}
839 function paddingLeft(display
) {
840 var e
= removeChildrenAndAdd(display
.measure
, elt("pre")).appendChild(elt("span", "x"));
844 function measureChar(cm
, line
, ch
, data
) {
845 var data
= data
|| measureLine(cm
, line
), dir
= -1;
846 for (var pos
= ch
;; pos
+= dir
) {
849 if (dir
< 0 && pos
== 0) dir
= 1;
851 return {left: pos
< ch
? r
.right : r
.left
,
852 right: pos
> ch
? r
.left : r
.right
,
853 top: r
.top
, bottom: r
.bottom
};
856 function measureLine(cm
, line
) {
857 // First look in the cache
858 var display
= cm
.display
, cache
= cm
.display
.measureLineCache
;
859 for (var i
= 0; i
< cache
.length
; ++i
) {
861 if (memo
.text
== line
.text
&& memo
.markedSpans
== line
.markedSpans
&&
862 display
.scroller
.clientWidth
== memo
.width
)
866 var measure
= measureLineInner(cm
, line
);
867 // Store result in the cache
868 var memo
= {text: line
.text
, width: display
.scroller
.clientWidth
,
869 markedSpans: line
.markedSpans
, measure: measure
};
870 if (cache
.length
== 16) cache
[++display
.measureLineCachePos
% 16] = memo
;
871 else cache
.push(memo
);
875 function measureLineInner(cm
, line
) {
876 var display
= cm
.display
, measure
= emptyArray(line
.text
.length
);
877 var pre
= lineContent(cm
, line
, measure
);
879 // IE does not cache element positions of inline elements between
880 // calls to getBoundingClientRect. This makes the loop below,
881 // which gathers the positions of all the characters on the line,
882 // do an amount of layout work quadratic to the number of
883 // characters. When line wrapping is off, we try to improve things
884 // by first subdividing the line into a bunch of inline blocks, so
885 // that IE can reuse most of the layout information from caches
886 // for those blocks. This does interfere with line wrapping, so it
887 // doesn't work when wrapping is on, but in that case the
888 // situation is slightly better, since IE does cache line-wrapping
889 // information and only recomputes per-line.
890 if (ie
&& !ie_lt8
&& !cm
.options
.lineWrapping
&& pre
.childNodes
.length
> 100) {
891 var fragment
= document
.createDocumentFragment();
892 var chunk
= 10, n
= pre
.childNodes
.length
;
893 for (var i
= 0, chunks
= Math
.ceil(n
/ chunk
); i
< chunks
; ++i
) {
894 var wrap
= elt("div", null, null, "display: inline-block");
895 for (var j
= 0; j
< chunk
&& n
; ++j
) {
896 wrap
.appendChild(pre
.firstChild
);
899 fragment
.appendChild(wrap
);
901 pre
.appendChild(fragment
);
904 removeChildrenAndAdd(display
.measure
, pre
);
906 var outer
= display
.lineDiv
.getBoundingClientRect();
907 var vranges
= [], data
= emptyArray(line
.text
.length
), maxBot
= pre
.offsetHeight
;
908 for (var i
= 0, cur
; i
< measure
.length
; ++i
) if (cur
= measure
[i
]) {
909 var size
= cur
.getBoundingClientRect();
910 var top
= Math
.max(0, size
.top
- outer
.top
), bot
= Math
.min(size
.bottom
- outer
.top
, maxBot
);
911 for (var j
= 0; j
< vranges
.length
; j
+= 2) {
912 var rtop
= vranges
[j
], rbot
= vranges
[j
+1];
913 if (rtop
> bot
|| rbot
< top
) continue;
914 if (rtop
<= top
&& rbot
>= bot
||
915 top
<= rtop
&& bot
>= rbot
||
916 Math
.min(bot
, rbot
) - Math
.max(top
, rtop
) >= (bot
- top
) >> 1) {
917 vranges
[j
] = Math
.min(top
, rtop
);
918 vranges
[j
+1] = Math
.max(bot
, rbot
);
922 if (j
== vranges
.length
) vranges
.push(top
, bot
);
923 data
[i
] = {left: size
.left
- outer
.left
, right: size
.right
- outer
.left
, top: j
};
925 for (var i
= 0, cur
; i
< data
.length
; ++i
) if (cur
= data
[i
]) {
927 cur
.top
= vranges
[vr
]; cur
.bottom
= vranges
[vr
+1];
932 function clearCaches(cm
) {
933 cm
.display
.measureLineCache
.length
= cm
.display
.measureLineCachePos
= 0;
934 cm
.display
.cachedCharWidth
= cm
.display
.cachedTextHeight
= null;
935 cm
.view
.maxLineChanged
= true;
938 // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
939 function intoCoordSystem(cm
, lineObj
, rect
, context
) {
940 if (lineObj
.widgets
) for (var i
= 0; i
< lineObj
.widgets
.length
; ++i
) if (lineObj
.widgets
[i
].above
) {
941 var size
= lineObj
.widgets
[i
].node
.offsetHeight
;
942 rect
.top
+= size
; rect
.bottom
+= size
;
944 if (context
== "line") return rect
;
945 if (!context
) context
= "local";
946 var yOff
= heightAtLine(cm
, lineObj
);
947 if (context
!= "local") yOff
-= cm
.display
.viewOffset
;
948 if (context
== "page") {
949 var lOff
= cm
.display
.lineSpace
.getBoundingClientRect();
950 yOff
+= lOff
.top
+ (window
.pageYOffset
|| (document
.documentElement
|| document
.body
).scrollTop
);
951 var xOff
= lOff
.left
+ (window
.pageXOffset
|| (document
.documentElement
|| document
.body
).scrollLeft
);
952 rect
.left
+= xOff
; rect
.right
+= xOff
;
954 rect
.top
+= yOff
; rect
.bottom
+= yOff
;
958 function charCoords(cm
, pos
, context
, lineObj
) {
959 if (!lineObj
) lineObj
= getLine(cm
.view
.doc
, pos
.line
);
960 return intoCoordSystem(cm
, lineObj
, measureChar(cm
, lineObj
, pos
.ch
), context
);
963 function cursorCoords(cm
, pos
, context
, lineObj
, measurement
) {
964 lineObj
= lineObj
|| getLine(cm
.view
.doc
, pos
.line
);
965 if (!measurement
) measurement
= measureLine(cm
, lineObj
);
966 function get(ch
, right
) {
967 var m
= measureChar(cm
, lineObj
, ch
, measurement
);
968 if (right
) m
.left
= m
.right
; else m
.right
= m
.left
;
969 return intoCoordSystem(cm
, lineObj
, m
, context
);
971 var order
= getOrder(lineObj
), ch
= pos
.ch
;
972 if (!order
) return get(ch
);
973 var main
, other
, linedir
= order
[0].level
;
974 for (var i
= 0; i
< order
.length
; ++i
) {
975 var part
= order
[i
], rtl
= part
.level
% 2, nb
, here
;
976 if (part
.from < ch
&& part
.to
> ch
) return get(ch
, rtl
);
977 var left
= rtl
? part
.to : part
.from, right
= rtl
? part
.from : part
.to
;
979 // Opera and IE return bogus offsets and widths for edges
980 // where the direction flips, but only for the side with the
981 // lower level. So we try to use the side with the higher
983 if (i
&& part
.level
< (nb
= order
[i
-1]).level
) here
= get(nb
.level
% 2 ? nb
.from : nb
.to
- 1, true);
984 else here
= get(rtl
&& part
.from != part
.to
? ch
- 1 : ch
);
985 if (rtl
== linedir
) main
= here
; else other
= here
;
986 } else if (right
== ch
) {
987 var nb
= i
< order
.length
- 1 && order
[i
+1];
988 if (!rtl
&& nb
&& nb
.from == nb
.to
) continue;
989 if (nb
&& part
.level
< nb
.level
) here
= get(nb
.level
% 2 ? nb
.to
- 1 : nb
.from);
990 else here
= get(rtl
? ch : ch
- 1, true);
991 if (rtl
== linedir
) main
= here
; else other
= here
;
994 if (linedir
&& !ch
) other
= get(order
[0].to
- 1);
995 if (!main
) return other
;
996 if (other
) main
.other
= other
;
1000 // Coords must be lineSpace-local
1001 function coordsChar(cm
, x
, y
) {
1002 var doc
= cm
.view
.doc
;
1003 y
+= cm
.display
.viewOffset
;
1004 if (y
< 0) return {line: 0, ch: 0, outside: true};
1005 var lineNo
= lineAtHeight(doc
, y
);
1006 if (lineNo
>= doc
.size
) return {line: doc
.size
- 1, ch: getLine(doc
, doc
.size
- 1).text
.length
};
1010 var lineObj
= getLine(doc
, lineNo
);
1011 var found
= coordsCharInner(cm
, lineObj
, lineNo
, x
, y
);
1012 var merged
= collapsedSpanAtEnd(lineObj
);
1013 if (merged
&& found
.ch
== lineRight(lineObj
))
1014 lineNo
= merged
.find().to
.line
;
1020 function coordsCharInner(cm
, lineObj
, lineNo
, x
, y
) {
1021 var innerOff
= y
- heightAtLine(cm
, lineObj
);
1022 var wrongLine
= false, cWidth
= cm
.display
.wrapper
.clientWidth
;
1023 var measurement
= measureLine(cm
, lineObj
);
1026 var sp
= cursorCoords(cm
, {line: lineNo
, ch: ch
}, "line",
1027 lineObj
, measurement
);
1029 if (innerOff
> sp
.bottom
) return Math
.max(0, sp
.left
- cWidth
);
1030 else if (innerOff
< sp
.top
) return sp
.left
+ cWidth
;
1031 else wrongLine
= false;
1035 var bidi
= getOrder(lineObj
), dist
= lineObj
.text
.length
;
1036 var from = lineLeft(lineObj
), to
= lineRight(lineObj
);
1037 var fromX
= paddingLeft(cm
.display
), toX
= getX(to
);
1039 if (x
> toX
) return {line: lineNo
, ch: to
, outside: wrongLine
};
1040 // Do a binary search between these bounds.
1042 if (bidi
? to
== from || to
== moveVisually(lineObj
, from, 1) : to
- from <= 1) {
1043 var after
= x
- fromX
< toX
- x
, ch
= after
? from : to
;
1044 while (isExtendingChar
.test(lineObj
.text
.charAt(ch
))) ++ch
;
1045 return {line: lineNo
, ch: ch
, after: after
, outside: wrongLine
};
1047 var step
= Math
.ceil(dist
/ 2), middle
= from + step
;
1050 for (var i
= 0; i
< step
; ++i
) middle
= moveVisually(lineObj
, middle
, 1);
1052 var middleX
= getX(middle
);
1053 if (middleX
> x
) {to
= middle
; toX
= middleX
; if (wrongLine
) toX
+= 1000; dist
-= step
;}
1054 else {from = middle
; fromX
= middleX
; dist
= step
;}
1059 function textHeight(display
) {
1060 if (display
.cachedTextHeight
!= null) return display
.cachedTextHeight
;
1061 if (measureText
== null) {
1062 measureText
= elt("pre");
1063 // Measure a bunch of lines, for browsers that compute
1064 // fractional heights.
1065 for (var i
= 0; i
< 49; ++i
) {
1066 measureText
.appendChild(document
.createTextNode("x"));
1067 measureText
.appendChild(elt("br"));
1069 measureText
.appendChild(document
.createTextNode("x"));
1071 removeChildrenAndAdd(display
.measure
, measureText
);
1072 var height
= measureText
.offsetHeight
/ 50;
1073 if (height
> 3) display
.cachedTextHeight
= height
;
1074 removeChildren(display
.measure
);
1078 function charWidth(display
) {
1079 if (display
.cachedCharWidth
!= null) return display
.cachedCharWidth
;
1080 var anchor
= elt("span", "x");
1081 var pre
= elt("pre", [anchor
]);
1082 removeChildrenAndAdd(display
.measure
, pre
);
1083 var width
= anchor
.offsetWidth
;
1084 if (width
> 2) display
.cachedCharWidth
= width
;
1090 // Operations are used to wrap changes in such a way that each
1091 // change won't have to update the cursor and display (which would
1092 // be awkward, slow, and error-prone), but instead updates are
1093 // batched and then all combined and executed at once.
1095 function startOperation(cm
) {
1096 if (cm
.curOp
) ++cm
.curOp
.depth
;
1098 // Nested operations delay update until the outermost one
1101 // An array of ranges of lines that have to be updated. See
1104 delayedCallbacks: [],
1106 userSelChange: null,
1108 selectionChanged: false,
1109 updateMaxLine: false,
1114 function endOperation(cm
) {
1116 if (--op
.depth
) return;
1118 var view
= cm
.view
, display
= cm
.display
;
1119 if (op
.updateMaxLine
) computeMaxLength(view
);
1120 if (view
.maxLineChanged
&& !cm
.options
.lineWrapping
) {
1121 var width
= measureChar(cm
, view
.maxLine
, view
.maxLine
.text
.length
).right
;
1122 display
.sizer
.style
.minWidth
= (width
+ 3 + scrollerCutOff
) + "px";
1123 view
.maxLineChanged
= false;
1125 var newScrollPos
, updated
;
1126 if (op
.selectionChanged
) {
1127 var coords
= cursorCoords(cm
, view
.sel
.head
);
1128 newScrollPos
= calculateScrollPos(cm
, coords
.left
, coords
.top
, coords
.left
, coords
.bottom
);
1130 if (op
.changes
.length
|| newScrollPos
&& newScrollPos
.scrollTop
!= null)
1131 updated
= updateDisplay(cm
, op
.changes
, newScrollPos
&& newScrollPos
.scrollTop
);
1132 if (!updated
&& op
.selectionChanged
) updateSelection(cm
);
1133 if (newScrollPos
) scrollCursorIntoView(cm
);
1134 if (op
.selectionChanged
) restartBlink(cm
);
1136 if (view
.focused
&& op
.updateInput
)
1137 resetInput(cm
, op
.userSelChange
);
1140 signal(cm
, "change", cm
, op
.textChanged
);
1141 if (op
.selectionChanged
) signal(cm
, "cursorActivity", cm
);
1142 for (var i
= 0; i
< op
.delayedCallbacks
.length
; ++i
) op
.delayedCallbacks
[i
](cm
);
1145 // Wraps a function in an operation. Returns the wrapped function.
1146 function operation(cm1
, f
) {
1148 var cm
= cm1
|| this;
1150 try {var result
= f
.apply(cm
, arguments
);}
1151 finally {endOperation(cm
);}
1156 function regChange(cm
, from, to
, lendiff
) {
1157 cm
.curOp
.changes
.push({from: from, to: to
, diff: lendiff
});
1162 function slowPoll(cm
) {
1163 if (cm
.view
.pollingFast
) return;
1164 cm
.display
.poll
.set(cm
.options
.pollInterval
, function() {
1166 if (cm
.view
.focused
) slowPoll(cm
);
1170 function fastPoll(cm
) {
1172 cm
.display
.pollingFast
= true;
1174 var changed
= readInput(cm
);
1175 if (!changed
&& !missed
) {missed
= true; cm
.display
.poll
.set(60, p
);}
1176 else {cm
.display
.pollingFast
= false; slowPoll(cm
);}
1178 cm
.display
.poll
.set(20, p
);
1181 // prevInput is a hack to work with IME. If we reset the textarea
1182 // on every change, that breaks IME. So we look for changes
1183 // compared to the previous content instead. (Modern browsers have
1184 // events that indicate IME taking place, but these are not widely
1185 // supported or compatible enough yet to rely on.)
1186 function readInput(cm
) {
1187 var input
= cm
.display
.input
, prevInput
= cm
.display
.prevInput
, view
= cm
.view
, sel
= view
.sel
;
1188 if (!view
.focused
|| hasSelection(input
) || isReadOnly(cm
)) return false;
1189 var text
= input
.value
;
1190 if (text
== prevInput
&& posEq(sel
.from, sel
.to
)) return false;
1192 view
.sel
.shift
= false;
1193 var same
= 0, l
= Math
.min(prevInput
.length
, text
.length
);
1194 while (same
< l
&& prevInput
[same
] == text
[same
]) ++same
;
1195 var from = sel
.from, to
= sel
.to
;
1196 if (same
< prevInput
.length
)
1197 from = {line: from.line
, ch: from.ch
- (prevInput
.length
- same
)};
1198 else if (view
.overwrite
&& posEq(from, to
) && !cm
.display
.pasteIncoming
)
1199 to
= {line: to
.line
, ch: Math
.min(getLine(cm
.view
.doc
, to
.line
).text
.length
, to
.ch
+ (text
.length
- same
))};
1200 var updateInput
= cm
.curOp
.updateInput
;
1201 updateDoc(cm
, from, to
, splitLines(text
.slice(same
)), "end",
1202 cm
.display
.pasteIncoming
? "paste" : "input", {from: from, to: to
});
1203 cm
.curOp
.updateInput
= updateInput
;
1204 if (text
.length
> 1000) input
.value
= cm
.display
.prevInput
= "";
1205 else cm
.display
.prevInput
= text
;
1207 cm
.display
.pasteIncoming
= false;
1211 function resetInput(cm
, user
) {
1212 var view
= cm
.view
, minimal
, selected
;
1213 if (!posEq(view
.sel
.from, view
.sel
.to
)) {
1214 cm
.display
.prevInput
= "";
1215 minimal
= hasCopyEvent
&&
1216 (view
.sel
.to
.line
- view
.sel
.from.line
> 100 || (selected
= cm
.getSelection()).length
> 1000);
1217 if (minimal
) cm
.display
.input
.value
= "-";
1218 else cm
.display
.input
.value
= selected
|| cm
.getSelection();
1219 if (view
.focused
) selectInput(cm
.display
.input
);
1220 } else if (user
) cm
.display
.prevInput
= cm
.display
.input
.value
= "";
1221 cm
.display
.inaccurateSelection
= minimal
;
1224 function focusInput(cm
) {
1225 if (cm
.options
.readOnly
!= "nocursor" && (ie
|| document
.activeElement
!= cm
.display
.input
))
1226 cm
.display
.input
.focus();
1229 function isReadOnly(cm
) {
1230 return cm
.options
.readOnly
|| cm
.view
.cantEdit
;
1235 function registerEventHandlers(cm
) {
1237 on(d
.scroller
, "mousedown", operation(cm
, onMouseDown
));
1238 on(d
.scroller
, "dblclick", operation(cm
, e_preventDefault
));
1239 on(d
.lineSpace
, "selectstart", function(e
) {
1240 if (!mouseEventInWidget(d
, e
)) e_preventDefault(e
);
1242 // Gecko browsers fire contextmenu *after* opening the menu, at
1243 // which point we can't mess with it anymore. Context menu is
1244 // handled in onMouseDown for Gecko.
1245 if (!gecko
) on(d
.scroller
, "contextmenu", function(e
) {onContextMenu(cm
, e
);});
1247 on(d
.scroller
, "scroll", function() {
1248 setScrollTop(cm
, d
.scroller
.scrollTop
);
1249 setScrollLeft(cm
, d
.scroller
.scrollLeft
, true);
1250 signal(cm
, "scroll", cm
);
1252 on(d
.scrollbarV
, "scroll", function() {
1253 setScrollTop(cm
, d
.scrollbarV
.scrollTop
);
1255 on(d
.scrollbarH
, "scroll", function() {
1256 setScrollLeft(cm
, d
.scrollbarH
.scrollLeft
);
1259 on(d
.scroller
, "mousewheel", function(e
){onScrollWheel(cm
, e
);});
1260 on(d
.scroller
, "DOMMouseScroll", function(e
){onScrollWheel(cm
, e
);});
1262 function reFocus() { if (cm
.view
.focused
) setTimeout(bind(focusInput
, cm
), 0); }
1263 on(d
.scrollbarH
, "mousedown", reFocus
);
1264 on(d
.scrollbarV
, "mousedown", reFocus
);
1265 // Prevent wrapper from ever scrolling
1266 on(d
.wrapper
, "scroll", function() { d
.wrapper
.scrollTop
= d
.wrapper
.scrollLeft
= 0; });
1267 on(window
, "resize", function resizeHandler() {
1268 // Might be a text scaling operation, clear size caches.
1269 d
.cachedCharWidth
= d
.cachedTextHeight
= null;
1271 if (d
.wrapper
.parentNode
) updateDisplay(cm
, true);
1272 else off(window
, "resize", resizeHandler
);
1275 on(d
.input
, "keyup", operation(cm
, function(e
) {
1276 if (cm
.options
.onKeyEvent
&& cm
.options
.onKeyEvent(cm
, addStop(e
))) return;
1277 if (e_prop(e
, "keyCode") == 16) cm
.view
.sel
.shift
= false;
1279 on(d
.input
, "input", bind(fastPoll
, cm
));
1280 on(d
.input
, "keydown", operation(cm
, onKeyDown
));
1281 on(d
.input
, "keypress", operation(cm
, onKeyPress
));
1282 on(d
.input
, "focus", bind(onFocus
, cm
));
1283 on(d
.input
, "blur", bind(onBlur
, cm
));
1286 if (cm
.options
.onDragEvent
&& cm
.options
.onDragEvent(cm
, addStop(e
))) return;
1289 if (cm
.options
.dragDrop
) {
1290 on(d
.scroller
, "dragstart", function(e
){onDragStart(cm
, e
);});
1291 on(d
.scroller
, "dragenter", drag_
);
1292 on(d
.scroller
, "dragover", drag_
);
1293 on(d
.scroller
, "drop", operation(cm
, onDrop
));
1295 on(d
.scroller
, "paste", function(){focusInput(cm
); fastPoll(cm
);});
1296 on(d
.input
, "paste", function() {
1297 d
.pasteIncoming
= true;
1301 function prepareCopy() {
1302 if (d
.inaccurateSelection
) {
1304 d
.inaccurateSelection
= false;
1305 d
.input
.value
= cm
.getSelection();
1306 selectInput(d
.input
);
1309 on(d
.input
, "cut", prepareCopy
);
1310 on(d
.input
, "copy", prepareCopy
);
1312 // Needed to handle Tab key in KHTML
1313 if (khtml
) on(d
.sizer
, "mouseup", function() {
1314 if (document
.activeElement
== d
.input
) d
.input
.blur();
1319 function mouseEventInWidget(display
, e
) {
1320 for (var n
= e_target(e
); n
!= display
.wrapper
; n
= n
.parentNode
)
1321 if (/\bCodeMirror-(?:line)?widget\b/.test(n
.className
) ||
1322 n
.parentNode
== display
.sizer
&& n
!= display
.mover
) return true;
1325 function posFromMouse(cm
, e
, liberal
) {
1326 var display
= cm
.display
;
1328 var target
= e_target(e
);
1329 if (target
== display
.scrollbarH
|| target
== display
.scrollbarH
.firstChild
||
1330 target
== display
.scrollbarV
|| target
== display
.scrollbarV
.firstChild
||
1331 target
== display
.scrollbarFiller
) return null;
1333 var x
, y
, space
= display
.lineSpace
.getBoundingClientRect();
1334 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1335 try { x
= e
.clientX
; y
= e
.clientY
; } catch (e
) { return null; }
1336 return coordsChar(cm
, x
- space
.left
, y
- space
.top
);
1339 var lastClick
, lastDoubleClick
;
1340 function onMouseDown(e
) {
1341 var cm
= this, display
= cm
.display
, view
= cm
.view
, sel
= view
.sel
, doc
= view
.doc
;
1342 sel
.shift
= e_prop(e
, "shiftKey");
1344 if (mouseEventInWidget(display
, e
)) {
1346 display
.scroller
.draggable
= false;
1347 setTimeout(function(){display
.scroller
.draggable
= true;}, 100);
1351 if (clickInGutter(cm
, e
)) return;
1352 var start
= posFromMouse(cm
, e
);
1354 switch (e_button(e
)) {
1356 if (gecko
) onContextMenu
.call(cm
, cm
, e
);
1359 if (start
) extendSelection(cm
, start
);
1360 setTimeout(bind(focusInput
, cm
), 20);
1361 e_preventDefault(e
);
1364 // For button 1, if it was clicked inside the editor
1365 // (posFromMouse returning non-null), we have to adjust the
1367 if (!start
) {if (e_target(e
) == display
.scroller
) e_preventDefault(e
); return;}
1369 if (!view
.focused
) onFocus(cm
);
1371 var now
= +new Date
, type
= "single";
1372 if (lastDoubleClick
&& lastDoubleClick
.time
> now
- 400 && posEq(lastDoubleClick
.pos
, start
)) {
1374 e_preventDefault(e
);
1375 setTimeout(bind(focusInput
, cm
), 20);
1376 selectLine(cm
, start
.line
);
1377 } else if (lastClick
&& lastClick
.time
> now
- 400 && posEq(lastClick
.pos
, start
)) {
1379 lastDoubleClick
= {time: now
, pos: start
};
1380 e_preventDefault(e
);
1381 var word
= findWordAt(getLine(doc
, start
.line
).text
, start
);
1382 extendSelection(cm
, word
.from, word
.to
);
1383 } else { lastClick
= {time: now
, pos: start
}; }
1386 if (cm
.options
.dragDrop
&& dragAndDrop
&& !isReadOnly(cm
) && !posEq(sel
.from, sel
.to
) &&
1387 !posLess(start
, sel
.from) && !posLess(sel
.to
, start
) && type
== "single") {
1388 var dragEnd
= operation(cm
, function(e2
) {
1389 if (webkit
) display
.scroller
.draggable
= false;
1390 view
.draggingText
= false;
1391 off(document
, "mouseup", dragEnd
);
1392 off(display
.scroller
, "drop", dragEnd
);
1393 if (Math
.abs(e
.clientX
- e2
.clientX
) + Math
.abs(e
.clientY
- e2
.clientY
) < 10) {
1394 e_preventDefault(e2
);
1395 extendSelection(cm
, start
);
1399 // Let the drag handler handle this.
1400 if (webkit
) display
.scroller
.draggable
= true;
1401 view
.draggingText
= dragEnd
;
1402 // IE's approach to draggable
1403 if (display
.scroller
.dragDrop
) display
.scroller
.dragDrop();
1404 on(document
, "mouseup", dragEnd
);
1405 on(display
.scroller
, "drop", dragEnd
);
1408 e_preventDefault(e
);
1409 if (type
== "single") extendSelection(cm
, clipPos(doc
, start
));
1411 var startstart
= sel
.from, startend
= sel
.to
;
1413 function doSelect(cur
) {
1414 if (type
== "single") {
1415 extendSelection(cm
, clipPos(doc
, start
), cur
);
1419 startstart
= clipPos(doc
, startstart
);
1420 startend
= clipPos(doc
, startend
);
1421 if (type
== "double") {
1422 var word
= findWordAt(getLine(doc
, cur
.line
).text
, cur
);
1423 if (posLess(cur
, startstart
)) extendSelection(cm
, word
.from, startend
);
1424 else extendSelection(cm
, startstart
, word
.to
);
1425 } else if (type
== "triple") {
1426 if (posLess(cur
, startstart
)) extendSelection(cm
, startend
, clipPos(doc
, {line: cur
.line
, ch: 0}));
1427 else extendSelection(cm
, startstart
, clipPos(doc
, {line: cur
.line
+ 1, ch: 0}));
1431 var editorSize
= display
.wrapper
.getBoundingClientRect();
1432 // Used to ensure timeout re-tries don't fire when another extend
1433 // happened in the meantime (clearTimeout isn't reliable -- at
1434 // least on Chrome, the timeouts still happen even when cleared,
1435 // if the clear happens after their scheduled firing time).
1438 function extend(e
) {
1439 var curCount
= ++counter
;
1440 var cur
= posFromMouse(cm
, e
, true);
1442 if (!posEq(cur
, last
)) {
1443 if (!view
.focused
) onFocus(cm
);
1446 var visible
= visibleLines(display
, doc
);
1447 if (cur
.line
>= visible
.to
|| cur
.line
< visible
.from)
1448 setTimeout(operation(cm
, function(){if (counter
== curCount
) extend(e
);}), 150);
1450 var outside
= e
.clientY
< editorSize
.top
? -20 : e
.clientY
> editorSize
.bottom
? 20 : 0;
1451 if (outside
) setTimeout(operation(cm
, function() {
1452 if (counter
!= curCount
) return;
1453 display
.scroller
.scrollTop
+= outside
;
1461 var cur
= posFromMouse(cm
, e
);
1462 if (cur
) doSelect(cur
);
1463 e_preventDefault(e
);
1465 off(document
, "mousemove", move);
1466 off(document
, "mouseup", up
);
1469 var move = operation(cm
, function(e
) {
1470 if (!ie
&& !e_button(e
)) done(e
);
1473 var up
= operation(cm
, done
);
1474 on(document
, "mousemove", move);
1475 on(document
, "mouseup", up
);
1478 function onDrop(e
) {
1480 if (cm
.options
.onDragEvent
&& cm
.options
.onDragEvent(cm
, addStop(e
))) return;
1481 e_preventDefault(e
);
1482 var pos
= posFromMouse(cm
, e
, true), files
= e
.dataTransfer
.files
;
1483 if (!pos
|| isReadOnly(cm
)) return;
1484 if (files
&& files
.length
&& window
.FileReader
&& window
.File
) {
1485 var n
= files
.length
, text
= Array(n
), read
= 0;
1486 var loadFile = function(file
, i
) {
1487 var reader
= new FileReader
;
1488 reader
.onload = function() {
1489 text
[i
] = reader
.result
;
1491 pos
= clipPos(cm
.view
.doc
, pos
);
1492 operation(cm
, function() {
1493 var end
= replaceRange(cm
, text
.join(""), pos
, pos
, "paste");
1494 setSelection(cm
, pos
, end
);
1498 reader
.readAsText(file
);
1500 for (var i
= 0; i
< n
; ++i
) loadFile(files
[i
], i
);
1502 // Don't do a replace if the drop happened inside of the selected text.
1503 if (cm
.view
.draggingText
&& !(posLess(pos
, cm
.view
.sel
.from) || posLess(cm
.view
.sel
.to
, pos
))) {
1504 cm
.view
.draggingText(e
);
1505 if (ie
) setTimeout(bind(focusInput
, cm
), 50);
1509 var text
= e
.dataTransfer
.getData("Text");
1511 var curFrom
= cm
.view
.sel
.from, curTo
= cm
.view
.sel
.to
;
1512 setSelection(cm
, pos
, pos
);
1513 if (cm
.view
.draggingText
) replaceRange(cm
, "", curFrom
, curTo
, "paste");
1514 cm
.replaceSelection(text
, null, "paste");
1523 function clickInGutter(cm
, e
) {
1524 var display
= cm
.display
;
1525 try { var mX
= e
.clientX
, mY
= e
.clientY
; }
1526 catch(e
) { return false; }
1528 if (mX
>= Math
.floor(display
.gutters
.getBoundingClientRect().right
)) return false;
1529 e_preventDefault(e
);
1530 if (!hasHandler(cm
, "gutterClick")) return true;
1532 var lineBox
= display
.lineDiv
.getBoundingClientRect();
1533 if (mY
> lineBox
.bottom
) return true;
1534 mY
-= lineBox
.top
- display
.viewOffset
;
1536 for (var i
= 0; i
< cm
.options
.gutters
.length
; ++i
) {
1537 var g
= display
.gutters
.childNodes
[i
];
1538 if (g
&& g
.getBoundingClientRect().right
>= mX
) {
1539 var line
= lineAtHeight(cm
.view
.doc
, mY
);
1540 var gutter
= cm
.options
.gutters
[i
];
1541 signalLater(cm
, cm
, "gutterClick", cm
, line
, gutter
, e
);
1548 function onDragStart(cm
, e
) {
1549 var txt
= cm
.getSelection();
1550 e
.dataTransfer
.setData("Text", txt
);
1552 // Use dummy image instead of default browsers image.
1553 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1554 if (e
.dataTransfer
.setDragImage
&& !safari
)
1555 e
.dataTransfer
.setDragImage(elt('img'), 0, 0);
1558 function setScrollTop(cm
, val
) {
1559 if (Math
.abs(cm
.view
.scrollTop
- val
) < 2) return;
1560 cm
.view
.scrollTop
= val
;
1561 if (!gecko
) updateDisplay(cm
, [], val
);
1562 if (cm
.display
.scroller
.scrollTop
!= val
) cm
.display
.scroller
.scrollTop
= val
;
1563 if (cm
.display
.scrollbarV
.scrollTop
!= val
) cm
.display
.scrollbarV
.scrollTop
= val
;
1564 if (gecko
) updateDisplay(cm
, []);
1566 function setScrollLeft(cm
, val
, isScroller
) {
1567 if (isScroller
? val
== cm
.view
.scrollLeft : Math
.abs(cm
.view
.scrollLeft
- val
) < 2) return;
1568 cm
.view
.scrollLeft
= val
;
1569 alignHorizontally(cm
);
1570 if (cm
.display
.scroller
.scrollLeft
!= val
) cm
.display
.scroller
.scrollLeft
= val
;
1571 if (cm
.display
.scrollbarH
.scrollLeft
!= val
) cm
.display
.scrollbarH
.scrollLeft
= val
;
1574 // Since the delta values reported on mouse wheel events are
1575 // unstandardized between browsers and even browser versions, and
1576 // generally horribly unpredictable, this code starts by measuring
1577 // the scroll effect that the first few mouse wheel events have,
1578 // and, from that, detects the way it can convert deltas to pixel
1579 // offsets afterwards.
1581 // The reason we want to know the amount a wheel event will scroll
1582 // is that it gives us a chance to update the display before the
1583 // actual scrolling happens, reducing flickering.
1585 var wheelSamples
= 0, wheelDX
, wheelDY
, wheelStartX
, wheelStartY
, wheelPixelsPerUnit
= null;
1586 // Fill in a browser-detected starting value on browsers where we
1587 // know one. These don't have to be accurate -- the result of them
1588 // being wrong would just be a slight flicker on the first wheel
1589 // scroll (if it is large enough).
1590 if (ie
) wheelPixelsPerUnit
= -.53;
1591 else if (gecko
) wheelPixelsPerUnit
= 15;
1592 else if (chrome
) wheelPixelsPerUnit
= -.7;
1593 else if (safari
) wheelPixelsPerUnit
= -1/3;
1595 function onScrollWheel(cm
, e
) {
1596 var dx
= e
.wheelDeltaX
, dy
= e
.wheelDeltaY
;
1597 if (dx
== null && e
.detail
&& e
.axis
== e
.HORIZONTAL_AXIS
) dx
= e
.detail
;
1598 if (dy
== null && e
.detail
&& e
.axis
== e
.VERTICAL_AXIS
) dy
= e
.detail
;
1599 else if (dy
== null) dy
= e
.wheelDelta
;
1601 // Webkit browsers on OS X abort momentum scrolls when the target
1602 // of the scroll event is removed from the scrollable element.
1603 // This hack (see related code in patchDisplay) makes sure the
1604 // element is kept around.
1605 if (dy
&& mac
&& webkit
) {
1606 for (var cur
= e
.target
; cur
!= scroll
; cur
= cur
.parentNode
) {
1608 cm
.display
.currentWheelTarget
= cur
;
1614 var scroll
= cm
.display
.scroller
;
1615 // On some browsers, horizontal scrolling will cause redraws to
1616 // happen before the gutter has been realigned, causing it to
1617 // wriggle around in a most unseemly way. When we have an
1618 // estimated pixels/delta value, we just handle horizontal
1619 // scrolling entirely here. It'll be slightly off from native, but
1620 // better than glitching out.
1621 if (dx
&& !gecko
&& !opera
&& wheelPixelsPerUnit
!= null) {
1623 setScrollTop(cm
, Math
.max(0, Math
.min(scroll
.scrollTop
+ dy
* wheelPixelsPerUnit
, scroll
.scrollHeight
- scroll
.clientHeight
)));
1624 setScrollLeft(cm
, Math
.max(0, Math
.min(scroll
.scrollLeft
+ dx
* wheelPixelsPerUnit
, scroll
.scrollWidth
- scroll
.clientWidth
)));
1625 e_preventDefault(e
);
1626 wheelStartX
= null; // Abort measurement, if in progress
1630 if (dy
&& wheelPixelsPerUnit
!= null) {
1631 var pixels
= dy
* wheelPixelsPerUnit
;
1632 var top
= cm
.view
.scrollTop
, bot
= top
+ cm
.display
.wrapper
.clientHeight
;
1633 if (pixels
< 0) top
= Math
.max(0, top
+ pixels
- 50);
1634 else bot
= Math
.min(cm
.view
.doc
.height
, bot
+ pixels
+ 50);
1635 updateDisplay(cm
, [], {top: top
, bottom: bot
});
1638 if (wheelSamples
< 20) {
1639 if (wheelStartX
== null) {
1640 wheelStartX
= scroll
.scrollLeft
; wheelStartY
= scroll
.scrollTop
;
1641 wheelDX
= dx
; wheelDY
= dy
;
1642 setTimeout(function() {
1643 if (wheelStartX
== null) return;
1644 var movedX
= scroll
.scrollLeft
- wheelStartX
;
1645 var movedY
= scroll
.scrollTop
- wheelStartY
;
1646 var sample
= (movedY
&& wheelDY
&& movedY
/ wheelDY
) ||
1647 (movedX
&& wheelDX
&& movedX
/ wheelDX
);
1648 wheelStartX
= wheelStartY
= null;
1649 if (!sample
) return;
1650 wheelPixelsPerUnit
= (wheelPixelsPerUnit
* wheelSamples
+ sample
) / (wheelSamples
+ 1);
1654 wheelDX
+= dx
; wheelDY
+= dy
;
1659 function doHandleBinding(cm
, bound
, dropShift
) {
1660 if (typeof bound
== "string") {
1661 bound
= commands
[bound
];
1662 if (!bound
) return false;
1664 // Ensure previous input has been read, so that the handler sees a
1665 // consistent view of the document
1666 if (cm
.display
.pollingFast
&& readInput(cm
)) cm
.display
.pollingFast
= false;
1667 var view
= cm
.view
, prevShift
= view
.sel
.shift
;
1669 if (isReadOnly(cm
)) view
.suppressEdits
= true;
1670 if (dropShift
) view
.sel
.shift
= false;
1673 if (e
!= Pass
) throw e
;
1676 view
.sel
.shift
= prevShift
;
1677 view
.suppressEdits
= false;
1682 function allKeyMaps(cm
) {
1683 var maps
= cm
.view
.keyMaps
.slice(0);
1684 maps
.push(cm
.options
.keyMap
);
1685 if (cm
.options
.extraKeys
) maps
.unshift(cm
.options
.extraKeys
);
1689 var maybeTransition
;
1690 function handleKeyBinding(cm
, e
) {
1691 // Handle auto keymap transitions
1692 var startMap
= getKeyMap(cm
.options
.keyMap
), next
= startMap
.auto
;
1693 clearTimeout(maybeTransition
);
1694 if (next
&& !isModifierKey(e
)) maybeTransition
= setTimeout(function() {
1695 if (getKeyMap(cm
.options
.keyMap
) == startMap
)
1696 cm
.options
.keyMap
= (next
.call
? next
.call(null, cm
) : next
);
1699 var name
= keyNames
[e_prop(e
, "keyCode")], handled
= false;
1700 var flipCtrlCmd
= mac
&& (opera
|| qtwebkit
);
1701 if (name
== null || e
.altGraphKey
) return false;
1702 if (e_prop(e
, "altKey")) name
= "Alt-" + name
;
1703 if (e_prop(e
, flipCtrlCmd
? "metaKey" : "ctrlKey")) name
= "Ctrl-" + name
;
1704 if (e_prop(e
, flipCtrlCmd
? "ctrlKey" : "metaKey")) name
= "Cmd-" + name
;
1706 var stopped
= false;
1707 function stop() { stopped
= true; }
1708 var keymaps
= allKeyMaps(cm
);
1710 if (e_prop(e
, "shiftKey")) {
1711 handled
= lookupKey("Shift-" + name
, keymaps
,
1712 function(b
) {return doHandleBinding(cm
, b
, true);}, stop
)
1713 || lookupKey(name
, keymaps
, function(b
) {
1714 if (typeof b
== "string" && /^go[A-Z]/.test(b
)) return doHandleBinding(cm
, b
);
1717 handled
= lookupKey(name
, keymaps
,
1718 function(b
) { return doHandleBinding(cm
, b
); }, stop
);
1720 if (stopped
) handled
= false;
1722 e_preventDefault(e
);
1724 if (ie_lt9
) { e
.oldKeyCode
= e
.keyCode
; e
.keyCode
= 0; }
1729 function handleCharBinding(cm
, e
, ch
) {
1730 var handled
= lookupKey("'" + ch
+ "'", allKeyMaps(cm
),
1731 function(b
) { return doHandleBinding(cm
, b
, true); });
1733 e_preventDefault(e
);
1739 var lastStoppedKey
= null;
1740 function onKeyDown(e
) {
1742 if (!cm
.view
.focused
) onFocus(cm
);
1743 if (ie
&& e
.keyCode
== 27) { e
.returnValue
= false; }
1744 if (cm
.options
.onKeyEvent
&& cm
.options
.onKeyEvent(cm
, addStop(e
))) return;
1745 var code
= e_prop(e
, "keyCode");
1746 // IE does strange things with escape.
1747 cm
.view
.sel
.shift
= code
== 16 || e_prop(e
, "shiftKey");
1748 // First give onKeyEvent option a chance to handle this.
1749 var handled
= handleKeyBinding(cm
, e
);
1751 lastStoppedKey
= handled
? code : null;
1752 // Opera has no cut event... we try to at least catch the key combo
1753 if (!handled
&& code
== 88 && !hasCopyEvent
&& e_prop(e
, mac
? "metaKey" : "ctrlKey"))
1754 cm
.replaceSelection("");
1758 function onKeyPress(e
) {
1760 if (cm
.options
.onKeyEvent
&& cm
.options
.onKeyEvent(cm
, addStop(e
))) return;
1761 var keyCode
= e_prop(e
, "keyCode"), charCode
= e_prop(e
, "charCode");
1762 if (opera
&& keyCode
== lastStoppedKey
) {lastStoppedKey
= null; e_preventDefault(e
); return;}
1763 if (((opera
&& (!e
.which
|| e
.which
< 10)) || khtml
) && handleKeyBinding(cm
, e
)) return;
1764 var ch
= String
.fromCharCode(charCode
== null ? keyCode : charCode
);
1765 if (this.options
.electricChars
&& this.view
.mode
.electricChars
&&
1766 this.options
.smartIndent
&& !isReadOnly(this) &&
1767 this.view
.mode
.electricChars
.indexOf(ch
) > -1)
1768 setTimeout(operation(cm
, function() {indentLine(cm
, cm
.view
.sel
.to
.line
, "smart");}), 75);
1769 if (handleCharBinding(cm
, e
, ch
)) return;
1773 function onFocus(cm
) {
1774 if (cm
.options
.readOnly
== "nocursor") return;
1775 if (!cm
.view
.focused
) {
1776 signal(cm
, "focus", cm
);
1777 cm
.view
.focused
= true;
1778 if (cm
.display
.scroller
.className
.search(/\bCodeMirror-focused\b/) == -1)
1779 cm
.display
.scroller
.className
+= " CodeMirror-focused";
1780 resetInput(cm
, true);
1785 function onBlur(cm
) {
1786 if (cm
.view
.focused
) {
1787 signal(cm
, "blur", cm
);
1788 cm
.view
.focused
= false;
1789 cm
.display
.scroller
.className
= cm
.display
.scroller
.className
.replace(" CodeMirror-focused", "");
1791 clearInterval(cm
.display
.blinker
);
1792 setTimeout(function() {if (!cm
.view
.focused
) cm
.view
.sel
.shift
= false;}, 150);
1795 var detectingSelectAll
;
1796 function onContextMenu(cm
, e
) {
1797 var display
= cm
.display
, sel
= cm
.view
.sel
;
1798 var pos
= posFromMouse(cm
, e
), scrollPos
= display
.scroller
.scrollTop
;
1799 if (!pos
|| opera
) return; // Opera is difficult.
1800 if (posEq(sel
.from, sel
.to
) || posLess(pos
, sel
.from) || !posLess(pos
, sel
.to
))
1801 operation(cm
, setSelection
)(cm
, pos
, pos
);
1803 var oldCSS
= display
.input
.style
.cssText
;
1804 display
.inputDiv
.style
.position
= "absolute";
1805 display
.input
.style
.cssText
= "position: fixed; width: 30px; height: 30px; top: " + (e
.clientY
- 5) +
1806 "px; left: " + (e
.clientX
- 5) + "px; z-index: 1000; background: white; outline: none;" +
1807 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1809 resetInput(cm
, true);
1810 // Adds "Select all" to context menu in FF
1811 if (posEq(sel
.from, sel
.to
)) display
.input
.value
= display
.prevInput
= " ";
1814 display
.inputDiv
.style
.position
= "relative";
1815 display
.input
.style
.cssText
= oldCSS
;
1816 if (ie_lt9
) display
.scrollbarV
.scrollTop
= display
.scroller
.scrollTop
= scrollPos
;
1819 // Try to detect the user choosing select-all
1820 if (display
.input
.selectionStart
!= null) {
1821 clearTimeout(detectingSelectAll
);
1822 var extval
= display
.input
.value
= " " + (posEq(sel
.from, sel
.to
) ? "" : display
.input
.value
), i
= 0;
1823 display
.prevInput
= " ";
1824 display
.input
.selectionStart
= 1; display
.input
.selectionEnd
= extval
.length
;
1825 detectingSelectAll
= setTimeout(function poll(){
1826 if (display
.prevInput
== " " && display
.input
.selectionStart
== 0)
1827 operation(cm
, commands
.selectAll
)(cm
);
1828 else if (i
++ < 10) detectingSelectAll
= setTimeout(poll
, 500);
1829 else resetInput(cm
);
1836 on(window
, "mouseup", function mouseup() {
1837 off(window
, "mouseup", mouseup
);
1838 setTimeout(rehide
, 20);
1841 setTimeout(rehide
, 50);
1847 // Replace the range from from to to by the strings in newText.
1848 // Afterwards, set the selection to selFrom, selTo.
1849 function updateDoc(cm
, from, to
, newText
, selUpdate
, origin
) {
1850 // Possibly split or suppress the update based on the presence
1851 // of read-only spans in its range.
1852 var split
= sawReadOnlySpans
&&
1853 removeReadOnlyRanges(cm
.view
.doc
, from, to
);
1855 for (var i
= split
.length
- 1; i
>= 1; --i
)
1856 updateDocInner(cm
, split
[i
].from, split
[i
].to
, [""], origin
);
1858 return updateDocInner(cm
, split
[0].from, split
[0].to
, newText
, selUpdate
, origin
);
1860 return updateDocInner(cm
, from, to
, newText
, selUpdate
, origin
);
1864 function updateDocInner(cm
, from, to
, newText
, selUpdate
, origin
) {
1865 if (cm
.view
.suppressEdits
) return;
1867 var view
= cm
.view
, doc
= view
.doc
, old
= [];
1868 doc
.iter(from.line
, to
.line
+ 1, function(line
) {
1869 old
.push(newHL(line
.text
, line
.markedSpans
));
1871 var startSelFrom
= view
.sel
.from, startSelTo
= view
.sel
.to
;
1872 var lines
= updateMarkedSpans(hlSpans(old
[0]), hlSpans(lst(old
)), from.ch
, to
.ch
, newText
);
1873 var retval
= updateDocNoUndo(cm
, from, to
, lines
, selUpdate
, origin
);
1874 if (view
.history
) addChange(cm
, from.line
, newText
.length
, old
, origin
,
1875 startSelFrom
, startSelTo
, view
.sel
.from, view
.sel
.to
);
1879 function unredoHelper(cm
, type
) {
1880 var doc
= cm
.view
.doc
, hist
= cm
.view
.history
;
1881 var set = (type
== "undo" ? hist
.done : hist
.undone
).pop();
1883 var anti
= {events: [], fromBefore: set.fromAfter
, toBefore: set.toAfter
,
1884 fromAfter: set.fromBefore
, toAfter: set.toBefore
};
1885 for (var i
= set.events
.length
- 1; i
>= 0; i
-= 1) {
1886 hist
.dirtyCounter
+= type
== "undo" ? -1 : 1;
1887 var change
= set.events
[i
];
1888 var replaced
= [], end
= change
.start
+ change
.added
;
1889 doc
.iter(change
.start
, end
, function(line
) { replaced
.push(newHL(line
.text
, line
.markedSpans
)); });
1890 anti
.events
.push({start: change
.start
, added: change
.old
.length
, old: replaced
});
1891 var selPos
= i
? null : {from: set.fromBefore
, to: set.toBefore
};
1892 updateDocNoUndo(cm
, {line: change
.start
, ch: 0}, {line: end
- 1, ch: getLine(doc
, end
-1).text
.length
},
1893 change
.old
, selPos
, type
);
1895 (type
== "undo" ? hist
.undone : hist
.done
).push(anti
);
1898 function updateDocNoUndo(cm
, from, to
, lines
, selUpdate
, origin
) {
1899 var view
= cm
.view
, doc
= view
.doc
, display
= cm
.display
;
1900 if (view
.suppressEdits
) return;
1902 var nlines
= to
.line
- from.line
, firstLine
= getLine(doc
, from.line
), lastLine
= getLine(doc
, to
.line
);
1903 var recomputeMaxLength
= false, checkWidthStart
= from.line
;
1904 if (!cm
.options
.lineWrapping
) {
1905 checkWidthStart
= lineNo(visualLine(doc
, firstLine
));
1906 doc
.iter(checkWidthStart
, to
.line
+ 1, function(line
) {
1907 if (lineLength(doc
, line
) == view
.maxLineLength
) {
1908 recomputeMaxLength
= true;
1914 var lastHL
= lst(lines
), th
= textHeight(display
);
1916 // First adjust the line structure
1917 if (from.ch
== 0 && to
.ch
== 0 && hlText(lastHL
) == "") {
1918 // This is a whole-line replace. Treated specially to make
1919 // sure line objects move the way they are supposed to.
1921 for (var i
= 0, e
= lines
.length
- 1; i
< e
; ++i
)
1922 added
.push(makeLine(hlText(lines
[i
]), hlSpans(lines
[i
]), th
));
1923 updateLine(cm
, lastLine
, lastLine
.text
, hlSpans(lastHL
));
1924 if (nlines
) doc
.remove(from.line
, nlines
, cm
);
1925 if (added
.length
) doc
.insert(from.line
, added
);
1926 } else if (firstLine
== lastLine
) {
1927 if (lines
.length
== 1) {
1928 updateLine(cm
, firstLine
, firstLine
.text
.slice(0, from.ch
) + hlText(lines
[0]) +
1929 firstLine
.text
.slice(to
.ch
), hlSpans(lines
[0]));
1931 for (var added
= [], i
= 1, e
= lines
.length
- 1; i
< e
; ++i
)
1932 added
.push(makeLine(hlText(lines
[i
]), hlSpans(lines
[i
]), th
));
1933 added
.push(makeLine(hlText(lastHL
) + firstLine
.text
.slice(to
.ch
), hlSpans(lastHL
), th
));
1934 updateLine(cm
, firstLine
, firstLine
.text
.slice(0, from.ch
) + hlText(lines
[0]), hlSpans(lines
[0]));
1935 doc
.insert(from.line
+ 1, added
);
1937 } else if (lines
.length
== 1) {
1938 updateLine(cm
, firstLine
, firstLine
.text
.slice(0, from.ch
) + hlText(lines
[0]) +
1939 lastLine
.text
.slice(to
.ch
), hlSpans(lines
[0]));
1940 doc
.remove(from.line
+ 1, nlines
, cm
);
1943 updateLine(cm
, firstLine
, firstLine
.text
.slice(0, from.ch
) + hlText(lines
[0]), hlSpans(lines
[0]));
1944 updateLine(cm
, lastLine
, hlText(lastHL
) + lastLine
.text
.slice(to
.ch
), hlSpans(lastHL
));
1945 for (var i
= 1, e
= lines
.length
- 1; i
< e
; ++i
)
1946 added
.push(makeLine(hlText(lines
[i
]), hlSpans(lines
[i
]), th
));
1947 if (nlines
> 1) doc
.remove(from.line
+ 1, nlines
- 1, cm
);
1948 doc
.insert(from.line
+ 1, added
);
1951 if (cm
.options
.lineWrapping
) {
1952 var perLine
= Math
.max(5, display
.scroller
.clientWidth
/ charWidth(display
) - 3);
1953 doc
.iter(from.line
, from.line
+ lines
.length
, function(line
) {
1954 if (line
.height
== 0) return;
1955 var guess
= (Math
.ceil(line
.text
.length
/ perLine
) || 1) * th
;
1956 if (guess
!= line
.height
) updateLineHeight(line
, guess
);
1959 doc
.iter(checkWidthStart
, from.line
+ lines
.length
, function(line
) {
1960 var len
= lineLength(doc
, line
);
1961 if (len
> view
.maxLineLength
) {
1962 view
.maxLine
= line
;
1963 view
.maxLineLength
= len
;
1964 view
.maxLineChanged
= true;
1965 recomputeMaxLength
= false;
1968 if (recomputeMaxLength
) cm
.curOp
.updateMaxLine
= true;
1971 // Adjust frontier, schedule worker
1972 view
.frontier
= Math
.min(view
.frontier
, from.line
);
1973 startWorker(cm
, 400);
1975 var lendiff
= lines
.length
- nlines
- 1;
1976 // Remember that these lines changed, for updating the display
1977 regChange(cm
, from.line
, to
.line
+ 1, lendiff
);
1978 if (hasHandler(cm
, "change")) {
1979 // Normalize lines to contain only strings, since that's what
1980 // the change event handler expects
1981 for (var i
= 0; i
< lines
.length
; ++i
)
1982 if (typeof lines
[i
] != "string") lines
[i
] = lines
[i
].text
;
1983 var changeObj
= {from: from, to: to
, text: lines
, origin: origin
};
1984 if (cm
.curOp
.textChanged
) {
1985 for (var cur
= cm
.curOp
.textChanged
; cur
.next
; cur
= cur
.next
) {}
1986 cur
.next
= changeObj
;
1987 } else cm
.curOp
.textChanged
= changeObj
;
1990 // Update the selection
1991 var newSelFrom
, newSelTo
, end
= {line: from.line
+ lines
.length
- 1,
1992 ch: hlText(lastHL
).length
+ (lines
.length
== 1 ? from.ch : 0)};
1993 if (selUpdate
&& typeof selUpdate
!= "string") {
1994 if (selUpdate
.from) { newSelFrom
= selUpdate
.from; newSelTo
= selUpdate
.to
; }
1995 else newSelFrom
= newSelTo
= selUpdate
;
1996 } else if (selUpdate
== "end") {
1997 newSelFrom
= newSelTo
= end
;
1998 } else if (selUpdate
== "start") {
1999 newSelFrom
= newSelTo
= from;
2000 } else if (selUpdate
== "around") {
2001 newSelFrom
= from; newSelTo
= end
;
2003 var adjustPos = function(pos
) {
2004 if (posLess(pos
, from)) return pos
;
2005 if (!posLess(to
, pos
)) return end
;
2006 var line
= pos
.line
+ lendiff
;
2008 if (pos
.line
== to
.line
)
2009 ch
+= hlText(lastHL
).length
- (to
.ch
- (to
.line
== from.line
? from.ch : 0));
2010 return {line: line
, ch: ch
};
2012 newSelFrom
= adjustPos(view
.sel
.from);
2013 newSelTo
= adjustPos(view
.sel
.to
);
2015 setSelection(cm
, newSelFrom
, newSelTo
, null, true);
2019 function replaceRange(cm
, code
, from, to
, origin
) {
2021 if (posLess(to
, from)) { var tmp
= to
; to
= from; from = tmp
; }
2022 return updateDoc(cm
, from, to
, splitLines(code
), null, origin
);
2027 function posEq(a
, b
) {return a
.line
== b
.line
&& a
.ch
== b
.ch
;}
2028 function posLess(a
, b
) {return a
.line
< b
.line
|| (a
.line
== b
.line
&& a
.ch
< b
.ch
);}
2029 function copyPos(x
) {return {line: x
.line
, ch: x
.ch
};}
2031 function clipLine(doc
, n
) {return Math
.max(0, Math
.min(n
, doc
.size
-1));}
2032 function clipPos(doc
, pos
) {
2033 if (pos
.line
< 0) return {line: 0, ch: 0};
2034 if (pos
.line
>= doc
.size
) return {line: doc
.size
-1, ch: getLine(doc
, doc
.size
-1).text
.length
};
2035 var ch
= pos
.ch
, linelen
= getLine(doc
, pos
.line
).text
.length
;
2036 if (ch
== null || ch
> linelen
) return {line: pos
.line
, ch: linelen
};
2037 else if (ch
< 0) return {line: pos
.line
, ch: 0};
2040 function isLine(doc
, l
) {return l
>= 0 && l
< doc
.size
;}
2042 // If shift is held, this will move the selection anchor. Otherwise,
2043 // it'll set the whole selection.
2044 function extendSelection(cm
, pos
, other
, bias
) {
2045 var sel
= cm
.view
.sel
;
2046 if (sel
.shift
|| sel
.extend
) {
2047 var anchor
= sel
.anchor
;
2049 var posBefore
= posLess(pos
, anchor
);
2050 if (posBefore
!= posLess(other
, anchor
)) {
2053 } else if (posBefore
!= posLess(pos
, other
)) {
2057 setSelection(cm
, anchor
, pos
, bias
);
2059 setSelection(cm
, pos
, other
|| pos
, bias
);
2061 cm
.curOp
.userSelChange
= true;
2064 // Update the selection. Last two args are only used by
2065 // updateDoc, since they have to be expressed in the line
2066 // numbers before the update.
2067 function setSelection(cm
, anchor
, head
, bias
, checkAtomic
) {
2068 cm
.view
.goalColumn
= null;
2069 var sel
= cm
.view
.sel
;
2070 // Skip over atomic spans.
2071 if (checkAtomic
|| !posEq(anchor
, sel
.anchor
))
2072 anchor
= skipAtomic(cm
, anchor
, bias
, checkAtomic
!= "push");
2073 if (checkAtomic
|| !posEq(head
, sel
.head
))
2074 head
= skipAtomic(cm
, head
, bias
, checkAtomic
!= "push");
2076 if (posEq(sel
.anchor
, anchor
) && posEq(sel
.head
, head
)) return;
2078 sel
.anchor
= anchor
; sel
.head
= head
;
2079 var inv
= posLess(head
, anchor
);
2080 sel
.from = inv
? head : anchor
;
2081 sel
.to
= inv
? anchor : head
;
2083 cm
.curOp
.updateInput
= true;
2084 cm
.curOp
.selectionChanged
= true;
2087 function reCheckSelection(cm
) {
2088 setSelection(cm
, cm
.view
.sel
.from, cm
.view
.sel
.to
, null, "push");
2091 function skipAtomic(cm
, pos
, bias
, mayClear
) {
2092 var doc
= cm
.view
.doc
, flipped
= false, curPos
= pos
;
2093 var dir
= bias
|| 1;
2094 cm
.view
.cantEdit
= false;
2096 var line
= getLine(doc
, curPos
.line
), toClear
;
2097 if (line
.markedSpans
) {
2098 for (var i
= 0; i
< line
.markedSpans
.length
; ++i
) {
2099 var sp
= line
.markedSpans
[i
], m
= sp
.marker
;
2100 if ((sp
.from == null || (m
.inclusiveLeft
? sp
.from <= curPos
.ch : sp
.from < curPos
.ch
)) &&
2101 (sp
.to
== null || (m
.inclusiveRight
? sp
.to
>= curPos
.ch : sp
.to
> curPos
.ch
))) {
2102 if (mayClear
&& m
.clearOnEnter
) {
2103 (toClear
|| (toClear
= [])).push(m
);
2105 } else if (!m
.atomic
) continue;
2106 var newPos
= m
.find()[dir
< 0 ? "from" : "to"];
2107 if (posEq(newPos
, curPos
)) {
2109 if (newPos
.ch
< 0) {
2110 if (newPos
.line
) newPos
= clipPos(doc
, {line: newPos
.line
- 1});
2112 } else if (newPos
.ch
> line
.text
.length
) {
2113 if (newPos
.line
< doc
.size
- 1) newPos
= {line: newPos
.line
+ 1, ch: 0};
2118 // Driven in a corner -- no valid cursor position found at all
2119 // -- try again *with* clearing, if we didn't already
2120 if (!mayClear
) return skipAtomic(cm
, pos
, bias
, true);
2121 // Otherwise, turn off editing until further notice, and return the start of the doc
2122 cm
.view
.cantEdit
= true;
2123 return {line: 0, ch: 0};
2125 flipped
= true; newPos
= pos
; dir
= -dir
;
2132 if (toClear
) for (var i
= 0; i
< toClear
.length
; ++i
) toClear
[i
].clear();
2140 function scrollCursorIntoView(cm
) {
2142 var coords
= scrollPosIntoView(cm
, view
.sel
.head
);
2143 if (!view
.focused
) return;
2144 var display
= cm
.display
, box
= display
.sizer
.getBoundingClientRect(), doScroll
= null;
2145 if (coords
.top
+ box
.top
< 0) doScroll
= true;
2146 else if (coords
.bottom
+ box
.top
> (window
.innerHeight
|| document
.documentElement
.clientHeight
)) doScroll
= false;
2147 if (doScroll
!= null && !phantom
) {
2148 var hidden
= display
.cursor
.style
.display
== "none";
2150 display
.cursor
.style
.display
= "";
2151 display
.cursor
.style
.left
= coords
.left
+ "px";
2152 display
.cursor
.style
.top
= (coords
.top
- display
.viewOffset
) + "px";
2154 display
.cursor
.scrollIntoView(doScroll
);
2155 if (hidden
) display
.cursor
.style
.display
= "none";
2159 function scrollPosIntoView(cm
, pos
) {
2161 var changed
= false, coords
= cursorCoords(cm
, pos
);
2162 var scrollPos
= calculateScrollPos(cm
, coords
.left
, coords
.top
, coords
.left
, coords
.bottom
);
2163 var startTop
= cm
.view
.scrollTop
, startLeft
= cm
.view
.scrollLeft
;
2164 if (scrollPos
.scrollTop
!= null) {
2165 setScrollTop(cm
, scrollPos
.scrollTop
);
2166 if (Math
.abs(cm
.view
.scrollTop
- startTop
) > 1) changed
= true;
2168 if (scrollPos
.scrollLeft
!= null) {
2169 setScrollLeft(cm
, scrollPos
.scrollLeft
);
2170 if (Math
.abs(cm
.view
.scrollLeft
- startLeft
) > 1) changed
= true;
2172 if (!changed
) return coords
;
2176 function scrollIntoView(cm
, x1
, y1
, x2
, y2
) {
2177 var scrollPos
= calculateScrollPos(cm
, x1
, y1
, x2
, y2
);
2178 if (scrollPos
.scrollTop
!= null) setScrollTop(cm
, scrollPos
.scrollTop
);
2179 if (scrollPos
.scrollLeft
!= null) setScrollLeft(cm
, scrollPos
.scrollLeft
);
2182 function calculateScrollPos(cm
, x1
, y1
, x2
, y2
) {
2183 var display
= cm
.display
, pt
= paddingTop(display
);
2185 var screen
= display
.scroller
.clientHeight
- scrollerCutOff
, screentop
= display
.scroller
.scrollTop
, result
= {};
2186 var docBottom
= cm
.view
.doc
.height
+ 2 * pt
;
2187 var atTop
= y1
< pt
+ 10, atBottom
= y2
+ pt
> docBottom
- 10;
2188 if (y1
< screentop
) result
.scrollTop
= atTop
? 0 : Math
.max(0, y1
);
2189 else if (y2
> screentop
+ screen
) result
.scrollTop
= (atBottom
? docBottom : y2
) - screen
;
2191 var screenw
= display
.scroller
.clientWidth
- scrollerCutOff
, screenleft
= display
.scroller
.scrollLeft
;
2192 x1
+= display
.gutters
.offsetWidth
; x2
+= display
.gutters
.offsetWidth
;
2193 var gutterw
= display
.gutters
.offsetWidth
;
2194 var atLeft
= x1
< gutterw
+ 10;
2195 if (x1
< screenleft
+ gutterw
|| atLeft
) {
2197 result
.scrollLeft
= Math
.max(0, x1
- 10 - gutterw
);
2198 } else if (x2
> screenw
+ screenleft
- 3) {
2199 result
.scrollLeft
= x2
+ 10 - screenw
;
2206 function indentLine(cm
, n
, how
, aggressive
) {
2207 var doc
= cm
.view
.doc
;
2208 if (!how
) how
= "add";
2209 if (how
== "smart") {
2210 if (!cm
.view
.mode
.indent
) how
= "prev";
2211 else var state
= getStateBefore(cm
, n
);
2214 var tabSize
= cm
.options
.tabSize
;
2215 var line
= getLine(doc
, n
), curSpace
= countColumn(line
.text
, null, tabSize
);
2216 var curSpaceString
= line
.text
.match(/^\s*/)[0], indentation
;
2217 if (how
== "smart") {
2218 indentation
= cm
.view
.mode
.indent(state
, line
.text
.slice(curSpaceString
.length
), line
.text
);
2219 if (indentation
== Pass
) {
2220 if (!aggressive
) return;
2224 if (how
== "prev") {
2225 if (n
) indentation
= countColumn(getLine(doc
, n
-1).text
, null, tabSize
);
2226 else indentation
= 0;
2228 else if (how
== "add") indentation
= curSpace
+ cm
.options
.indentUnit
;
2229 else if (how
== "subtract") indentation
= curSpace
- cm
.options
.indentUnit
;
2230 indentation
= Math
.max(0, indentation
);
2232 var indentString
= "", pos
= 0;
2233 if (cm
.options
.indentWithTabs
)
2234 for (var i
= Math
.floor(indentation
/ tabSize
); i
; --i
) {pos
+= tabSize
; indentString
+= "\t";}
2235 if (pos
< indentation
) indentString
+= spaceStr(indentation
- pos
);
2237 if (indentString
!= curSpaceString
)
2238 replaceRange(cm
, indentString
, {line: n
, ch: 0}, {line: n
, ch: curSpaceString
.length
}, "input");
2239 line
.stateAfter
= null;
2242 function changeLine(cm
, handle
, op
) {
2243 var no
= handle
, line
= handle
, doc
= cm
.view
.doc
;
2244 if (typeof handle
== "number") line
= getLine(doc
, clipLine(doc
, handle
));
2245 else no
= lineNo(handle
);
2246 if (no
== null) return null;
2247 if (op(line
, no
)) regChange(cm
, no
, no
+ 1);
2252 function findPosH(cm
, dir
, unit
, visually
) {
2253 var doc
= cm
.view
.doc
, end
= cm
.view
.sel
.head
, line
= end
.line
, ch
= end
.ch
;
2254 var lineObj
= getLine(doc
, line
);
2255 function findNextLine() {
2257 if (l
< 0 || l
== doc
.size
) return false;
2259 return lineObj
= getLine(doc
, l
);
2261 function moveOnce(boundToLine
) {
2262 var next
= (visually
? moveVisually : moveLogically
)(lineObj
, ch
, dir
, true);
2264 if (!boundToLine
&& findNextLine()) {
2265 if (visually
) ch
= (dir
< 0 ? lineRight : lineLeft
)(lineObj
);
2266 else ch
= dir
< 0 ? lineObj
.text
.length : 0;
2267 } else return false;
2271 if (unit
== "char") moveOnce();
2272 else if (unit
== "column") moveOnce(true);
2273 else if (unit
== "word") {
2274 var sawWord
= false;
2276 if (dir
< 0) if (!moveOnce()) break;
2277 if (isWordChar(lineObj
.text
.charAt(ch
))) sawWord
= true;
2278 else if (sawWord
) {if (dir
< 0) {dir
= 1; moveOnce();} break;}
2279 if (dir
> 0) if (!moveOnce()) break;
2282 return skipAtomic(cm
, {line: line
, ch: ch
}, dir
, true);
2285 function findWordAt(line
, pos
) {
2286 var start
= pos
.ch
, end
= pos
.ch
;
2288 if (pos
.after
=== false || end
== line
.length
) --start
; else ++end
;
2289 var startChar
= line
.charAt(start
);
2290 var check
= isWordChar(startChar
) ? isWordChar :
2291 /\s/.test(startChar
) ? function(ch
) {return /\s/.test(ch
);} :
2292 function(ch
) {return !/\s/.test(ch
) && !isWordChar(ch
);};
2293 while (start
> 0 && check(line
.charAt(start
- 1))) --start
;
2294 while (end
< line
.length
&& check(line
.charAt(end
))) ++end
;
2296 return {from: {line: pos
.line
, ch: start
}, to: {line: pos
.line
, ch: end
}};
2299 function selectLine(cm
, line
) {
2300 extendSelection(cm
, {line: line
, ch: 0}, clipPos(cm
.view
.doc
, {line: line
+ 1, ch: 0}));
2305 // The publicly visible API. Note that operation(null, f) means
2306 // 'wrap f in an operation, performed on its `this` parameter'
2308 CodeMirror
.prototype = {
2309 getValue: function(lineSep
) {
2310 var text
= [], doc
= this.view
.doc
;
2311 doc
.iter(0, doc
.size
, function(line
) { text
.push(line
.text
); });
2312 return text
.join(lineSep
|| "\n");
2315 setValue: operation(null, function(code
) {
2316 var doc
= this.view
.doc
, top
= {line: 0, ch: 0}, lastLen
= getLine(doc
, doc
.size
-1).text
.length
;
2317 updateDocInner(this, top
, {line: doc
.size
- 1, ch: lastLen
}, splitLines(code
), top
, top
, "setValue");
2320 getSelection: function(lineSep
) { return this.getRange(this.view
.sel
.from, this.view
.sel
.to
, lineSep
); },
2322 replaceSelection: operation(null, function(code
, collapse
, origin
) {
2323 var sel
= this.view
.sel
;
2324 updateDoc(this, sel
.from, sel
.to
, splitLines(code
), collapse
|| "around", origin
);
2327 focus: function(){window
.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2329 setOption: function(option
, value
) {
2330 var options
= this.options
, old
= options
[option
];
2331 if (options
[option
] == value
&& option
!= "mode") return;
2332 options
[option
] = value
;
2333 if (optionHandlers
.hasOwnProperty(option
))
2334 operation(this, optionHandlers
[option
])(this, value
, old
);
2337 getOption: function(option
) {return this.options
[option
];},
2339 getMode: function() {return this.view
.mode
;},
2341 addKeyMap: function(map
) {
2342 this.view
.keyMaps
.push(map
);
2345 removeKeyMap: function(map
) {
2346 var maps
= this.view
.keyMaps
;
2347 for (var i
= 0; i
< maps
.length
; ++i
)
2348 if ((typeof map
== "string" ? maps
[i
].name : maps
[i
]) == map
) {
2354 undo: operation(null, function() {unredoHelper(this, "undo");}),
2355 redo: operation(null, function() {unredoHelper(this, "redo");}),
2357 indentLine: operation(null, function(n
, dir
, aggressive
) {
2358 if (typeof dir
!= "string") {
2359 if (dir
== null) dir
= this.options
.smartIndent
? "smart" : "prev";
2360 else dir
= dir
? "add" : "subtract";
2362 if (isLine(this.view
.doc
, n
)) indentLine(this, n
, dir
, aggressive
);
2365 indentSelection: operation(null, function(how
) {
2366 var sel
= this.view
.sel
;
2367 if (posEq(sel
.from, sel
.to
)) return indentLine(this, sel
.from.line
, how
);
2368 var e
= sel
.to
.line
- (sel
.to
.ch
? 0 : 1);
2369 for (var i
= sel
.from.line
; i
<= e
; ++i
) indentLine(this, i
, how
);
2372 historySize: function() {
2373 var hist
= this.view
.history
;
2374 return {undo: hist
.done
.length
, redo: hist
.undone
.length
};
2377 clearHistory: function() {this.view
.history
= makeHistory();},
2379 markClean: function() {
2380 this.view
.history
.dirtyCounter
= 0;
2381 this.view
.history
.lastOp
= this.view
.history
.lastOrigin
= null;
2384 isClean: function () {return this.view
.history
.dirtyCounter
== 0;},
2386 getHistory: function() {
2387 var hist
= this.view
.history
;
2389 for (var i
= 0, nw
= [], nwelt
; i
< arr
.length
; ++i
) {
2391 nw
.push({events: nwelt
= [], fromBefore: set.fromBefore
, toBefore: set.toBefore
,
2392 fromAfter: set.fromAfter
, toAfter: set.toAfter
});
2393 for (var j
= 0, elt
= set.events
; j
< elt
.length
; ++j
) {
2394 var old
= [], cur
= elt
[j
];
2395 nwelt
.push({start: cur
.start
, added: cur
.added
, old: old
});
2396 for (var k
= 0; k
< cur
.old
.length
; ++k
) old
.push(hlText(cur
.old
[k
]));
2401 return {done: cp(hist
.done
), undone: cp(hist
.undone
)};
2404 setHistory: function(histData
) {
2405 var hist
= this.view
.history
= makeHistory();
2406 hist
.done
= histData
.done
;
2407 hist
.undone
= histData
.undone
;
2410 // Fetch the parser token for a given character. Useful for hacks
2411 // that want to inspect the mode state (say, for completion).
2412 getTokenAt: function(pos
) {
2413 var doc
= this.view
.doc
;
2414 pos
= clipPos(doc
, pos
);
2415 var state
= getStateBefore(this, pos
.line
), mode
= this.view
.mode
;
2416 var line
= getLine(doc
, pos
.line
);
2417 var stream
= new StringStream(line
.text
, this.options
.tabSize
);
2418 while (stream
.pos
< pos
.ch
&& !stream
.eol()) {
2419 stream
.start
= stream
.pos
;
2420 var style
= mode
.token(stream
, state
);
2422 return {start: stream
.start
,
2424 string: stream
.current(),
2425 className: style
|| null, // Deprecated, use 'type' instead
2426 type: style
|| null,
2430 getStateAfter: function(line
) {
2431 var doc
= this.view
.doc
;
2432 line
= clipLine(doc
, line
== null ? doc
.size
- 1: line
);
2433 return getStateBefore(this, line
+ 1);
2436 cursorCoords: function(start
, mode
) {
2437 var pos
, sel
= this.view
.sel
;
2438 if (start
== null) pos
= sel
.head
;
2439 else if (typeof start
== "object") pos
= clipPos(this.view
.doc
, start
);
2440 else pos
= start
? sel
.from : sel
.to
;
2441 return cursorCoords(this, pos
, mode
|| "page");
2444 charCoords: function(pos
, mode
) {
2445 return charCoords(this, clipPos(this.view
.doc
, pos
), mode
|| "page");
2448 coordsChar: function(coords
) {
2449 var off
= this.display
.lineSpace
.getBoundingClientRect();
2450 return coordsChar(this, coords
.left
- off
.left
, coords
.top
- off
.top
);
2453 defaultTextHeight: function() { return textHeight(this.display
); },
2455 markText: operation(null, function(from, to
, options
) {
2456 return markText(this, clipPos(this.view
.doc
, from), clipPos(this.view
.doc
, to
),
2460 setBookmark: operation(null, function(pos
, widget
) {
2461 pos
= clipPos(this.view
.doc
, pos
);
2462 return markText(this, pos
, pos
, widget
? {replacedWith: widget
} : {}, "bookmark");
2465 findMarksAt: function(pos
) {
2466 var doc
= this.view
.doc
;
2467 pos
= clipPos(doc
, pos
);
2468 var markers
= [], spans
= getLine(doc
, pos
.line
).markedSpans
;
2469 if (spans
) for (var i
= 0; i
< spans
.length
; ++i
) {
2470 var span
= spans
[i
];
2471 if ((span
.from == null || span
.from <= pos
.ch
) &&
2472 (span
.to
== null || span
.to
>= pos
.ch
))
2473 markers
.push(span
.marker
);
2478 setGutterMarker: operation(null, function(line
, gutterID
, value
) {
2479 return changeLine(this, line
, function(line
) {
2480 var markers
= line
.gutterMarkers
|| (line
.gutterMarkers
= {});
2481 markers
[gutterID
] = value
;
2482 if (!value
&& isEmpty(markers
)) line
.gutterMarkers
= null;
2487 clearGutter: operation(null, function(gutterID
) {
2488 var i
= 0, cm
= this, doc
= cm
.view
.doc
;
2489 doc
.iter(0, doc
.size
, function(line
) {
2490 if (line
.gutterMarkers
&& line
.gutterMarkers
[gutterID
]) {
2491 line
.gutterMarkers
[gutterID
] = null;
2492 regChange(cm
, i
, i
+ 1);
2493 if (isEmpty(line
.gutterMarkers
)) line
.gutterMarkers
= null;
2499 addLineClass: operation(null, function(handle
, where
, cls
) {
2500 return changeLine(this, handle
, function(line
) {
2501 var prop
= where
== "text" ? "textClass" : where
== "background" ? "bgClass" : "wrapClass";
2502 if (!line
[prop
]) line
[prop
] = cls
;
2503 else if (new RegExp("\\b" + cls
+ "\\b").test(line
[prop
])) return false;
2504 else line
[prop
] += " " + cls
;
2509 removeLineClass: operation(null, function(handle
, where
, cls
) {
2510 return changeLine(this, handle
, function(line
) {
2511 var prop
= where
== "text" ? "textClass" : where
== "background" ? "bgClass" : "wrapClass";
2512 var cur
= line
[prop
];
2513 if (!cur
) return false;
2514 else if (cls
== null) line
[prop
] = null;
2516 var upd
= cur
.replace(new RegExp("^" + cls
+ "\\b\\s*|\\s*\\b" + cls
+ "\\b"), "");
2517 if (upd
== cur
) return false;
2518 line
[prop
] = upd
|| null;
2524 addLineWidget: operation(null, function(handle
, node
, options
) {
2525 var widget
= options
|| {};
2527 if (widget
.noHScroll
) this.display
.alignWidgets
= true;
2528 changeLine(this, handle
, function(line
) {
2529 (line
.widgets
|| (line
.widgets
= [])).push(widget
);
2536 removeLineWidget: operation(null, function(widget
) {
2537 var ws
= widget
.line
.widgets
, no
= lineNo(widget
.line
);
2538 if (no
== null) return;
2539 for (var i
= 0; i
< ws
.length
; ++i
) if (ws
[i
] == widget
) ws
.splice(i
--, 1);
2540 regChange(this, no
, no
+ 1);
2543 lineInfo: function(line
) {
2544 if (typeof line
== "number") {
2545 if (!isLine(this.view
.doc
, line
)) return null;
2547 line
= getLine(this.view
.doc
, line
);
2548 if (!line
) return null;
2550 var n
= lineNo(line
);
2551 if (n
== null) return null;
2553 return {line: n
, handle: line
, text: line
.text
, gutterMarkers: line
.gutterMarkers
,
2554 textClass: line
.textClass
, bgClass: line
.bgClass
, wrapClass: line
.wrapClass
,
2555 widgets: line
.widgets
};
2558 getViewport: function() { return {from: this.display
.showingFrom
, to: this.display
.showingTo
};},
2560 addWidget: function(pos
, node
, scroll
, vert
, horiz
) {
2561 var display
= this.display
;
2562 pos
= cursorCoords(this, clipPos(this.view
.doc
, pos
));
2563 var top
= pos
.top
, left
= pos
.left
;
2564 node
.style
.position
= "absolute";
2565 display
.sizer
.appendChild(node
);
2566 if (vert
== "over") top
= pos
.top
;
2567 else if (vert
== "near") {
2568 var vspace
= Math
.max(display
.wrapper
.clientHeight
, this.view
.doc
.height
),
2569 hspace
= Math
.max(display
.sizer
.clientWidth
, display
.lineSpace
.clientWidth
);
2570 if (pos
.bottom
+ node
.offsetHeight
> vspace
&& pos
.top
> node
.offsetHeight
)
2571 top
= pos
.top
- node
.offsetHeight
;
2572 if (left
+ node
.offsetWidth
> hspace
)
2573 left
= hspace
- node
.offsetWidth
;
2575 node
.style
.top
= (top
+ paddingTop(display
)) + "px";
2576 node
.style
.left
= node
.style
.right
= "";
2577 if (horiz
== "right") {
2578 left
= display
.sizer
.clientWidth
- node
.offsetWidth
;
2579 node
.style
.right
= "0px";
2581 if (horiz
== "left") left
= 0;
2582 else if (horiz
== "middle") left
= (display
.sizer
.clientWidth
- node
.offsetWidth
) / 2;
2583 node
.style
.left
= left
+ "px";
2586 scrollIntoView(this, left
, top
, left
+ node
.offsetWidth
, top
+ node
.offsetHeight
);
2589 lineCount: function() {return this.view
.doc
.size
;},
2591 clipPos: function(pos
) {return clipPos(this.view
.doc
, pos
);},
2593 getCursor: function(start
) {
2594 var sel
= this.view
.sel
, pos
;
2595 if (start
== null || start
== "head") pos
= sel
.head
;
2596 else if (start
== "anchor") pos
= sel
.anchor
;
2597 else if (start
== "end" || start
=== false) pos
= sel
.to
;
2598 else pos
= sel
.from;
2599 return copyPos(pos
);
2602 somethingSelected: function() {return !posEq(this.view
.sel
.from, this.view
.sel
.to
);},
2604 setCursor: operation(null, function(line
, ch
, extend
) {
2605 var pos
= clipPos(this.view
.doc
, typeof line
== "number" ? {line: line
, ch: ch
|| 0} : line
);
2606 if (extend
) extendSelection(this, pos
);
2607 else setSelection(this, pos
, pos
);
2610 setSelection: operation(null, function(anchor
, head
) {
2611 var doc
= this.view
.doc
;
2612 setSelection(this, clipPos(doc
, anchor
), clipPos(doc
, head
|| anchor
));
2615 extendSelection: operation(null, function(from, to
) {
2616 var doc
= this.view
.doc
;
2617 extendSelection(this, clipPos(doc
, from), to
&& clipPos(doc
, to
));
2620 setExtending: function(val
) {this.view
.sel
.extend
= val
;},
2622 getLine: function(line
) {var l
= this.getLineHandle(line
); return l
&& l
.text
;},
2624 getLineHandle: function(line
) {
2625 var doc
= this.view
.doc
;
2626 if (isLine(doc
, line
)) return getLine(doc
, line
);
2629 getLineNumber: function(line
) {return lineNo(line
);},
2631 setLine: operation(null, function(line
, text
) {
2632 if (isLine(this.view
.doc
, line
))
2633 replaceRange(this, text
, {line: line
, ch: 0}, {line: line
, ch: getLine(this.view
.doc
, line
).text
.length
});
2636 removeLine: operation(null, function(line
) {
2637 if (isLine(this.view
.doc
, line
))
2638 replaceRange(this, "", {line: line
, ch: 0}, clipPos(this.view
.doc
, {line: line
+1, ch: 0}));
2641 replaceRange: operation(null, function(code
, from, to
) {
2642 var doc
= this.view
.doc
;
2643 from = clipPos(doc
, from);
2644 to
= to
? clipPos(doc
, to
) : from;
2645 return replaceRange(this, code
, from, to
);
2648 getRange: function(from, to
, lineSep
) {
2649 var doc
= this.view
.doc
;
2650 from = clipPos(doc
, from); to
= clipPos(doc
, to
);
2651 var l1
= from.line
, l2
= to
.line
;
2652 if (l1
== l2
) return getLine(doc
, l1
).text
.slice(from.ch
, to
.ch
);
2653 var code
= [getLine(doc
, l1
).text
.slice(from.ch
)];
2654 doc
.iter(l1
+ 1, l2
, function(line
) { code
.push(line
.text
); });
2655 code
.push(getLine(doc
, l2
).text
.slice(0, to
.ch
));
2656 return code
.join(lineSep
|| "\n");
2659 triggerOnKeyDown: operation(null, onKeyDown
),
2661 execCommand: function(cmd
) {return commands
[cmd
](this);},
2663 // Stuff used by commands, probably not much use to outside code.
2664 moveH: operation(null, function(dir
, unit
) {
2665 var sel
= this.view
.sel
, pos
= dir
< 0 ? sel
.from : sel
.to
;
2666 if (sel
.shift
|| sel
.extend
|| posEq(sel
.from, sel
.to
)) pos
= findPosH(this, dir
, unit
, true);
2667 extendSelection(this, pos
, pos
, dir
);
2670 deleteH: operation(null, function(dir
, unit
) {
2671 var sel
= this.view
.sel
;
2672 if (!posEq(sel
.from, sel
.to
)) replaceRange(this, "", sel
.from, sel
.to
, "delete");
2673 else replaceRange(this, "", sel
.from, findPosH(this, dir
, unit
, false), "delete");
2674 this.curOp
.userSelChange
= true;
2677 moveV: operation(null, function(dir
, unit
) {
2678 var view
= this.view
, doc
= view
.doc
, display
= this.display
;
2679 var cur
= view
.sel
.head
, pos
= cursorCoords(this, cur
, "div");
2680 var x
= pos
.left
, y
;
2681 if (view
.goalColumn
!= null) x
= view
.goalColumn
;
2682 if (unit
== "page") {
2683 var pageSize
= Math
.min(display
.wrapper
.clientHeight
, window
.innerHeight
|| document
.documentElement
.clientHeight
);
2684 y
= pos
.top
+ dir
* pageSize
;
2685 } else if (unit
== "line") {
2686 y
= dir
> 0 ? pos
.bottom
+ 3 : pos
.top
- 3;
2689 var target
= coordsChar(this, x
, y
);
2691 } while (target
.outside
&& (dir
< 0 ? y
> 0 : y
< doc
.height
));
2693 if (unit
== "page") display
.scrollbarV
.scrollTop
+= charCoords(this, target
, "div").top
- pos
.top
;
2694 extendSelection(this, target
, target
, dir
);
2695 view
.goalColumn
= x
;
2698 toggleOverwrite: function() {
2699 if (this.view
.overwrite
= !this.view
.overwrite
)
2700 this.display
.cursor
.className
+= " CodeMirror-overwrite";
2702 this.display
.cursor
.className
= this.display
.cursor
.className
.replace(" CodeMirror-overwrite", "");
2705 posFromIndex: function(off
) {
2706 var lineNo
= 0, ch
, doc
= this.view
.doc
;
2707 doc
.iter(0, doc
.size
, function(line
) {
2708 var sz
= line
.text
.length
+ 1;
2709 if (sz
> off
) { ch
= off
; return true; }
2713 return clipPos(doc
, {line: lineNo
, ch: ch
});
2715 indexFromPos: function (coords
) {
2716 if (coords
.line
< 0 || coords
.ch
< 0) return 0;
2717 var index
= coords
.ch
;
2718 this.view
.doc
.iter(0, coords
.line
, function (line
) {
2719 index
+= line
.text
.length
+ 1;
2724 scrollTo: function(x
, y
) {
2725 if (x
!= null) this.display
.scrollbarH
.scrollLeft
= this.display
.scroller
.scrollLeft
= x
;
2726 if (y
!= null) this.display
.scrollbarV
.scrollTop
= this.display
.scroller
.scrollTop
= y
;
2727 updateDisplay(this, []);
2729 getScrollInfo: function() {
2730 var scroller
= this.display
.scroller
, co
= scrollerCutOff
;
2731 return {left: scroller
.scrollLeft
, top: scroller
.scrollTop
,
2732 height: scroller
.scrollHeight
- co
, width: scroller
.scrollWidth
- co
,
2733 clientHeight: scroller
.clientHeight
- co
, clientWidth: scroller
.clientWidth
- co
};
2736 scrollIntoView: function(pos
) {
2737 if (typeof pos
== "number") pos
= {line: pos
, ch: 0};
2738 pos
= pos
? clipPos(this.view
.doc
, pos
) : this.view
.sel
.head
;
2739 scrollPosIntoView(this, pos
);
2742 setSize: function(width
, height
) {
2743 function interpret(val
) {
2744 return typeof val
== "number" || /^\d+$/.test(String(val
)) ? val
+ "px" : val
;
2746 if (width
!= null) this.display
.wrapper
.style
.width
= interpret(width
);
2747 if (height
!= null) this.display
.wrapper
.style
.height
= interpret(height
);
2751 on: function(type
, f
) {on(this, type
, f
);},
2752 off: function(type
, f
) {off(this, type
, f
);},
2754 operation: function(f
){return operation(this, f
)();},
2756 refresh: function() {
2758 if (this.display
.scroller
.scrollHeight
> this.view
.scrollTop
)
2759 this.display
.scrollbarV
.scrollTop
= this.display
.scroller
.scrollTop
= this.view
.scrollTop
;
2760 updateDisplay(this, true);
2763 getInputField: function(){return this.display
.input
;},
2764 getWrapperElement: function(){return this.display
.wrapper
;},
2765 getScrollerElement: function(){return this.display
.scroller
;},
2766 getGutterElement: function(){return this.display
.gutters
;}
2771 var optionHandlers
= CodeMirror
.optionHandlers
= {};
2773 // The default configuration options.
2774 var defaults
= CodeMirror
.defaults
= {};
2776 function option(name
, deflt
, handle
, notOnInit
) {
2777 CodeMirror
.defaults
[name
] = deflt
;
2778 if (handle
) optionHandlers
[name
] =
2779 notOnInit
? function(cm
, val
, old
) {if (old
!= Init
) handle(cm
, val
, old
);} : handle
;
2782 var Init
= CodeMirror
.Init
= {toString: function(){return "CodeMirror.Init";}};
2784 // These two are, on init, called from the constructor because they
2785 // have to be initialized before the editor can start at all.
2786 option("value", "", function(cm
, val
) {cm
.setValue(val
);}, true);
2787 option("mode", null, loadMode
, true);
2789 option("indentUnit", 2, loadMode
, true);
2790 option("indentWithTabs", false);
2791 option("smartIndent", true);
2792 option("tabSize", 4, function(cm
) {
2795 updateDisplay(cm
, true);
2797 option("electricChars", true);
2799 option("theme", "default", function(cm
) {
2803 option("keyMap", "default", keyMapChanged
);
2804 option("extraKeys", null);
2806 option("onKeyEvent", null);
2807 option("onDragEvent", null);
2809 option("lineWrapping", false, wrappingChanged
, true);
2810 option("gutters", [], function(cm
) {
2811 setGuttersForLineNumbers(cm
.options
);
2814 option("lineNumbers", false, function(cm
) {
2815 setGuttersForLineNumbers(cm
.options
);
2818 option("firstLineNumber", 1, guttersChanged
, true);
2819 option("lineNumberFormatter", function(integer
) {return integer
;}, guttersChanged
, true);
2820 option("showCursorWhenSelecting", false, updateSelection
, true);
2822 option("readOnly", false, function(cm
, val
) {
2823 if (val
== "nocursor") {onBlur(cm
); cm
.display
.input
.blur();}
2824 else if (!val
) resetInput(cm
, true);
2826 option("dragDrop", true);
2828 option("cursorBlinkRate", 530);
2829 option("cursorHeight", 1);
2830 option("workTime", 100);
2831 option("workDelay", 100);
2832 option("flattenSpans", true);
2833 option("pollInterval", 100);
2834 option("undoDepth", 40);
2835 option("viewportMargin", 10, function(cm
){cm
.refresh();}, true);
2837 option("tabindex", null, function(cm
, val
) {
2838 cm
.display
.input
.tabIndex
= val
|| "";
2840 option("autofocus", null);
2842 // MODE DEFINITION AND QUERYING
2844 // Known modes, by name and by MIME
2845 var modes
= CodeMirror
.modes
= {}, mimeModes
= CodeMirror
.mimeModes
= {};
2847 CodeMirror
.defineMode = function(name
, mode
) {
2848 if (!CodeMirror
.defaults
.mode
&& name
!= "null") CodeMirror
.defaults
.mode
= name
;
2849 if (arguments
.length
> 2) {
2850 mode
.dependencies
= [];
2851 for (var i
= 2; i
< arguments
.length
; ++i
) mode
.dependencies
.push(arguments
[i
]);
2856 CodeMirror
.defineMIME = function(mime
, spec
) {
2857 mimeModes
[mime
] = spec
;
2860 CodeMirror
.resolveMode = function(spec
) {
2861 if (typeof spec
== "string" && mimeModes
.hasOwnProperty(spec
))
2862 spec
= mimeModes
[spec
];
2863 else if (typeof spec
== "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec
))
2864 return CodeMirror
.resolveMode("application/xml");
2865 if (typeof spec
== "string") return {name: spec
};
2866 else return spec
|| {name: "null"};
2869 CodeMirror
.getMode = function(options
, spec
) {
2870 var spec
= CodeMirror
.resolveMode(spec
);
2871 var mfactory
= modes
[spec
.name
];
2872 if (!mfactory
) return CodeMirror
.getMode(options
, "text/plain");
2873 var modeObj
= mfactory(options
, spec
);
2874 if (modeExtensions
.hasOwnProperty(spec
.name
)) {
2875 var exts
= modeExtensions
[spec
.name
];
2876 for (var prop
in exts
) {
2877 if (!exts
.hasOwnProperty(prop
)) continue;
2878 if (modeObj
.hasOwnProperty(prop
)) modeObj
["_" + prop
] = modeObj
[prop
];
2879 modeObj
[prop
] = exts
[prop
];
2882 modeObj
.name
= spec
.name
;
2886 CodeMirror
.defineMode("null", function() {
2887 return {token: function(stream
) {stream
.skipToEnd();}};
2889 CodeMirror
.defineMIME("text/plain", "null");
2891 var modeExtensions
= CodeMirror
.modeExtensions
= {};
2892 CodeMirror
.extendMode = function(mode
, properties
) {
2893 var exts
= modeExtensions
.hasOwnProperty(mode
) ? modeExtensions
[mode
] : (modeExtensions
[mode
] = {});
2894 for (var prop
in properties
) if (properties
.hasOwnProperty(prop
))
2895 exts
[prop
] = properties
[prop
];
2900 CodeMirror
.defineExtension = function(name
, func
) {
2901 CodeMirror
.prototype[name
] = func
;
2904 CodeMirror
.defineOption
= option
;
2907 CodeMirror
.defineInitHook = function(f
) {initHooks
.push(f
);};
2909 // MODE STATE HANDLING
2911 // Utility functions for working with state. Exported because modes
2912 // sometimes need to do this.
2913 function copyState(mode
, state
) {
2914 if (state
=== true) return state
;
2915 if (mode
.copyState
) return mode
.copyState(state
);
2917 for (var n
in state
) {
2919 if (val
instanceof Array
) val
= val
.concat([]);
2924 CodeMirror
.copyState
= copyState
;
2926 function startState(mode
, a1
, a2
) {
2927 return mode
.startState
? mode
.startState(a1
, a2
) : true;
2929 CodeMirror
.startState
= startState
;
2931 CodeMirror
.innerMode = function(mode
, state
) {
2932 while (mode
.innerMode
) {
2933 var info
= mode
.innerMode(state
);
2937 return info
|| {mode: mode
, state: state
};
2940 // STANDARD COMMANDS
2942 var commands
= CodeMirror
.commands
= {
2943 selectAll: function(cm
) {cm
.setSelection({line: 0, ch: 0}, {line: cm
.lineCount() - 1});},
2944 killLine: function(cm
) {
2945 var from = cm
.getCursor(true), to
= cm
.getCursor(false), sel
= !posEq(from, to
);
2946 if (!sel
&& cm
.getLine(from.line
).length
== from.ch
)
2947 cm
.replaceRange("", from, {line: from.line
+ 1, ch: 0}, "delete");
2948 else cm
.replaceRange("", from, sel
? to : {line: from.line
}, "delete");
2950 deleteLine: function(cm
) {
2951 var l
= cm
.getCursor().line
;
2952 cm
.replaceRange("", {line: l
, ch: 0}, {line: l
}, "delete");
2954 undo: function(cm
) {cm
.undo();},
2955 redo: function(cm
) {cm
.redo();},
2956 goDocStart: function(cm
) {cm
.extendSelection({line: 0, ch: 0});},
2957 goDocEnd: function(cm
) {cm
.extendSelection({line: cm
.lineCount() - 1});},
2958 goLineStart: function(cm
) {
2959 cm
.extendSelection(lineStart(cm
, cm
.getCursor().line
));
2961 goLineStartSmart: function(cm
) {
2962 var cur
= cm
.getCursor(), start
= lineStart(cm
, cur
.line
);
2963 var line
= cm
.getLineHandle(start
.line
);
2964 var order
= getOrder(line
);
2965 if (!order
|| order
[0].level
== 0) {
2966 var firstNonWS
= Math
.max(0, line
.text
.search(/\S/));
2967 var inWS
= cur
.line
== start
.line
&& cur
.ch
<= firstNonWS
&& cur
.ch
;
2968 cm
.extendSelection({line: start
.line
, ch: inWS
? 0 : firstNonWS
});
2969 } else cm
.extendSelection(start
);
2971 goLineEnd: function(cm
) {
2972 cm
.extendSelection(lineEnd(cm
, cm
.getCursor().line
));
2974 goLineUp: function(cm
) {cm
.moveV(-1, "line");},
2975 goLineDown: function(cm
) {cm
.moveV(1, "line");},
2976 goPageUp: function(cm
) {cm
.moveV(-1, "page");},
2977 goPageDown: function(cm
) {cm
.moveV(1, "page");},
2978 goCharLeft: function(cm
) {cm
.moveH(-1, "char");},
2979 goCharRight: function(cm
) {cm
.moveH(1, "char");},
2980 goColumnLeft: function(cm
) {cm
.moveH(-1, "column");},
2981 goColumnRight: function(cm
) {cm
.moveH(1, "column");},
2982 goWordLeft: function(cm
) {cm
.moveH(-1, "word");},
2983 goWordRight: function(cm
) {cm
.moveH(1, "word");},
2984 delCharBefore: function(cm
) {cm
.deleteH(-1, "char");},
2985 delCharAfter: function(cm
) {cm
.deleteH(1, "char");},
2986 delWordBefore: function(cm
) {cm
.deleteH(-1, "word");},
2987 delWordAfter: function(cm
) {cm
.deleteH(1, "word");},
2988 indentAuto: function(cm
) {cm
.indentSelection("smart");},
2989 indentMore: function(cm
) {cm
.indentSelection("add");},
2990 indentLess: function(cm
) {cm
.indentSelection("subtract");},
2991 insertTab: function(cm
) {cm
.replaceSelection("\t", "end", "input");},
2992 defaultTab: function(cm
) {
2993 if (cm
.somethingSelected()) cm
.indentSelection("add");
2994 else cm
.replaceSelection("\t", "end", "input");
2996 transposeChars: function(cm
) {
2997 var cur
= cm
.getCursor(), line
= cm
.getLine(cur
.line
);
2998 if (cur
.ch
> 0 && cur
.ch
< line
.length
- 1)
2999 cm
.replaceRange(line
.charAt(cur
.ch
) + line
.charAt(cur
.ch
- 1),
3000 {line: cur
.line
, ch: cur
.ch
- 1}, {line: cur
.line
, ch: cur
.ch
+ 1});
3002 newlineAndIndent: function(cm
) {
3003 operation(cm
, function() {
3004 cm
.replaceSelection("\n", "end", "input");
3005 cm
.indentLine(cm
.getCursor().line
, null, true);
3008 toggleOverwrite: function(cm
) {cm
.toggleOverwrite();}
3013 var keyMap
= CodeMirror
.keyMap
= {};
3015 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
3016 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
3017 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3018 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
3020 // Note that the save and find-related commands aren't defined by
3021 // default. Unknown commands are simply ignored.
3022 keyMap
.pcDefault
= {
3023 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3024 "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3025 "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3026 "Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3027 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3028 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3029 fallthrough: "basic"
3031 keyMap
.macDefault
= {
3032 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3033 "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
3034 "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore",
3035 "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find",
3036 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3037 "Cmd-[": "indentLess", "Cmd-]": "indentMore",
3038 fallthrough: ["basic", "emacsy"]
3040 keyMap
["default"] = mac
? keyMap
.macDefault : keyMap
.pcDefault
;
3042 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
3043 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
3044 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
3045 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
3050 function getKeyMap(val
) {
3051 if (typeof val
== "string") return keyMap
[val
];
3055 function lookupKey(name
, maps
, handle
, stop
) {
3056 function lookup(map
) {
3057 map
= getKeyMap(map
);
3058 var found
= map
[name
];
3059 if (found
=== false) {
3063 if (found
!= null && handle(found
)) return true;
3064 if (map
.nofallthrough
) {
3068 var fallthrough
= map
.fallthrough
;
3069 if (fallthrough
== null) return false;
3070 if (Object
.prototype.toString
.call(fallthrough
) != "[object Array]")
3071 return lookup(fallthrough
);
3072 for (var i
= 0, e
= fallthrough
.length
; i
< e
; ++i
) {
3073 if (lookup(fallthrough
[i
])) return true;
3078 for (var i
= 0; i
< maps
.length
; ++i
)
3079 if (lookup(maps
[i
])) return true;
3081 function isModifierKey(event
) {
3082 var name
= keyNames
[e_prop(event
, "keyCode")];
3083 return name
== "Ctrl" || name
== "Alt" || name
== "Shift" || name
== "Mod";
3085 CodeMirror
.isModifierKey
= isModifierKey
;
3089 CodeMirror
.fromTextArea = function(textarea
, options
) {
3090 if (!options
) options
= {};
3091 options
.value
= textarea
.value
;
3092 if (!options
.tabindex
&& textarea
.tabindex
)
3093 options
.tabindex
= textarea
.tabindex
;
3094 // Set autofocus to true if this textarea is focused, or if it has
3095 // autofocus and no other element is focused.
3096 if (options
.autofocus
== null) {
3097 var hasFocus
= document
.body
;
3098 // doc.activeElement occasionally throws on IE
3099 try { hasFocus
= document
.activeElement
; } catch(e
) {}
3100 options
.autofocus
= hasFocus
== textarea
||
3101 textarea
.getAttribute("autofocus") != null && hasFocus
== document
.body
;
3104 function save() {textarea
.value
= cm
.getValue();}
3105 if (textarea
.form
) {
3106 // Deplorable hack to make the submit method do the right thing.
3107 on(textarea
.form
, "submit", save
);
3108 var form
= textarea
.form
, realSubmit
= form
.submit
;
3110 form
.submit
= function wrappedSubmit() {
3112 form
.submit
= realSubmit
;
3114 form
.submit
= wrappedSubmit
;
3119 textarea
.style
.display
= "none";
3120 var cm
= CodeMirror(function(node
) {
3121 textarea
.parentNode
.insertBefore(node
, textarea
.nextSibling
);
3124 cm
.getTextArea = function() { return textarea
; };
3125 cm
.toTextArea = function() {
3127 textarea
.parentNode
.removeChild(cm
.getWrapperElement());
3128 textarea
.style
.display
= "";
3129 if (textarea
.form
) {
3130 off(textarea
.form
, "submit", save
);
3131 if (typeof textarea
.form
.submit
== "function")
3132 textarea
.form
.submit
= realSubmit
;
3140 // Fed to the mode parsers, provides helper functions to make
3141 // parsers more succinct.
3143 // The character stream used by a mode's parser.
3144 function StringStream(string
, tabSize
) {
3145 this.pos
= this.start
= 0;
3146 this.string
= string
;
3147 this.tabSize
= tabSize
|| 8;
3150 StringStream
.prototype = {
3151 eol: function() {return this.pos
>= this.string
.length
;},
3152 sol: function() {return this.pos
== 0;},
3153 peek: function() {return this.string
.charAt(this.pos
) || undefined;},
3155 if (this.pos
< this.string
.length
)
3156 return this.string
.charAt(this.pos
++);
3158 eat: function(match
) {
3159 var ch
= this.string
.charAt(this.pos
);
3160 if (typeof match
== "string") var ok
= ch
== match
;
3161 else var ok
= ch
&& (match
.test
? match
.test(ch
) : match(ch
));
3162 if (ok
) {++this.pos
; return ch
;}
3164 eatWhile: function(match
) {
3165 var start
= this.pos
;
3166 while (this.eat(match
)){}
3167 return this.pos
> start
;
3169 eatSpace: function() {
3170 var start
= this.pos
;
3171 while (/[\s\u00a0]/.test(this.string
.charAt(this.pos
))) ++this.pos
;
3172 return this.pos
> start
;
3174 skipToEnd: function() {this.pos
= this.string
.length
;},
3175 skipTo: function(ch
) {
3176 var found
= this.string
.indexOf(ch
, this.pos
);
3177 if (found
> -1) {this.pos
= found
; return true;}
3179 backUp: function(n
) {this.pos
-= n
;},
3180 column: function() {return countColumn(this.string
, this.start
, this.tabSize
);},
3181 indentation: function() {return countColumn(this.string
, null, this.tabSize
);},
3182 match: function(pattern
, consume
, caseInsensitive
) {
3183 if (typeof pattern
== "string") {
3184 var cased = function(str
) {return caseInsensitive
? str
.toLowerCase() : str
;};
3185 if (cased(this.string
).indexOf(cased(pattern
), this.pos
) == this.pos
) {
3186 if (consume
!== false) this.pos
+= pattern
.length
;
3190 var match
= this.string
.slice(this.pos
).match(pattern
);
3191 if (match
&& match
.index
> 0) return null;
3192 if (match
&& consume
!== false) this.pos
+= match
[0].length
;
3196 current: function(){return this.string
.slice(this.start
, this.pos
);}
3198 CodeMirror
.StringStream
= StringStream
;
3202 function TextMarker(cm
, type
) {
3208 TextMarker
.prototype.clear = function() {
3209 if (this.explicitlyCleared
) return;
3210 startOperation(this.cm
);
3211 var min
= null, max
= null;
3212 for (var i
= 0; i
< this.lines
.length
; ++i
) {
3213 var line
= this.lines
[i
];
3214 var span
= getMarkedSpanFor(line
.markedSpans
, this);
3215 if (span
.to
!= null) max
= lineNo(line
);
3216 line
.markedSpans
= removeMarkedSpan(line
.markedSpans
, span
);
3217 if (span
.from != null)
3219 else if (this.collapsed
&& !lineIsHidden(line
))
3220 updateLineHeight(line
, textHeight(this.cm
.display
));
3222 if (min
!= null) regChange(this.cm
, min
, max
+ 1);
3223 this.lines
.length
= 0;
3224 this.explicitlyCleared
= true;
3225 if (this.collapsed
&& this.cm
.view
.cantEdit
) {
3226 this.cm
.view
.cantEdit
= false;
3227 reCheckSelection(this.cm
);
3229 endOperation(this.cm
);
3230 signalLater(this.cm
, this, "clear");
3233 TextMarker
.prototype.find = function() {
3235 for (var i
= 0; i
< this.lines
.length
; ++i
) {
3236 var line
= this.lines
[i
];
3237 var span
= getMarkedSpanFor(line
.markedSpans
, this);
3238 if (span
.from != null || span
.to
!= null) {
3239 var found
= lineNo(line
);
3240 if (span
.from != null) from = {line: found
, ch: span
.from};
3241 if (span
.to
!= null) to
= {line: found
, ch: span
.to
};
3244 if (this.type
== "bookmark") return from;
3245 return from && {from: from, to: to
};
3248 function markText(cm
, from, to
, options
, type
) {
3249 var doc
= cm
.view
.doc
;
3250 var marker
= new TextMarker(cm
, type
);
3251 if (type
== "range" && !posLess(from, to
)) return marker
;
3252 if (options
) for (var opt
in options
) if (options
.hasOwnProperty(opt
))
3253 marker
[opt
] = options
[opt
];
3254 if (marker
.replacedWith
) {
3255 marker
.collapsed
= true;
3256 marker
.replacedWith
= elt("span", [marker
.replacedWith
], "CodeMirror-widget");
3258 if (marker
.collapsed
) sawCollapsedSpans
= true;
3260 var curLine
= from.line
, size
= 0, collapsedAtStart
, collapsedAtEnd
;
3261 doc
.iter(curLine
, to
.line
+ 1, function(line
) {
3262 var span
= {from: null, to: null, marker: marker
};
3263 size
+= line
.text
.length
;
3264 if (curLine
== from.line
) {span
.from = from.ch
; size
-= from.ch
;}
3265 if (curLine
== to
.line
) {span
.to
= to
.ch
; size
-= line
.text
.length
- to
.ch
;}
3266 if (marker
.collapsed
) {
3267 if (curLine
== to
.line
) collapsedAtEnd
= collapsedSpanAt(line
, to
.ch
);
3268 if (curLine
== from.line
) collapsedAtStart
= collapsedSpanAt(line
, from.ch
);
3269 else updateLineHeight(line
, 0);
3271 addMarkedSpan(line
, span
);
3272 if (marker
.collapsed
&& curLine
== from.line
&& lineIsHidden(line
))
3273 updateLineHeight(line
, 0);
3277 if (marker
.readOnly
) {
3278 sawReadOnlySpans
= true;
3279 if (cm
.view
.history
.done
.length
|| cm
.view
.history
.undone
.length
)
3282 if (marker
.collapsed
) {
3283 if (collapsedAtStart
!= collapsedAtEnd
)
3284 throw new Error("Inserting collapsed marker overlapping an existing one");
3286 marker
.atomic
= true;
3288 if (marker
.className
|| marker
.startStyle
|| marker
.endStyle
|| marker
.collapsed
)
3289 regChange(cm
, from.line
, to
.line
+ 1);
3290 if (marker
.atomic
) reCheckSelection(cm
);
3296 function getMarkedSpanFor(spans
, marker
) {
3297 if (spans
) for (var i
= 0; i
< spans
.length
; ++i
) {
3298 var span
= spans
[i
];
3299 if (span
.marker
== marker
) return span
;
3302 function removeMarkedSpan(spans
, span
) {
3303 for (var r
, i
= 0; i
< spans
.length
; ++i
)
3304 if (spans
[i
] != span
) (r
|| (r
= [])).push(spans
[i
]);
3307 function addMarkedSpan(line
, span
) {
3308 line
.markedSpans
= line
.markedSpans
? line
.markedSpans
.concat([span
]) : [span
];
3309 span
.marker
.lines
.push(line
);
3312 function markedSpansBefore(old
, startCh
) {
3313 if (old
) for (var i
= 0, nw
; i
< old
.length
; ++i
) {
3314 var span
= old
[i
], marker
= span
.marker
;
3315 var startsBefore
= span
.from == null || (marker
.inclusiveLeft
? span
.from <= startCh : span
.from < startCh
);
3316 if (startsBefore
|| marker
.type
== "bookmark" && span
.from == startCh
) {
3317 var endsAfter
= span
.to
== null || (marker
.inclusiveRight
? span
.to
>= startCh : span
.to
> startCh
);
3318 (nw
|| (nw
= [])).push({from: span
.from,
3319 to: endsAfter
? null : span
.to
,
3326 function markedSpansAfter(old
, startCh
, endCh
) {
3327 if (old
) for (var i
= 0, nw
; i
< old
.length
; ++i
) {
3328 var span
= old
[i
], marker
= span
.marker
;
3329 var endsAfter
= span
.to
== null || (marker
.inclusiveRight
? span
.to
>= endCh : span
.to
> endCh
);
3330 if (endsAfter
|| marker
.type
== "bookmark" && span
.from == endCh
&& span
.from != startCh
) {
3331 var startsBefore
= span
.from == null || (marker
.inclusiveLeft
? span
.from <= endCh : span
.from < endCh
);
3332 (nw
|| (nw
= [])).push({from: startsBefore
? null : span
.from - endCh
,
3333 to: span
.to
== null ? null : span
.to
- endCh
,
3340 function updateMarkedSpans(oldFirst
, oldLast
, startCh
, endCh
, newText
) {
3341 if (!oldFirst
&& !oldLast
) return newText
;
3342 // Get the spans that 'stick out' on both sides
3343 var first
= markedSpansBefore(oldFirst
, startCh
);
3344 var last
= markedSpansAfter(oldLast
, startCh
, endCh
);
3346 // Next, merge those two ends
3347 var sameLine
= newText
.length
== 1, offset
= lst(newText
).length
+ (sameLine
? startCh : 0);
3349 // Fix up .to properties of first
3350 for (var i
= 0; i
< first
.length
; ++i
) {
3351 var span
= first
[i
];
3352 if (span
.to
== null) {
3353 var found
= getMarkedSpanFor(last
, span
.marker
);
3354 if (!found
) span
.to
= startCh
;
3355 else if (sameLine
) span
.to
= found
.to
== null ? null : found
.to
+ offset
;
3360 // Fix up .from in last (or move them into first in case of sameLine)
3361 for (var i
= 0; i
< last
.length
; ++i
) {
3363 if (span
.to
!= null) span
.to
+= offset
;
3364 if (span
.from == null) {
3365 var found
= getMarkedSpanFor(first
, span
.marker
);
3368 if (sameLine
) (first
|| (first
= [])).push(span
);
3371 span
.from += offset
;
3372 if (sameLine
) (first
|| (first
= [])).push(span
);
3377 var newMarkers
= [newHL(newText
[0], first
)];
3379 // Fill gap with whole-line-spans
3380 var gap
= newText
.length
- 2, gapMarkers
;
3381 if (gap
> 0 && first
)
3382 for (var i
= 0; i
< first
.length
; ++i
)
3383 if (first
[i
].to
== null)
3384 (gapMarkers
|| (gapMarkers
= [])).push({from: null, to: null, marker: first
[i
].marker
});
3385 for (var i
= 0; i
< gap
; ++i
)
3386 newMarkers
.push(newHL(newText
[i
+1], gapMarkers
));
3387 newMarkers
.push(newHL(lst(newText
), last
));
3392 function removeReadOnlyRanges(doc
, from, to
) {
3394 doc
.iter(from.line
, to
.line
+ 1, function(line
) {
3395 if (line
.markedSpans
) for (var i
= 0; i
< line
.markedSpans
.length
; ++i
) {
3396 var mark
= line
.markedSpans
[i
].marker
;
3397 if (mark
.readOnly
&& (!markers
|| indexOf(markers
, mark
) == -1))
3398 (markers
|| (markers
= [])).push(mark
);
3401 if (!markers
) return null;
3402 var parts
= [{from: from, to: to
}];
3403 for (var i
= 0; i
< markers
.length
; ++i
) {
3404 var m
= markers
[i
].find();
3405 for (var j
= 0; j
< parts
.length
; ++j
) {
3407 if (!posLess(m
.from, p
.to
) || posLess(m
.to
, p
.from)) continue;
3408 var newParts
= [j
, 1];
3409 if (posLess(p
.from, m
.from)) newParts
.push({from: p
.from, to: m
.from});
3410 if (posLess(m
.to
, p
.to
)) newParts
.push({from: m
.to
, to: p
.to
});
3411 parts
.splice
.apply(parts
, newParts
);
3412 j
+= newParts
.length
- 1;
3418 function collapsedSpanAt(line
, ch
) {
3419 var sps
= sawCollapsedSpans
&& line
.markedSpans
, found
;
3420 if (sps
) for (var sp
, i
= 0; i
< sps
.length
; ++i
) {
3422 if (!sp
.marker
.collapsed
) continue;
3423 if ((sp
.from == null || sp
.from < ch
) &&
3424 (sp
.to
== null || sp
.to
> ch
) &&
3425 (!found
|| found
.width
< sp
.marker
.width
))
3430 function collapsedSpanAtStart(line
) { return collapsedSpanAt(line
, -1); }
3431 function collapsedSpanAtEnd(line
) { return collapsedSpanAt(line
, line
.text
.length
+ 1); }
3433 function visualLine(doc
, line
) {
3435 while (merged
= collapsedSpanAtStart(line
))
3436 line
= getLine(doc
, merged
.find().from.line
);
3440 function lineIsHidden(line
) {
3441 var sps
= sawCollapsedSpans
&& line
.markedSpans
;
3442 if (sps
) for (var sp
, i
= 0; i
< sps
.length
; ++i
) {
3444 if (!sp
.marker
.collapsed
) continue;
3445 if (sp
.from == null) return true;
3446 if (sp
.from == 0 && sp
.marker
.inclusiveLeft
&& lineIsHiddenInner(line
, sp
))
3450 window
.lineIsHidden
= lineIsHidden
;
3451 function lineIsHiddenInner(line
, span
) {
3452 if (span
.to
== null || span
.marker
.inclusiveRight
&& span
.to
== line
.text
.length
)
3454 for (var sp
, i
= 0; i
< line
.markedSpans
.length
; ++i
) {
3455 sp
= line
.markedSpans
[i
];
3456 if (sp
.marker
.collapsed
&& sp
.from == span
.to
&&
3457 (sp
.marker
.inclusiveLeft
|| span
.marker
.inclusiveRight
) &&
3458 lineIsHiddenInner(line
, sp
)) return true;
3462 // hl stands for history-line, a data structure that can be either a
3463 // string (line without markers) or a {text, markedSpans} object.
3464 function hlText(val
) { return typeof val
== "string" ? val : val
.text
; }
3465 function hlSpans(val
) {
3466 if (typeof val
== "string") return null;
3467 var spans
= val
.markedSpans
, out
= null;
3468 for (var i
= 0; i
< spans
.length
; ++i
) {
3469 if (spans
[i
].marker
.explicitlyCleared
) { if (!out
) out
= spans
.slice(0, i
); }
3470 else if (out
) out
.push(spans
[i
]);
3472 return !out
? spans : out
.length
? out : null;
3474 function newHL(text
, spans
) { return spans
? {text: text
, markedSpans: spans
} : text
; }
3476 function detachMarkedSpans(line
) {
3477 var spans
= line
.markedSpans
;
3479 for (var i
= 0; i
< spans
.length
; ++i
) {
3480 var lines
= spans
[i
].marker
.lines
;
3481 var ix
= indexOf(lines
, line
);
3482 lines
.splice(ix
, 1);
3484 line
.markedSpans
= null;
3487 function attachMarkedSpans(line
, spans
) {
3489 for (var i
= 0; i
< spans
.length
; ++i
)
3490 spans
[i
].marker
.lines
.push(line
);
3491 line
.markedSpans
= spans
;
3494 // LINE DATA STRUCTURE
3496 // Line objects. These hold state related to a line, including
3497 // highlighting info (the styles array).
3498 function makeLine(text
, markedSpans
, height
) {
3499 var line
= {text: text
, height: height
};
3500 attachMarkedSpans(line
, markedSpans
);
3501 if (lineIsHidden(line
)) line
.height
= 0;
3505 function updateLine(cm
, line
, text
, markedSpans
) {
3507 line
.stateAfter
= line
.styles
= null;
3508 if (line
.order
!= null) line
.order
= null;
3509 detachMarkedSpans(line
);
3510 attachMarkedSpans(line
, markedSpans
);
3511 if (lineIsHidden(line
)) line
.height
= 0;
3512 else if (!line
.height
) line
.height
= textHeight(cm
.display
);
3513 signalLater(cm
, line
, "change");
3516 function cleanUpLine(line
) {
3518 detachMarkedSpans(line
);
3521 // Run the given mode's parser over a line, update the styles
3522 // array, which contains alternating fragments of text and CSS
3524 function highlightLine(cm
, line
, state
) {
3525 var mode
= cm
.view
.mode
, flattenSpans
= cm
.options
.flattenSpans
;
3526 var changed
= !line
.styles
, pos
= 0, curText
= "", curStyle
= null;
3527 var stream
= new StringStream(line
.text
, cm
.options
.tabSize
), st
= line
.styles
|| (line
.styles
= []);
3528 if (line
.text
== "" && mode
.blankLine
) mode
.blankLine(state
);
3529 while (!stream
.eol()) {
3530 var style
= mode
.token(stream
, state
), substr
= stream
.current();
3531 stream
.start
= stream
.pos
;
3532 if (!flattenSpans
|| curStyle
!= style
) {
3534 changed
= changed
|| pos
>= st
.length
|| curText
!= st
[pos
] || curStyle
!= st
[pos
+1];
3535 st
[pos
++] = curText
; st
[pos
++] = curStyle
;
3537 curText
= substr
; curStyle
= style
;
3538 } else curText
= curText
+ substr
;
3539 // Give up when line is ridiculously long
3540 if (stream
.pos
> 5000) break;
3543 changed
= changed
|| pos
>= st
.length
|| curText
!= st
[pos
] || curStyle
!= st
[pos
+1];
3544 st
[pos
++] = curText
; st
[pos
++] = curStyle
;
3546 if (stream
.pos
> 5000) { st
[pos
++] = line
.text
.slice(stream
.pos
); st
[pos
++] = null; }
3547 if (pos
!= st
.length
) { st
.length
= pos
; changed
= true; }
3551 // Lightweight form of highlight -- proceed over this line and
3552 // update state, but don't save a style array.
3553 function processLine(cm
, line
, state
) {
3554 var mode
= cm
.view
.mode
;
3555 var stream
= new StringStream(line
.text
, cm
.options
.tabSize
);
3556 if (line
.text
== "" && mode
.blankLine
) mode
.blankLine(state
);
3557 while (!stream
.eol() && stream
.pos
<= 5000) {
3558 mode
.token(stream
, state
);
3559 stream
.start
= stream
.pos
;
3563 var styleToClassCache
= {};
3564 function styleToClass(style
) {
3565 if (!style
) return null;
3566 return styleToClassCache
[style
] ||
3567 (styleToClassCache
[style
] = "cm-" + style
.replace(/ +/g
, " cm-"));
3570 function lineContent(cm
, realLine
, measure
) {
3571 var merged
, line
= realLine
, lineBefore
, sawBefore
, simple
= true;
3572 while (merged
= collapsedSpanAtStart(line
)) {
3574 line
= getLine(cm
.view
.doc
, merged
.find().from.line
);
3575 if (!lineBefore
) lineBefore
= line
;
3578 var builder
= {pre: elt("pre"), col: 0, pos: 0, display: !measure
,
3579 measure: null, addedOne: false, cm: cm
};
3580 if (line
.textClass
) builder
.pre
.className
= line
.textClass
;
3584 highlightLine(cm
, line
, line
.stateAfter
= getStateBefore(cm
, lineNo(line
)));
3585 builder
.measure
= line
== realLine
&& measure
;
3587 builder
.addToken
= builder
.measure
? buildTokenMeasure : buildToken
;
3588 if (measure
&& sawBefore
&& line
!= realLine
&& !builder
.addedOne
) {
3589 measure
[0] = builder
.pre
.appendChild(zeroWidthElement(cm
.display
.measure
));
3590 builder
.addedOne
= true;
3592 var next
= insertLineContent(line
, builder
);
3593 sawBefore
= line
== lineBefore
;
3595 line
= getLine(cm
.view
.doc
, next
.to
.line
);
3600 if (measure
&& !builder
.addedOne
)
3601 measure
[0] = builder
.pre
.appendChild(simple
? elt("span", "\u00a0") : zeroWidthElement(cm
.display
.measure
));
3602 if (!builder
.pre
.firstChild
&& !lineIsHidden(realLine
))
3603 builder
.pre
.appendChild(document
.createTextNode("\u00a0"));
3608 var tokenSpecialChars
= /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
3609 function buildToken(builder
, text
, style
, startStyle
, endStyle
) {
3611 if (!tokenSpecialChars
.test(text
)) {
3612 builder
.col
+= text
.length
;
3613 var content
= document
.createTextNode(text
);
3615 var content
= document
.createDocumentFragment(), pos
= 0;
3617 tokenSpecialChars
.lastIndex
= pos
;
3618 var m
= tokenSpecialChars
.exec(text
);
3619 var skipped
= m
? m
.index
- pos : text
.length
- pos
;
3621 content
.appendChild(document
.createTextNode(text
.slice(pos
, pos
+ skipped
)));
3622 builder
.col
+= skipped
;
3627 var tabSize
= builder
.cm
.options
.tabSize
, tabWidth
= tabSize
- builder
.col
% tabSize
;
3628 content
.appendChild(elt("span", spaceStr(tabWidth
), "cm-tab"));
3629 builder
.col
+= tabWidth
;
3631 var token
= elt("span", "\u2022", "cm-invalidchar");
3632 token
.title
= "\\u" + m
[0].charCodeAt(0).toString(16);
3633 content
.appendChild(token
);
3638 if (style
|| startStyle
|| endStyle
|| builder
.measure
) {
3639 var fullStyle
= style
|| "";
3640 if (startStyle
) fullStyle
+= startStyle
;
3641 if (endStyle
) fullStyle
+= endStyle
;
3642 return builder
.pre
.appendChild(elt("span", [content
], fullStyle
));
3644 builder
.pre
.appendChild(content
);
3647 function buildTokenMeasure(builder
, text
, style
, startStyle
, endStyle
) {
3648 for (var i
= 0; i
< text
.length
; ++i
) {
3649 if (i
&& i
< text
.length
- 1 &&
3650 builder
.cm
.options
.lineWrapping
&&
3651 spanAffectsWrapping
.test(text
.slice(i
- 1, i
+ 1)))
3652 builder
.pre
.appendChild(elt("wbr"));
3653 builder
.measure
[builder
.pos
++] =
3654 buildToken(builder
, text
.charAt(i
), style
,
3655 i
== 0 && startStyle
, i
== text
.length
- 1 && endStyle
);
3657 if (text
.length
) builder
.addedOne
= true;
3660 function buildCollapsedSpan(builder
, size
, widget
) {
3662 if (!builder
.display
) widget
= widget
.cloneNode(true);
3663 builder
.pre
.appendChild(widget
);
3664 if (builder
.measure
&& size
) {
3665 builder
.measure
[builder
.pos
] = widget
;
3666 builder
.addedOne
= true;
3669 builder
.pos
+= size
;
3672 // Outputs a number of spans to make up a line, taking highlighting
3673 // and marked text into account.
3674 function insertLineContent(line
, builder
) {
3675 var st
= line
.styles
, spans
= line
.markedSpans
;
3677 for (var i
= 0; i
< st
.length
; i
+=2)
3678 builder
.addToken(builder
, st
[i
], styleToClass(st
[i
+1]));
3682 var allText
= line
.text
, len
= allText
.length
;
3683 var pos
= 0, i
= 0, text
= "", style
;
3684 var nextChange
= 0, spanStyle
, spanEndStyle
, spanStartStyle
, collapsed
;
3686 if (nextChange
== pos
) { // Update current marker set
3687 spanStyle
= spanEndStyle
= spanStartStyle
= "";
3688 collapsed
= null; nextChange
= Infinity
;
3689 var foundBookmark
= null;
3690 for (var j
= 0; j
< spans
.length
; ++j
) {
3691 var sp
= spans
[j
], m
= sp
.marker
;
3692 if (sp
.from <= pos
&& (sp
.to
== null || sp
.to
> pos
)) {
3693 if (sp
.to
!= null && nextChange
> sp
.to
) { nextChange
= sp
.to
; spanEndStyle
= ""; }
3694 if (m
.className
) spanStyle
+= " " + m
.className
;
3695 if (m
.startStyle
&& sp
.from == pos
) spanStartStyle
+= " " + m
.startStyle
;
3696 if (m
.endStyle
&& sp
.to
== nextChange
) spanEndStyle
+= " " + m
.endStyle
;
3697 if (m
.collapsed
&& (!collapsed
|| collapsed
.marker
.width
< m
.width
))
3699 } else if (sp
.from > pos
&& nextChange
> sp
.from) {
3700 nextChange
= sp
.from;
3702 if (m
.type
== "bookmark" && sp
.from == pos
&& m
.replacedWith
)
3703 foundBookmark
= m
.replacedWith
;
3705 if (collapsed
&& (collapsed
.from || 0) == pos
) {
3706 buildCollapsedSpan(builder
, (collapsed
.to
== null ? len : collapsed
.to
) - pos
,
3707 collapsed
.from != null && collapsed
.marker
.replacedWith
);
3708 if (collapsed
.to
== null) return collapsed
.marker
.find();
3710 if (foundBookmark
&& !collapsed
) buildCollapsedSpan(builder
, 0, foundBookmark
);
3712 if (pos
>= len
) break;
3714 var upto
= Math
.min(len
, nextChange
);
3717 var end
= pos
+ text
.length
;
3719 var tokenText
= end
> upto
? text
.slice(0, upto
- pos
) : text
;
3720 builder
.addToken(builder
, tokenText
, style
+ spanStyle
,
3721 spanStartStyle
, pos
+ tokenText
.length
== nextChange
? spanEndStyle : "");
3723 if (end
>= upto
) {text
= text
.slice(upto
- pos
); pos
= upto
; break;}
3725 spanStartStyle
= "";
3727 text
= st
[i
++]; style
= styleToClass(st
[i
++]);
3732 // DOCUMENT DATA STRUCTURE
3734 function LeafChunk(lines
) {
3737 for (var i
= 0, e
= lines
.length
, height
= 0; i
< e
; ++i
) {
3738 lines
[i
].parent
= this;
3739 height
+= lines
[i
].height
;
3741 this.height
= height
;
3744 LeafChunk
.prototype = {
3745 chunkSize: function() { return this.lines
.length
; },
3746 remove: function(at
, n
, cm
) {
3747 for (var i
= at
, e
= at
+ n
; i
< e
; ++i
) {
3748 var line
= this.lines
[i
];
3749 this.height
-= line
.height
;
3751 signalLater(cm
, line
, "delete");
3753 this.lines
.splice(at
, n
);
3755 collapse: function(lines
) {
3756 lines
.splice
.apply(lines
, [lines
.length
, 0].concat(this.lines
));
3758 insertHeight: function(at
, lines
, height
) {
3759 this.height
+= height
;
3760 this.lines
= this.lines
.slice(0, at
).concat(lines
).concat(this.lines
.slice(at
));
3761 for (var i
= 0, e
= lines
.length
; i
< e
; ++i
) lines
[i
].parent
= this;
3763 iterN: function(at
, n
, op
) {
3764 for (var e
= at
+ n
; at
< e
; ++at
)
3765 if (op(this.lines
[at
])) return true;
3769 function BranchChunk(children
) {
3770 this.children
= children
;
3771 var size
= 0, height
= 0;
3772 for (var i
= 0, e
= children
.length
; i
< e
; ++i
) {
3773 var ch
= children
[i
];
3774 size
+= ch
.chunkSize(); height
+= ch
.height
;
3778 this.height
= height
;
3782 BranchChunk
.prototype = {
3783 chunkSize: function() { return this.size
; },
3784 remove: function(at
, n
, callbacks
) {
3786 for (var i
= 0; i
< this.children
.length
; ++i
) {
3787 var child
= this.children
[i
], sz
= child
.chunkSize();
3789 var rm
= Math
.min(n
, sz
- at
), oldHeight
= child
.height
;
3790 child
.remove(at
, rm
, callbacks
);
3791 this.height
-= oldHeight
- child
.height
;
3792 if (sz
== rm
) { this.children
.splice(i
--, 1); child
.parent
= null; }
3793 if ((n
-= rm
) == 0) break;
3797 if (this.size
- n
< 25) {
3799 this.collapse(lines
);
3800 this.children
= [new LeafChunk(lines
)];
3801 this.children
[0].parent
= this;
3804 collapse: function(lines
) {
3805 for (var i
= 0, e
= this.children
.length
; i
< e
; ++i
) this.children
[i
].collapse(lines
);
3807 insert: function(at
, lines
) {
3809 for (var i
= 0, e
= lines
.length
; i
< e
; ++i
) height
+= lines
[i
].height
;
3810 this.insertHeight(at
, lines
, height
);
3812 insertHeight: function(at
, lines
, height
) {
3813 this.size
+= lines
.length
;
3814 this.height
+= height
;
3815 for (var i
= 0, e
= this.children
.length
; i
< e
; ++i
) {
3816 var child
= this.children
[i
], sz
= child
.chunkSize();
3818 child
.insertHeight(at
, lines
, height
);
3819 if (child
.lines
&& child
.lines
.length
> 50) {
3820 while (child
.lines
.length
> 50) {
3821 var spilled
= child
.lines
.splice(child
.lines
.length
- 25, 25);
3822 var newleaf
= new LeafChunk(spilled
);
3823 child
.height
-= newleaf
.height
;
3824 this.children
.splice(i
+ 1, 0, newleaf
);
3825 newleaf
.parent
= this;
3834 maybeSpill: function() {
3835 if (this.children
.length
<= 10) return;
3838 var spilled
= me
.children
.splice(me
.children
.length
- 5, 5);
3839 var sibling
= new BranchChunk(spilled
);
3840 if (!me
.parent
) { // Become the parent node
3841 var copy
= new BranchChunk(me
.children
);
3843 me
.children
= [copy
, sibling
];
3846 me
.size
-= sibling
.size
;
3847 me
.height
-= sibling
.height
;
3848 var myIndex
= indexOf(me
.parent
.children
, me
);
3849 me
.parent
.children
.splice(myIndex
+ 1, 0, sibling
);
3851 sibling
.parent
= me
.parent
;
3852 } while (me
.children
.length
> 10);
3853 me
.parent
.maybeSpill();
3855 iter: function(from, to
, op
) { this.iterN(from, to
- from, op
); },
3856 iterN: function(at
, n
, op
) {
3857 for (var i
= 0, e
= this.children
.length
; i
< e
; ++i
) {
3858 var child
= this.children
[i
], sz
= child
.chunkSize();
3860 var used
= Math
.min(n
, sz
- at
);
3861 if (child
.iterN(at
, used
, op
)) return true;
3862 if ((n
-= used
) == 0) break;
3871 function getLine(chunk
, n
) {
3872 while (!chunk
.lines
) {
3873 for (var i
= 0;; ++i
) {
3874 var child
= chunk
.children
[i
], sz
= child
.chunkSize();
3875 if (n
< sz
) { chunk
= child
; break; }
3879 return chunk
.lines
[n
];
3882 function updateLineHeight(line
, height
) {
3883 var diff
= height
- line
.height
;
3884 for (var n
= line
; n
; n
= n
.parent
) n
.height
+= diff
;
3887 function lineNo(line
) {
3888 if (line
.parent
== null) return null;
3889 var cur
= line
.parent
, no
= indexOf(cur
.lines
, line
);
3890 for (var chunk
= cur
.parent
; chunk
; cur
= chunk
, chunk
= chunk
.parent
) {
3891 for (var i
= 0;; ++i
) {
3892 if (chunk
.children
[i
] == cur
) break;
3893 no
+= chunk
.children
[i
].chunkSize();
3899 function lineAtHeight(chunk
, h
) {
3902 for (var i
= 0, e
= chunk
.children
.length
; i
< e
; ++i
) {
3903 var child
= chunk
.children
[i
], ch
= child
.height
;
3904 if (h
< ch
) { chunk
= child
; continue outer
; }
3906 n
+= child
.chunkSize();
3909 } while (!chunk
.lines
);
3910 for (var i
= 0, e
= chunk
.lines
.length
; i
< e
; ++i
) {
3911 var line
= chunk
.lines
[i
], lh
= line
.height
;
3918 function heightAtLine(cm
, lineObj
) {
3919 lineObj
= visualLine(cm
.view
.doc
, lineObj
);
3921 var h
= 0, chunk
= lineObj
.parent
;
3922 for (var i
= 0; i
< chunk
.lines
.length
; ++i
) {
3923 var line
= chunk
.lines
[i
];
3924 if (line
== lineObj
) break;
3925 else h
+= line
.height
;
3927 for (var p
= chunk
.parent
; p
; chunk
= p
, p
= chunk
.parent
) {
3928 for (var i
= 0; i
< p
.children
.length
; ++i
) {
3929 var cur
= p
.children
[i
];
3930 if (cur
== chunk
) break;
3931 else h
+= cur
.height
;
3937 function getOrder(line
) {
3938 var order
= line
.order
;
3939 if (order
== null) order
= line
.order
= bidiOrdering(line
.text
);
3945 function makeHistory() {
3947 // Arrays of history events. Doing something adds an event to
3948 // done and clears undo. Undoing moves events from done to
3949 // undone, redoing moves them in the other direction.
3950 done: [], undone: [],
3951 // Used to track when changes can be merged into a single undo
3953 lastTime: 0, lastOp: null, lastOrigin: null,
3954 // Used by the isClean() method
3959 function addChange(cm
, start
, added
, old
, origin
, fromBefore
, toBefore
, fromAfter
, toAfter
) {
3960 var history
= cm
.view
.history
;
3961 history
.undone
.length
= 0;
3962 var time
= +new Date
, cur
= lst(history
.done
);
3965 (history
.lastOp
== cm
.curOp
.id
||
3966 history
.lastOrigin
== origin
&& (origin
== "input" || origin
== "delete") &&
3967 history
.lastTime
> time
- 600)) {
3968 // Merge this change into the last event
3969 var last
= lst(cur
.events
);
3970 if (last
.start
> start
+ old
.length
|| last
.start
+ last
.added
< start
) {
3971 // Doesn't intersect with last sub-event, add new sub-event
3972 cur
.events
.push({start: start
, added: added
, old: old
});
3974 // Patch up the last sub-event
3975 var startBefore
= Math
.max(0, last
.start
- start
),
3976 endAfter
= Math
.max(0, (start
+ old
.length
) - (last
.start
+ last
.added
));
3977 for (var i
= startBefore
; i
> 0; --i
) last
.old
.unshift(old
[i
- 1]);
3978 for (var i
= endAfter
; i
> 0; --i
) last
.old
.push(old
[old
.length
- i
]);
3979 if (startBefore
) last
.start
= start
;
3980 last
.added
+= added
- (old
.length
- startBefore
- endAfter
);
3982 cur
.fromAfter
= fromAfter
; cur
.toAfter
= toAfter
;
3984 // Can not be merged, start a new event.
3985 cur
= {events: [{start: start
, added: added
, old: old
}],
3986 fromBefore: fromBefore
, toBefore: toBefore
, fromAfter: fromAfter
, toAfter: toAfter
};
3987 history
.done
.push(cur
);
3988 while (history
.done
.length
> cm
.options
.undoDepth
)
3989 history
.done
.shift();
3990 if (history
.dirtyCounter
< 0)
3991 // The user has made a change after undoing past the last clean state.
3992 // We can never get back to a clean state now until markClean() is called.
3993 history
.dirtyCounter
= NaN
;
3995 history
.dirtyCounter
++;
3997 history
.lastTime
= time
;
3998 history
.lastOp
= cm
.curOp
.id
;
3999 history
.lastOrigin
= origin
;
4004 function stopMethod() {e_stop(this);}
4005 // Ensure an event has a stop method.
4006 function addStop(event
) {
4007 if (!event
.stop
) event
.stop
= stopMethod
;
4011 function e_preventDefault(e
) {
4012 if (e
.preventDefault
) e
.preventDefault();
4013 else e
.returnValue
= false;
4015 function e_stopPropagation(e
) {
4016 if (e
.stopPropagation
) e
.stopPropagation();
4017 else e
.cancelBubble
= true;
4019 function e_stop(e
) {e_preventDefault(e
); e_stopPropagation(e
);}
4020 CodeMirror
.e_stop
= e_stop
;
4021 CodeMirror
.e_preventDefault
= e_preventDefault
;
4022 CodeMirror
.e_stopPropagation
= e_stopPropagation
;
4024 function e_target(e
) {return e
.target
|| e
.srcElement
;}
4025 function e_button(e
) {
4028 if (e
.button
& 1) b
= 1;
4029 else if (e
.button
& 2) b
= 3;
4030 else if (e
.button
& 4) b
= 2;
4032 if (mac
&& e
.ctrlKey
&& b
== 1) b
= 3;
4036 // Allow 3rd-party code to override event properties by adding an override
4037 // object to an event object.
4038 function e_prop(e
, prop
) {
4039 var overridden
= e
.override
&& e
.override
.hasOwnProperty(prop
);
4040 return overridden
? e
.override
[prop
] : e
[prop
];
4045 function on(emitter
, type
, f
) {
4046 if (emitter
.addEventListener
)
4047 emitter
.addEventListener(type
, f
, false);
4048 else if (emitter
.attachEvent
)
4049 emitter
.attachEvent("on" + type
, f
);
4051 var map
= emitter
._handlers
|| (emitter
._handlers
= {});
4052 var arr
= map
[type
] || (map
[type
] = []);
4057 function off(emitter
, type
, f
) {
4058 if (emitter
.removeEventListener
)
4059 emitter
.removeEventListener(type
, f
, false);
4060 else if (emitter
.detachEvent
)
4061 emitter
.detachEvent("on" + type
, f
);
4063 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
4065 for (var i
= 0; i
< arr
.length
; ++i
)
4066 if (arr
[i
] == f
) { arr
.splice(i
, 1); break; }
4070 function signal(emitter
, type
/*, values...*/) {
4071 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
4073 var args
= Array
.prototype.slice
.call(arguments
, 2);
4074 for (var i
= 0; i
< arr
.length
; ++i
) arr
[i
].apply(null, args
);
4077 function signalLater(cm
, emitter
, type
/*, values...*/) {
4078 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
4080 var args
= Array
.prototype.slice
.call(arguments
, 3), flist
= cm
.curOp
&& cm
.curOp
.delayedCallbacks
;
4081 function bnd(f
) {return function(){f
.apply(null, args
);};};
4082 for (var i
= 0; i
< arr
.length
; ++i
)
4083 if (flist
) flist
.push(bnd(arr
[i
]));
4084 else arr
[i
].apply(null, args
);
4087 function hasHandler(emitter
, type
) {
4088 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
4089 return arr
&& arr
.length
> 0;
4092 CodeMirror
.on
= on
; CodeMirror
.off
= off
; CodeMirror
.signal
= signal
;
4096 // Number of pixels added to scroller and sizer to hide scrollbar
4097 var scrollerCutOff
= 30;
4099 // Returned or thrown by various protocols to signal 'I'm not
4101 var Pass
= CodeMirror
.Pass
= {toString: function(){return "CodeMirror.Pass";}};
4103 function Delayed() {this.id
= null;}
4104 Delayed
.prototype = {set: function(ms
, f
) {clearTimeout(this.id
); this.id
= setTimeout(f
, ms
);}};
4106 // Counts the column offset in a string, taking tabs into account.
4107 // Used mostly to find indentation.
4108 function countColumn(string
, end
, tabSize
) {
4110 end
= string
.search(/[^\s\u00a0]/);
4111 if (end
== -1) end
= string
.length
;
4113 for (var i
= 0, n
= 0; i
< end
; ++i
) {
4114 if (string
.charAt(i
) == "\t") n
+= tabSize
- (n
% tabSize
);
4119 CodeMirror
.countColumn
= countColumn
;
4121 var spaceStrs
= [""];
4122 function spaceStr(n
) {
4123 while (spaceStrs
.length
<= n
)
4124 spaceStrs
.push(lst(spaceStrs
) + " ");
4125 return spaceStrs
[n
];
4128 function lst(arr
) { return arr
[arr
.length
-1]; }
4130 function selectInput(node
) {
4131 if (ios
) { // Mobile Safari apparently has a bug where select() is broken.
4132 node
.selectionStart
= 0;
4133 node
.selectionEnd
= node
.value
.length
;
4134 } else node
.select();
4137 function indexOf(collection
, elt
) {
4138 if (collection
.indexOf
) return collection
.indexOf(elt
);
4139 for (var i
= 0, e
= collection
.length
; i
< e
; ++i
)
4140 if (collection
[i
] == elt
) return i
;
4144 function emptyArray(size
) {
4145 for (var a
= [], i
= 0; i
< size
; ++i
) a
.push(undefined);
4150 var args
= Array
.prototype.slice
.call(arguments
, 1);
4151 return function(){return f
.apply(null, args
);};
4154 var nonASCIISingleCaseWordChar
= /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
4155 function isWordChar(ch
) {
4156 return /\w/.test(ch
) || ch
> "\x80" &&
4157 (ch
.toUpperCase() != ch
.toLowerCase() || nonASCIISingleCaseWordChar
.test(ch
));
4160 function isEmpty(obj
) {
4162 for (var n
in obj
) if (obj
.hasOwnProperty(n
) && obj
[n
]) ++c
;
4166 var isExtendingChar
= /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/;
4170 function elt(tag
, content
, className
, style
) {
4171 var e
= document
.createElement(tag
);
4172 if (className
) e
.className
= className
;
4173 if (style
) e
.style
.cssText
= style
;
4174 if (typeof content
== "string") setTextContent(e
, content
);
4175 else if (content
) for (var i
= 0; i
< content
.length
; ++i
) e
.appendChild(content
[i
]);
4179 function removeChildren(e
) {
4184 function removeChildrenAndAdd(parent
, e
) {
4185 return removeChildren(parent
).appendChild(e
);
4188 function setTextContent(e
, str
) {
4191 e
.appendChild(document
.createTextNode(str
));
4192 } else e
.textContent
= str
;
4195 // FEATURE DETECTION
4197 // Detect drag-and-drop
4198 var dragAndDrop = function() {
4199 // There is *some* kind of drag-and-drop support in IE6-8, but I
4200 // couldn't get it to work yet.
4201 if (ie_lt9
) return false;
4202 var div
= elt('div');
4203 return "draggable" in div
|| "dragDrop" in div
;
4206 // For a reason I have yet to figure out, some browsers disallow
4207 // word wrapping between certain characters *only* if a new inline
4208 // element is started between them. This makes it hard to reliably
4209 // measure the position of things, since that requires inserting an
4210 // extra span. This terribly fragile set of regexps matches the
4211 // character combinations that suffer from this phenomenon on the
4212 // various browsers.
4213 var spanAffectsWrapping
= /^$/; // Won't match any two-character string
4214 if (gecko
) spanAffectsWrapping
= /$'/;
4215 else if (safari
) spanAffectsWrapping
= /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
4216 else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
4218 var knownScrollbarWidth
;
4219 function scrollbarWidth(measure
) {
4220 if (knownScrollbarWidth
!= null) return knownScrollbarWidth
;
4221 var test
= elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
4222 removeChildrenAndAdd(measure
, test
);
4223 if (test
.offsetWidth
)
4224 knownScrollbarWidth
= test
.offsetHeight
- test
.clientHeight
;
4225 return knownScrollbarWidth
|| 0;
4229 function zeroWidthElement(measure
) {
4230 if (zwspSupported
== null) {
4231 var test
= elt("span", "\u200b");
4232 removeChildrenAndAdd(measure
, elt("span", [test
, document
.createTextNode("x")]));
4233 if (measure
.firstChild
.offsetHeight
!= 0)
4234 zwspSupported
= test
.offsetWidth
<= 1 && test
.offsetHeight
> 2 && !ie_lt8
;
4236 if (zwspSupported
) return elt("span", "\u200b");
4237 else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
4240 // See if "".split is the broken IE version, if so, provide an
4241 // alternative way to split lines.
4242 var splitLines
= "\n\nb".split(/\n/).length
!= 3 ? function(string
) {
4243 var pos
= 0, result
= [], l
= string
.length
;
4245 var nl
= string
.indexOf("\n", pos
);
4246 if (nl
== -1) nl
= string
.length
;
4247 var line
= string
.slice(pos
, string
.charAt(nl
- 1) == "\r" ? nl
- 1 : nl
);
4248 var rt
= line
.indexOf("\r");
4250 result
.push(line
.slice(0, rt
));
4258 } : function(string
){return string
.split(/\r\n?|\n/);};
4259 CodeMirror
.splitLines
= splitLines
;
4261 var hasSelection
= window
.getSelection
? function(te
) {
4262 try { return te
.selectionStart
!= te
.selectionEnd
; }
4263 catch(e
) { return false; }
4265 try {var range
= te
.ownerDocument
.selection
.createRange();}
4267 if (!range
|| range
.parentElement() != te
) return false;
4268 return range
.compareEndPoints("StartToEnd", range
) != 0;
4271 var hasCopyEvent
= (function() {
4273 if ("oncopy" in e
) return true;
4274 e
.setAttribute("oncopy", "return;");
4275 return typeof e
.oncopy
== 'function';
4280 var keyNames
= {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
4281 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
4282 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
4283 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
4284 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
4285 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
4286 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
4287 CodeMirror
.keyNames
= keyNames
;
4290 for (var i
= 0; i
< 10; i
++) keyNames
[i
+ 48] = String(i
);
4292 for (var i
= 65; i
<= 90; i
++) keyNames
[i
] = String
.fromCharCode(i
);
4294 for (var i
= 1; i
<= 12; i
++) keyNames
[i
+ 111] = keyNames
[i
+ 63235] = "F" + i
;
4299 function iterateBidiSections(order
, from, to
, f
) {
4300 if (!order
) return f(from, to
, "ltr");
4301 for (var i
= 0; i
< order
.length
; ++i
) {
4302 var part
= order
[i
];
4303 if (part
.from < to
&& part
.to
> from)
4304 f(Math
.max(part
.from, from), Math
.min(part
.to
, to
), part
.level
== 1 ? "rtl" : "ltr");
4308 function bidiLeft(part
) { return part
.level
% 2 ? part
.to : part
.from; }
4309 function bidiRight(part
) { return part
.level
% 2 ? part
.from : part
.to
; }
4311 function lineLeft(line
) { var order
= getOrder(line
); return order
? bidiLeft(order
[0]) : 0; }
4312 function lineRight(line
) {
4313 var order
= getOrder(line
);
4314 if (!order
) return line
.text
.length
;
4315 return bidiRight(lst(order
));
4318 function lineStart(cm
, lineN
) {
4319 var line
= getLine(cm
.view
.doc
, lineN
);
4320 var visual
= visualLine(cm
.view
.doc
, line
);
4321 if (visual
!= line
) lineN
= lineNo(visual
);
4322 var order
= getOrder(visual
);
4323 var ch
= !order
? 0 : order
[0].level
% 2 ? lineRight(visual
) : lineLeft(visual
);
4324 return {line: lineN
, ch: ch
};
4326 function lineEnd(cm
, lineNo
) {
4328 while (merged
= collapsedSpanAtEnd(line
= getLine(cm
.view
.doc
, lineNo
)))
4329 lineNo
= merged
.find().to
.line
;
4330 var order
= getOrder(line
);
4331 var ch
= !order
? line
.text
.length : order
[0].level
% 2 ? lineLeft(line
) : lineRight(line
);
4332 return {line: lineNo
, ch: ch
};
4335 // This is somewhat involved. It is needed in order to move
4336 // 'visually' through bi-directional text -- i.e., pressing left
4337 // should make the cursor go left, even when in RTL text. The
4338 // tricky part is the 'jumps', where RTL and LTR text touch each
4339 // other. This often requires the cursor offset to move more than
4340 // one unit, in order to visually move one unit.
4341 function moveVisually(line
, start
, dir
, byUnit
) {
4342 var bidi
= getOrder(line
);
4343 if (!bidi
) return moveLogically(line
, start
, dir
, byUnit
);
4344 var moveOneUnit
= byUnit
? function(pos
, dir
) {
4346 while (pos
> 0 && isExtendingChar
.test(line
.text
.charAt(pos
)));
4348 } : function(pos
, dir
) { return pos
+ dir
; };
4349 var linedir
= bidi
[0].level
;
4350 for (var i
= 0; i
< bidi
.length
; ++i
) {
4351 var part
= bidi
[i
], sticky
= part
.level
% 2 == linedir
;
4352 if ((part
.from < start
&& part
.to
> start
) ||
4353 (sticky
&& (part
.from == start
|| part
.to
== start
))) break;
4355 var target
= moveOneUnit(start
, part
.level
% 2 ? -dir : dir
);
4357 while (target
!= null) {
4358 if (part
.level
% 2 == linedir
) {
4359 if (target
< part
.from || target
> part
.to
) {
4360 part
= bidi
[i
+= dir
];
4361 target
= part
&& (dir
> 0 == part
.level
% 2 ? moveOneUnit(part
.to
, -1) : moveOneUnit(part
.from, 1));
4364 if (target
== bidiLeft(part
)) {
4366 target
= part
&& bidiRight(part
);
4367 } else if (target
== bidiRight(part
)) {
4369 target
= part
&& bidiLeft(part
);
4374 return target
< 0 || target
> line
.text
.length
? null : target
;
4377 function moveLogically(line
, start
, dir
, byUnit
) {
4378 var target
= start
+ dir
;
4379 if (byUnit
) while (target
> 0 && isExtendingChar
.test(line
.text
.charAt(target
))) target
+= dir
;
4380 return target
< 0 || target
> line
.text
.length
? null : target
;
4383 // Bidirectional ordering algorithm
4384 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
4385 // that this (partially) implements.
4387 // One-char codes used for character types:
4388 // L (L): Left-to-Right
4389 // R (R): Right-to-Left
4390 // r (AL): Right-to-Left Arabic
4391 // 1 (EN): European Number
4392 // + (ES): European Number Separator
4393 // % (ET): European Number Terminator
4394 // n (AN): Arabic Number
4395 // , (CS): Common Number Separator
4396 // m (NSM): Non-Spacing Mark
4397 // b (BN): Boundary Neutral
4398 // s (B): Paragraph Separator
4399 // t (S): Segment Separator
4400 // w (WS): Whitespace
4401 // N (ON): Other Neutrals
4403 // Returns null if characters are ordered as they appear
4404 // (left-to-right), or an array of sections ({from, to, level}
4405 // objects) in the order in which they occur visually.
4406 var bidiOrdering
= (function() {
4407 // Character types for codepoints 0 to 0xff
4408 var lowTypes
= "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
4409 // Character types for codepoints 0x600 to 0x6ff
4410 var arabicTypes
= "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
4411 function charType(code
) {
4412 if (code
<= 0xff) return lowTypes
.charAt(code
);
4413 else if (0x590 <= code
&& code
<= 0x5f4) return "R";
4414 else if (0x600 <= code
&& code
<= 0x6ff) return arabicTypes
.charAt(code
- 0x600);
4415 else if (0x700 <= code
&& code
<= 0x8ac) return "r";
4419 var bidiRE
= /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
4420 var isNeutral
= /[stwN]/, isStrong
= /[LRr]/, countsAsLeft
= /[Lb1n]/, countsAsNum
= /[1n]/;
4422 return function charOrdering(str
) {
4423 if (!bidiRE
.test(str
)) return false;
4424 var len
= str
.length
, types
= [], startType
= null;
4425 for (var i
= 0, type
; i
< len
; ++i
) {
4426 types
.push(type
= charType(str
.charCodeAt(i
)));
4427 if (startType
== null) {
4428 if (type
== "L") startType
= "L";
4429 else if (type
== "R" || type
== "r") startType
= "R";
4432 if (startType
== null) startType
= "L";
4434 // W1. Examine each non-spacing mark (NSM) in the level run, and
4435 // change the type of the NSM to the type of the previous
4436 // character. If the NSM is at the start of the level run, it will
4437 // get the type of sor.
4438 for (var i
= 0, prev
= startType
; i
< len
; ++i
) {
4439 var type
= types
[i
];
4440 if (type
== "m") types
[i
] = prev
;
4444 // W2. Search backwards from each instance of a European number
4445 // until the first strong type (R, L, AL, or sor) is found. If an
4446 // AL is found, change the type of the European number to Arabic
4448 // W3. Change all ALs to R.
4449 for (var i
= 0, cur
= startType
; i
< len
; ++i
) {
4450 var type
= types
[i
];
4451 if (type
== "1" && cur
== "r") types
[i
] = "n";
4452 else if (isStrong
.test(type
)) { cur
= type
; if (type
== "r") types
[i
] = "R"; }
4455 // W4. A single European separator between two European numbers
4456 // changes to a European number. A single common separator between
4457 // two numbers of the same type changes to that type.
4458 for (var i
= 1, prev
= types
[0]; i
< len
- 1; ++i
) {
4459 var type
= types
[i
];
4460 if (type
== "+" && prev
== "1" && types
[i
+1] == "1") types
[i
] = "1";
4461 else if (type
== "," && prev
== types
[i
+1] &&
4462 (prev
== "1" || prev
== "n")) types
[i
] = prev
;
4466 // W5. A sequence of European terminators adjacent to European
4467 // numbers changes to all European numbers.
4468 // W6. Otherwise, separators and terminators change to Other
4470 for (var i
= 0; i
< len
; ++i
) {
4471 var type
= types
[i
];
4472 if (type
== ",") types
[i
] = "N";
4473 else if (type
== "%") {
4474 for (var end
= i
+ 1; end
< len
&& types
[end
] == "%"; ++end
) {}
4475 var replace
= (i
&& types
[i
-1] == "!") || (end
< len
- 1 && types
[end
] == "1") ? "1" : "N";
4476 for (var j
= i
; j
< end
; ++j
) types
[j
] = replace
;
4481 // W7. Search backwards from each instance of a European number
4482 // until the first strong type (R, L, or sor) is found. If an L is
4483 // found, then change the type of the European number to L.
4484 for (var i
= 0, cur
= startType
; i
< len
; ++i
) {
4485 var type
= types
[i
];
4486 if (cur
== "L" && type
== "1") types
[i
] = "L";
4487 else if (isStrong
.test(type
)) cur
= type
;
4490 // N1. A sequence of neutrals takes the direction of the
4491 // surrounding strong text if the text on both sides has the same
4492 // direction. European and Arabic numbers act as if they were R in
4493 // terms of their influence on neutrals. Start-of-level-run (sor)
4494 // and end-of-level-run (eor) are used at level run boundaries.
4495 // N2. Any remaining neutrals take the embedding direction.
4496 for (var i
= 0; i
< len
; ++i
) {
4497 if (isNeutral
.test(types
[i
])) {
4498 for (var end
= i
+ 1; end
< len
&& isNeutral
.test(types
[end
]); ++end
) {}
4499 var before
= (i
? types
[i
-1] : startType
) == "L";
4500 var after
= (end
< len
- 1 ? types
[end
] : startType
) == "L";
4501 var replace
= before
|| after
? "L" : "R";
4502 for (var j
= i
; j
< end
; ++j
) types
[j
] = replace
;
4507 // Here we depart from the documented algorithm, in order to avoid
4508 // building up an actual levels array. Since there are only three
4509 // levels (0, 1, 2) in an implementation that doesn't take
4510 // explicit embedding into account, we can build up the order on
4511 // the fly, without following the level-based algorithm.
4513 for (var i
= 0; i
< len
;) {
4514 if (countsAsLeft
.test(types
[i
])) {
4516 for (++i
; i
< len
&& countsAsLeft
.test(types
[i
]); ++i
) {}
4517 order
.push({from: start
, to: i
, level: 0});
4519 var pos
= i
, at
= order
.length
;
4520 for (++i
; i
< len
&& types
[i
] != "L"; ++i
) {}
4521 for (var j
= pos
; j
< i
;) {
4522 if (countsAsNum
.test(types
[j
])) {
4523 if (pos
< j
) order
.splice(at
, 0, {from: pos
, to: j
, level: 1});
4525 for (++j
; j
< i
&& countsAsNum
.test(types
[j
]); ++j
) {}
4526 order
.splice(at
, 0, {from: nstart
, to: j
, level: 2});
4530 if (pos
< i
) order
.splice(at
, 0, {from: pos
, to: i
, level: 1});
4533 if (order
[0].level
== 1 && (m
= str
.match(/^\s+/))) {
4534 order
[0].from = m
[0].length
;
4535 order
.unshift({from: 0, to: m
[0].length
, level: 0});
4537 if (lst(order
).level
== 1 && (m
= str
.match(/\s+$/))) {
4538 lst(order
).to
-= m
[0].length
;
4539 order
.push({from: len
- m
[0].length
, to: len
, level: 0});
4541 if (order
[0].level
!= lst(order
).level
)
4542 order
.push({from: len
, to: len
, level: order
[0].level
});
4550 CodeMirror
.version
= "3.0";